# 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>
  )
}
```
