Passer au contenu principal

DataTable

Implemented

Canonical table organism for the entire design system — generic over its row type. Column config (key/header/format/sortable/numeric/hideBelow/sortValue/sortLabel/headerSrOnly), three-state sort cycle (null→asc→desc→null with aria-sort on <th>), accessible empty state (role=status / aria-live=polite), optional row selection (leading checkbox column with aria-selected + highlight), whole-row anchor navigation (rowHref stretches the first cell's <a>::after over the row — keyboard focus, accessible name, middle-click and open-in-new-tab all come from the anchor), interactive rows (onRowClick — focusable <tr>, Enter/Space activation, the <tr> KEEPS its row role so cell semantics survive), and grouped mode (rowGroups — full-width <th scope='rowgroup'> per group, sorting bypassed). Wave-3 L5-M3: the five Admin*Table organisms compose it instead of re-implementing table chrome; Wave-3 L8c: the five domain tables (BookingsTable, TierTable, UserTable, PromoTable, EventComparisonTable) compose it for the storefront. Exported from the barrel along with DataTableProps, DataTableColumn, DataTableRowGroup, DataTableSelection and SortDirection.

Categoryorganisms
Sourcecomponents/organisms/datatable/DataTable.tsx
AI HintCanonical table organism — reach for it FIRST. Exported from the barrel along with DataTableProps, DataTableColumn, DataTableRowGroup, DataTableSelection, SortDirection. Composed by the five Admin*Table wrappers (Wave-3 L5-M3 — selection/rowHref/onRowClick/opt-in sort are delivered by them on top of this surface) and by the five storefront domain tables (Wave-3 L8c — BookingsTable, TierTable, UserTable, PromoTable, EventComparisonTable). Sorting is component-owned and uncontrolled after defaultSort; a controlled onSort is not yet exposed (future iteration). The selectAllLabel / selectRowLabel defaults are FR — EN consumers pass their own templates; this matches the house convention for DataTable + AdminSortableTable (the other three Admin* wrappers default to EN).

Tableau générique typé (TypeScript generic) utilisé pour les listes de tableaux de bord. FR-CA : en-têtes triables, alignement à droite pour les nombres, badge de statut sémantique.

DataTable

Organism — Admin family (Family 9)

Composant générique <T> qui sérialise n'importe quel tableau d'objets. Chaque colonne peut spécifier son alignement, sa largeur CSS et une fonction `format` pour personnaliser le rendu des cellules. Table canonique du design system : les cinq Admin*Table et les cinq tables de domaine (BookingsTable, TierTable, UserTable, PromoTable, EventComparisonTable) la composent.

Réservations — 7 derniers jours
Événement
Marie TremblayFestival Jazz MTL96,00 $
Jean-Pierre GauthierOSM Symphonie n° 5288,00 $
Sophie LavoieLion d'Or Gala54,00 $

This component supports light and dark mode.

Props

Prop
Type
Default
Description
rows*
T[]
—

Lignes du tableau.

columns*
DataTableColumn<T>[]
—

Définition des colonnes : key, header, align, width, format, sortable, numeric, hideBelow, sortValue, sortLabel, headerSrOnly.

caption*
string
—

Légende accessible (sr-only par défaut) — sert aussi de nom au conteneur défilant.

emptyMessage
string
Aucune donnée.

Message si rows est vide (rangée role="status" aria-live="polite").

ariaLabel
string
—

aria-label du conteneur et du <table>. Retombe sur la légende.

className
string
—

Classes CSS additionnelles ajoutées au conteneur défilant.

id
string
—

id du conteneur défilant (branchement aria-controls).

captionVisible
boolean
false

Affiche la légende à l’écran (sinon sr-only).

rowGroups
DataTableRowGroup<T>[]
—

Mode groupé : rangée d’en-tête th scope="rowgroup" pleine largeur avant chaque groupe (le tri par colonne est désactivé).

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

Clé React stable par ligne (défaut : index).

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

Classe par ligne (ex. teinte de mise en évidence).

defaultSort
{ key: string; direction: "asc" | "desc" }
—

État de tri initial (non contrôlé ; cycle null→asc→desc→null).

onRowClick
(row: T) => void
—

Rangées interactives : <tr> focalisable, activation Entrée/Espace — le rôle « row » est conservé.

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

Navigation par ligne : ancre réelle étirée sur toute la rangée (première cellule).

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

Crochet SPA pour les clics simples ; les clics modifiés restent au navigateur.

selection
DataTableSelection
—

Colonne de cases à cocher : selectedKeys, allSelected, someSelected, onToggleRow, onToggleAll, selectAllLabel (défaut « Tout sélectionner »), selectRowLabel.

Props

Preview

DataTable

Prop
Type
Required
Default
Description
rows
T[] (T extends Record<string, unknown>)
Required
—

Row data — typed by the generic. Drives the empty-state check.

columns
DataTableColumn<T>[]
Required
—

Column config — key, header, optional align/width/format/sortable/numeric/hideBelow/sortValue/sortLabel/headerSrOnly. UI Designer rule: columns are NON-sortable by default (set sortable: true to opt in); action / status / pass 'Used By' columns must opt out.

caption
string
Required
—

Accessible caption. sr-only by default — pass captionVisible to render for sighted users. Doubles as the scroll-region aria-label fallback.

emptyMessage
string
Optional
'Aucune donnée.'

Empty-state cell copy (role=status / aria-live=polite). FR default — EN consumers pass 'No data to display'.

ariaLabel
string
Optional
—

aria-label for the scroll wrapper and the <table>. Falls back to the caption.

className
string
Optional
—

Additional CSS classes appended to the scroll wrapper.

id
string
Optional
—

id on the scroll wrapper (useful for aria-controls wiring from a parent filter/search).

captionVisible
boolean
Optional
false

Render the caption for sighted users (instead of the sr-only default). Used by AdminDataTable / AdminBulkActionTable when a visible caption is wanted.

rowGroups
readonly DataTableRowGroup<T>[]
Optional
—

Grouped mode — each group gets a full-width <th scope='rowgroup'> before its rows, in caller order. Per-column sorting is bypassed (group order is caller-defined); rows keeps serving as the flat fallback and drives the empty-state check.

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

Stable React key per row. Defaults to the index — pass a domain id for selection / highlighting to survive re-sorts.

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

Optional per-row className (e.g. highlight tint on featured rows, used by TierTable).

defaultSort
{ key: string; direction: 'asc' | 'desc' }
Optional
—

Initial sort state (uncontrolled; the cycle starts from here and component-owns afterwards).

onRowClick
(row: T) => void
Optional
—

Interactive rows — the <tr> becomes focusable (tabIndex=0) and fires on Enter / Space / click. The row KEEPS its row role (no role='button' swap) so cell semantics survive. Mutually independent from rowHref.

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

Destination for a row. When supplied, the FIRST data cell renders 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 come from a genuine link rather than a click handler on <tr>. 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.

selection
DataTableSelection
Optional
—

Leading checkbox column for bulk selection — selectedKeys, allSelected, someSelected, onToggleRow, onToggleAll. selectAllLabel defaults to 'Tout sélectionner' (FR) and selectRowLabel to `(key) => 'Sélectionner la ligne ${key}'` — EN consumers pass their own templates. Per-row aria-selected + highlight are derived from the keys.

density
'comfortable' | 'compact'
Optional
'comfortable'

Row padding density — compact switches the per-cell padding to a tighter rhythm (AdminSortableTable density='compact' plumbs through here).

Quick use:<DataTable />

Accessibility

ARIA roletable

ARIA

3 rules

WCAG 1.3.1

<caption> is sr-only unless captionVisible (composes DataTable's empty-state role='status' / aria-live='polite'); every <th> renders scope='col'; sortable <th> declares aria-sort ('ascending' | 'descending' | 'none') and non-sortable <th> omits it (WCAG 1.3.1)

WCAG 2.1.1

Sortable header is a real <button> — Enter / Space cycle the sort state; when onRowClick is set the <tr> becomes focusable (tabIndex=0, Enter/Space activation) and KEEPS its row role so cell semantics survive (WCAG 2.1.1)

WCAG 4.1.2

Scroll wrapper is role='region' with aria-label (falls back to caption) and is focusable for keyboard scroll; selection column checkboxes have unique accessible labels (selectAllLabel / selectRowLabel)

Type

1 rule

NASA Rule 2 + 5

columns bounded 1-8 (NASA Rule 2)

Usage

Import

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

Example 1

tsxSnippet
<DataTable rows={rows} columns={columns} caption="Réservations — 7 derniers jours" />

Example 2

tsxSnippet
<DataTable rows={rows} columns={columns} caption="Bookings" captionVisible rowHref={(r) => `/bookings/${r.id}`} />

Example 3

tsxSnippet
<DataTable rows={rows} columns={columns} caption="Bulk actions" selection={{ selectedKeys, allSelected, someSelected, onToggleRow, onToggleAll }} selectAllLabel="Select all rows" />

Anti-patterns

— Do not fetch data directly in organisms

— Avoid tight coupling to specific data sources

— Do not swap <tr> role='row' for role='button' to make rows interactive — onRowClick keeps the row role so cell semantics survive

— Do not format rich nodes inside `format` without also providing sortValue when the column is sortable — the comparator cannot rank JSX

— Do not stack headerSrOnly on a sortable column — sort button needs a visible label

Design System v4.1.0 | ePassLive