Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,734 changes: 1,294 additions & 440 deletions apps/www/components/ui/data-table.tsx

Large diffs are not rendered by default.

240 changes: 133 additions & 107 deletions apps/www/config/components.ts

Large diffs are not rendered by default.

1,042 changes: 159 additions & 883 deletions apps/www/config/demos.tsx

Large diffs are not rendered by default.

19 changes: 12 additions & 7 deletions apps/www/config/docs-scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,17 +648,22 @@ export const docsScenarios: Record<string, ComponentDocs> = {
},
"data-table": {
"slug": "data-table",
"overview": "DataTable renders a fully-featured table with optional client-side sorting, pagination with configurable page sizes, and sticky column freezing on the left and/or right edges. Theming and all colour tokens are customisable.",
"overview": "DataTable is a typed, zero-dependency data grid built for 10 to 100,000 rows. It auto-switches between two engines: a hand-rolled virtualized engine that windows rows for large flat data, and a standard engine that supports row grouping (groupBy) and rowspan merging. Search, single and multi sort (shift-click), multi-select with a filtered-aware select-all, row expansion, frozen columns, column groups, pinned rows, and sticky headers work across engines. Presets (basic / advanced / enterprise) bundle feature defaults; any explicit prop wins.",
"scenarios": [
{
"title": "Sortable team roster",
"description": "Display a team list with column-level sorting so managers can quickly find members by name or role.",
"code": "\"use client\";\nimport { DataTable } from \"@/components/ui/data-table\";\n\nconst columns = [\n { key: \"name\", label: \"Name\", sortKey: \"name\" },\n { key: \"department\", label: \"Department\", sortKey: \"department\" },\n { key: \"joined\", label: \"Joined\", sortKey: \"joined\" },\n];\n\nconst data = [\n { id: \"1\", name: \"Alice Chen\", department: \"Engineering\", joined: \"2023-01\" },\n { id: \"2\", name: \"Bob Martin\", department: \"Design\", joined: \"2022-08\" },\n { id: \"3\", name: \"Carol White\", department: \"Product\", joined: \"2021-11\" },\n { id: \"4\", name: \"Dan Brown\", department: \"Engineering\", joined: \"2024-03\" },\n];\n\nexport default function TeamRoster() {\n return (\n <div className=\"p-6\">\n <DataTable columns={columns} data={data} sortable border theme=\"dark\" />\n </div>\n );\n}"
"title": "100k-row event log",
"description": "Render an unbounded event stream with windowed rows — only the visible slice is in the DOM, and aria-rowcount still reports the true filtered total.",
"code": "\"use client\";\nimport { useMemo } from \"react\";\nimport { DataTable, type DataTableColumn } from \"@/components/ui/data-table\";\n\ninterface EventRow {\n id: string;\n event: string;\n service: string;\n latency: number;\n}\n\nconst columns: DataTableColumn<EventRow>[] = [\n { id: \"event\", header: \"Event\", accessor: (row) => row.event },\n { id: \"service\", header: \"Service\", accessor: (row) => row.service },\n { id: \"latency\", header: \"Latency (ms)\", accessor: (row) => row.latency, align: \"right\" },\n];\n\nexport default function EventLog() {\n const data = useMemo<EventRow[]>(\n () =>\n Array.from({ length: 100000 }, (_, i) => ({\n id: `evt-${i}`,\n event: `Event ${i + 1}`,\n service: [\"api\", \"web\", \"worker\"][i % 3],\n latency: ((i * 37) % 900) + 12,\n })),\n []\n );\n return (\n <DataTable\n data={data}\n columns={columns}\n getRowId={(row) => row.id}\n preset=\"enterprise\"\n maxHeight={480}\n theme=\"dark\"\n />\n );\n}"
},
{
"title": "Paginated transaction log",
"description": "Handle large datasets with pagination by using paginated, pageSize, and pageSizeOptions props.",
"code": "\"use client\";\nimport { DataTable } from \"@/components/ui/data-table\";\n\nconst columns = [\n { key: \"txn\", label: \"Txn ID\" },\n { key: \"amount\", label: \"Amount\", sortKey: \"amount\" },\n { key: \"status\", label: \"Status\" },\n { key: \"date\", label: \"Date\", sortKey: \"date\" },\n];\n\nconst data = Array.from({ length: 20 }, (_, i) => ({\n id: String(i + 1),\n txn: \"TXN-\" + String(i + 1).padStart(4, \"0\"),\n amount: \"$\" + ((i + 1) * 47).toFixed(2),\n status: i % 3 === 0 ? \"Pending\" : \"Completed\",\n date: \"2026-04-\" + String((i % 28) + 1).padStart(2, \"0\"),\n}));\n\nexport default function TransactionLog() {\n return (\n <div className=\"p-6\">\n <DataTable columns={columns} data={data} paginated pageSize={5} pageSizeOptions={[5, 10, 20]} sortable theme=\"dark\" />\n </div>\n );\n}"
"title": "Order management with selection and expansion",
"description": "Multi-select orders across pages (select-all targets every filtered row) and expand a row for details, with multi-sort via shift-click.",
"code": "\"use client\";\nimport { useState } from \"react\";\nimport { DataTable, type DataTableColumn } from \"@/components/ui/data-table\";\n\ninterface Order {\n id: string;\n order: string;\n customer: string;\n total: number;\n}\n\nconst data: Order[] = Array.from({ length: 40 }, (_, i) => ({\n id: `ord-${i}`,\n order: `#${1000 + i}`,\n customer: [\"Alex\", \"Sara\", \"Jordan\", \"Maya\"][i % 4],\n total: (i + 1) * 19.5,\n}));\n\nconst columns: DataTableColumn<Order>[] = [\n { id: \"order\", header: \"Order\", accessor: (row) => row.order },\n { id: \"customer\", header: \"Customer\", accessor: (row) => row.customer },\n { id: \"total\", header: \"Total\", accessor: (row) => row.total, cell: (row) => `$${row.total.toFixed(2)}`, align: \"right\" },\n];\n\nexport default function Orders() {\n const [selected, setSelected] = useState<string[]>([]);\n return (\n <DataTable\n data={data}\n columns={columns}\n getRowId={(row) => row.id}\n preset=\"advanced\"\n selectedIds={selected}\n onSelectionChange={setSelected}\n renderExpanded={(row) => <div>Details for {row.order}</div>}\n theme=\"dark\"\n />\n );\n}"
},
{
"title": "Quarterly report with grouped headers and a frozen name column",
"description": "Nested columns render a two-level header while the name column stays pinned during horizontal scroll.",
"code": "\"use client\";\nimport { DataTable, type DataTableColumn } from \"@/components/ui/data-table\";\n\ninterface ReportRow {\n id: string;\n name: string;\n q1: number;\n q2: number;\n q3: number;\n q4: number;\n}\n\nconst data: ReportRow[] = [\n { id: \"1\", name: \"Alex Kim\", q1: 42, q2: 51, q3: 48, q4: 60 },\n { id: \"2\", name: \"Sara Chen\", q1: 38, q2: 47, q3: 52, q4: 58 },\n];\n\nconst columns: DataTableColumn<ReportRow>[] = [\n { id: \"name\", header: \"Name\", accessor: (row) => row.name, freeze: \"left\", width: 140 },\n {\n id: \"h1\",\n header: \"H1\",\n accessor: () => null,\n columns: [\n { id: \"q1\", header: \"Q1\", accessor: (row) => row.q1, align: \"right\" },\n { id: \"q2\", header: \"Q2\", accessor: (row) => row.q2, align: \"right\" },\n ],\n },\n {\n id: \"h2\",\n header: \"H2\",\n accessor: () => null,\n columns: [\n { id: \"q3\", header: \"Q3\", accessor: (row) => row.q3, align: \"right\" },\n { id: \"q4\", header: \"Q4\", accessor: (row) => row.q4, align: \"right\" },\n ],\n },\n];\n\nexport default function Report() {\n return <DataTable data={data} columns={columns} getRowId={(row) => row.id} sortable border theme=\"dark\" />;\n}"
}
]
},
Expand Down
4 changes: 2 additions & 2 deletions apps/www/public/r/data-table.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/www/public/r/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@
"name": "data-table",
"type": "registry:ui",
"title": "Data Table",
"description": "Configurable data table with optional column freeze (left/right), customizable header and body colors and backgrounds, optional borders, and optional sortable columns.",
"description": "Typed, zero-dependency data grid that auto-switches between a hand-rolled virtualized engine (10 to 100,000 rows) and a standard engine with row grouping and rowspan. Search, single and multi sort, multi-select, row expansion, frozen columns, column groups, pinned rows, and feature presets.",
"dependencies": [
"lucide-react",
"clsx",
Expand Down
39 changes: 29 additions & 10 deletions apps/www/public/registry.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions apps/www/public/registry/changelogs.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,20 @@
}
],
"data-table": [
{
"version": "2.0.0",
"date": "2026-06-11",
"changes": [
"BREAKING: columns are now typed definitions ({ id, header, accessor, cell?, ... }) and rows are your own typed objects — Record<string, ReactNode> cells and { key, label, sortKey } columns are gone.",
"BREAKING: getRowId is required; freezeColumns/freezeCount/freezeLeftCount/freezeRightCount are replaced by per-column freeze: \"left\" | \"right\"; initialPage/onPageChange/getRowKey/paginationPreviousLabel/paginationNextLabel were removed.",
"Hand-rolled row virtualization (windowing) handles up to 100,000 rows; the engine auto-switches to a standard renderer when groupBy or rowSpan is used.",
"New: global debounced search, typed multi-sort with shift-click, multi-select with filtered-aware select-all, row expansion, row grouping with collapsible headers, rowspan merging, column groups (multi-level headers), pinned rows, sticky header, and basic/advanced/enterprise presets.",
"Accessibility: aria-sort, aria-selected, aria-expanded, labelled controls, and true aria-rowcount/aria-rowindex under windowing.",
"Accessibility: multi-sort priority is now announced to assistive tech via an sr-only label instead of being hidden.",
"The root element forwards arbitrary div props (id, style, role, aria-*, data-*).",
"Fixed virtual spacer math and select-all to stay correct when pinned rows, expansion, or active search are combined, and row spans now align with grouped render order."
]
},
{
"version": "1.0.0",
"date": "2026-05-20",
Expand Down
29 changes: 24 additions & 5 deletions apps/www/public/registry/data-table.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/www/public/registry/dot-grid-background.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@
"shooting-stars-grid",
"hero-noise-dot-field",
"bento-grid",
"aurora-background",
"glow-hero-section"
"data-table",
"aurora-background"
],
"usedByBlocks": [
"hero-noise-dot-field"
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/registry/particle-field.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"screenReaderNotes": "Decorative background; should be aria-hidden."
},
"relatedSlugs": [
"shooting-stars-grid",
"ripple",
"hero-flow-field",
"hero-noise-dot-field",
"aurora-background",
Expand Down
4 changes: 2 additions & 2 deletions apps/www/public/registry/shooting-stars-grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@
"relatedSlugs": [
"dot-grid-background",
"bento-grid",
"data-table",
"aurora-background",
"glow-hero-section",
"meteors-card",
"particle-field"
"meteors-card"
],
"motion": {
"reducedMotion": "full"
Expand Down
Loading
Loading