@payfit/unity-components
Version:
238 lines (210 loc) • 6.74 kB
Markdown
# 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>
)
}
```