Passer au contenu principal

AdminDataTable

Implemented

Plain admin data table with optional row navigation — Wave-3 L5-M3 thin composition over the canonical DataTable organism. The historical Admin column API is preserved (id / cell / width / align — no sorting, no selection); DataTable owns the table chrome (header rhythm, row hover, responsive overflow, accessible empty state). Its distinctive feature is row navigation: rowHref(row) makes the FIRST cell a real <a> whose ::after stretches over the whole row, so the entire row is clickable while the accessible name, keyboard focus, middle-click and open-in-new-tab all come from a genuine link rather than a click handler on <tr>; onRowActivate(row, event) is the SPA hook for unmodified primary clicks. Exported from the barrel along with AdminDataTableProps and AdminDataTableColumn.

Categoryorganisms
Sourcecomponents/organisms/AdminDataTable/AdminDataTable.tsx
AI HintComposes the canonical DataTable organism — use it for STATIC admin lists (no sort, no selection). Add rowHref when each row navigates to a detail page; onRowActivate is the SPA hook for unmodified primary clicks (the DS is framework-agnostic — the consumer owns the actual router integration). Columns use the historical Admin API (id / cell) — sortable and selection-bearing behaviour live on the sibling Admin*Table wrappers (AdminSortableTable, AdminBulkActionTable). Exported from the barrel along with AdminDataTableProps and AdminDataTableColumn. i18n: this wrapper defaults to English for emptyMessage / ariaLabel — DataTable and AdminSortableTable default to French; the house convention is documented in DataTable's aiHints.

Plain admin data table with optional row navigation — a thin composition over the canonical DataTable organism that keeps the historical Admin column API (id/cell) while DataTable owns the table chrome and the accessible empty state; rowHref turns each row into a real whole-row anchor.

AdminDataTable

Organism — Admin family

Wave-3 L5-M3 composition over the canonical DataTable organism. The historical Admin column API is preserved (id / cell / width / align — no sorting, no selection). rowHref(row) makes the FIRST cell a real anchor whose ::after stretches over the whole row, so the entire row is clickable while the accessible name, keyboard focus, middle-click and open-in-new-tab all come from a genuine link; onRowActivate(row, event) is the SPA hook for unmodified primary clicks.

Every row is a real link — click anywhere on one, or Tab into it and press Enter. Middle-click and open-in-new-tab work natively.

Private links
Private linkURLClicks
Board promoepass.live/p/board26412
Newsletterepass.live/p/news1204
Partner portalepass.live/p/partner87

No activation yet — click a row (unmodified primary click).

This component supports light and dark mode.

Usage

AdminDataTable is generic over its row type — define columns with a cell renderer per column, pass rows and a stable rowKey, and add rowHref when each row navigates to a detail page. The design system is framework-agnostic: rowHref governs the link semantics while onRowActivate lets your router take over unmodified clicks — the preview above wires both to keep them observable.

import { AdminDataTable } from '@epasslive/design-system'

const columns: AdminDataTableColumn<LinkRow>[] = [
  { id: 'label', header: 'Private link', cell: (r) => r.label },
  { id: 'url', header: 'URL', cell: (r) => <code>{r.url}</code> },
  { id: 'clicks', header: 'Clicks', align: 'right', cell: (r) => r.clicks },
]

<AdminDataTable
  columns={columns}
  rows={rows}
  rowKey={(r) => r.id}
  ariaLabel={t('privateLinks')}
  emptyMessage={t('noLinksYet')}
  rowHref={(r) => `/admin/links/${r.id}`}
  onRowActivate={(r) => router.push(`/admin/links/${r.id}`)}
/>

Row-navigation contract

rowHref renders the first cell as a genuine <a> whose ::after stretches across the row — clicking anywhere navigates, while focus, the accessible name, middle-click and open-in-new-tab come from the anchor itself. onRowActivate fires only for unmodified primary clicks, so ⌘/Ctrl-click and middle-click keep the native anchor behaviour. Return undefined from rowHref for rows that must stay non-navigable.

Accessibility

Table chrome comes from the canonical DataTable organism: th scope="col" on every column (WCAG 1.3.1), the table named by ariaLabel (WCAG 4.1.2), and the empty state rendered in a role="status" / aria-live="polite" cell. Navigable rows are real anchors — keyboard users Tab into the link and modified clicks work natively (WCAG 2.1.1).

Props

Prop
Type
Default
Description
columns*
AdminDataTableColumn<T>[]
—

Column definitions — see the column table below.

rows*
T[]
—

Row data — one item per row, typed by the generic.

rowKey*
(row: T, index: number) => string
—

Stable React key per row.

caption
string
—

Table caption — renders visibly when set; ariaLabel doubles as the sr-only caption otherwise.

emptyMessage
string
"No data to display"

Rendered in the empty state cell (role="status", aria-live="polite").

ariaLabel
string
"Data table"

Accessible name for the table — also feeds the caption.

className
string
—

Class on the rendered table wrapper.

id
string
—

Stable id for the scroll wrapper (auto-generated adt-<useId()> fallback).

rowHref
(row: T) => string | undefined
—

Destination per row — the first cell becomes a whole-row anchor. Return undefined to leave a row non-navigable.

onRowActivate
(row: T, event: React.MouseEvent) => void
—

SPA hook for unmodified primary clicks; rowHref still governs semantics and modified clicks.

Column definition

AdminDataTableColumn<T> — the historical Admin column API: plain cell renderers, no sort config.

Prop
Type
Default
Description
id*
string
—

Column identifier.

header*
string
—

Header label.

cell*
(row: T) => React.ReactNode
—

Cell renderer — receives the original row object (format here, not in the data).

width
string
—

CSS width for the column.

align
"left" | "right" | "center"
—

Cell + header alignment.

Props

Preview

AdminDataTable

Prop
Type
Required
Default
Description
columns
AdminDataTableColumn<T>[]
Required
—

Column definitions — id / header / cell / width / align. See AdminDataTableColumn below.

rows
T[]
Required
—

Row data — one item per row, typed by the generic.

rowKey
(row: T, index: number) => string
Required
—

Stable React key per row. Doubles as the selection key when this row is composed into a selection-bearing wrapper.

caption
string
Optional
—

Visible table caption. When set it renders via DataTable's captionVisible path; otherwise ariaLabel doubles as the sr-only caption.

emptyMessage
string
Optional
'No data to display'

Empty-state cell copy (role=status / aria-live=polite via DataTable). EN default — FR consumers pass 'Aucune donnée à afficher'.

ariaLabel
string
Optional
'Data table'

Accessible name for the table — also feeds the sr-only caption fallback. FR: 'Table de données'.

className
string
Optional
—

Additional CSS classes on the rendered DataTable wrapper.

id
string
Optional
adt-<useId()>

Stable id for the scroll wrapper (auto-generated adt-<useId()> fallback). Useful for aria-controls wiring from a parent search or filter.

rowHref
(row: T) => string | undefined
Optional
—

Destination per row. When supplied, the FIRST cell renders a real anchor whose ::after stretches over the whole row, so the entire row is clickable while the accessible name, keyboard focus, middle-click and open-in-new-tab all come from the anchor itself (WCAG 2.1.1). Return undefined to leave an individual row non-navigable.

onRowActivate
(row: T, event: React.MouseEvent) => void
Optional
—

Optional SPA hook — fires only for an unmodified primary click so a router can take over; rowHref still governs semantics and modified clicks (⌘/Ctrl/Shift/Alt, middle-click). DS is framework-agnostic (no next dep) — the consumer owns the actual navigation.

Quick use:<AdminDataTable />

Accessibility

ARIA roletable

ARIA

2 rules

WCAG 1.3.1 + 2.1.1

Every column renders <th scope='col'> via DataTable (WCAG 1.3.1); navigable rows expose a real anchor (first cell <a>::after stretched), NOT a click handler on <tr> — keyboard focus, accessible name, middle-click and open-in-new-tab all come from the anchor (WCAG 2.1.1)

WCAG 4.1.3

Empty state rendered in DataTable's role='status' / aria-live='polite' cell so 'No data to display' (or the localized emptyMessage) is announced without focus steal (WCAG 4.1.3)

Usage

Import

tsxSnippet
import { AdminDataTable } from "@epasslive/design-system"

Example 1

tsxSnippet
<AdminDataTable columns={cols} rows={rows} rowKey={(r) => r.id} ariaLabel="Private links" emptyMessage="No private links yet — create one above." />

Example 2

tsxSnippet
<AdminDataTable columns={cols} rows={rows} rowKey={(r) => r.id} ariaLabel="Tickets" rowHref={(r) => `/admin/tickets/${r.id}`} onRowActivate={(r) => router.push(`/admin/tickets/${r.id}`)} />

Anti-patterns

— Do not fetch data directly in organisms

— Avoid tight coupling to specific data sources

— Do not attach onClick to <tr> or <td> for row navigation — that discards the anchor semantics rowHref exists to provide (keyboard focus, middle-click, open-in-new-tab)

— Do not reach for this organism when you need per-column sorting (use AdminSortableTable), bulk selection (use AdminBulkActionTable), search filtering (use AdminFilterableTable) or pagination (use AdminPaginatedTable) — those are sibling compositions over the same DataTable

Design System v4.1.0 | ePassLive