Repository navigation
feat(ui-kit): DataTable organism (TanStack, headless) #521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
88 commits
Select commit
Hold shift + click to select a range
7c29701
docs(data-table): add library comparison (VTable/S2/TanStack)
depthark 23437eb
build(ui-kit): add TanStack Table, virtual and dnd-kit deps
depthark 29da94a
docs(ui-kit): add data-table-usage skill + changelog entry
depthark 8eacecb
feat(ui-kit): add DataTable organism (TanStack, headless)
depthark 6fb522d
test(ui-kit): add DataTable stories with interaction tests
depthark 72d28f8
fix(ui-kit): address DataTable review (Greptile P1)
depthark 6ee7bc1
docs(data-table): address review nits (CodeRabbit)
depthark 9b883f1
fix(ui-kit): second review round (Greptile/CodeRabbit)
depthark 7c3ba69
fix(ui-kit): preserve slotProps.row ref when not reordering
depthark c75f2c5
fix(ui-kit): type slotProps.row ref so the dts build passes
depthark f6fe01a
feat(ui-kit): type-driven filters/editors, inline edit mode, size pro…
depthark 5645395
test(ui-kit): stories for typed filters, custom filter, inline edit a…
depthark 7de9cf7
fix(ui-kit): review round on typed fields and actions column
depthark 583ba77
feat(ui-kit): constrainable row selection (mode, cap, custom predicate)
depthark e4bb06b
feat(ui-kit): per-row action permissions; fix sticky header/frozen st…
depthark 025ff09
fix(ui-kit): visual fixes found by browsing every DataTable story
depthark 19c70e5
feat(ui-kit): drag affordances and loading skeletons
depthark fd46663
feat(ui-kit): standalone striped boolean; drop click affordance while…
depthark 2ed9e94
feat(ui-kit): accessibility for DataTable
depthark 272a89f
fix(ui-kit): address DataTable a11y review findings
depthark bbdf992
fix(ui-kit): correctness review findings (P0/P1)
depthark 80b1433
fix(ui-kit): filter-row pinning and edit-lock bypass
depthark a2faa56
docs(ui-kit): correct bundled skill count in agent-plugin README
depthark 5b862f3
fix(ui-kit): opaque header cells so frozen columns don't bleed through
depthark 683d830
feat(ui-kit): compact filter row, Button row actions, drop quick actions
depthark 4371a63
feat(ui-kit): expander in actions cell, free-form detail row, right-a…
depthark 1a67e14
fix(ui-kit): compact filter condition menu, translatable operator labels
depthark d082ca5
docs(ui-kit): record size-scale inconsistencies as follow-up tasks
depthark a42c4fa
fix(ui): allow sorting the demo Status column
depthark 5e0f3c4
feat(ui): column widths, alignment, wrapper radius, hover-only drag h…
depthark 16a9eda
feat(ui): data-align styling in Table, rework InlineEdit story
depthark 894e120
fix(ui): address CodeRabbit review on DataTable
depthark dcc8ba4
fix(ui): mirror numeric column widths into TanStack's size model
depthark 2c49fa5
ci(ui): gate Storybook play functions, fix what that exposed
depthark e710c3e
fix(ui): give the DataTable footer the header background
depthark 43961ad
Revert: drop the Storybook interaction-test CI infrastructure
depthark 528632c
feat(ui): split the DataTable footer — range left, pager and page siz…
depthark 746d460
feat(ui): toolbar actions, SearchForm-based search, wrapper border
depthark 9035fd9
fix(ui): make the toolbar search fill the row and join its search button
depthark 05e3279
feat(ui): icon-only column settings trigger, keep its checkbox list open
depthark 9bbc9ef
fix(ui): make the column-settings trigger clickable again
depthark 24afa24
fix(ui): resolve outstanding CodeRabbit findings on DataTable
depthark 55aae8f
docs: translate the DataTable library comparison to English
depthark 75e4ce0
build(ui-kit): migrate DataTable to TanStack Table v9
depthark 35def96
fix(ui): resolve the open CodeRabbit threads on DataTable
depthark 5e9979c
fix(ui): correct the v9 migration against the TanStack migration guide
depthark 0eef9b7
fix(ui): remove unreachable lock reporting, guard blank numeric cells
depthark 39db663
style(ui): use type aliases for the Table prop definitions
depthark 0794761
fix(ui): drop the now-dead blocked ref, align between with matchNumber
depthark c505c04
fix(ui): default filterFn so the typed filter row actually filters
depthark 379c72c
fix(ui): stack grouped sticky headers, scope the rowClick block report
depthark cdc4b80
fix(ui): render the toolbar for actions alone, stop swallowing row on…
depthark ffd327b
fix(ui): empty date/time filters no longer hide blank cells
depthark 40db5ea
fix(ui): stack the sticky actions cell above frozen body columns
depthark e6e1d55
fix(ui): give meta.type: "custom" a dispatch case in typedFilterMatch
depthark 58fd4b2
fix(ui): honour deprecated filterVariant, stop silently truncating vi…
depthark 193ee48
fix(ui): restore sticky headers, stop the filter select reflowing the…
depthark 05b18f9
fix(ui): widen the filter select's stable-width buffer
depthark b18bd1e
feat(ui): striped rows drop their border, add tintNestedRows
depthark 59fbd7e
fix(ui): fire the virtualization warning once, tokenize the nested-ro…
depthark c0ab9ea
fix(ui): re-warn on virtualization configs that turn invalid after mount
depthark 66c1a1e
fix(ui): blank cells no longer pass numeric conditional filters, fix …
depthark c1fc479
fix(ui): stop swallowing infinite-scroll reach-end, fix inverted nume…
depthark b101499
fix(ui): numeric conditional-filter equals, cell-editor type, virtual…
depthark 7599f9a
fix(ui): treat "int" columns as numeric in conditional filter, unify …
depthark 27d5757
fix(ui): re-export filter/editor types, share between-range logic, fo…
depthark dd608c2
fix(ui): re-clamp maxSelectedRows on prop shrink, gate toolbar-overfl…
depthark 92d3e17
fix(ui): fire onReady once, measure sticky header before paint, share…
depthark 5d56803
fix(ui): render the numeric inline editor, label FieldSelect, auto-re…
depthark 8423483
fix(ui): keep chosen filter operators, unbreak row+column reorder, re…
depthark 13bd81b
fix(ui): stop infinite reach-end loop, fix date filters across timezones
depthark 9f9c9b2
fix(ui): restore numeric arrow stepping, report column-reorder indice…
depthark 9ac03c8
fix(ui): make sticky-actions stacking deterministic over end-pinned c…
depthark c60c1c9
fix(ui): render filter row, tree indent and skeleton in pinned column…
depthark 8250f54
fix(ui): re-arm window-mode infinite scroll, stop paginationProps clo…
depthark 9a21160
fix(ui): honour pageCount in the pager, restore leaf-column memoization
depthark 4c9e30a
fix(ui): show row colour in frozen columns, actually memoize leaf col…
depthark 3aebdaf
fix(ui): reset selection-limit latch, include blanks in numeric ≠, va…
depthark fa4b8ba
fix(ui): make plain TanStack filter values work, fix two broken story…
depthark 9c36238
Merge branch 'master' into feat/data-table-tanstack
BleedingDev fca0144
fix(ui): unbreak conditional filters on non-operator column types
depthark 4c982bf
fix(ui): coerce bare date/time and boolean filter values instead of n…
depthark 8510791
fix(ui): seed the edit draft for controlled editingRowId, stop wideni…
depthark 60d408d
fix(ui): stop undefined translations crashing the table, tag drags by…
depthark da435ec
fix(ui): stop frozen columns going see-through on striped and selecte…
depthark aeb2167
fix(ui): commit the current draft, unstick underfilled infinite scroll
depthark 79dbca2
fix(ui): guard window-scroll reach-end, keep built-in columns leading
depthark 2970cb1
Merge branch 'master' into feat/data-table-tanstack
BleedingDev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # DataTable — library comparison and rationale | ||
|
|
||
| > Background for the decision on which library to build the new `DataTable` component in `libs/ui` on. | ||
| > Each library's documentation was fetched via **context7 MCP** (official sources) and mapped onto our requirement list. | ||
| > Analysis date: 2026-07-25. | ||
|
|
||
| ## TL;DR | ||
|
|
||
| The hard requirement was: **headless, with open paths through props — able to shape the DOM and reach nested layers**, so the table fits our DS (Zag.js + Tailwind, DOM-first, `data-[...]` states, design tokens via CSS). | ||
|
|
||
| The whole comparison turns on **a single axis: canvas vs. DOM rendering.** | ||
|
|
||
| | Library | Rendering | Headless | Tailwind on cells | DS integration | Verdict | | ||
| |---|---|---|---|:--:|---| | ||
| | **TanStack Table** v8/v9 | **DOM** (you render it) | **yes, 100%** | **yes** | **1–2 / 5** | ✅ natural fit for the DS | | ||
| | **VTable** (Bytedance) | **canvas** (VRender) | no | no | 5 / 5 | ⚠️ most features out of the box, but outside the DS | | ||
| | **AntV S2** (Alibaba/Ant) | **canvas** (AntV/G) | no | no | 5 / 5 | ❌ pivot/analytics, not a styleable grid | | ||
| | ali-react-table (Alibaba) | DOM | partially | yes | 4 / 5 | ❌ dead project, Chinese-only docs, no context7 coverage | | ||
| | _(baseline)_ antd Table | DOM | no (CSS-in-JS) | override wars | 3 / 5 | ⚠️ not headless | | ||
|
|
||
| > **The "DS integration" scale is integration cost/effort (1 = least, 5 = most), not library quality.** That is why TanStack scores `1–2 / 5` and wins, while the canvas libraries score `5 / 5` and lose. | ||
|
|
||
| **A clarification on "the Alibaba table":** what we had in mind (`ali-react-table`) is effectively unmaintained, documented only in Chinese, with zero context7 coverage. The genuinely live "Alibaba/Ant Group" table is **AntV S2** — but that is a canvas pivot/analytics table. So VTable (Bytedance) and S2 (Alibaba) are **both canvas** and hit the same problem. | ||
|
|
||
| ## Why canvas is a problem for our DS | ||
|
|
||
| VTable and S2 both draw cells as **pixels on a single `<canvas>`**: | ||
|
|
||
| - **No per-cell DOM nodes** → no `className`, no `data-[validation=error]`, no CSS variables, no Tailwind on the table's content. | ||
| - Styling goes through a **parallel JS "theme" object** (`themeCfg`, VRender style objects) — we would have to **duplicate our entire token system** into a second language and maintain a bridge adapter. | ||
| - "Nested layers through props" does not really exist — you only get **overlay escape hatches** (absolutely positioned HTML/React above the canvas for editors, filter menus, popups). That does not scale to styling every cell. | ||
| - Bonus problems: accessibility (ARIA on elements) and debugging (S2 needs a dedicated "G devtools" plugin). | ||
|
|
||
| That is the philosophical opposite of a Zag.js + Tailwind DS, whose whole premise is "you own the DOM and the styling, we own the behaviour". | ||
|
|
||
| ## Requirement matrix (native support) | ||
|
|
||
| Legend: ✅ native · 🟡 logic yes / you build the UI (or via a plugin) · ⚙️ only through custom or external code · ❌ missing | ||
|
|
||
| | # | Requirement | TanStack | VTable | AntV S2 | | ||
| |---|---|:--:|:--:|:--:| | ||
| | 1 | Column filters with conditions | 🟡 logic, UI DIY | ✅ FilterPlugin (byCondition) | 🟡 `onFilter` pipeline | | ||
| | 2 | Header filter template | ⚙️ build it in `<th>` | ✅ `headerCustomLayout` | ✅ `custom-header`/`colCell` | | ||
| | 3 | Fulltext search | ✅ `globalFilter` | 🟡 search plugin | ⚙️ pre-filter the data | | ||
| | 4 | Sorting | ✅ `getSortedRowModel` | ✅ `sort` + comparator | ✅ `sortParams` | | ||
| | 5 | Empty data template | ⚙️ DIY | ✅ empty-tip | ✅ `placeholder` | | ||
| | 6 | Row actions | ⚙️ display column | ✅ `cellType:'button'`/icon | ⚙️ custom/overlay | | ||
| | 7 | Freeze columns L/R | 🟡 `columnPinning` (+CSS) | ✅ `frozenColCount`/`rightFrozenColCount` | ✅ `frozen{...}` | | ||
| | 8 | Sticky header | ⚙️ CSS `sticky` | ✅ native | ✅ `stickyHeader` | | ||
| | 9 | Striped rows | ⚙️ CSS `nth-child` | 🟡 via theme | 🟡 via theme | | ||
| | 10 | Infinite scroll / virtual | ⚙️ +`@tanstack/react-virtual` | ✅ strong native | ✅ strong native | | ||
| | 11 | ColSpan / RowSpan | ❌ header groups only | ✅ `customMergeCell` | ✅ `mergedCell` | | ||
| | 12 | onRowClick | ⚙️ `onClick` on `<tr>` | ✅ `click_cell`→row | ✅ `ROW_CELL_CLICK` | | ||
| | 13 | Selectable rows (checkbox) | 🟡 `rowSelection` state | ✅ `cellType:'checkbox'` | 🟡 cell selection | | ||
| | 14 | Column reorder | 🟡 `columnOrder` (drag=dnd-kit) | ✅ `dragOrder` | 🟡 via API | | ||
| | 15 | Row reorder | ⚙️ dnd-kit + mutate data | ✅ `rowSeriesNumber.dragOrder` | ⚙️ custom | | ||
| | 16 | Show/hide columns | ✅ `columnVisibility` | 🟡 `updateColumns` | ✅ `fields.columns` | | ||
| | 17 | Row custom content template | ✅ `cell` + `flexRender` | ✅ `customLayout`/VRender | ✅ subclass `DataCell` | | ||
| | 18 | Tree structure | ✅ `getExpandedRowModel` | ✅ strong `tree:true` | ✅ strong tree mode | | ||
| | 19 | Quick actions | ⚙️ display column | ✅ button/icon | ⚙️ interaction API | | ||
| | 20 | Inline row edit | ❌ DIY (`meta.updateData`) | ✅ `vtable-editors` | 🟡 editable-sheet/custom | | ||
| | 21 | Pagination (count, page size) | ✅ `getPaginationRowModel` | ✅ `pagination` | ✅ `pagination` | | ||
|
|
||
| **How to read this:** VTable and S2 win on ✅ count — they are "batteries-included". TanStack has more 🟡/⚙️ because it **deliberately ships no UI** — it gives you the state machine and you build the markup from your own atoms (which is an advantage for a DS, not a drawback). | ||
|
|
||
| ## Integration effort for our DS (Zag.js + Tailwind) | ||
|
|
||
| - **TanStack — 1–2/5:** the same philosophy as Zag.js (logic only, zero markup/CSS). Filter inputs, checkboxes, pager and sort icons are rendered through our existing Zag atoms → the table inherits our tokens and the `data-[validation]` pattern for free. The cost: you build every piece of UI yourself, and add virtualization (`@tanstack/react-virtual`), drag (`dnd-kit`) and inline edit by hand; **rowSpan/colSpan on data cells is genuinely missing**. | ||
| - **VTable — 5/5:** canvas cannot be styled with Tailwind or wired to tokens. You get many features out of the box but lose the whole DS — cells are not members of the headless family, just an isolated widget behind a token→theme adapter. It only makes sense at canvas-scale performance (100k+ cells, pivot). | ||
| - **AntV S2 — 5/5:** the same, plus it is over-engineered for pivot analytics, and the English docs are a thin subset. | ||
| - **ali-react-table — 4/5 with high uncertainty:** DOM-based and virtualized, but a dead project with Chinese-only docs and no context7 coverage. | ||
|
|
||
| ## Decision (final) | ||
|
|
||
| **Chosen: `@tanstack/react-table` v9.** For a **headless, Tailwind-styleable, DOM-accessible** grid inside a Zag.js DS it is architecturally the only clean fit. VTable (Bytedance) and AntV S2 (Alibaba) are **canvas** → they cannot use our stack (`--color-table-*` tokens, `tailwind-variants` slots, `data-[…]` states, Zag atoms), so they were ruled out. Bonus: `@tanstack/react-table` was already in the repo (`apps/medusa-be`), so no new third-party platform. The organism was first built against v8 and migrated to v9 before merge; v9 moves feature registration and the row models onto a module-scope `tableFeatures()` set and renames column pinning from `left`/`right` to `start`/`end`. | ||
|
|
||
| **Why not VTable/S2 despite more out-of-the-box features:** their canvas rendering would mean a parallel JS theme system (a duplicate of our token set), no per-cell DOM access through props, and losing accessibility and DOM-level testability. They only make sense as an **isolated canvas analytics widget** outside the DS (canvas-scale 100k+ cells, pivot). | ||
|
|
||
| **How we covered TanStack's weak spots:** | ||
| - colSpan/rowSpan on data cells → a custom `getCellSpan` prop (TanStack has none). | ||
| - virtualization / infinite scroll → `@tanstack/react-virtual` (windowing that preserves native table alignment) + `onReachEnd`. | ||
| - column & row reorder → `@dnd-kit`. | ||
| - inline edit → `meta.updateData` → `onCellEditCommit`. | ||
|
|
||
| **Implementation:** the `DataTable` organism in `libs/ui/src/organisms/data-table.tsx`, rendering through the existing presentational `Table` organism (so it inherits the `--color-table-*` tokens). It covers all 21 requirements, every feature exposes a callback, with Storybook stories carrying `play` interaction tests and the `data-table-usage` usage skill. The MVP is styled with existing and semantic tokens; component-level `--color-data-table-*` tokens and the Figma export follow once the MVP look is signed off. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.