Skip to content

Commit 8d3b156

Browse files
committed
docs: add design spec for UniversalDataGrid component (#611)
1 parent 810b5df commit 8d3b156

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# UniversalDataGrid Design
2+
3+
Implements issue #611: "Build a UniversalDataGrid Component" — a reusable
4+
enterprise-grade data grid with virtualization, sorting, filtering, column
5+
pinning, export, and keyboard accessibility.
6+
7+
## Architecture
8+
9+
Built on `@tanstack/react-table` (headless sorting/filtering/column-pinning
10+
state) and `@tanstack/react-virtual` (row virtualization). Both are new
11+
dependencies; `@tanstack/react-query` is already used elsewhere in this repo,
12+
so this keeps the grid ecosystem consistent.
13+
14+
Self-contained, generic, typed component following the pattern established by
15+
`ConferenceFloorMap`/`EventCapacityIndicator`: its own folder, its own mock
16+
data, wired into `app/demo/page.tsx`. It does not replace or touch the
17+
existing `DataTable.tsx`/`ReusableTable.tsx` components — those are out of
18+
scope for this issue.
19+
20+
## Files
21+
22+
- `components/UniversalDataGrid/types.ts``UniversalDataGridColumn<T>` and
23+
`UniversalDataGridProps<T>` generic types.
24+
- `components/UniversalDataGrid/mockAttendees.ts` — 5,000 generated
25+
synthetic attendee rows (id, name, email, ticketType, status,
26+
registeredAt, checkedIn) plus the column definitions for the demo.
27+
- `components/UniversalDataGrid/useCsvExport.ts` — hook that serializes a
28+
given rowset to CSV and triggers a browser download.
29+
- `components/UniversalDataGrid/UniversalDataGrid.tsx` — the grid.
30+
- `components/UniversalDataGrid/index.ts` — barrel export.
31+
32+
## Component API
33+
34+
```ts
35+
interface UniversalDataGridColumn<T> {
36+
id: string;
37+
header: string;
38+
accessor: (row: T) => string | number;
39+
width?: number;
40+
enableSort?: boolean; // default true
41+
enableFilter?: boolean; // default true
42+
}
43+
44+
interface UniversalDataGridProps<T> {
45+
data: T[];
46+
columns: UniversalDataGridColumn<T>[];
47+
rowHeight?: number; // default 40, used by the virtualizer
48+
height?: number; // default 480, px height of the scroll viewport
49+
getRowId: (row: T) => string;
50+
className?: string;
51+
}
52+
```
53+
54+
## Interaction Model
55+
56+
- **Sorting**: clicking a header cycles asc → desc → none. Driven by
57+
TanStack Table's `sorting` state; only one column sorts at a time.
58+
- **Filtering**: a small text input under each filterable header does
59+
substring matching against that column's accessed value, debounced
60+
~200ms, driven by TanStack Table's `columnFilters` state.
61+
- **Column pinning**: a pin icon in each header opens a small menu with
62+
"Pin left" / "Pin right" / "Unpin". Pinned columns use TanStack Table's
63+
pinning API and get `position: sticky` with a shadow divider at the
64+
pinned/unpinned boundary.
65+
- **Export**: a toolbar button exports the currently filtered+sorted
66+
rowset (respecting active filters and sort order, but not virtualization
67+
— all matching rows are exported, not just the rendered window) as a
68+
CSV file via `useCsvExport`.
69+
- **Virtualization**: the row body uses `@tanstack/react-virtual`'s
70+
`useVirtualizer` with a fixed `rowHeight`, rendering only the rows
71+
currently in (or near) the viewport inside a scrollable container of
72+
`height`.
73+
- **Keyboard accessibility**: the grid follows the WAI-ARIA grid pattern.
74+
`role="grid"` on the container, `role="row"`/`role="columnheader"`/
75+
`role="gridcell"` on the relevant elements. Arrow keys move a
76+
roving-tabindex focused-cell indicator between cells (Up/Down/Left/
77+
Right); Enter/Space on a focused header cell triggers sort; Tab reaches
78+
the toolbar (export button) and each column's filter input in DOM
79+
order, before/after the grid body.
80+
81+
## Mock Data
82+
83+
5,000 generated attendee rows, large enough that virtualization's
84+
performance benefit is visually obvious (an unvirtualized 5,000-row table
85+
would noticeably jank on scroll). Deterministic generation (seeded index-based
86+
values, not `Math.random()`), so the dataset is stable across renders/tests.
87+
88+
## Testing Approach
89+
90+
No `.test.tsx` file — this repo has no wired-up test runner (no `test`
91+
script, no jest/vitest config), matching the precedent set by
92+
`ConferenceFloorMap`. Verification is via `tsc --noEmit`, `eslint`, and a
93+
live browser check (Playwright-driven): sort a column, filter a column,
94+
pin a column, export CSV, and confirm keyboard arrow-key navigation moves
95+
focus between cells with no console errors.
96+
97+
## Out of Scope
98+
99+
- Replacing `DataTable.tsx` or `ReusableTable.tsx` call sites.
100+
- Server-side pagination/sorting/filtering (all data is client-side, in
101+
keeping with the mock-data-demo-component convention).
102+
- Row selection/editing.
103+
- Excel (.xlsx) export — CSV only.

0 commit comments

Comments
 (0)