Passer au contenu principal

AdminPaginatedTable

Implemented

Controlled-pagination admin data table — Wave-3 L5-M3 composition of the canonical DataTable organism with the Pagination molecule (first / prev / pages / next / last + page-size readout, hidden on a single page). Pagination is FULLY CONTROLLED: currentPage + onPageChange are required, the wrapper never owns page state, the page is clamped into [1, totalPages] and sliced out of the full rows array. The Pagination molecule hides itself on a single page so short tables don't show dead chrome. Exported from the barrel along with AdminPaginatedTableProps and AdminPaginatedTableColumn.

Categoryorganisms
Sourcecomponents/organisms/AdminPaginatedTable/AdminPaginatedTable.tsx
AI HintComposes the canonical DataTable organism + Pagination molecule. Use for server-driven pages where the consumer controls the current page (typically route state — shareable admin URLs via ?page=2). Pagination is fully controlled: currentPage + onPageChange are required and the wrapper never sets page state itself. Pass the FULL rows array — pre-slicing breaks the counter and the [1, totalPages] clamping. rowKey's second argument is the absolute pre-slice index, so keys stay stable as pages flip. EN-default i18n across paginationLabel / emptyMessage / ariaLabel / pageAnnouncement — FR consumers pass their own per the JSDoc recipes. Exported from the barrel along with AdminPaginatedTableProps and AdminPaginatedTableColumn. For storefront CRUD pages prefer ListPanel (ships its own lightweight native-button pagination) — reach for AdminPaginatedTable when the Pagination molecule's full UX (numbered pages, first/last, page-size readout) is required.

Controlled-pagination admin data table — a Wave-3 L5-M3 composition of the canonical DataTable organism with the Pagination molecule. The wrapper owns the page slicing while the consumer controls currentPage/onPageChange, mirroring server-driven pages; the Pagination molecule hides itself on a single page.

AdminPaginatedTable

Organism — Admin family

Composes DataTable (table chrome, empty state) with the Pagination molecule (first/prev/pages/next/last + page-size readout, hidden on a single page). The wrapper owns the slicing: pagination is CONTROLLED by the consumer (currentPage + onPageChange), clamped into [1, totalPages]; the table caption gains a 'page X of Y' suffix via ariaLabel.

Controlled pagination — the page lives in this component's state; use first / prev / pages / next / last below (5 rows per page, 23 total).

Producer payouts
ProducerAmountPaid
Spectacles XYZ$250.002026-08-01
Prod 4SAISON$287.002026-08-02
Billetterie MTL$324.002026-08-03
Agence Nord$361.002026-08-04
Spectacles XYZ$398.002026-08-05
Page 1 of 5

Current page: 1 of 5

This component supports light and dark mode.

Usage

Pagination is controlled, not owned — keep currentPage in the URL or parent state (shareable admin URLs), pass pageSize matching your API page size, and hand the full rows array over (the wrapper slices — pre-slicing breaks the counter and clamping).

import { useState } from 'react'
import { AdminPaginatedTable } from '@epasslive/design-system'

function PayoutsTable({ rows }: { rows: PayoutRow[] }) {
  const [page, setPage] = useState(1)
  return (
    <AdminPaginatedTable
      columns={columns}
      rows={rows}
      rowKey={(r) => r.id}
      pageSize={10}
      currentPage={page}
      onPageChange={setPage}
      ariaLabel="Producer payouts"
    />
  )
}

Conventions

onPageChange fires only on real moves — out-of-range or same-page requests are swallowed, so no guard is needed in your handler. rowKey indices are absolute (page offset added), so keys stay stable as pages flip. When a single page remains, the Pagination molecule removes itself — no dead chrome on short tables.

Accessibility

Pagination renders as a <nav> landmark with labeled first / previous / next / last controls, numbered page buttons and aria-current on the active page (WCAG 4.1.2). The table caption gains a "page X of Y" suffix, so screen readers announce position within the dataset when the page flips.

Props

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

Column definitions — see the column table below.

rows*
T[]
—

Full row data — the wrapper slices the current page out of it.

pageSize*
number
—

Rows per page (floored, minimum 1).

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

Stable React key per row — index is the ABSOLUTE pre-slice index.

currentPage*
number
—

Current page (1-indexed, controlled). Clamped into [1, totalPages].

onPageChange*
(page: number) => void
—

Controlled page-change callback — fired only for a valid, different page.

paginationLabel
string
"Table pagination"

Accessible label for the pagination nav landmark.

emptyMessage
string
"No data to display"

Rendered in DataTable's accessible empty state.

ariaLabel
string
"Paginated table"

Accessible name — the table caption also gains 'page X of Y'.

pageAnnouncement
string
"Page {page} of {total}"

Template for the sr-only aria-live announcement when the page changes — tokens {page} and {total} are substituted. EN default — FR: 'Page {page} sur {total}'.

className
string
—

Class on the outer section wrapper.

id
string
—

Stable id (auto-generated apg-<useId()> fallback).

Column definition

Prop
Type
Default
Description
id*
string
—

Column identifier.

header*
string
—

Header label.

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

Cell renderer — receives the row object.

width
string
—

CSS width for the column.

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

Cell + header alignment.

Props

Preview

AdminPaginatedTable

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

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

rows
T[]
Required
—

FULL row data — the wrapper slices the current page out of it. Do not pre-slice (breaks the counter and clamping).

pageSize
number
Required
—

Rows per page — floored, minimum 1.

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

Stable React key per row — index is the ABSOLUTE pre-slice index, so keys stay stable as pages flip.

currentPage
number
Required
—

Current page (1-indexed, CONTROLLED). Clamped into [1, totalPages].

onPageChange
(page: number) => void
Required
—

Controlled page-change callback — fires ONLY for a valid, different page (out-of-range / same-page requests are swallowed).

paginationLabel
string
Optional
'Table pagination'

Accessible label for the pagination <nav> landmark. EN default — FR: 'Pagination de la table'.

emptyMessage
string
Optional
'No data to display'

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

ariaLabel
string
Optional
'Paginated table'

Accessible name for the section and the table caption — the caption also gains 'page X of Y'. EN default — FR: 'Table paginée'.

pageAnnouncement
string
Optional
'Page {page} of {total}'

Template for the sr-only aria-live announcement when the page changes. Tokens: {page}, {total}. EN default — FR: 'Page {page} sur {total}'.

className
string
Optional
—

Class on the outer section wrapper.

id
string
Optional
apg-<useId()>

Stable id for the wrapper section (auto-generated apg-<useId()> fallback).

Quick use:<AdminPaginatedTable />

Accessibility

ARIA roletable

ARIA

2 rules

WCAG 4.1.2

Pagination renders as a <nav> landmark with labeled first / prev / next / last controls, numbered page buttons and aria-current='page' on the active button (WCAG 4.1.2); table caption gains a 'page X of Y' suffix via ariaLabel

WCAG 4.1.3

An sr-only role='status' / aria-live='polite' region announces page changes using the pageAnnouncement template ({page}/{total} tokens) so screen-reader users hear position within the dataset on flip (WCAG 4.1.3)

control

1 rule

ADR-035 + Wave-3 L5-M3 controlled contract

Fully controlled component — currentPage + onPageChange are REQUIRED; the wrapper never owns page state. Pre-slicing rows breaks the counter and clamping; pass the FULL rows array and let the wrapper slice

Usage

Import

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

Example 1

tsxSnippet
<AdminPaginatedTable columns={cols} rows={rows} rowKey={(r) => r.id} pageSize={10} currentPage={page} onPageChange={setPage} ariaLabel="Producer payouts" />

Example 2

tsxSnippet
<AdminPaginatedTable columns={cols} rows={commandes} rowKey={(r) => r.id} pageSize={10} currentPage={page} onPageChange={setPage} ariaLabel="Paiements producteurs" paginationLabel="Pagination des paiements" pageAnnouncement="Page {page} sur {total}" emptyMessage="Aucune donnée à afficher" />

Anti-patterns

— Do not fetch data directly in organisms

— Avoid tight coupling to specific data sources

— Do not pre-slice rows before passing them — the wrapper slices (pre-slicing breaks the counter and the [1, totalPages] clamping)

— Do not use this organism for client-side pagination of a small dataset that fits in memory — use AdminDataTable without a wrapper; pagination has UX cost (numbered pages, first/last)

— Do not try to control the page via local useState AND skip the onPageChange wiring — currentPage is the source of truth; pass it through

— Do not reach for this organism when you also need sorting or selection — those are canonical DataTable props the wrapper does not expose; compose DataTable + Pagination (or ListPanel) directly

Design System v4.1.0 | ePassLive