Data Table

import { DataTable, DataTableColumnHeader } from '@hanzo/ui'

Basic

One column per field, read straight off the row by `accessorKey`.

Status
Email
Amount
processing
125
0 of 5 row(s) selected.
export function Basic() {
const columns: DataTableColumnDef<Payment>[] = [
{ id: "status", accessorKey: "status", header: "Status" },
{ id: "email", accessorKey: "email", header: "Email" },
{ id: "amount", accessorKey: "amount", header: "Amount", align: "right" },
]
return <DataTable columns={columns} data={payments} />
}

Sortable and filterable

A custom `cell` formats the amount and colors the status; `DataTableColumnHeader` gives the email column a click-to-sort button; the toolbar's text field searches it.

Status
pending
$100.00
processing
$125.00
success
$316.00
0 of 5 row(s) selected.
export function SortableFilterable() {
const columns: DataTableColumnDef<Payment>[] = [
{
id: "status",
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const status = row.getValue("status") as Payment["status"]
const variant = status === "failed" ? "destructive" : status === "success" ? "default" : "secondary"
return <Badge variant={variant}>{status}</Badge>
},
},
{
id: "email",
accessorKey: "email",
header: ({ column }) => <DataTableColumnHeader column={column} title="Email" />,
},
{
id: "amount",
accessorKey: "amount",
header: ({ column }) => <DataTableColumnHeader column={column} title="Amount" />,
align: "right",
cell: ({ row }) =>
new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(row.getValue("amount") as number),
},
]
return <DataTable columns={columns} data={payments} filterColumnId="email" filterPlaceholder="Filter emails…" pageSize={3} />
}

Selectable with row actions

An `id: 'select'` column gets its checkbox for free; an `id: 'actions'` column is just a normal `cell` reading `row.original`, the same convention the upstream guide's own demo uses.

Email
Amount
actions
0 of 5 row(s) selected.
export function SelectableWithActions() {
const columns: DataTableColumnDef<Payment>[] = [
{ id: "select", enableSorting: false, enableHiding: false },
{ id: "email", accessorKey: "email", header: "Email" },
{ id: "amount", accessorKey: "amount", header: "Amount", align: "right" },
{
id: "actions",
enableHiding: false,
cell: ({ row }) => (
<Button variant="ghost" size="sm" onClick={() => navigator.clipboard?.writeText(row.original.id)}>
Copy ID
</Button>
),
},
]
return <DataTable columns={columns} data={payments} />
}

Types

export type DataTableAlign = 'left' | 'center' | 'right'
export type DataTableSortDirection = 'asc' | 'desc' | false
export interface DataTableColumnApi<TData> {
readonly id: string
getCanSort: () => boolean
getIsSorted: () => DataTableSortDirection
toggleSorting: (desc?: boolean) => void
getCanHide: () => boolean
getIsVisible: () => boolean
toggleVisibility: (visible?: boolean) => void
}
export interface DataTableRow<TData> {
readonly id: string
readonly original: TData
getValue: (columnId: string) => unknown
getIsSelected: () => boolean
toggleSelected: (value?: boolean) => void
}
export interface DataTableColumnDef<TData> {
id: string
/** Read directly off the row when no `cell` is given. */
accessorKey?: keyof TData
header?: ReactNode | ((ctx: { column: DataTableColumnApi<TData> }) => ReactNode)
cell?: (ctx: { row: DataTableRow<TData> }) => ReactNode
/** A CSS grid track for this column. Defaults to `1fr`. */
width?: number | string
align?: DataTableAlign
enableSorting?: boolean
enableHiding?: boolean
/** The value compared when this column is the active sort. Defaults to `accessorKey`. */
sortingValue?: (row: TData) => string | number
/** The text searched when this column is the active filter. Defaults to `accessorKey`. */
filterValue?: (row: TData) => string
}
export interface DataTableInstance<TData> {
getColumn: (id: string) => DataTableColumnApi<TData> | undefined
getAllColumns: () => DataTableColumnApi<TData>[]
getRowModel: () => { rows: DataTableRow<TData>[] }
getFilteredRowModel: () => { rows: DataTableRow<TData>[] }
getFilteredSelectedRowModel: () => { rows: DataTableRow<TData>[] }
getIsAllPageRowsSelected: () => boolean
getIsSomePageRowsSelected: () => boolean
toggleAllPageRowsSelected: (value?: boolean) => void
getCanPreviousPage: () => boolean
getCanNextPage: () => boolean
previousPage: () => void
nextPage: () => void
getPageCount: () => number
}
export interface DataTableProps<TData> {
columns: DataTableColumnDef<TData>[]
data: TData[]
/** Identifies a row for selection/sort stability. Defaults to its index. */
getRowId?: (row: TData, index: number) => string
/** The column id the toolbar's text field searches. Omit to hide the field. */
filterColumnId?: string
filterPlaceholder?: string
/** Rows per page. `0` (the default) shows every row with no pager. */
pageSize?: number
emptyMessage?: ReactNode
onRowSelectionChange?: (rows: TData[]) => void
}
export interface DataTableColumnHeaderProps<TData> {
column: DataTableColumnApi<TData>
title: ReactNode
className?: string
}

Exports

DataTableDataTableColumnHeadertype DataTableColumnApitype DataTableColumnDeftype DataTableColumnHeaderPropstype DataTableInstancetype DataTablePropstype DataTableRowtype DataTableSortDirection