UNPKG

@payfit/unity-components

Version:

238 lines (210 loc) 6.74 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 { createColumnHelper } from '@tanstack/react-table' const columnHelper = createColumnHelper<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: 'arrIncludesSome', 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 `pageCount`. ```tsx import { useMemo, useState } from 'react' import { getCoreRowModel, useReactTable } 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 table = useReactTable({ data, columns, manualPagination: true, pageCount: Math.ceil(totalCount / pagination.pageSize), state: { pagination }, onPaginationChange: setPagination, getCoreRowModel: getCoreRowModel(), }) 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 { getFilteredRowModel } 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 [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]) const table = useReactTable({ data, columns, state: { columnFilters, pagination }, onColumnFiltersChange: setColumnFilters, onPaginationChange: setPagination, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), }) return ( <> <FilterToolbar filterDefs={filterDefs} onChange={applied => 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 [rowSelection, setRowSelection] = useState({}) const columns = useMemo(() => [checkboxColumn, ...employeeColumns], []) const table = useReactTable({ data, columns, state: { rowSelection, pagination }, onRowSelectionChange: setRowSelection, onPaginationChange: setPagination, enableRowSelection: true, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), }) 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> ) } ```