8.4 KiB
DataTable
(no submodules)
- data-table.variants — `dataTableVariants`, `dataTab…-
data-table.variants —
dataTableVariants,dataTablePaginationVariants, anddataTableSortHeaderVariantsdrive the chassis<DataTable>o… -
data-table.variants —
dataTableVariants, `dataTab… -
data-table.variants —
dataTableVariants,dataTablePaginationVariants, anddataTableSortHeaderVariantsdrive the chassis<DataTable>o… -
data-table.variants —
dataTableVariants, `dataTab… -
data-table.variants —
dataTableVariants,dataTablePaginationVariants, anddataTableSortHeaderVariantsdrive the chassis<DataTable>o… -
data-table.variants —
dataTableVariants, `dataTab… -
data-table.variants —
dataTableVariants,dataTablePaginationVariants, anddataTableSortHeaderVariantsdrive the chassis<DataTable>o… -
data-table.variants —
dataTableVariants,dataTablePaginationVariants, anddataTableSortHeaderVariantsdrive the chassis<DataTable>o…
Purpose
<DataTable> is the chassis headless data-table built on @tanstack/react-table (v8) and the chassis <Table> family. It is a suaveplan-only addition — shadcn does not ship data-table as a registry item upstream (per Wave 3a's reconciliation finding #7); shadcn's docs treat data-tables as recipes consumers build on top of the <Table> primitive. The chassis exposes <DataTable> as a discrete component so consumers do not hand-roll sorting / pagination / selection / empty-state treatment per surface.
The component is generic over TData (the row shape) and TValue (the column value union, defaults to unknown). Consumers build column definitions with the re-exported createColumnHelper from @tanstack/react-table. Every chassis-rendered string (no-results, pagination, rows-per-page, selection summary) flows through the active i18n via t() calls keyed on bundled react-ui.dataTable.* translation keys. The selected key uses ICU plural so the count agrees with the active locale's plural rules — Welsh's six-form scale is bundled.
Features
- Built on TanStack Table v8 — the de-facto headless table for React.
- Sortable columns — click any sortable column header to toggle asc / desc / none; the header button shows a chevron icon indicating the active sort direction.
- Pagination —
pageSizedefaults to 10 rows; previous / next buttons disable at the boundaries. - Row selection (opt-in via
enableRowSelection) — chassis renders a selection summary using ICU plural translation. - Empty state — when no rows match, the chassis renders the bundled
react-ui.dataTable.noResultsmessage in a single full-width row. - Stable row keys via
getRowId(recommended) — without it, TanStack uses array-index keys which collide on re-order. data-state="selected"on selected rows so chassis<TableRow>hover / selected styling activates automatically.- Re-exports
createColumnHelperso consumers don't need to add@tanstack/react-tableas a direct dep.
Usage: Basic Example
import { createColumnHelper, DataTable } from "@suaveplan/react-ui";
interface Invoice {
readonly id: string;
readonly invoice: string;
readonly status: string;
readonly amount: number;
}
const columnHelper = createColumnHelper<Invoice>();
const COLUMNS = [
columnHelper.accessor("invoice", { header: "Invoice" }),
columnHelper.accessor("status", { header: "Status" }),
columnHelper.accessor("amount", {
header: "Amount",
cell: (info) => `$${info.getValue().toFixed(2)}`,
}),
];
export function InvoiceDataTable({ data }: { data: Invoice[] }) {
return (
<DataTable<Invoice>
columns={COLUMNS}
data={data}
getRowId={(row) => row.id}
/>
);
}
Usage: Advanced Example
import { createColumnHelper, DataTable } from "@suaveplan/react-ui";
interface User {
readonly id: string;
readonly name: string;
readonly email: string;
readonly role: "admin" | "member";
}
const columnHelper = createColumnHelper<User>();
const COLUMNS = [
columnHelper.display({
id: "select",
header: "",
cell: ({ row }) => (
<input
type="checkbox"
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
aria-label="select-row"
/>
),
}),
columnHelper.accessor("name", { header: "Name", enableSorting: true }),
columnHelper.accessor("email", { header: "Email", enableSorting: false }),
columnHelper.accessor("role", { header: "Role", enableSorting: true }),
];
export function UserDataTable({ users }: { users: User[] }) {
return (
<DataTable<User>
columns={COLUMNS}
data={users}
getRowId={(row) => row.id}
enableRowSelection
pageSize={5}
initialSorting={[{ id: "name", desc: false }]}
/>
);
}
API Reference
<DataTable<TData, TValue = unknown>>
| Prop | Type | Default | Description |
|---|---|---|---|
columns |
ReadonlyArray<ColumnDef<TData, TValue>> |
— | Column definitions; build with createColumnHelper<TData>(). |
data |
ReadonlyArray<TData> |
— | The rows to render. Must be stable identity-wise (use useMemo on the consumer side). |
getRowId |
(row: TData, index: number) => string |
undefined | Stable row id for selection / keying. Recommended. |
enableRowSelection |
boolean |
false |
When true, renders a selection summary using ICU plural translation. |
pageSize |
number |
10 |
Rows per page. |
initialSorting |
SortingState |
[] |
Initial sort state. |
noResultsKey |
TranslationKey |
react-ui.dataTable.noResults |
Empty-state message. |
rowsPerPageKey |
TranslationKey |
react-ui.dataTable.rowsPerPage |
Pagination footer label. |
selectedKey |
TranslationKey |
react-ui.dataTable.selected |
ICU-plural selection summary; receives { count }. |
previousKey |
TranslationKey |
react-ui.dataTable.previous |
Previous-page button label. |
nextKey |
TranslationKey |
react-ui.dataTable.next |
Next-page button label. |
createColumnHelper<TData>()
Re-exported from @tanstack/react-table. Returns a builder with accessor, display, and group methods for typed column definitions.
Errors
The DataTable component calls useTranslation() to resolve the chassis-rendered translation keys. It MUST render inside a <TranslationProvider> / <ReactUiProvider>; otherwise useTranslation throws.
Implementation Notes
- The component is generic over
TDataandTValueso consumers get full type-safety on column definitions and cell renderers. The cast toColumnDef<TData, TValue>[]insideuseReactTableis a TanStack ergonomics workaround — TanStack'sdataacceptsTData[]but itscolumnsslot accepts a value-erased shape, so the chassis re-asserts the typed shape at the boundary. - The
selectedplural translation includes the Welsh six-form scale (zero | one | two | few | many | other) so the count agrees with the active locale's plural rules. Other locales (en-GB,zh-CN,ar-AE,ru-RU) use their respective plural categories from the bundled JSON. getRowIdis strongly recommended even whendataitems contain anidfield — without a stable getter, TanStack uses array index keys, which collide when consumers reorder the data array. The chassis does NOT defaultgetRowIdto(row) => row.idbecause not everyTDatashape has anidfield.- The pagination controls are intentionally bare
<button>elements withborder px-2 py-1— this keeps the data-table self-contained without forcing a<Button>import. Consumers wanting fancier pagination compose their own footer instead of using the built-in one (read pagination state fromtable.getState().paginationand calltable.setPageIndex/table.setPageSizedirectly). - The sort indicator chevron icon is
aria-hidden="true"because the column header text already conveys the sort target; screen-readers should not double-announce. - Empty state spans
colSpan={columns.length}so the no-results message centres across the full table width regardless of column count.