UNPKG

@payfit/unity-components

Version:

257 lines (230 loc) 6.8 kB
# Unity DataTable patterns ### Define columns with the column helper and ColumnMeta ColumnMeta drives accessibility (`isRowHeader`), keyboard navigation (`isFocusable: false` for cells whose children are themselves focusable like checkboxes), `helperText` (renders a tooltip next to the header), and `headerClassName` (required when `layout="fixed"`). ```tsx import { Badge } from '@payfit/unity-components' import { columnFilteringFeature, createColumnHelper, createFilteredRowModel, createSortedRowModel, filterFn_equalsString, rowSortingFeature, tableFeatures, } from '@tanstack/react-table' const features = tableFeatures({ columnFilteringFeature, filteredRowModel: createFilteredRowModel(), rowSortingFeature, sortedRowModel: createSortedRowModel(), }) const columnHelper = createColumnHelper<typeof features, Employee>() export const employeeColumns = [ columnHelper.accessor('name', { id: 'employee', header: 'Employee', enableSorting: true, meta: { isRowHeader: true, headerClassName: 'uy:w-[260px]' }, }), columnHelper.accessor('status', { header: 'Status', enableColumnFilter: true, filterFn: filterFn_equalsString, cell: info => { const status = info.getValue() return ( <Badge variant={status === 'active' ? 'success' : 'neutral'}> {status} </Badge> ) }, meta: { helperText: 'Active employees can sign in.' }, }), columnHelper.display({ id: 'actions', header: '', cell: ({ row }) => <RowMenu row={row.original} />, meta: { isFocusable: false }, }), ] ``` ### Server-side pagination `manualPagination: true` disables Tanstack's internal slicing. Pass only the current page's slice as `data` and supply `rowCount` or `pageCount`. ```tsx import { useMemo, useState } from 'react' import { rowPaginationFeature, tableFeatures, useTable, } from '@tanstack/react-table' export function ServerTable({ totalCount, fetchPage }: Props) { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 20 }) const { data: pageRows = [] } = useQuery({ queryKey: ['employees', pagination], queryFn: () => fetchPage(pagination.pageIndex, pagination.pageSize), }) const columns = useMemo(() => employeeColumns, []) const data = useMemo(() => pageRows, [pageRows]) const features = tableFeatures({ rowPaginationFeature }) const table = useTable({ features, data, columns, state: { pagination }, onPaginationChange: setPagination, manualPagination: true, rowCount: totalCount, }) return ( <DataTableRoot> <DataTable table={table}> {row => ( <TableRow key={row.id}> {row.getVisibleCells().map(cell => ( <TableCell key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </TableCell> ))} </TableRow> )} </DataTable> </DataTableRoot> ) } ``` ### Filtering with FilterToolbar `FilterToolbar.onChange` emits `SerializableAppliedFilter[]`. Map that onto `table.setColumnFilters` / `table.setGlobalFilter`. `renderControl` takes any control — it is a render function on purpose so you can drop in date pickers, multi-selects, text fields, etc. ```tsx import type { FilterDef } from '@payfit/unity-components' import { FilterToolbar, Select, SelectItem } from '@payfit/unity-components' import { columnFilteringFeature, createFilteredRowModel, tableFeatures, useTable, } from '@tanstack/react-table' const filterDefs: FilterDef[] = [ { id: 'status', label: 'Status', renderControl: (value, onChange) => ( <Select selectedKey={value as string} onSelectionChange={onChange}> <SelectItem id="active">Active</SelectItem> <SelectItem id="inactive">Inactive</SelectItem> </Select> ), renderLabel: value => String(value), }, ] export function FilteredTable() { const features = tableFeatures({ columnFilteringFeature, filteredRowModel: createFilteredRowModel(), }) const table = useTable({ features, data, columns, }) return ( <> <FilterToolbar filterDefs={filterDefs} onChange={applied => table.setColumnFilters( applied.map(f => ({ id: f.id, value: f.value })), ) } /> <DataTableRoot> <DataTable table={table}> {row => ( <TableRow key={row.id}> {row.getVisibleCells().map(cell => ( <TableCell key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </TableCell> ))} </TableRow> )} </DataTable> </DataTableRoot> </> ) } ``` ### Bulk actions with row selection `DataTableBulkActions` lives next to `DataTable` inside `DataTableRoot`. It auto-shows when rows are selected and provides the F6 focus trap. Use a display column with `meta.isFocusable: false` for the checkbox. ```tsx import { CheckboxStandalone, DataTable, DataTableBulkActions, DataTableRoot, } from '@payfit/unity-components' const checkboxColumn = columnHelper.display({ id: 'select', header: ({ table }) => ( <CheckboxStandalone isSelected={table.getIsAllPageRowsSelected()} isIndeterminate={table.getIsSomePageRowsSelected()} onChange={value => table.toggleAllPageRowsSelected(value)} slot="selection" > Select all </CheckboxStandalone> ), cell: ({ row }) => ( <CheckboxStandalone isSelected={row.getIsSelected()} onChange={value => row.toggleSelected(value)} slot="selection" > Select row </CheckboxStandalone> ), enableSorting: false, meta: { isFocusable: false }, }) export function BulkTable() { const columns = useMemo(() => [checkboxColumn, ...employeeColumns], []) const features = tableFeatures({ rowSelectionFeature, rowPaginationFeature }) const table = useTable({ features, data, columns, enableRowSelection: true, }) return ( <DataTableRoot> <DataTable table={table}> {row => ( <TableRow key={row.id} isSelected={row.getIsSelected()}> {row.getVisibleCells().map(cell => ( <TableCell key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </TableCell> ))} </TableRow> )} </DataTable> <DataTableBulkActions table={table} actions={[ { id: 'archive', label: 'Archive', onAction: rows => archive(rows) }, { id: 'delete', label: 'Delete', onAction: rows => remove(rows) }, ]} /> </DataTableRoot> ) } ```