@acronis-platform/ui-react 3.0.0 → 4.0.0

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.
Files changed (219) hide show
  1. package/dist/components/ui/accordion-container/accordion-container.js +14 -14
  2. package/dist/components/ui/accordion-container/accordion-container.js.map +1 -1
  3. package/dist/components/ui/app-shell-chat/app-shell-chat.js.map +1 -1
  4. package/dist/components/ui/area-chart/area-chart.js +141 -139
  5. package/dist/components/ui/area-chart/area-chart.js.map +1 -1
  6. package/dist/components/ui/bar-chart/bar-chart.js +295 -293
  7. package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
  8. package/dist/components/ui/category-bar/category-bar.js +109 -93
  9. package/dist/components/ui/category-bar/category-bar.js.map +1 -1
  10. package/dist/components/ui/chart/chart-palette.js +113 -0
  11. package/dist/components/ui/chart/chart-palette.js.map +1 -0
  12. package/dist/components/ui/chart/chart.js +176 -129
  13. package/dist/components/ui/chart/chart.js.map +1 -1
  14. package/dist/components/ui/chart-state/chart-state-illustrations.js +310 -0
  15. package/dist/components/ui/chart-state/chart-state-illustrations.js.map +1 -0
  16. package/dist/components/ui/chart-state/chart-state.js +48 -34
  17. package/dist/components/ui/chart-state/chart-state.js.map +1 -1
  18. package/dist/components/ui/chart-widget/chart-widget.js +87 -0
  19. package/dist/components/ui/chart-widget/chart-widget.js.map +1 -0
  20. package/dist/components/ui/composed-chart/composed-chart.js +117 -115
  21. package/dist/components/ui/composed-chart/composed-chart.js.map +1 -1
  22. package/dist/components/ui/confidence-cone/confidence-cone.js +166 -158
  23. package/dist/components/ui/confidence-cone/confidence-cone.js.map +1 -1
  24. package/dist/components/ui/funnel-chart/funnel-chart.js +162 -161
  25. package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
  26. package/dist/components/ui/histogram/histogram.js +94 -92
  27. package/dist/components/ui/histogram/histogram.js.map +1 -1
  28. package/dist/components/ui/input/input.js.map +1 -1
  29. package/dist/components/ui/label/label.js +2 -9
  30. package/dist/components/ui/label/label.js.map +1 -1
  31. package/dist/components/ui/line-chart/line-chart.js +143 -141
  32. package/dist/components/ui/line-chart/line-chart.js.map +1 -1
  33. package/dist/components/ui/metric/metric.js +74 -185
  34. package/dist/components/ui/metric/metric.js.map +1 -1
  35. package/dist/components/ui/page-content/page-content.js.map +1 -1
  36. package/dist/components/ui/pie-chart/pie-chart.js +201 -195
  37. package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
  38. package/dist/components/ui/radar-chart/radar-chart.js +133 -131
  39. package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
  40. package/dist/components/ui/radial-bar-chart/radial-bar-chart.js +297 -265
  41. package/dist/components/ui/radial-bar-chart/radial-bar-chart.js.map +1 -1
  42. package/dist/components/ui/sankey-chart/sankey-chart.js +123 -101
  43. package/dist/components/ui/sankey-chart/sankey-chart.js.map +1 -1
  44. package/dist/components/ui/scatter-chart/scatter-chart.js +76 -74
  45. package/dist/components/ui/scatter-chart/scatter-chart.js.map +1 -1
  46. package/dist/components/ui/section/section.js +157 -31
  47. package/dist/components/ui/section/section.js.map +1 -1
  48. package/dist/components/ui/stepper/stepper.js +24 -24
  49. package/dist/components/ui/stepper/stepper.js.map +1 -1
  50. package/dist/components/ui/stepper-item/stepper-item.js +43 -24
  51. package/dist/components/ui/stepper-item/stepper-item.js.map +1 -1
  52. package/dist/components/ui/treemap/treemap.js +133 -124
  53. package/dist/components/ui/treemap/treemap.js.map +1 -1
  54. package/dist/components/ui/trend-indicator/trend-indicator.js +34 -69
  55. package/dist/components/ui/trend-indicator/trend-indicator.js.map +1 -1
  56. package/dist/components/ui/wizard/wizard.js +59 -0
  57. package/dist/components/ui/wizard/wizard.js.map +1 -0
  58. package/dist/hooks/use-wizard.js +75 -0
  59. package/dist/hooks/use-wizard.js.map +1 -0
  60. package/dist/index.js +502 -497
  61. package/dist/index.js.map +1 -1
  62. package/dist/llms/accordion-container.md +181 -0
  63. package/dist/llms/accordion.md +124 -0
  64. package/dist/llms/alert.md +225 -0
  65. package/dist/llms/app-shell-chat.md +415 -0
  66. package/dist/llms/area-chart.md +355 -0
  67. package/dist/llms/avatar.md +208 -0
  68. package/dist/llms/bar-chart.md +406 -0
  69. package/dist/llms/breadcrumb.md +326 -0
  70. package/dist/llms/button-group.md +298 -0
  71. package/dist/llms/button-icon-input.md +142 -0
  72. package/dist/llms/button-icon-menu.md +209 -0
  73. package/dist/llms/button-icon.md +128 -0
  74. package/dist/llms/button-menu.md +172 -0
  75. package/dist/llms/button.md +185 -0
  76. package/dist/llms/calendar-panel.md +733 -0
  77. package/dist/llms/calendar.md +188 -0
  78. package/dist/llms/card-filter.md +214 -0
  79. package/dist/llms/card-section.md +293 -0
  80. package/dist/llms/card.md +447 -0
  81. package/dist/llms/carousel-dialog.md +156 -0
  82. package/dist/llms/category-bar.md +193 -0
  83. package/dist/llms/chart-state.md +190 -0
  84. package/dist/llms/chart-widget.md +209 -0
  85. package/dist/llms/chart.md +182 -0
  86. package/dist/llms/checkbox.md +257 -0
  87. package/dist/llms/chip.md +195 -0
  88. package/dist/llms/combobox.md +164 -0
  89. package/dist/llms/composed-chart.md +508 -0
  90. package/dist/llms/confidence-cone.md +277 -0
  91. package/dist/llms/data-table.md +474 -0
  92. package/dist/llms/date-range-picker.md +272 -0
  93. package/dist/llms/description-list.md +131 -0
  94. package/dist/llms/dialog-footer-carousel.md +147 -0
  95. package/dist/llms/dialog-footer-default.md +142 -0
  96. package/dist/llms/dialog-root.md +246 -0
  97. package/dist/llms/dialog-welcome.md +223 -0
  98. package/dist/llms/dialog.md +403 -0
  99. package/dist/llms/dropdown-menu.md +193 -0
  100. package/dist/llms/empty-overlay.md +146 -0
  101. package/dist/llms/empty.md +156 -0
  102. package/dist/llms/field.md +185 -0
  103. package/dist/llms/filter-cards.md +157 -0
  104. package/dist/llms/filter-chips.md +323 -0
  105. package/dist/llms/filter-search.md +512 -0
  106. package/dist/llms/form.md +154 -0
  107. package/dist/llms/funnel-chart.md +333 -0
  108. package/dist/llms/grid.md +80 -0
  109. package/dist/llms/histogram.md +190 -0
  110. package/dist/llms/input-date-picker.md +203 -0
  111. package/dist/llms/input-otp.md +173 -0
  112. package/dist/llms/input-password.md +197 -0
  113. package/dist/llms/input-search.md +170 -0
  114. package/dist/llms/input-select.md +278 -0
  115. package/dist/llms/input-text-area.md +271 -0
  116. package/dist/llms/input-text.md +192 -0
  117. package/dist/llms/line-chart.md +369 -0
  118. package/dist/llms/link.md +209 -0
  119. package/dist/llms/loading.md +130 -0
  120. package/dist/llms/meter.md +177 -0
  121. package/dist/llms/metric.md +204 -0
  122. package/dist/llms/number-field.md +122 -0
  123. package/dist/llms/page-content.md +69 -0
  124. package/dist/llms/page-header.md +182 -0
  125. package/dist/llms/pagination.md +133 -0
  126. package/dist/llms/pie-chart.md +307 -0
  127. package/dist/llms/popover.md +252 -0
  128. package/dist/llms/progress-circle.md +126 -0
  129. package/dist/llms/progress.md +111 -0
  130. package/dist/llms/radar-chart.md +316 -0
  131. package/dist/llms/radial-bar-chart.md +319 -0
  132. package/dist/llms/radio.md +255 -0
  133. package/dist/llms/resizable.md +195 -0
  134. package/dist/llms/sankey-chart.md +208 -0
  135. package/dist/llms/scatter-chart.md +239 -0
  136. package/dist/llms/scroll-area.md +183 -0
  137. package/dist/llms/section.md +401 -0
  138. package/dist/llms/select.md +210 -0
  139. package/dist/llms/separator.md +87 -0
  140. package/dist/llms/sheet.md +202 -0
  141. package/dist/llms/sidebar-primary.md +360 -0
  142. package/dist/llms/sidebar-secondary.md +417 -0
  143. package/dist/llms/skeleton.md +81 -0
  144. package/dist/llms/slider.md +97 -0
  145. package/dist/llms/spinner.md +100 -0
  146. package/dist/llms/stack.md +80 -0
  147. package/dist/llms/stepper-item.md +309 -0
  148. package/dist/llms/stepper.md +251 -0
  149. package/dist/llms/switch.md +140 -0
  150. package/dist/llms/table.md +436 -0
  151. package/dist/llms/tabs.md +197 -0
  152. package/dist/llms/tag.md +139 -0
  153. package/dist/llms/timeline.md +361 -0
  154. package/dist/llms/timer.md +259 -0
  155. package/dist/llms/toast.md +241 -0
  156. package/dist/llms/toggle-group.md +121 -0
  157. package/dist/llms/toolbar.md +453 -0
  158. package/dist/llms/tooltip.md +164 -0
  159. package/dist/llms/treemap.md +254 -0
  160. package/dist/llms/trend-indicator.md +132 -0
  161. package/dist/llms/widget-placeholder.md +169 -0
  162. package/dist/llms/wizard.md +412 -0
  163. package/dist/llms.txt +142 -0
  164. package/dist/react.js +502 -497
  165. package/dist/react.js.map +1 -1
  166. package/dist/src/components/ui/area-chart/area-chart.d.ts +9 -4
  167. package/dist/src/components/ui/avatar/avatar.d.ts +1 -1
  168. package/dist/src/components/ui/bar-chart/bar-chart.d.ts +9 -4
  169. package/dist/src/components/ui/category-bar/category-bar.d.ts +10 -4
  170. package/dist/src/components/ui/chart/__stories__/palette-control.d.ts +48 -0
  171. package/dist/src/components/ui/chart/__tests__/chart-palette.test.d.ts +1 -0
  172. package/dist/src/components/ui/chart/chart-palette.d.ts +128 -0
  173. package/dist/src/components/ui/chart/chart.d.ts +66 -15
  174. package/dist/src/components/ui/chart/index.d.ts +4 -2
  175. package/dist/src/components/ui/chart-state/chart-state-illustrations.d.ts +6 -0
  176. package/dist/src/components/ui/chart-state/chart-state.d.ts +13 -2
  177. package/dist/src/components/ui/chart-state/index.d.ts +1 -0
  178. package/dist/src/components/ui/chart-widget/chart-widget.d.ts +64 -0
  179. package/dist/src/components/ui/chart-widget/index.d.ts +1 -0
  180. package/dist/src/components/ui/composed-chart/composed-chart.d.ts +12 -7
  181. package/dist/src/components/ui/confidence-cone/confidence-cone.d.ts +10 -5
  182. package/dist/src/components/ui/empty-overlay/empty-overlay.d.ts +1 -1
  183. package/dist/src/components/ui/funnel-chart/funnel-chart.d.ts +11 -5
  184. package/dist/src/components/ui/histogram/histogram.d.ts +10 -4
  185. package/dist/src/components/ui/label/index.d.ts +1 -1
  186. package/dist/src/components/ui/label/label.d.ts +1 -5
  187. package/dist/src/components/ui/line-chart/line-chart.d.ts +9 -4
  188. package/dist/src/components/ui/metric/metric.d.ts +13 -14
  189. package/dist/src/components/ui/pie-chart/pie-chart.d.ts +17 -8
  190. package/dist/src/components/ui/radar-chart/radar-chart.d.ts +10 -5
  191. package/dist/src/components/ui/radial-bar-chart/radial-bar-chart.d.ts +17 -6
  192. package/dist/src/components/ui/sankey-chart/sankey-chart.d.ts +10 -5
  193. package/dist/src/components/ui/scatter-chart/scatter-chart.d.ts +10 -5
  194. package/dist/src/components/ui/section/index.d.ts +1 -1
  195. package/dist/src/components/ui/section/section.d.ts +75 -6
  196. package/dist/src/components/ui/stepper-item/stepper-item.d.ts +6 -5
  197. package/dist/src/components/ui/tag/tag.d.ts +1 -1
  198. package/dist/src/components/ui/treemap/treemap.d.ts +11 -5
  199. package/dist/src/components/ui/trend-indicator/trend-indicator.d.ts +3 -7
  200. package/dist/src/components/ui/wizard/index.d.ts +1 -0
  201. package/dist/src/components/ui/wizard/wizard.d.ts +6 -0
  202. package/dist/src/hooks/index.d.ts +1 -0
  203. package/dist/src/hooks/use-wizard.d.ts +80 -0
  204. package/dist/src/index.d.ts +2 -3
  205. package/dist/ui-react.css +1 -1
  206. package/package.json +9 -3
  207. package/dist/components/ui/app-shell/app-shell.js +0 -75
  208. package/dist/components/ui/app-shell/app-shell.js.map +0 -1
  209. package/dist/components/ui/auth-layout/auth-layout.js +0 -56
  210. package/dist/components/ui/auth-layout/auth-layout.js.map +0 -1
  211. package/dist/components/ui/collapsible/collapsible.js +0 -22
  212. package/dist/components/ui/collapsible/collapsible.js.map +0 -1
  213. package/dist/src/components/ui/app-shell/app-shell.d.ts +0 -8
  214. package/dist/src/components/ui/app-shell/index.d.ts +0 -1
  215. package/dist/src/components/ui/auth-layout/auth-layout.d.ts +0 -6
  216. package/dist/src/components/ui/auth-layout/index.d.ts +0 -1
  217. package/dist/src/components/ui/collapsible/collapsible.d.ts +0 -5
  218. package/dist/src/components/ui/collapsible/index.d.ts +0 -1
  219. package/dist/src/components/ui/label/label.docs.d.ts +0 -10
@@ -0,0 +1,474 @@
1
+ # DataTable
2
+
3
+ > A data grid built on TanStack react-table v8, composed over the Table primitives — sorting, filtering (text + per-column), column visibility, row selection, pagination, optional row expansion (row-level or an in-cell expand trigger), column resizing, drag-to-reorder columns, and sticky (pinned) columns. Renders a built-in trailing action column (the column-settings cog in the header, a per-row overflow-actions trigger in each row) and per-header capability-hint tooltips, and navigates rows with a roving tabindex. Ships with companion parts (column header, toolbar, pagination, view options, expand trigger, bulk-actions bar). Also supports server-driven usage: rendering from an externally-built TanStack `table` instance, manual (server-driven) sorting, infinite-scroll pagination, and custom row/empty-state rendering. Ported from the legacy shadcn-uikit `data-table`.
4
+
5
+ **Status:** draft · **Category:** data-display · **Since:** v0.42.0
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import { DataTable } from '@acronis-platform/ui-react'
11
+ ```
12
+
13
+ ## Quick example
14
+
15
+ ```tsx
16
+ <DataTable columns={columns} data={rows} />
17
+ ```
18
+
19
+ A data grid built on TanStack react-table v8, composed over the Table
20
+ primitives — sorting, filtering, column visibility, row selection, pagination,
21
+ optional row expansion, column resizing, and sticky (pinned) columns.
22
+
23
+ > **Status: draft (design-pending v1).** Ported from the legacy
24
+ > `@acronis-platform/shadcn-uikit` `data-table`. Adds no color of its own — it
25
+ > composes already-themed ui-react components. Reconcile with
26
+ > `/figma-component DataTable <url> --update` once a mockup lands.
27
+
28
+ ## When to use
29
+
30
+ - Tabular data that needs interaction — sorting, filtering, selection, paging,
31
+ column resizing, drag-to-reorder columns, or a few columns pinned while the
32
+ rest scroll horizontally.
33
+
34
+ ## When not to use
35
+
36
+ - A simple static table — use the `Table` primitives directly.
37
+ - A key/value list or cards layout — a table is the wrong shape.
38
+ - Grouped/aggregated rows — **not available yet** (deferred to a future
39
+ revision); do not rely on them. (Drag-to-reorder columns _is_ supported now,
40
+ via `enableColumnReordering`.)
41
+
42
+ ## Parts
43
+
44
+ | Export | Purpose |
45
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | `DataTable` | The grid. Owns table state by default (`columns` / `data`), or renders a caller-built `table` instance. Supports resizing, pinned columns, server-driven sorting/pagination, and custom row/empty-state rendering. |
47
+ | `DataTableColumnHeader` | A sortable column header — single-click toggle (↑/↓/↕). Use in a column's `header`. |
48
+ | `DataTableToolbar` | Search box + per-column filters + applied-filter chips. Takes a `table` instance. |
49
+ | `DataTablePagination` | Selection count, rows-per-page, page controls. Takes a `table`. |
50
+ | `DataTableViewOptions` | Column-visibility menu (a thin TanStack adapter over the `TableViewOptions` primitive). Pass `iconOnly` for the cog trigger that belongs in the trailing settings column. |
51
+ | `DataTableExpandTrigger` | A chevron toggle wired to a row's expansion state, placed inside a column's `cell` render function. |
52
+ | `DataTableBulkActionsBar` | A selection-aware bar above the grid: the consumer's bulk actions in a `<fieldset>` disabled until a selection is active, plus the selection summary and a Deselect control (or `loadedLabel` when nothing is selected). Takes a `table`. |
53
+ | `isBulkSelectionActive` | The single predicate for the bulk-selection threshold (one or more rows selected). Pass it to `TableActionsCell`'s `bulkSelectionActive` rather than re-deriving it. |
54
+
55
+ `DataTable` manages its own table state by default. The companion parts
56
+ operate on a TanStack `table` instance you build with `useReactTable` — render
57
+ them around a `DataTable` (or your own `<Table>`), passing the same instance.
58
+ Pass that same instance to `DataTable`'s own `table` prop to have it render
59
+ from it too (see **Server-driven usage** below).
60
+
61
+ ## Server-driven usage
62
+
63
+ - **External `table`** — pass an externally-built TanStack `table` instance
64
+ via the `table` prop and DataTable renders from it as-is, owning no state of
65
+ its own. Use this to share one instance across `DataTable` and its companion
66
+ parts, or when the caller needs full control (e.g. a table already wired to
67
+ a URL/query-state hook). Several props become no-ops when `table` is passed
68
+ — see `table`'s own description in `api.yaml`.
69
+ - **Manual sorting** — set `manualSorting` and drive `sorting` /
70
+ `onSortingChange` yourself; DataTable skips its own comparator so already
71
+ server-sorted `data` isn't re-sorted client-side.
72
+ - **Infinite scroll** — set `paginationMode="infinite"` and pass the full
73
+ accumulated `data`, `hasNextPage`, `isLoadingMore`, and an `onLoadMore`
74
+ callback. DataTable renders a sentinel row that calls `onLoadMore` once it
75
+ scrolls into view, and a trailing loading row while `isLoadingMore` is true.
76
+ The sentinel needs at least one row already rendered — it can't drive an
77
+ empty table's very first fetch, so seed the first page yourself (e.g. on
78
+ mount). Pass `loadMoreRootMargin` (e.g. `'400px'`) to fire `onLoadMore`
79
+ before the sentinel is literally visible, so the fetch has a head start on
80
+ the user's scroll — tune it relative to your page size, since a large
81
+ margin with small pages can trigger several `onLoadMore` calls back-to-back
82
+ as the user scrolls normally. Does not compose with virtualization — for a
83
+ very large accumulated list, virtualize the raw `Table` primitives yourself
84
+ (e.g. `useVirtualizer` from `@tanstack/react-virtual`) instead.
85
+ - **Custom rows** — `renderRow` swaps in a fully custom row, bypassing
86
+ DataTable's per-cell `flexRender` path (and, as a result, its own row
87
+ expansion — a row rendered via `renderRow` must implement any expanded
88
+ content itself). `renderEmptyState` swaps in a custom "no data" row,
89
+ receiving `hasFilters` so the caller can distinguish "no data at all" from
90
+ "no matches".
91
+
92
+ ## Advanced columns
93
+
94
+ - **Column resizing** — set `enableColumnResizing` on `DataTable` to render a
95
+ drag handle at the trailing edge of each resizable header (TanStack's native
96
+ `columnResizing`). Pass `onColumnSizingChange` to persist widths.
97
+ - **Sticky (pinned) columns** — set `meta.pin: 'left' | 'right'` on a
98
+ `ColumnDef`. DataTable drives TanStack's native column-pinning and renders the
99
+ column as `position: sticky` cells with an opaque row background.
100
+ - **Wrapping columns** — set `meta.wrap: true` on a `ColumnDef` to let that
101
+ column's header and cell content wrap onto multiple lines instead of
102
+ truncating, mirroring the `Table` primitives' `wrap` prop.
103
+ - **Expandable column** — put a `DataTableExpandTrigger` in a column's `cell`
104
+ render function so the expand affordance sits in a real column instead of a
105
+ whole-row click. It reads `row.getCanExpand()` / `getIsExpanded()` and calls
106
+ `row.toggleExpanded()`; the row-level `getRowCanExpand` / `renderExpandedRow`
107
+ contract is unchanged.
108
+ - **Per-column filtering** — pass filter fields as `children` to
109
+ `DataTableToolbar`. They render inside a `FilterSearchFilters` popover (wire
110
+ each field via `useFilterSearchFilters()` keyed by column id), and the toolbar
111
+ renders a `FilterSearchAppliedFilters` chip row below itself. `filtersLabel`
112
+ and `getFilterChipLabel` customize the trigger and chip labels.
113
+
114
+ ## Example
115
+
116
+ ```tsx
117
+ import { DataTable, DataTableColumnHeader } from '@acronis-platform/ui-react';
118
+ import type { ColumnDef } from '@tanstack/react-table';
119
+
120
+ const columns: ColumnDef<Payment>[] = [
121
+ {
122
+ accessorKey: 'name',
123
+ header: ({ column }) => (
124
+ <DataTableColumnHeader column={column} title="Name" />
125
+ ),
126
+ meta: { pin: 'left' }, // sticky column
127
+ },
128
+ { accessorKey: 'amount', header: 'Amount' },
129
+ ];
130
+
131
+ // Resizable, with the first column pinned left
132
+ <DataTable columns={columns} data={payments} enableColumnResizing />;
133
+ ```
134
+
135
+ For a toolbar + pagination, build a `table` with `useReactTable` and pass it to
136
+ `DataTableToolbar` / `DataTablePagination` alongside the grid; pass filter fields
137
+ as `children` of the toolbar for per-column filtering.
138
+
139
+ ## Props
140
+
141
+ | Prop | Type | Default | Required | Description |
142
+ |------|------|---------|----------|-------------|
143
+ | `columns` | `ColumnDef<TData, TValue>[]` | — | no | TanStack column definitions (header / cell renderers, accessors). Required unless `table` (an externally-built instance) is passed. |
144
+ | `data` | `TData[]` | — | no | The row data. Required unless `table` is passed. |
145
+ | `resize-column-label` | `string` | `'Resize column'` | no | Accessible name of a column's resize handle (`enableColumnResizing` only). Override to localize. |
146
+ | `empty-label` | `string` | `'No results.'` | no | Text of the default empty-state row; ignored when `renderEmptyState` is given. Override to localize. |
147
+ | `rows-per-page-label` | `string` | `'Rows per page'` | no | On the pagination part — label of the rows-per-page select (also its accessible name). Override to localize. The pagination part also takes `first-page-label`, `previous-page-label`, `next-page-label`, `last-page-label`, `page-label` and `summary-label`. `summary-label` is `(selected: number, total: number) => string`, always called with concrete counts — unlike `TablePagination`'s `(selectedRows: number | undefined, totalRows: number) => string | null`, it never receives `undefined`/`null`. |
148
+ | `expand-label` | `string` | `'Expand row'` | no | On the expand-trigger part — accessible name while the row is collapsed. Override to localize. |
149
+ | `collapse-label` | `string` | `'Collapse row'` | no | On the expand-trigger part — accessible name while the row is expanded. Override to localize. |
150
+ | `sort-label` | `(title: string) => string` | `(title) => `Sort by ${title}`` | no | On the column-header part — builds the accessible label of the sort toggle from the column title. Override to localize. |
151
+ | `table` | `Table<TData>` | — | no | Render from an externally-built TanStack `table` instance instead of DataTable's own — DataTable then owns no state. Makes the following no-ops (configure the equivalent on the external instance instead): columnVisibility, onColumnVisibilityChange, onColumnSizingChange, enableColumnResizing, getRowCanExpand, manualSorting, sorting, onSortingChange, paginationMode, onLoadMore, loadMoreRootMargin, hasNextPage, isLoadingMore. meta.pin-driven column pinning is also skipped — pin/unpin the external instance directly via TanStack's `column.pin()`. |
152
+ | `getRowCanExpand` | `(row: Row<TData>) => boolean` | — | no | Enables row expansion for rows that return true. Pair with renderExpandedRow. |
153
+ | `renderExpandedRow` | `(row: Row<TData>) => ReactNode` | — | no | Renders the detail content for an expanded row. |
154
+ | `striped` | `boolean` | `false` | no | Alternating row backgrounds (borrowed from the Vue AvTable). |
155
+ | `bordered` | `boolean` | `false` | no | Vertical borders between columns (rows already have horizontal borders). |
156
+ | `highlightCurrentRow` | `boolean` | `false` | no | Highlight the row the user last clicked (the "current" row). |
157
+ | `skeleton` | `boolean` | `false` | no | Render placeholder skeleton rows instead of data (loading state). |
158
+ | `skeletonRows` | `number` | `5` | no | Number of skeleton rows when skeleton is set. |
159
+ | `enableColumnResizing` | `boolean` | `false` | no | Opt in to interactive column resizing — renders a drag handle at the trailing edge of each resizable header (TanStack's native `columnResizing`, `columnResizeMode: 'onChange'`). |
160
+ | `enableColumnReordering` | `boolean` | `false` | no | Opt in to column reordering by dragging a header cell onto another one (native HTML5 drag-and-drop driving TanStack's `columnOrder`; the dragged header dims while the gesture is in flight). Pinned columns are excluded — they're anchored to a table edge by definition. Pointer-only: there is no keyboard equivalent yet. No-op when `table` is passed. |
161
+ | `columnOrder` | `ColumnOrderState` | — | no | Controlled column-order state; pairs with `onColumnOrderChange` (e.g. to persist the user's order). Uncontrolled (internal state) when omitted. No-op when `table` is passed. |
162
+ | `rowSelection` | `RowSelectionState` | — | no | Controlled row-selection state; pairs with `onRowSelectionChange` (e.g. to share one selection state with an external `useReactTable` instance that feeds a composed toolbar/bulk-actions bar). Uncontrolled (internal state) when omitted. |
163
+ | `hideActionColumn` | `boolean` | `false` | no | Hide the trailing sticky action column — the column-visibility cog in the header and each row's overflow-actions ellipsis. Shown by default. No-op when `table` is passed (build the column into that instance's own `columns` instead). |
164
+ | `renderRowActions` | `(row: Row<TData>) => ReactNode` | — | no | Renders the content of a row's overflow-actions menu (e.g. Edit/Delete items), opened from the ellipsis trigger in the trailing action column. Omit to render that row without a trigger — the 48px column is still reserved. Suppressed (like the trigger itself) while a bulk selection is active, per `TableActionsCell`. |
165
+ | `rowActionsLabel` | `string` | `'Row actions'` | no | Accessible name of a row's overflow-actions trigger. Override to localize. |
166
+ | `columnSettingsLabel` | `string` | — | no | Accessible name of the action column's column-visibility trigger. Override to localize; falls through to the view-options part's own default ("Column settings") when omitted. |
167
+ | `headerHints` | `Partial<DataTableHeaderHints>` | `{ sort: { label: 'Sort column', action: 'Click' }, reorder: { label: 'Reorder column', action: 'Drag' }, resize: { label: 'Resize column', action: 'Drag border' } }` | no | Copy of the tooltip shown while a header cell is hovered/focused — one `{ label, action }` line per capability that column actually has (`sort` / `reorder` / `resize`). Merged over the defaults, so any subset can be overridden to localize; a column with no capability shows no tooltip at all. |
168
+ | `bulkSelectionActive` | `boolean` | `false` | no | On the `TableActionsCell` part — suppress this row's actions because a *bulk* selection is in play. The cell still reserves its 48px column but renders no children and no hover/press tint. Pass `isBulkSelectionActive(table)` (exported alongside DataTable) rather than re-deriving the threshold: **one or more rows selected**, including exactly one. |
169
+ | `meta.pin` | `'left' | 'right'` | — | no | Set on a `ColumnDef.meta` (not a DataTable prop) — pins the column to a table edge. DataTable drives TanStack's native column-pinning and renders the column as `position: sticky` cells with an opaque row background. |
170
+ | `meta.wrap` | `boolean` | — | no | Set on a `ColumnDef.meta` (not a DataTable prop) — allows a column's header and cell content to wrap onto multiple lines instead of truncating, mirroring the `Table` primitives' `wrap` prop. |
171
+ | `children` | `ReactNode` | — | no | On DataTableToolbar — filter fields rendered inside a `FilterSearchFilters` popover (wire each to a column via `useFilterSearchFilters()` keyed by column id). When provided, the toolbar renders the filter popover plus a `FilterSearchAppliedFilters` chip row below it. |
172
+ | `filtersLabel` | `ReactNode` | `'Filters'` | no | On DataTableToolbar — label for the filters trigger button. |
173
+ | `getFilterChipLabel` | `(key: string, value: unknown) => ReactNode` | — | no | On DataTableToolbar — formats an applied-filter chip's label (defaults to "<id>: <value>"). |
174
+ | `selectedLabel` | `(count: number) => string` | `(count) => `${count} item${count === 1 ? '' : 's'} selected:`` | no | On DataTableBulkActionsBar — builds the trailing selection summary from `table.getSelectedRowModel().rows.length`. Override to localize. The bar also takes `clear-label` (default "Deselect"), the trailing control that calls `table.resetRowSelection()`, and `children` — the bulk actions, rendered *leading* (ahead of the trailing summary/Deselect group, which is pushed to the far edge with `ms-auto`) and wrapped in a native `<fieldset disabled>` while nothing is selected, so every nested control picks up the disabled state without per-action wiring. The bar is not rendered by DataTable: the consumer mounts it over the same external `table` instance that feeds the checkbox column (see the `data-table-bulk-actions` pattern). It is **always mounted** — it never renders nothing. Instead it toggles between two states on `isBulkSelectionActive(table)` (one or more rows selected): with nothing selected the actions are disabled and the trailing side shows `loadedLabel`; from the first selected row on, the actions enable and the trailing side shows the selection summary plus the clear control. |
175
+ | `loadedLabel` | `ReactNode` | — | no | On DataTableBulkActionsBar — trailing text shown in place of the selection summary while nothing is selected (e.g. "25 of 1250 items loaded"). Omit to render nothing in that state. Override to localize. |
176
+ | `manualSorting` | `boolean` | `false` | no | Opt out of client-side sorting — pass already-sorted `data` and drive sorting via `sorting`/`onSortingChange` (e.g. mapped to a server query by the caller). Mapping sort state to a query and refetching stays the caller's job. No-op when `table` is passed. |
177
+ | `sorting` | `SortingState` | — | no | Controlled sorting state; pairs with `onSortingChange`. Uncontrolled (internal state) when omitted. No-op when `table` is passed. |
178
+ | `renderRow` | `(row: Row<TData>, rowIndex: number) => ReactNode` | — | no | Renders a full row, bypassing DataTable's default per-cell `flexRender` path entirely. Use to swap in a custom, independently memoizable row component — the caller owns the row's markup and equality semantics. Reuse the exported `getCellStyle` / `getPinnedStyle` / `getColumnWidth` helpers to match DataTable's default cell styling if desired. Also bypasses `renderExpandedRow` — a row rendered via `renderRow` never gets an expanded-content row appended; read `row.getIsExpanded()` and render it yourself if needed. |
179
+ | `renderEmptyState` | `(context: { hasFilters: boolean }) => ReactNode` | — | no | Renders a custom empty state instead of the default "No results." row. Receives `hasFilters` (whether any column filter is currently applied) so the caller can distinguish "no data at all" from "no matches" — the copy/wording/localization stays the caller's job. |
180
+ | `paginationMode` | `'page' | 'infinite'` | `'page'` | no | `'page'` keeps today's client-paginated behavior. `'infinite'` omits the paginated row model — `data` is the full accumulated array the caller appends to on each `onLoadMore` — and renders a sentinel row that calls `onLoadMore` once it scrolls into view. The sentinel requires at least one row already rendered, so it cannot drive an empty table's very first fetch — seed the first page yourself (e.g. on mount) and use `onLoadMore` for subsequent pages. No-op when `table` is passed. Does not compose with virtualization; for a large accumulated list, virtualize the raw `Table` primitives yourself instead. |
181
+ | `loadMoreRootMargin` | `string` | — | no | Expands the sentinel's `IntersectionObserver` root margin (native CSS margin syntax, e.g. "400px") so `onLoadMore` fires before the sentinel is literally visible, reducing how often the trailing loading row is seen. `paginationMode="infinite"` only; no-op when `table` is passed. How far this actually prefetches also depends on page size — a large margin with small pages can trigger several `onLoadMore` calls back-to-back as the user scrolls normally, which is expected; tune it relative to page size. |
182
+ | `hasNextPage` | `boolean` | `false` | no | Whether more rows are available to load. `paginationMode="infinite"` only. |
183
+ | `isLoadingMore` | `boolean` | `false` | no | Whether a load is in flight — suppresses further `onLoadMore` calls and renders a trailing loading row. `paginationMode="infinite"` only. |
184
+
185
+ ## Events
186
+
187
+ | Event | Payload | Description |
188
+ |-------|---------|-------------|
189
+ | `onColumnOrderChange` | `ColumnOrderState (TanStack OnChangeFn)` | Passthrough for the `columnOrder` state; pairs with `columnOrder`. Fires with the new order after a header drop when `enableColumnReordering` is set. No-op when `table` is passed. |
190
+ | `onRowSelectionChange` | `RowSelectionState (TanStack OnChangeFn)` | Passthrough for the `rowSelection` state; pairs with `rowSelection`. |
191
+ | `onColumnSizingChange` | `ColumnSizingState (TanStack OnChangeFn)` | Passthrough for the `columnSizing` state so a consumer can persist column widths when `enableColumnResizing` is set. |
192
+ | `onSortingChange` | `SortingState (TanStack OnChangeFn)` | Passthrough for the `sorting` state; pairs with `sorting`. No-op when `table` is passed. |
193
+ | `onLoadMore` | `undefined` | Called when the infinite-scroll sentinel intersects the viewport. `paginationMode="infinite"` only. Requires at least one row already rendered — never fires from an empty table. The fetch-more call, cursor/offset tracking, dedup, and accumulating `data` stay the caller's job. |
194
+
195
+ ## Behavior
196
+
197
+ DataTable renders a TanStack react-table over the Table primitives. By default
198
+ the grid state (sorting, filtering, visibility, selection, pagination,
199
+ expansion) lives in the component; the companion parts operate on a
200
+ caller-built `table` instance. DataTable can also render from a caller-built
201
+ `table` instance itself (see "Render from an external table instance" below),
202
+ in which case it owns none of that state.
203
+
204
+ ```gherkin
205
+ Scenario: Render rows
206
+ Given columns and data
207
+ Then the headers and a row per datum render
208
+ ```
209
+
210
+ ```gherkin
211
+ Scenario: Empty
212
+ Given an empty data array
213
+ Then a single "No results." row spans only the visible columns
214
+ ```
215
+
216
+ ```gherkin
217
+ Scenario: Custom empty state
218
+ Given renderEmptyState is provided
219
+ When there are no rows to render
220
+ Then renderEmptyState is called with hasFilters (whether a column filter is applied)
221
+ And its return value replaces the default "No results." row
222
+ ```
223
+
224
+ ```gherkin
225
+ Scenario: Render from an external table instance
226
+ Given a `table` instance built by the caller with useReactTable
227
+ When it is passed to DataTable's `table` prop
228
+ Then DataTable renders from that instance and owns no state of its own
229
+ And columnVisibility/onColumnVisibilityChange, onColumnSizingChange,
230
+ enableColumnResizing, getRowCanExpand, manualSorting, sorting,
231
+ onSortingChange, and paginationMode-related props are no-ops
232
+ And DataTable does not drive column pinning from meta.pin on that instance —
233
+ the caller pins/unpins its own columns via TanStack's column.pin()
234
+ ```
235
+
236
+ ```gherkin
237
+ Scenario: Manual (server-side) sorting
238
+ Given manualSorting and controlled sorting/onSortingChange
239
+ When the user clicks a DataTableColumnHeader
240
+ Then onSortingChange fires with the next sort
241
+ And DataTable does not reorder the rows itself (no client-side comparator runs)
242
+ ```
243
+
244
+ ```gherkin
245
+ Scenario: Custom row rendering
246
+ Given renderRow is provided
247
+ Then each row is rendered by calling renderRow(row, rowIndex)
248
+ And DataTable's own per-cell flexRender/pinning/styling path is skipped for that row
249
+ And no expanded-content row is appended even if getRowCanExpand returns true for it —
250
+ the caller must read row.getIsExpanded() and render it themselves inside renderRow
251
+ ```
252
+
253
+ ```gherkin
254
+ Scenario: Infinite scroll
255
+ Given paginationMode="infinite" and hasNextPage is true
256
+ And at least one row is already rendered
257
+ Then a sentinel row renders as the last row of the body
258
+ When the sentinel scrolls into view and isLoadingMore is false
259
+ Then onLoadMore fires
260
+ And no further onLoadMore calls fire while isLoadingMore is true
261
+ When isLoadingMore is true
262
+ Then a trailing loading row renders below the sentinel
263
+ ```
264
+
265
+ ```gherkin
266
+ Scenario: Infinite scroll cannot drive the very first fetch
267
+ Given paginationMode="infinite", data=[], and hasNextPage is true
268
+ Then no sentinel renders (rows.length is 0) — the default "No results." row renders instead
269
+ And onLoadMore never fires
270
+ # The caller must seed the first page itself (e.g. on mount); the sentinel
271
+ # only drives subsequent pages once at least one row exists.
272
+ ```
273
+
274
+ ```gherkin
275
+ Scenario: Prefetching ahead of the literal scroll position
276
+ Given paginationMode="infinite" and loadMoreRootMargin="400px"
277
+ Then the sentinel's IntersectionObserver root margin is expanded by that amount
278
+ And onLoadMore can fire before the sentinel is literally visible in the viewport
279
+ # How far ahead this effectively prefetches also depends on page size — a
280
+ # large margin with small pages can trigger several onLoadMore calls
281
+ # back-to-back as the user scrolls normally; that is expected.
282
+ ```
283
+
284
+ ```gherkin
285
+ Scenario: Sort a column in a single click
286
+ Given a column whose header is a DataTableColumnHeader
287
+ And the column is unsorted (a muted up/down arrow)
288
+ When the user clicks the header once
289
+ Then the rows reorder ascending and the header shows an up arrow in the active blue
290
+ When the user clicks the header again
291
+ Then the rows reorder descending and the header shows a down arrow in the active blue
292
+ ```
293
+
294
+ ```gherkin
295
+ Scenario: Hide a column
296
+ Given the toolbar's view-options menu
297
+ When the user unchecks the column
298
+ Then that column is removed from the grid
299
+ ```
300
+
301
+ ```gherkin
302
+ Scenario: Filter via the toolbar
303
+ Given a DataTableToolbar with searchKey="email"
304
+ When the user types into the search box
305
+ Then only rows whose email matches remain
306
+ And a Reset button clears the filter (when no filter fields are provided)
307
+ ```
308
+
309
+ ```gherkin
310
+ Scenario: Per-column filtering
311
+ Given a DataTableToolbar given filter-field children
312
+ And each field is wired to a column via useFilterSearchFilters() by column id
313
+ When the user sets a field in the filters popover
314
+ Then that column's filter is committed to the table (the text searchKey is preserved)
315
+ And an applied-filter chip appears in the row below the toolbar
316
+ When the user removes the chip
317
+ Then that column's filter clears
318
+ ```
319
+
320
+ ```gherkin
321
+ Scenario: Paginate
322
+ Given a DataTablePagination bound to the table
323
+ When the user clicks next / prev / first / last
324
+ Then the visible page of rows changes
325
+ And the rows-per-page select changes the page size
326
+ ```
327
+
328
+ ```gherkin
329
+ Scenario: Expand a row
330
+ Given getRowCanExpand returns true and renderExpandedRow is provided
331
+ When a row is toggled expanded
332
+ Then a detail row renders beneath it spanning all columns
333
+ ```
334
+
335
+ ```gherkin
336
+ Scenario: Expand from a column trigger
337
+ Given a column whose cell renders a DataTableExpandTrigger
338
+ And getRowCanExpand returns true and renderExpandedRow is provided
339
+ When the user clicks the chevron trigger in that cell
340
+ Then the row toggles expanded (aria-expanded flips) and the detail row renders
341
+ And the trigger renders nothing for a row that can't expand
342
+ ```
343
+
344
+ ```gherkin
345
+ Scenario: Resize a column
346
+ Given a DataTable with enableColumnResizing
347
+ When the user drags the handle at a header's trailing edge
348
+ Then that column's width changes live (columnResizeMode: "onChange")
349
+ And onColumnSizingChange fires so a consumer can persist the widths
350
+ ```
351
+
352
+ ```gherkin
353
+ Scenario: Reorder a column by dragging its header
354
+ Given a DataTable with enableColumnReordering
355
+ Then every non-pinned header cell is draggable and shows the grab cursor
356
+ (cursor-grab; cursor-grabbing while pressed)
357
+ When the user drags one header and drops it on another
358
+ Then the dragged column moves to the drop target's position (headers and body cells alike)
359
+ And onColumnOrderChange fires with the new order so a consumer can persist it
360
+ And a pinned column is never draggable — it is anchored to a table edge
361
+ # Pointer-only: there is no keyboard equivalent for the gesture yet.
362
+ ```
363
+
364
+ ```gherkin
365
+ Scenario: Row actions vs. bulk actions — the selection threshold
366
+ Given a table with a selection column, per-row TableActionsCell actions, and a
367
+ DataTableBulkActionsBar over the same table instance
368
+ When nothing is selected
369
+ Then every row shows its own actions
370
+ And the bulk-actions bar is mounted in its idle state — the bulk actions are
371
+ disabled (a native <fieldset disabled>) and the trailing side shows
372
+ `loadedLabel` instead of a selection summary
373
+ When exactly one row is selected
374
+ Then the bulk scope is active: the bar's actions enable and its trailing side
375
+ shows the selection summary plus the clear control
376
+ And the per-row actions are suppressed on every row — the cell keeps its 48px
377
+ column but renders no trigger and no hover/press tint
378
+ When further rows are selected, or the header select-all checkbox is checked
379
+ Then nothing changes but the count in the selection summary
380
+ # One predicate owns this: isBulkSelectionActive(table) = one or more rows
381
+ # selected. TableActionsCell takes it as its `bulkSelectionActive` prop; the bar
382
+ # derives it internally. Consumers do not re-derive the threshold.
383
+ # The bar is always mounted — it never renders nothing, it only switches state.
384
+ ```
385
+
386
+ ```gherkin
387
+ Scenario: Sticky (pinned) columns
388
+ Given columns with meta.pin = "left" and/or "right"
389
+ When the grid scrolls horizontally
390
+ Then the pinned columns stay fixed at their edges (position: sticky)
391
+ And their cells keep an opaque row background so scrolled cells don't show through
392
+ ```
393
+
394
+ ```gherkin
395
+ Scenario: Wrapping columns
396
+ Given a column with meta.wrap = true
397
+ Then that column's header and cell content wrap onto multiple lines instead of truncating
398
+ ```
399
+
400
+ ```gherkin
401
+ Scenario: Select rows
402
+ Given a selection column with checkboxes
403
+ When rows are checked
404
+ Then they are tinted and the pagination shows "N of M row(s) selected"
405
+ ```
406
+
407
+ ```gherkin
408
+ Scenario: Selection counts under an active filter — two deliberate scopes
409
+ Given rows are selected and a column filter is then applied that hides some of them
410
+ Then DataTableBulkActionsBar's selection summary still counts the *whole*
411
+ selection, filtered-out rows included (table.getSelectedRowModel())
412
+ And isBulkSelectionActive(table) reads that same unfiltered selection, so the
413
+ per-row actions stay suppressed even if every selected row is filtered out of view
414
+ But DataTablePagination's summary counts only the *currently filtered*
415
+ selection (table.getFilteredSelectedRowModel())
416
+ So the two counts can legitimately disagree while a filter is active
417
+ # By design, not a bug: a bulk action applies to everything the user has
418
+ # selected, not just to what the current filter leaves visible, while the
419
+ # pagination summary describes the page/filter the user is looking at.
420
+ ```
421
+
422
+ ## Accessibility
423
+
424
+ - Renders a real `<table>` via the Table primitives, so column headers and cells
425
+ carry native table semantics for assistive tech.
426
+ - The sort control is a real `<button>` that toggles the sort on a single click
427
+ (and Enter/Space); it carries an `aria-label` ("Sort by <title>") and the sort
428
+ direction is conveyed by the arrow icon alongside the always-rendered text title.
429
+ - Pagination controls are icon buttons with explicit labels ("Go to next page",
430
+ etc.); the rows-per-page control is a labelled select.
431
+ - Selection checkboxes need an `aria-label` ("Select row" / "Select all") since
432
+ they have no visible label in the cell.
433
+ - The view-options menu is keyboard-navigable (Base UI menu).
434
+ - `DataTableExpandTrigger` is a real `<button>` carrying `aria-expanded` and an
435
+ "Expand row" / "Collapse row" label; it renders nothing for a row that can't
436
+ expand.
437
+ - The column resize handle is a `role="separator"` with `aria-orientation`
438
+ vertical and an "Resize column" label (override via `resizeColumnLabel`)
439
+ (`enableColumnResizing`).
440
+ - **Row navigation uses a roving tabindex**: exactly one data row is a Tab stop
441
+ (`tabIndex={0}`, the rest `-1`), and Arrow Up / Arrow Down move both DOM focus
442
+ and which row that is. Focusing a row by click syncs the roving index too,
443
+ so the last-interacted row stays the Tab stop. The index is clamped against the
444
+ live row count, so filtering or paging to fewer rows can't leave it pointing
445
+ past the end.
446
+ - The trailing action column's controls are labelled buttons: the header's
447
+ column-visibility cog (`columnSettingsLabel`) and each row's overflow-actions
448
+ ellipsis (`rowActionsLabel`), both overridable to localize.
449
+ - Each header cell's capability hint is a `Tooltip` on the cell itself, so it is
450
+ announced on keyboard focus as well as hover; a column with no capability
451
+ renders no tooltip.
452
+ - Per-column filter fields live in a keyboard-navigable `FilterSearchFilters`
453
+ popover; each applied-filter chip below the toolbar removes its own filter.
454
+ - The infinite-scroll loading row (`paginationMode="infinite"`, `isLoadingMore`)
455
+ carries `role="status"`/`aria-live="polite"` with an `sr-only` "Loading more
456
+ rows…" text, so a screen-reader user is told a fetch is in flight without
457
+ having to notice the animated placeholder.
458
+
459
+ ## Known limitations
460
+
461
+ - **Column reordering (`enableColumnReordering`) is pointer-only.** It is native
462
+ HTML5 drag-and-drop on the header cells, with no keyboard equivalent — a
463
+ keyboard-only user cannot reorder columns. Resizing (arrow keys on the handle)
464
+ and sorting (Enter/Space on the header button) are unaffected, and the reorder
465
+ hint in the header tooltip describes a gesture only a pointer user can perform.
466
+ Recorded here deliberately: closing the gap needs a designed keyboard model
467
+ (e.g. a move-mode toggle plus arrow-key steps), not a spot fix.
468
+
469
+ ## Contrast
470
+
471
+ DataTable composes already-themed components; their tokens meet contrast in light
472
+ and dark. The wrapper border (`--ui-border-on-surface-border`) and muted
473
+ empty-state / pagination text (`--ui-text-on-surface-secondary`) meet the
474
+ relevant contrast over the page surface.