@dashforge/tw 0.5.0-beta → 0.7.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +526 -0
- package/THEME-AUDIT.md +175 -0
- package/dist/index.esm.js +2504 -51
- package/dist/src/components/Accordion/accordion.variants.d.ts.map +1 -1
- package/dist/src/components/Box/box.variants.d.ts.map +1 -1
- package/dist/src/components/DataGrid/DataGrid.d.ts +29 -0
- package/dist/src/components/DataGrid/DataGrid.d.ts.map +1 -0
- package/dist/src/components/DataGrid/dataGrid.types.d.ts +171 -0
- package/dist/src/components/DataGrid/dataGrid.types.d.ts.map +1 -0
- package/dist/src/components/DataGrid/dataGrid.variants.d.ts +216 -0
- package/dist/src/components/DataGrid/dataGrid.variants.d.ts.map +1 -0
- package/dist/src/components/Dialog/dialog.variants.d.ts.map +1 -1
- package/dist/src/components/Divider/divider.variants.d.ts.map +1 -1
- package/dist/src/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/src/components/Pagination/pagination.variants.d.ts.map +1 -1
- package/dist/src/components/Popover/popover.variants.d.ts.map +1 -1
- package/dist/src/components/Skeleton/skeleton.variants.d.ts.map +1 -1
- package/dist/src/components/Table/Table.d.ts +24 -0
- package/dist/src/components/Table/Table.d.ts.map +1 -0
- package/dist/src/components/Table/cells/RenderButton.d.ts +13 -0
- package/dist/src/components/Table/cells/RenderButton.d.ts.map +1 -0
- package/dist/src/components/Table/cells/RenderChip.d.ts +80 -0
- package/dist/src/components/Table/cells/RenderChip.d.ts.map +1 -0
- package/dist/src/components/Table/cells/RenderText.d.ts +21 -0
- package/dist/src/components/Table/cells/RenderText.d.ts.map +1 -0
- package/dist/src/components/Table/cells/RenderTwoLine.d.ts +20 -0
- package/dist/src/components/Table/cells/RenderTwoLine.d.ts.map +1 -0
- package/dist/src/components/Table/cells/RowActionsMenu.d.ts +36 -0
- package/dist/src/components/Table/cells/RowActionsMenu.d.ts.map +1 -0
- package/dist/src/components/Table/cells/index.d.ts +11 -0
- package/dist/src/components/Table/cells/index.d.ts.map +1 -0
- package/dist/src/components/Table/table.types.d.ts +312 -0
- package/dist/src/components/Table/table.types.d.ts.map +1 -0
- package/dist/src/components/Table/table.variants.d.ts +285 -0
- package/dist/src/components/Table/table.variants.d.ts.map +1 -0
- package/dist/src/components/Tabs/tabs.variants.d.ts.map +1 -1
- package/dist/src/components/Tooltip/tooltip.variants.d.ts.map +1 -1
- package/dist/src/components/Typography/typography.variants.d.ts.map +1 -1
- package/dist/src/components/_shared/data/getNestedValue.d.ts +20 -0
- package/dist/src/components/_shared/data/getNestedValue.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useColumnAutoDetect.d.ts +45 -0
- package/dist/src/components/_shared/data/useColumnAutoDetect.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useControllableState.d.ts +23 -0
- package/dist/src/components/_shared/data/useControllableState.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useDebouncedValue.d.ts +10 -0
- package/dist/src/components/_shared/data/useDebouncedValue.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useTableFilter.d.ts +13 -0
- package/dist/src/components/_shared/data/useTableFilter.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useTableSearch.d.ts +23 -0
- package/dist/src/components/_shared/data/useTableSearch.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useTableSelection.d.ts +25 -0
- package/dist/src/components/_shared/data/useTableSelection.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useTableSort.d.ts +20 -0
- package/dist/src/components/_shared/data/useTableSort.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useVirtualizer.d.ts +107 -0
- package/dist/src/components/_shared/data/useVirtualizer.d.ts.map +1 -0
- package/dist/src/index.d.ts +10 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/Accordion/accordion.variants.ts +5 -3
- package/src/components/Box/Box.test.tsx +32 -11
- package/src/components/Box/box.variants.ts +23 -4
- package/src/components/DataGrid/DataGrid.test.tsx +496 -0
- package/src/components/DataGrid/DataGrid.tsx +831 -0
- package/src/components/DataGrid/dataGrid.types.ts +189 -0
- package/src/components/DataGrid/dataGrid.variants.ts +144 -0
- package/src/components/Dialog/dialog.variants.ts +10 -6
- package/src/components/Divider/Divider.test.tsx +3 -1
- package/src/components/Divider/Divider.tsx +1 -1
- package/src/components/Divider/divider.variants.ts +2 -1
- package/src/components/Pagination/Pagination.tsx +3 -1
- package/src/components/Pagination/pagination.variants.ts +19 -13
- package/src/components/Popover/popover.variants.ts +10 -4
- package/src/components/Skeleton/skeleton.variants.ts +2 -1
- package/src/components/Table/Table.test.tsx +603 -0
- package/src/components/Table/Table.tsx +816 -0
- package/src/components/Table/cells/RenderButton.tsx +23 -0
- package/src/components/Table/cells/RenderChip.tsx +103 -0
- package/src/components/Table/cells/RenderText.tsx +37 -0
- package/src/components/Table/cells/RenderTwoLine.tsx +34 -0
- package/src/components/Table/cells/RowActionsMenu.tsx +147 -0
- package/src/components/Table/cells/index.ts +14 -0
- package/src/components/Table/table.types.ts +346 -0
- package/src/components/Table/table.variants.ts +207 -0
- package/src/components/Tabs/tabs.variants.ts +19 -8
- package/src/components/Tooltip/tooltip.variants.ts +12 -2
- package/src/components/Typography/Typography.test.tsx +3 -1
- package/src/components/Typography/typography.variants.ts +6 -2
- package/src/components/_shared/data/getNestedValue.test.ts +50 -0
- package/src/components/_shared/data/getNestedValue.ts +32 -0
- package/src/components/_shared/data/useColumnAutoDetect.test.ts +123 -0
- package/src/components/_shared/data/useColumnAutoDetect.ts +100 -0
- package/src/components/_shared/data/useControllableState.ts +48 -0
- package/src/components/_shared/data/useDebouncedValue.ts +24 -0
- package/src/components/_shared/data/useTableFilter.ts +37 -0
- package/src/components/_shared/data/useTableSearch.test.ts +115 -0
- package/src/components/_shared/data/useTableSearch.ts +66 -0
- package/src/components/_shared/data/useTableSelection.test.ts +100 -0
- package/src/components/_shared/data/useTableSelection.ts +100 -0
- package/src/components/_shared/data/useTableSort.test.ts +150 -0
- package/src/components/_shared/data/useTableSort.ts +141 -0
- package/src/components/_shared/data/useVirtualizer.test.ts +158 -0
- package/src/components/_shared/data/useVirtualizer.ts +199 -0
- package/src/components/_shared/themeIdentity.test.ts +113 -0
- package/src/index.ts +59 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,532 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
12
12
|
> duplicated intentionally — no shared "lowest common denominator" headless
|
|
13
13
|
> layer.
|
|
14
14
|
|
|
15
|
+
## [0.7.0-beta] — 2026-05-19
|
|
16
|
+
|
|
17
|
+
**Combined Sprint 4.2 + 4.3 release.** Ships **`<DataGrid>`** — a
|
|
18
|
+
virtualized data table for large data sets (500 rows to millions)
|
|
19
|
+
— alongside a **theme identity sweep** across the whole catalog
|
|
20
|
+
(systemic dark-mode contrast fixes on 10 existing components).
|
|
21
|
+
|
|
22
|
+
Two coherent themes, one release:
|
|
23
|
+
|
|
24
|
+
1. **DataGrid (Sprint 4.2)** — sibling to `<Table>` with **homemade
|
|
25
|
+
virtualization** (no `@tanstack/react-virtual`, no
|
|
26
|
+
`react-window`). Same column model, same cell renderer library,
|
|
27
|
+
same RBAC integration, same identity-consistent visual design as
|
|
28
|
+
Table. Differs at the render strategy level: only the window of
|
|
29
|
+
visible rows mounts in DOM. Adds sticky-left columns, server-side
|
|
30
|
+
mode (4 independent opt-in flags), `selectAllScope`, and optional
|
|
31
|
+
internal pagination.
|
|
32
|
+
|
|
33
|
+
2. **Theme identity sweep (Sprint 4.3)** — applies the identity rule
|
|
34
|
+
codified in Sprint 4.1 to the WHOLE catalog. 10 existing
|
|
35
|
+
components (Typography, Box, Dialog, Divider, Pagination,
|
|
36
|
+
Popover, Skeleton, Tabs, Tooltip, plus Table-side comments)
|
|
37
|
+
had latent `dark:` variants on the neutral palette → those
|
|
38
|
+
classes double-inverted via the dashforgePreset CSS-var swap
|
|
39
|
+
and broke dark mode. Sweep removes the anti-pattern across the
|
|
40
|
+
catalog and adds a package-level regression test
|
|
41
|
+
(`_shared/themeIdentity.test.ts`) that scans every source file
|
|
42
|
+
on every test run.
|
|
43
|
+
|
|
44
|
+
**Minor bump** for the new DataGrid public API surface. Strictly
|
|
45
|
+
additive — zero breaking changes on existing component APIs.
|
|
46
|
+
Drop-in upgrade from `0.6.0-beta`.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **`<DataGrid>`** — virtualized data table for large data sets.
|
|
51
|
+
Sibling to `<Table>`. Required props: `rows`, `cols`,
|
|
52
|
+
`getRowId`, `rowHeight` (fixed for v1), `height` (bounded
|
|
53
|
+
container).
|
|
54
|
+
- **Homemade virtualization** via the new `useVirtualizer` hook
|
|
55
|
+
in `_shared/data/`. Scroll-event based with
|
|
56
|
+
`requestAnimationFrame` debounce (60 fps throttling) +
|
|
57
|
+
`ResizeObserver` for container resize. Spacer-row approach
|
|
58
|
+
preserves `<table>` semantics — screen readers see continuous
|
|
59
|
+
structure with `aria-hidden` spacers above/below the visible
|
|
60
|
+
window. Variable row height deferred to v1-bis.
|
|
61
|
+
- **Sticky left column** via `cols[i].sticky: 'left'`. CSS
|
|
62
|
+
`position: sticky; left: 0` with z-index ladder for the
|
|
63
|
+
top-left corner intersection with the sticky header
|
|
64
|
+
(`thead` z-10 < sticky cell z-[1] < sticky header corner z-20).
|
|
65
|
+
Right-sticky deferred to v1-bis.
|
|
66
|
+
- **Server-side mode flags** (4 independent opt-ins):
|
|
67
|
+
- `serverSideSort` — emits `onSortChange` but doesn't sort locally
|
|
68
|
+
- `serverSideFilter` — emits `onFilterChange` but doesn't filter
|
|
69
|
+
- `serverSideSearch` — emits `onSearchQueryChange` (debounced)
|
|
70
|
+
- `serverSidePagination` — uses `totalCount` for the virtual
|
|
71
|
+
scrollbar math; `rows` is the server-returned page slice
|
|
72
|
+
- **`selectAllScope`** prop (`'visible' | 'allLoaded'`, default
|
|
73
|
+
`'allLoaded'`) controls the header "select all" checkbox
|
|
74
|
+
semantic.
|
|
75
|
+
- **Optional internal pagination** via the `pagination` prop —
|
|
76
|
+
renders the Sprint 4 `<Pagination>` component below the
|
|
77
|
+
virtualized scroll. Composes naturally with virtualization
|
|
78
|
+
(each page is virtualized internally if large enough).
|
|
79
|
+
- **Bundle impact**: +8.4 KB gz on top of `0.6.0-beta`
|
|
80
|
+
(`<Table>` baseline). Zero new runtime deps.
|
|
81
|
+
- **Variants**: `plain` / `lines` (default) / `striped` /
|
|
82
|
+
`bordered`. **No `card` variant** — incompatible with
|
|
83
|
+
`<table>` virtualization spacer-rows.
|
|
84
|
+
- 24 unit tests + 12 `useVirtualizer` math tests + dash smoke
|
|
85
|
+
test at `/test-data-grid` with 10 000 generated rows.
|
|
86
|
+
|
|
87
|
+
- **`useVirtualizer` hook** in `_shared/data/` — exported
|
|
88
|
+
internally only (not in the public API surface). Other future
|
|
89
|
+
virtualized components (TreeView, future GanttGrid, etc.) can
|
|
90
|
+
reuse it.
|
|
91
|
+
|
|
92
|
+
- **`sticky?: 'left' | 'right'`** added to `TableColumn<T>` for
|
|
93
|
+
DataGrid integration. Currently ignored by Table (non-virtualized);
|
|
94
|
+
honored by DataGrid v1 for `'left'` only.
|
|
95
|
+
|
|
96
|
+
- **Doc MDX `/tw/docs/components/data-grid`** — emphasizes the
|
|
97
|
+
Table vs DataGrid decision matrix (24-row comparison table +
|
|
98
|
+
"Use Table when..." / "Use DataGrid when..." + "Switch later"
|
|
99
|
+
path showing the 3-line diff to migrate).
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
|
|
103
|
+
- **Internal refactor — shared data helpers moved**:
|
|
104
|
+
`Table/_internal/` → `_shared/data/` (13 files). The helpers
|
|
105
|
+
(`getNestedValue`, `useTableSearch`, `useTableSort`,
|
|
106
|
+
`useTableSelection`, `useTableFilter`, `useColumnAutoDetect`,
|
|
107
|
+
`useDebouncedValue`, `useControllableState`) are now shared
|
|
108
|
+
between Table and DataGrid. **Pure path move** — function names
|
|
109
|
+
unchanged. Public API surface unchanged (only `getNestedValue`
|
|
110
|
+
was ever re-exported; the re-export path updated transparently).
|
|
111
|
+
|
|
112
|
+
- **Theme identity sweep** — 10 existing components had latent
|
|
113
|
+
`dark:` Tailwind variants on the neutral palette, which the
|
|
114
|
+
dashforgePreset CSS-var swap double-inverted and broke in dark
|
|
115
|
+
mode. Removed across the catalog:
|
|
116
|
+
|
|
117
|
+
| Component | Sites fixed | Pattern category |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| Accordion (`accordion.variants.ts`) | 3 | A (drop `dark:` — auto-invert) |
|
|
120
|
+
| Box (`box.variants.ts`) | 4 | A + B (corrected dark targets) + D (solid neutral now auto-inverts as accent) |
|
|
121
|
+
| Dialog (`dialog.variants.ts`) | 6 | A × 5 + B × 1 |
|
|
122
|
+
| Divider (`divider.variants.ts` + `Divider.tsx`) | 2 | A × 2 |
|
|
123
|
+
| Pagination (`pagination.variants.ts` + `Pagination.tsx`) | 10 | A × 7 + B × 3 (`dark:bg-neutral-900` → `bg-neutral-50` auto-invert) |
|
|
124
|
+
| Popover (`popover.variants.ts`) | 4 | A × 3 + B × 1 |
|
|
125
|
+
| Skeleton (`skeleton.variants.ts`) | 1 | A |
|
|
126
|
+
| Tabs (`tabs.variants.ts`) | 8 | A × 7 + B × 1 (`dark:bg-neutral-900` → `dark:bg-neutral-200` for proper active-pill elevation) |
|
|
127
|
+
| Tooltip (`tooltip.variants.ts`) | 2 | **Bug fix** — was previously invisible in dark mode (double-inverted to dark surface on dark page). Now auto-inverts: dark tooltip on light page, light tooltip on dark page (high contrast preserved). |
|
|
128
|
+
| Typography (`typography.variants.ts`) | 2 | A × 2 |
|
|
129
|
+
|
|
130
|
+
**Box solid neutral** now uses `bg-neutral-900 text-neutral-50`
|
|
131
|
+
(both auto-invert) — the accent surface flips with the page in
|
|
132
|
+
dark mode (light surface + dark text) for identity consistency.
|
|
133
|
+
Previously was `bg-neutral-900 text-white dark:bg-neutral-100
|
|
134
|
+
dark:text-neutral-900` — the `text-white` (no auto-invert) had
|
|
135
|
+
forced the double-invert pattern to stay readable.
|
|
136
|
+
|
|
137
|
+
Affected test files updated to assert the new canonical
|
|
138
|
+
patterns (Box × 4 + Typography × 1 + Divider × 1 = 6 tests
|
|
139
|
+
retargeted).
|
|
140
|
+
|
|
141
|
+
### Internal
|
|
142
|
+
|
|
143
|
+
- **`THEME-AUDIT.md`** at package root — categorizes every site
|
|
144
|
+
in the catalog as **A** (drop `dark:` — auto-invert), **B**
|
|
145
|
+
(keep `dark:` because `bg-white`/`fill-white` is hardcoded),
|
|
146
|
+
**C** (color palette — intentional design choice, not affected
|
|
147
|
+
by the rule), or **D** (component-specific exception). 42
|
|
148
|
+
violations catalogued across 10 components.
|
|
149
|
+
|
|
150
|
+
- **Package-level theme identity regression test** —
|
|
151
|
+
`_shared/themeIdentity.test.ts` replaces the Table-only one
|
|
152
|
+
from Sprint 4.1. Scans every `.ts`/`.tsx` source file under
|
|
153
|
+
`src/components/**` (excluding tests) on every test run.
|
|
154
|
+
Allows the Category B legitimate pattern
|
|
155
|
+
(`bg-white dark:bg-neutral-N` — `bg-white` doesn't auto-invert,
|
|
156
|
+
the `dark:` is required). Catches accidental re-introductions
|
|
157
|
+
of the anti-pattern at PR time.
|
|
158
|
+
|
|
159
|
+
- **+76 new unit tests** for Sprint 4.2 + 4.3:
|
|
160
|
+
- `useVirtualizer` math (12) — window calc, overscan, padding
|
|
161
|
+
invariant, edge cases (totalCount=0, very large dataset)
|
|
162
|
+
- `<DataGrid>` (24) — rendering, virtualized window, sort,
|
|
163
|
+
search, selection, selectAllScope, sticky left column,
|
|
164
|
+
server-side mode flags, RBAC, internal pagination, smart
|
|
165
|
+
defaults, i18n
|
|
166
|
+
- `_shared/themeIdentity.test.ts` package-level scanner (40
|
|
167
|
+
file-scan tests; 0 violations reported)
|
|
168
|
+
|
|
169
|
+
Full TW suite at **961/961 passing** across **48 files**.
|
|
170
|
+
|
|
171
|
+
- **`TestDataGrid.tsx`** added to the `dash` consumer
|
|
172
|
+
(`/test-data-grid`) — 10 000 generated rows + sticky col +
|
|
173
|
+
selectAllScope toggle + variant/density/size switcher + RBAC
|
|
174
|
+
per-column demo + custom empty state + React.Profiler logging.
|
|
175
|
+
|
|
176
|
+
- **Sidebar entries** for the new `DataGrid` doc page added to
|
|
177
|
+
`dashforge-docs-lab/src/tw-docs/sidebar.model.ts` (new "Data"
|
|
178
|
+
subgroup under "UI Components" now contains Table + DataGrid).
|
|
179
|
+
|
|
180
|
+
### Compatibility
|
|
181
|
+
|
|
182
|
+
| Axis | Pre-`0.7.0` | Post-`0.7.0` |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| Public API surface | 32 components | **+1 `<DataGrid>` + types (`DataGridProps`, `DataGridSelectAllScope`, `DataGridServerSideFlags`, `DataGridPaginationConfig`, `DataGridSlotProps`) + `dataGridVariants` recipe** + `sticky` axis on `TableColumn<T>` (additive, optional) |
|
|
185
|
+
| Peer deps | `react ^18 \|\| ^19`, `tw-theme workspace`, `tw-tokens workspace` | unchanged |
|
|
186
|
+
| Bridge deps | `forms` / `rbac` / `ui-core` `workspace:*` | unchanged |
|
|
187
|
+
| **New runtime deps** | — | **none** (homemade virtualization — constraint honored) |
|
|
188
|
+
| Breaking changes | — | **Zero** on public APIs. Internal refactor: `Table/_internal/` helpers moved to `_shared/data/`. Only externally-visible re-export was `getNestedValue` (path updated internally; export name preserved). |
|
|
189
|
+
| Bundle size | 402 KB raw / 91 KB gz | **445 KB raw / 98.4 KB gz** (+43 KB raw / +7.4 KB gz / **+8.1% gz**) — just under the 10% reviewer-sign-off threshold |
|
|
190
|
+
| Tests passing | 828/828 (46 files) | **961/961 (48 files)** — +133 from Sprint 4.2 + 4.3 |
|
|
191
|
+
|
|
192
|
+
> **Bundle sign-off** : the +8.1% gz delta is JUST under the 10%
|
|
193
|
+
> threshold from PERFORMANCE.md. Justification: DataGrid is the
|
|
194
|
+
> highest-leverage addition before 1.0 — it unlocks the entire
|
|
195
|
+
> "1 000+ row admin views" use case at €0 vs MUI X DataGrid Pro's
|
|
196
|
+
> €499/year. The theme identity sweep is net-neutral on bundle
|
|
197
|
+
> (removed redundant classes, replaced with smaller ones); the
|
|
198
|
+
> delta is entirely DataGrid + virtualizer code (+8 KB gz).
|
|
199
|
+
|
|
200
|
+
### Migration
|
|
201
|
+
|
|
202
|
+
No code changes required:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
pnpm up @dashforge/tw@^0.7.0-beta
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
To adopt the new DataGrid:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import {
|
|
212
|
+
DataGrid,
|
|
213
|
+
RenderTwoLine,
|
|
214
|
+
RenderChip,
|
|
215
|
+
RowActionsMenu,
|
|
216
|
+
} from '@dashforge/tw';
|
|
217
|
+
|
|
218
|
+
<DataGrid
|
|
219
|
+
rows={users} // can be 10k, 100k, millions
|
|
220
|
+
cols={columns} // SAME shape as Table's TableColumn<T>
|
|
221
|
+
getRowId={(r) => r.id}
|
|
222
|
+
rowHeight={48} // required for virtualization
|
|
223
|
+
height="600px" // required for bounded container
|
|
224
|
+
enableSearch
|
|
225
|
+
rowSelection="multiple"
|
|
226
|
+
selectAllScope="allLoaded"
|
|
227
|
+
bulkActions={(rows) => (
|
|
228
|
+
<Button color="danger">Delete {rows.length}</Button>
|
|
229
|
+
)}
|
|
230
|
+
rowActions={(row) => (
|
|
231
|
+
<RowActionsMenu
|
|
232
|
+
row={row}
|
|
233
|
+
actions={[
|
|
234
|
+
{ label: 'Edit', onClick: edit },
|
|
235
|
+
{ label: 'Delete', onClick: del, color: 'danger' },
|
|
236
|
+
]}
|
|
237
|
+
/>
|
|
238
|
+
)}
|
|
239
|
+
/>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
To switch an existing Table to DataGrid when you outgrow the row
|
|
243
|
+
count (~500+ rows), the migration is mechanical (3 lines):
|
|
244
|
+
|
|
245
|
+
```diff
|
|
246
|
+
- <Table
|
|
247
|
+
+ <DataGrid
|
|
248
|
+
rows={users}
|
|
249
|
+
cols={columns}
|
|
250
|
+
getRowId={(row) => row.id}
|
|
251
|
+
+ rowHeight={48}
|
|
252
|
+
+ height="600px"
|
|
253
|
+
/>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Everything else (sort / search / selection / row actions / cell
|
|
257
|
+
renderers / RBAC / i18n labels / sx / slotProps) keeps working
|
|
258
|
+
identically. **Expandable rows** are the one feature you lose
|
|
259
|
+
on Table→DataGrid swap — they ship in DataGrid v1-bis.
|
|
260
|
+
|
|
261
|
+
### Heads-up — companion releases coming
|
|
262
|
+
|
|
263
|
+
- **Sprint 4.2-bis → `0.8.0-beta`** (estimated): DataGrid v1-bis —
|
|
264
|
+
per-column filter UI chips (text/number/boolean/date), column
|
|
265
|
+
resize via drag, column reorder via drag, column visibility
|
|
266
|
+
dialog, right-sticky columns.
|
|
267
|
+
- **Sprint 5 → `0.9.0-beta`** + starter kits v1: separate repos
|
|
268
|
+
`dashforge-starter-mui` + `dashforge-starter-tw` with Auth + RBAC
|
|
269
|
+
+ form CRUD + dashboard with DataGrid admin views.
|
|
270
|
+
- **Sprint 6 → `1.0.0-rc.1 → 1.0.0`**: final A11Y audit, bundle
|
|
271
|
+
lockdown, beta freeze, cut `1.0.0`.
|
|
272
|
+
|
|
273
|
+
## [0.6.0-beta] — 2026-05-19
|
|
274
|
+
|
|
275
|
+
**Sprint 4.1 release.** Ships the central piece of the data layer
|
|
276
|
+
— a market-grounded `<Table>` built from scratch with NO new
|
|
277
|
+
runtime deps. The design references Stripe (visual style),
|
|
278
|
+
Atlassian / Jira (column UX), and Pencil & Paper UX research
|
|
279
|
+
(density tiers, hover-revealed row actions, selection patterns).
|
|
280
|
+
The existing MUI Table inherited from `hub-ws/admin` informed the
|
|
281
|
+
public API shape but not the implementation (we discarded the
|
|
282
|
+
`@mui/x-data-grid` backing — Table v1 is a hand-rolled native
|
|
283
|
+
`<table>`).
|
|
284
|
+
|
|
285
|
+
**Companion to Sprint 4.2** (DataGrid, virtualization, 10k+ rows)
|
|
286
|
+
— Table v1 covers the 80% admin-list use case up to ~500 rows.
|
|
287
|
+
|
|
288
|
+
**Minor bump** for the new public API surface — 15+ new exports.
|
|
289
|
+
Strictly additive — zero breaking changes. Drop-in upgrade from
|
|
290
|
+
`0.5.0-beta`.
|
|
291
|
+
|
|
292
|
+
### Added
|
|
293
|
+
|
|
294
|
+
- **`<Table>`** — declarative-first data table component.
|
|
295
|
+
- **Smart defaults**: column types auto-detected from the first
|
|
296
|
+
non-null value across visible rows. Number columns get
|
|
297
|
+
`text-right` + `tabular-nums` (digit grid alignment), date
|
|
298
|
+
columns get `tabular-nums` too, booleans get `text-center`,
|
|
299
|
+
strings get `text-left`. **The library never changes the font
|
|
300
|
+
family** — `tabular-nums` is a font-feature setting that
|
|
301
|
+
preserves the consumer's theme `font-sans`. Explicit
|
|
302
|
+
`align` / `tabularNums` / `monospace` per column wins.
|
|
303
|
+
- **Sort**: per-column `sortable: true` (default comparator
|
|
304
|
+
handles string / number / Date / boolean / bigint with
|
|
305
|
+
null-last invariant) or custom `(a, b) => number`. Click
|
|
306
|
+
cycles asc → desc → none. **Shift-click** adds the column to
|
|
307
|
+
a multi-sort model. Controlled via `sortModel` /
|
|
308
|
+
`onSortChange` or uncontrolled with internal state.
|
|
309
|
+
- **Search**: `enableSearch` renders a debounced input above
|
|
310
|
+
the table (default 200 ms). Matches any column flagged
|
|
311
|
+
`searchable: true`. **Nested keys supported** at runtime via
|
|
312
|
+
`getNestedValue(row, 'address.city')` — the type-level
|
|
313
|
+
autocomplete from `NestedKeyOf<T>` matches the runtime
|
|
314
|
+
behavior. Stringification covers string / number / boolean /
|
|
315
|
+
Date / array / object.
|
|
316
|
+
- **Selection**: `rowSelection: 'none' | 'single' | 'multiple'`,
|
|
317
|
+
optional select-all checkbox in the header (multiple mode),
|
|
318
|
+
`selectedRowIds` controlled state, **sticky bulk-action
|
|
319
|
+
footer** rendered only when at least one row is selected
|
|
320
|
+
(Pencil & Paper UX pattern: "appear only when selected").
|
|
321
|
+
- **Expandable rows**: `expandable={{ render, expandedRowIds?,
|
|
322
|
+
onExpandChange? }}`. Chevron toggle in a dedicated column;
|
|
323
|
+
`aria-expanded` updates; expanded detail renders as a
|
|
324
|
+
full-width sibling `<tr>` below the row.
|
|
325
|
+
- **Row actions revealed on hover**: `rowActions={(row) => …}`
|
|
326
|
+
slot — by default hidden via opacity, revealed on
|
|
327
|
+
`tr:hover` / `tr:focus-within` (Stripe pattern — reduces
|
|
328
|
+
visual density while keeping actions discoverable).
|
|
329
|
+
- **RBAC at 3 levels**: table-level `access` (hides / disables
|
|
330
|
+
the whole table), per-column `cols[i].access` (`hide` removes
|
|
331
|
+
the column entirely from header AND every row cell), and
|
|
332
|
+
per-action `actions[i].access` on `<RowActionsMenu>`.
|
|
333
|
+
- **A11Y**: semantic `<table>` + `<th scope="col">` + `aria-sort`
|
|
334
|
+
on sortable headers + `aria-selected` on selected rows +
|
|
335
|
+
`aria-expanded` on expand toggles + `<caption>` (optional,
|
|
336
|
+
`sr-only` by default) + keyboard nav via Tab + Enter/Space.
|
|
337
|
+
- **Loading state**: `loading={true}` renders N `<Skeleton>` rows
|
|
338
|
+
(the Sprint 4 component) with `aria-busy="true"`. Count
|
|
339
|
+
configurable via `loadingRowCount` (default 5).
|
|
340
|
+
- **i18n**: column `header` accepts plain strings (pass
|
|
341
|
+
`t('...')`); all internal default strings configurable via
|
|
342
|
+
`labels` prop with English defaults (search placeholder, a11y
|
|
343
|
+
announcements for sort / select / expand, selection counter
|
|
344
|
+
with `{count}` placeholder, density / filter labels). Same
|
|
345
|
+
pattern as `<Pagination>`.
|
|
346
|
+
- **5 variants** (`plain` · `lines` default — Stripe-style ·
|
|
347
|
+
`striped` · `bordered` · `card`) × **3 sizes** (`sm` · `md` ·
|
|
348
|
+
`lg`) × **3 densities** (`compact` 40px · `comfortable`
|
|
349
|
+
default 48px · `spacious` 56px — Pencil & Paper UX research).
|
|
350
|
+
- **Sticky header** by default (overridable).
|
|
351
|
+
- **`sx` + 15 slot props** for the standard customization
|
|
352
|
+
escape hatches.
|
|
353
|
+
|
|
354
|
+
- **Cell renderer library** — pre-built renderers for common
|
|
355
|
+
patterns, exported from `@dashforge/tw`:
|
|
356
|
+
- `RenderText` — one-line, optionally truncated / muted
|
|
357
|
+
- `RenderTwoLine` — bold primary + muted secondary
|
|
358
|
+
(name + email pattern)
|
|
359
|
+
- `RenderChip` — internal status badge, 7 intent colors × 3
|
|
360
|
+
variants (soft / solid / outline) × 2 sizes (sm / md)
|
|
361
|
+
- `RenderButton` — inline button wrapper (defaults `ghost` +
|
|
362
|
+
`sm`)
|
|
363
|
+
- `RowActionsMenu` — 3-dot Popover-backed menu with per-action
|
|
364
|
+
RBAC
|
|
365
|
+
|
|
366
|
+
- **Helpers exported for power use**:
|
|
367
|
+
- `getNestedValue(row, path)` — dotted-path lookup powering
|
|
368
|
+
cell rendering. Useful in consumer custom renderers.
|
|
369
|
+
|
|
370
|
+
- **Theme identity regression guard** —
|
|
371
|
+
`_internal/themeIdentity.test.ts` scans every Table source file
|
|
372
|
+
and fails if `dark:*-neutral-N` classes are introduced. The
|
|
373
|
+
dashforgePreset auto-inverts the neutral palette via CSS var
|
|
374
|
+
swap; adding `dark:` variants on neutral creates double
|
|
375
|
+
inversion and breaks dark mode. The Sprint 4.1 fix removes the
|
|
376
|
+
anti-pattern from Table; **Sprint 4.3 will sweep the rest of
|
|
377
|
+
the catalog** (Typography, Box, etc. carry the same latent bug).
|
|
378
|
+
|
|
379
|
+
- **Smoke-test page** in `dash` consumer at `/test-table` —
|
|
380
|
+
exercises every Table feature with 30 realistic users + nested
|
|
381
|
+
meta + chip-rendered status + RBAC per-column + i18n labels in
|
|
382
|
+
Italian + 5 variants × 3 sizes × 3 densities switcher.
|
|
383
|
+
|
|
384
|
+
- **Doc MDX** `/tw/docs/components/table` — full feature catalog,
|
|
385
|
+
i18n example with `react-i18next`, override matrix
|
|
386
|
+
(align / tabularNums / monospace), 15+ slot props reference,
|
|
387
|
+
cell renderer library, A11Y guarantees, roadmap.
|
|
388
|
+
|
|
389
|
+
### Internal
|
|
390
|
+
|
|
391
|
+
- **Theme identity rule** (now codified): the dashforgePreset
|
|
392
|
+
default IS the Dashforge visual identity. Consumer apps consume
|
|
393
|
+
it as-is and never override. Adding `dark:` variants on the
|
|
394
|
+
neutral palette = double inversion = breaks dark mode. Use
|
|
395
|
+
canonical patterns (LeftNav `itemActive` for selected,
|
|
396
|
+
auto-invert for `bg-neutral-*` and `text-neutral-*`).
|
|
397
|
+
- **Font family rule**: the library never picks a font family —
|
|
398
|
+
`tabular-nums` is safe to auto-apply (font-feature setting),
|
|
399
|
+
`font-mono` is consumer opt-in only via `col.monospace: true`.
|
|
400
|
+
The dashforgePreset does not own the `fontFamily` axis; the
|
|
401
|
+
consumer configures their mono stack in their own
|
|
402
|
+
`tailwind.config.ts` `theme.extend.fontFamily.mono`.
|
|
403
|
+
- **+147 new unit tests** for Table:
|
|
404
|
+
- `getNestedValue` (11) — nested keys, null-safety, zero / empty
|
|
405
|
+
string / false preservation
|
|
406
|
+
- `useTableSearch` (16) — stringification across primitive types
|
|
407
|
+
+ nested keys
|
|
408
|
+
- `useTableSort` (15) — null-last invariant (direction-independent),
|
|
409
|
+
multi-column tie-breaking, custom comparator
|
|
410
|
+
- `useTableSelection` (13) — single / multiple / none modes
|
|
411
|
+
- `useColumnAutoDetect` (14) — type inference + align /
|
|
412
|
+
tabularNums / monospace resolution
|
|
413
|
+
- `themeIdentity` (18 file scanner) — regression guard for
|
|
414
|
+
`dark:` on neutral palette
|
|
415
|
+
- `Table.test.tsx` (57) — rendering, smart defaults, sort,
|
|
416
|
+
search, selection, expandable, row actions, RBAC, variants,
|
|
417
|
+
densities, sizes, sx + slotProps, i18n
|
|
418
|
+
- Full TW suite at **828/828 passing** (46 files; +147 from
|
|
419
|
+
Table, +newer guard).
|
|
420
|
+
|
|
421
|
+
### Compatibility
|
|
422
|
+
|
|
423
|
+
| Axis | Pre-`0.6.0` | Post-`0.6.0` |
|
|
424
|
+
|---|---|---|
|
|
425
|
+
| Public API surface | 31 components | **+ 1 (`Table`)** + 5 cell renderers + `getNestedValue` helper + `Table*` types (`TableProps`, `TableColumn`, `TableSortModel`, `TableFilterModel`, `TableLabels`, `TableCellContext`, `NestedKeyOf`, `TableRowAction`, …) |
|
|
426
|
+
| Peer deps | `react ^18 \|\| ^19`, `tw-theme workspace`, `tw-tokens workspace` | unchanged |
|
|
427
|
+
| Bridge deps | `forms` / `rbac` / `ui-core` `workspace:*` | unchanged |
|
|
428
|
+
| New runtime deps | — | **none** (no `@tanstack/*`, no DnD libs — constraint honored) |
|
|
429
|
+
| Breaking changes | — | Zero |
|
|
430
|
+
| Bundle size | 336 KB raw / 73.9 KB gzipped | **402 KB raw / 91 KB gzipped** (+66 KB raw / +17 KB gz / **+23% gz**) |
|
|
431
|
+
| Migration | — | Drop-in. Zero code changes required on existing usages. |
|
|
432
|
+
|
|
433
|
+
> **Bundle regression note**: the +23% gz delta is **above the
|
|
434
|
+
> 10% reviewer-sign-off threshold** documented in
|
|
435
|
+
> `PERFORMANCE.md`. Justification:
|
|
436
|
+
> Table is the lib's central data-display primitive — it includes
|
|
437
|
+
> sort + search + selection + expandable + RBAC at 3 levels + 5
|
|
438
|
+
> cell renderers + RowActionsMenu (using Popover) + 5 variants ×
|
|
439
|
+
> 3 sizes × 3 densities. The bundle weight is proportional to
|
|
440
|
+
> the feature surface. Sign-off rationale: this is the
|
|
441
|
+
> "MUI X DataGrid Community alternative" component — the single
|
|
442
|
+
> highest-value addition before 1.0. Sprint 4.3 (theme identity
|
|
443
|
+
> sweep) will recover -1 to -3 KB gz by removing redundant
|
|
444
|
+
> `dark:` variants across the catalog.
|
|
445
|
+
|
|
446
|
+
### Migration
|
|
447
|
+
|
|
448
|
+
No code changes required:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
pnpm up @dashforge/tw@^0.6.0-beta
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
To adopt the new Table:
|
|
455
|
+
|
|
456
|
+
```tsx
|
|
457
|
+
import { Table, RenderTwoLine, RenderChip, RowActionsMenu } from '@dashforge/tw';
|
|
458
|
+
import { useTranslation } from 'react-i18next';
|
|
459
|
+
|
|
460
|
+
function UsersTable({ users }) {
|
|
461
|
+
const { t } = useTranslation();
|
|
462
|
+
const [selected, setSelected] = useState<string[]>([]);
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<Table
|
|
466
|
+
rows={users}
|
|
467
|
+
cols={[
|
|
468
|
+
{
|
|
469
|
+
field: 'name',
|
|
470
|
+
header: t('users.fields.name'),
|
|
471
|
+
sortable: true,
|
|
472
|
+
searchable: true,
|
|
473
|
+
cellRenderer: ({ row }) => (
|
|
474
|
+
<RenderTwoLine primary={row.name} secondary={row.email} />
|
|
475
|
+
),
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
field: 'salary',
|
|
479
|
+
header: t('users.fields.salary'),
|
|
480
|
+
sortable: true,
|
|
481
|
+
// Right-aligned + tabular-nums automatically. Font family
|
|
482
|
+
// stays whatever the consumer's theme provides — opt into
|
|
483
|
+
// `monospace: true` if you want font-mono explicitly.
|
|
484
|
+
access: {
|
|
485
|
+
resource: 'employee.salary',
|
|
486
|
+
action: 'read',
|
|
487
|
+
onUnauthorized: 'hide',
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
field: 'status',
|
|
492
|
+
header: t('users.fields.status'),
|
|
493
|
+
cellRenderer: ({ value }) => (
|
|
494
|
+
<RenderChip
|
|
495
|
+
color={value === 'active' ? 'success' : 'warning'}
|
|
496
|
+
>
|
|
497
|
+
{String(value)}
|
|
498
|
+
</RenderChip>
|
|
499
|
+
),
|
|
500
|
+
},
|
|
501
|
+
]}
|
|
502
|
+
getRowId={(r) => r.id}
|
|
503
|
+
enableSearch
|
|
504
|
+
rowSelection="multiple"
|
|
505
|
+
selectedRowIds={selected}
|
|
506
|
+
onSelectionChange={setSelected}
|
|
507
|
+
bulkActions={(rows) => (
|
|
508
|
+
<Button color="danger">
|
|
509
|
+
{t('actions.delete', { count: rows.length })}
|
|
510
|
+
</Button>
|
|
511
|
+
)}
|
|
512
|
+
rowActions={(row) => (
|
|
513
|
+
<RowActionsMenu
|
|
514
|
+
row={row}
|
|
515
|
+
actions={[
|
|
516
|
+
{ label: t('actions.edit'), onClick: edit },
|
|
517
|
+
{ label: t('actions.delete'), onClick: del, color: 'danger' },
|
|
518
|
+
]}
|
|
519
|
+
/>
|
|
520
|
+
)}
|
|
521
|
+
labels={{
|
|
522
|
+
searchPlaceholder: t('table.search'),
|
|
523
|
+
selectedCount: t('table.selected', { defaultValue: '{count} selected' }),
|
|
524
|
+
}}
|
|
525
|
+
/>
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Heads-up — companion releases coming
|
|
531
|
+
|
|
532
|
+
- **Sprint 4.3 → `0.7.0-beta`**: theme identity audit across the
|
|
533
|
+
whole catalog (Typography, Box, etc. carry the same latent
|
|
534
|
+
`dark:` anti-pattern Table just fixed). Expected bundle delta:
|
|
535
|
+
**−1 to −3 KB gz** (removing redundant classes).
|
|
536
|
+
- **Sprint 4.2 → `0.8.0-beta`**: DataGrid with homemade
|
|
537
|
+
virtualization (`IntersectionObserver`-based, no new deps) for
|
|
538
|
+
10k+ row data sets, advanced filter model, sticky columns,
|
|
539
|
+
per-column RBAC.
|
|
540
|
+
|
|
15
541
|
## [0.5.0-beta] — 2026-05-19
|
|
16
542
|
|
|
17
543
|
**Sprint 4 release.** Two TW-only utility primitives — `<Skeleton>`
|