DataTable¶
Generic table component for admin and list views. Handles sortable columns, row actions, pagination, and selection without re-implementing table UI per entity.
Quick Start¶
import { DataTable } from "@/components/ui/DataTable";
import { Pencil, Trash2 } from "lucide-react";
const headers: DataTableHeader<Product>[] = [
{ key: "name", label: "Name" },
{ key: "status", label: "Status", align: "center", width: "120px" },
];
const cells: DataTableCell<Product>[] = [
{ content: (row) => row.name },
{
align: "center",
dataCell: (row) => <StatusBadge status={row.status} />,
},
];
<DataTable
data={products}
headers={headers}
cells={cells}
onRowClick={(e, row) => router.push(`/products/${row.id}`)}
/>
Note
DataTable requires each data item to have an id: string field.
DataTableHeader<T>¶
Defines a column header:
| Field | Type | Description |
|---|---|---|
key |
string |
Unique column key |
label |
string |
Display text |
align |
"left" \| "center" \| "right" |
Header text alignment |
width |
string |
CSS width (e.g. "120px", "20%") |
sortIcon |
ReactNode |
Sort direction indicator |
onClick |
() => void |
Called when header is clicked (for sort) |
onCheckedChange |
(checked: boolean) => void |
Select-all checkbox callback |
checked |
boolean |
Select-all checkbox state |
headerCell |
() => ReactNode |
Fully custom header cell renderer |
DataTableCell<T>¶
Defines a data cell in the same column position:
| Field | Type | Description |
|---|---|---|
content |
string \| ((row: T) => ReactNode) |
Cell content (string or renderer) |
align |
"left" \| "center" \| "right" |
Cell content alignment |
className |
string |
Additional cell class |
dataCell |
(row: T) => ReactNode |
Fully custom cell renderer (overrides content) |
onCheckedChange |
(row: T, checked: boolean) => void |
Per-row checkbox callback |
checked |
(row: T) => boolean |
Per-row checkbox state |
actions |
DataTableActionsConfig<T> |
Inline actions dropdown (see below) |
Row Actions¶
Pass an actions config on the last cell to render a ⋯ dropdown:
const cells: DataTableCell<Product>[] = [
{ content: (row) => row.name },
{
actions: {
onEdit: (row) => router.push(`/products/${row.id}`),
onDelete: (row) => deleteProduct(row.id),
customActions: [
{
key: "archive",
label: "Archive",
icon: Archive,
onClick: (row) => archiveProduct(row.id),
},
],
},
},
];
DataTableActionsConfig<T>¶
| Field | Type | Description |
|---|---|---|
onEdit |
(row: T) => void |
Wires the built-in "Edit" action |
onDelete |
(row: T) => void |
Wires the built-in "Delete" action |
customActions |
DataTableAction<T>[] |
Additional actions (see below) |
triggerClassName |
string |
Class on the dropdown trigger |
menuWidth |
string |
Dropdown menu width |
hideCopy |
boolean |
Hide the built-in "Copy ID" action |
DataTableAction<T>¶
| Field | Type | Description |
|---|---|---|
key |
string |
Unique key |
label |
string |
Display label |
icon |
LucideIcon |
Item icon |
onClick |
(row: T) => void |
Click handler |
getLabel |
(row: T) => string |
Dynamic label |
getIcon |
(row: T) => LucideIcon |
Dynamic icon |
visible |
(row: T) => boolean |
Conditionally show |
disabled |
(row: T) => boolean |
Conditionally disable |
variant |
"default" \| "destructive" |
Destructive items render in red |
DataTable Component Props¶
| Prop | Type | Description |
|---|---|---|
data |
T[] |
Row data (items must have id: string) |
headers |
DataTableHeader<T>[] |
Column header definitions |
cells |
DataTableCell<T>[] |
Cell definitions (parallel array to headers) |
onRowClick |
(e: MouseEvent, row: T) => void |
Row click handler |
gridTemplateColumns |
string |
CSS grid template columns (auto-generated if omitted) |
scrollable |
boolean |
Sticky header + scrollable body |
pinnedData |
T[] |
Rows pinned to the top (visually distinct) |
firstCellPadding |
boolean |
Extra left padding on the first cell |
DataTablePagination¶
Standalone pagination controls. Wire to any list hook:
import { DataTablePagination } from "@/components/ui/DataTable";
<DataTablePagination
currentPage={currentPage}
totalPages={totalPages}
pageSize={pageSize}
totalCount={totalCount}
pageCount={totalPages}
onPageChange={setCurrentPage}
onPageSizeChange={setPageSize}
/>
DataTableSkeleton¶
Loading placeholder that matches the table layout:
import { DataTableSkeleton } from "@/components/ui/DataTable";
if (isLoading) return <DataTableSkeleton />;
Pinned Rows¶
Pass items in pinnedData to pin them at the top of the table with a visual highlight. Useful for "recently created" items:
<DataTable
data={subjects}
pinnedData={recentlyCopied ? [recentlyCopied] : []}
headers={headers}
cells={cells}
/>
Source Files¶
| File | Contents |
|---|---|
src/components/ui/DataTable/DataTable.tsx |
Main table component |
src/components/ui/DataTable/types.ts |
DataTableHeader, DataTableCell, DataTableAction, DataTableActionsConfig |
src/components/ui/DataTable/DataTablePagination.tsx |
Pagination controls |
src/components/ui/DataTable/DataTableSkeleton.tsx |
Loading skeleton |
src/components/ui/DataTable/DataTableActionsDropdown.tsx |
Row actions dropdown |
src/components/ui/DataTable/index.ts |
Public exports |