Skip to main content

Overview

The AgrospAI Data Space Portal provides a comprehensive library of accessible, tested React components.

Atoms

Basic building block components.

Button

Primary interaction component with multiple styles and link support.
children
ReactNode
required
Button content
style
'primary' | 'ghost' | 'text'
default:"primary"
Visual style variant
size
'small'
Size variant
href
string
External link URL (opens in new tab)
to
string
Internal Next.js link path
onClick
(e: FormEvent) => void
Click handler
disabled
boolean
Disabled state
type
'submit' | 'button'
default:"button"
Button type for forms
arrow
boolean
Show arrow indicator for internal links
import Button from '@shared/atoms/Button'

// Primary button
<Button onClick={handleClick}>Click Me</Button>

// Ghost style
<Button style="ghost" size="small">Cancel</Button>

// Internal link
<Button to="/publish" arrow>Publish Asset</Button>

// External link
<Button href="https://docs.oceanprotocol.com">Documentation</Button>

// Submit button
<form onSubmit={handleSubmit}>
  <Button type="submit" style="primary">Submit</Button>
</form>

Alert

Display important messages with optional actions.
text
string
required
Alert message (supports Markdown)
state
'error' | 'warning' | 'info' | 'success'
required
Alert severity level
title
string
Alert title
badge
string
Badge text to show next to title
action
object
Action button configuration
onDismiss
() => void
Dismissal handler (shows close button)
import Alert from '@shared/atoms/Alert'
import { useState } from 'react'

// Simple error alert
<Alert state="error" text="Transaction failed. Please try again." />

// Warning with title
<Alert
  state="warning"
  title="Network Sync"
  text="Subgraph is syncing. Data may be outdated."
/>

// Info with badge
<Alert
  state="info"
  title="Beta Feature"
  badge="NEW"
  text="This feature is currently in beta testing."
/>

// With action button
<Alert
  state="success"
  text="Asset published successfully!"
  action={{
    name: 'View Asset',
    style: 'primary',
    handleAction: () => router.push(`/asset/${did}`)
  }}
/>

// Dismissible
function DismissibleAlert() {
  const [show, setShow] = useState(true)
  if (!show) return null

  return (
    <Alert
      state="info"
      text="Welcome to the portal!"
      onDismiss={() => setShow(false)}
    />
  )
}

Loader

Loading spinner with optional message.
message
string
Loading message to display
white
boolean
Use white color (for dark backgrounds)
import Loader from '@shared/atoms/Loader'

// Simple loader
<Loader />

// With message
<Loader message="Loading assets..." />

// White variant
<div style={{ background: 'black', padding: '2rem' }}>
  <Loader white message="Processing..." />
</div>

// In Suspense boundary
import { Suspense } from 'react'

<Suspense fallback={<Loader message="Loading data..." />}>
  <AssetList />
</Suspense>
Accessible modal dialog component.
title
string
required
Modal title
isOpen
boolean
required
Whether modal is open
onToggleModal
() => void
required
Function to toggle modal open/closed
children
ReactNode
required
Modal content
import Modal from '@shared/atoms/Modal'
import Button from '@shared/atoms/Button'
import { useState } from 'react'

function ModalExample() {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <>
      <Button onClick={() => setIsOpen(true)}>Open Modal</Button>

      <Modal
        title="Confirm Action"
        isOpen={isOpen}
        onToggleModal={() => setIsOpen(false)}
      >
        <div>
          <p>Are you sure you want to proceed?</p>
          <div style={{ display: 'flex', gap: '1rem' }}>
            <Button onClick={() => setIsOpen(false)} style="ghost">
              Cancel
            </Button>
            <Button onClick={handleConfirm}>
              Confirm
            </Button>
          </div>
        </div>
      </Modal>
    </>
  )
}

Badge

Small label for status or categorization.
label
string
required
Badge text
className
string
Additional CSS classes
import Badge from '@shared/atoms/Badge'

<Badge label="Beta" />
<Badge label="New" className={styles.newBadge} />

// With other components
<h2>
  Advanced Features <Badge label="Pro" />
</h2>

Container

Layout container with max-width constraint.
children
ReactNode
required
Container content
className
string
Additional CSS classes
import Container from '@shared/atoms/Container'

<Container>
  <h1>Page Title</h1>
  <p>Content goes here...</p>
</Container>

Tooltip

Accessible tooltip component.
import Tooltip from '@shared/atoms/Tooltip'

<Tooltip content="Additional information">
  <span>Hover me</span>
</Tooltip>

Table

Data table with sorting and pagination support.
import Table from '@shared/atoms/Table'

const columns = [
  { name: 'Name', selector: 'name' },
  { name: 'Price', selector: 'price' },
  { name: 'Owner', selector: 'owner' }
]

const data = [
  { name: 'Dataset 1', price: '10 OCEAN', owner: '0x123...' },
  { name: 'Dataset 2', price: '5 OCEAN', owner: '0x456...' }
]

<Table columns={columns} data={data} />

Molecules

Composite components combining atoms.

Price

Display asset price with token symbol.
price
AssetPrice
required
Price object from Ocean Protocol
orderPriceAndFees
OrderPriceAndFees
Detailed price breakdown
size
'small' | 'mini' | 'large'
Display size
className
string
Additional CSS classes
import Price from '@shared/Price'

// Simple price display
<Price price={asset.price} />

// With fees breakdown
<Price 
  price={asset.price}
  orderPriceAndFees={orderDetails}
  size="large"
/>

// In asset card
function AssetCard({ asset }) {
  return (
    <div>
      <h3>{asset.metadata.name}</h3>
      <Price price={asset.price} size="small" />
    </div>
  )
}

Publisher

Display publisher information with avatar and address.
import Publisher from '@shared/Publisher'

<Publisher account={asset.nft.owner} />

NetworkName

Display network name with icon.
import NetworkName from '@shared/NetworkName'

<NetworkName networkId={137} />
// Shows "Polygon" with Polygon icon

Pagination

Pagination controls for lists.
import Pagination from '@shared/Pagination'
import { useState } from 'react'

function AssetList() {
  const [page, setPage] = useState(1)
  const { data } = useAssets(address, 'dataset', chainId, page)

  return (
    <>
      {data.results.map(asset => (
        <AssetCard key={asset.id} asset={asset} />
      ))}
      <Pagination
        currentPage={page}
        totalPages={data.totalPages}
        onPageChange={setPage}
      />
    </>
  )
}

Time

Format and display timestamps.
import Time from '@shared/atoms/Time'

// Relative time ("2 hours ago")
<Time date={asset.metadata.created} relative />

// Absolute time
<Time date={asset.metadata.created} />

Tags

Display asset tags.
import Tags from '@shared/atoms/Tags'

<Tags items={asset.metadata.tags} />

Complex Components

Web3Feedback

Display transaction status and feedback.
import Web3Feedback from '@shared/Web3Feedback'

function TransactionButton() {
  const [txHash, setTxHash] = useState()

  return (
    <>
      <Button onClick={handleTransaction}>Execute</Button>
      {txHash && <Web3Feedback txHash={txHash} />}
    </>
  )
}

WalletNetworkSwitcher

Network switching component for multi-chain support.
import WalletNetworkSwitcher from '@shared/WalletNetworkSwitcher'

<WalletNetworkSwitcher />

Page

Page layout wrapper with SEO support.
import Page from '@shared/Page'

function AssetPage({ asset }) {
  return (
    <Page 
      title={asset.metadata.name}
      description={asset.metadata.description}
      uri={`/asset/${asset.id}`}
    >
      {/* Page content */}
    </Page>
  )
}

Component Composition

Components are designed to work together:
import Container from '@shared/atoms/Container'
import Alert from '@shared/atoms/Alert'
import Button from '@shared/atoms/Button'
import Loader from '@shared/atoms/Loader'
import Modal from '@shared/atoms/Modal'
import { useState, Suspense } from 'react'

function AssetPublisher() {
  const [showModal, setShowModal] = useState(false)
  const [error, setError] = useState(null)

  return (
    <Container>
      {error && (
        <Alert
          state="error"
          text={error}
          onDismiss={() => setError(null)}
        />
      )}

      <Button onClick={() => setShowModal(true)}>
        Publish New Asset
      </Button>

      <Modal
        title="Publish Asset"
        isOpen={showModal}
        onToggleModal={() => setShowModal(false)}
      >
        <Suspense fallback={<Loader message="Loading form..." />}>
          <PublishForm onError={setError} />
        </Suspense>
      </Modal>
    </Container>
  )
}

Accessibility

All components follow accessibility best practices:
  • Semantic HTML elements
  • ARIA labels and roles
  • Keyboard navigation support
  • Focus management
  • Screen reader compatibility
// Buttons announce their purpose
<Button aria-label="Download dataset">Download</Button>

// Modals trap focus and support Escape key
<Modal title="Settings" isOpen={open} onToggleModal={close}>
  {/* Focus is trapped within modal */}
</Modal>

// Alerts use appropriate ARIA roles
<Alert state="error" text="Error message" />
// Rendered with role="alert" for screen readers

Testing Components

All components include comprehensive test suites:
import { render, screen } from '@testing-library/react'
import Button from '@shared/atoms/Button'

test('Button renders with children', () => {
  render(<Button>Click me</Button>)
  expect(screen.getByText('Click me')).toBeInTheDocument()
})

test('Button calls onClick handler', () => {
  const handleClick = jest.fn()
  render(<Button onClick={handleClick}>Click me</Button>)
  screen.getByText('Click me').click()
  expect(handleClick).toHaveBeenCalledTimes(1)
})

Styling

Components use CSS Modules for scoped styling:
import styles from './MyComponent.module.css'
import Button from '@shared/atoms/Button'

function MyComponent() {
  return (
    <div className={styles.wrapper}>
      <Button className={styles.customButton}>Custom Styled</Button>
    </div>
  )
}

Build docs developers (and LLMs) love