@uselets/ui 1.3.0 → 1.4.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 (212) hide show
  1. package/README.md +91 -1
  2. package/dist/chart-DHS2QeM8.d.ts +80 -0
  3. package/dist/cli/uselets.mjs.map +1 -1
  4. package/dist/components/accordion.d.ts +4 -5
  5. package/dist/components/accordion.js.map +1 -1
  6. package/dist/components/alert-dialog.d.ts +12 -13
  7. package/dist/components/alert-dialog.js.map +1 -1
  8. package/dist/components/alert.d.ts +5 -7
  9. package/dist/components/alert.js.map +1 -1
  10. package/dist/components/anchor.d.ts +2 -4
  11. package/dist/components/anchor.js.map +1 -1
  12. package/dist/components/area-chart.d.ts +58 -0
  13. package/dist/components/area-chart.js +2 -0
  14. package/dist/components/area-chart.js.map +1 -0
  15. package/dist/components/avatar.d.ts +6 -7
  16. package/dist/components/avatar.js.map +1 -1
  17. package/dist/components/badge.d.ts +2 -4
  18. package/dist/components/badge.js.map +1 -1
  19. package/dist/components/bar-chart.d.ts +54 -0
  20. package/dist/components/bar-chart.js +2 -0
  21. package/dist/components/bar-chart.js.map +1 -0
  22. package/dist/components/breadcrumb.d.ts +7 -8
  23. package/dist/components/breadcrumb.js.map +1 -1
  24. package/dist/components/button-group.d.ts +4 -6
  25. package/dist/components/button-group.js.map +1 -1
  26. package/dist/components/button.d.ts +2 -4
  27. package/dist/components/button.js.map +1 -1
  28. package/dist/components/calendar.d.ts +2 -3
  29. package/dist/components/calendar.js.map +1 -1
  30. package/dist/components/card.d.ts +7 -8
  31. package/dist/components/card.js.map +1 -1
  32. package/dist/components/carousel.d.ts +6 -7
  33. package/dist/components/carousel.js.map +1 -1
  34. package/dist/components/carousel.spec.js +1 -1
  35. package/dist/components/carousel.spec.js.map +1 -1
  36. package/dist/components/center.d.ts +2 -4
  37. package/dist/components/center.js.map +1 -1
  38. package/dist/components/chart.d.ts +2 -263
  39. package/dist/components/chart.js +1 -1
  40. package/dist/components/chart.js.map +1 -1
  41. package/dist/components/checkbox.d.ts +2 -4
  42. package/dist/components/checkbox.js +1 -1
  43. package/dist/components/checkbox.js.map +1 -1
  44. package/dist/components/code.d.ts +1 -2
  45. package/dist/components/code.js.map +1 -1
  46. package/dist/components/collapsible.d.ts +3 -4
  47. package/dist/components/collapsible.js.map +1 -1
  48. package/dist/components/combobox.d.ts +15 -16
  49. package/dist/components/combobox.js.map +1 -1
  50. package/dist/components/command.d.ts +9 -10
  51. package/dist/components/command.js.map +1 -1
  52. package/dist/components/container.d.ts +2 -4
  53. package/dist/components/container.js.map +1 -1
  54. package/dist/components/data-table.d.ts +164 -0
  55. package/dist/components/data-table.js +2 -0
  56. package/dist/components/data-table.js.map +1 -0
  57. package/dist/components/date-range-picker.d.ts +11 -12
  58. package/dist/components/date-range-picker.js.map +1 -1
  59. package/dist/components/dialog.d.ts +11 -13
  60. package/dist/components/dialog.js.map +1 -1
  61. package/dist/components/donut-chart.d.ts +22 -0
  62. package/dist/components/donut-chart.js +2 -0
  63. package/dist/components/donut-chart.js.map +1 -0
  64. package/dist/components/drawer.d.ts +12 -14
  65. package/dist/components/drawer.js.map +1 -1
  66. package/dist/components/dropdown-menu.d.ts +15 -16
  67. package/dist/components/dropdown-menu.js.map +1 -1
  68. package/dist/components/dropzone.d.ts +1 -2
  69. package/dist/components/dropzone.js.map +1 -1
  70. package/dist/components/empty.d.ts +7 -9
  71. package/dist/components/empty.js.map +1 -1
  72. package/dist/components/field.d.ts +11 -13
  73. package/dist/components/field.js.map +1 -1
  74. package/dist/components/field.spec.js +1 -1
  75. package/dist/components/field.spec.js.map +1 -1
  76. package/dist/components/flex.d.ts +4 -6
  77. package/dist/components/flex.js.map +1 -1
  78. package/dist/components/grid.d.ts +2 -3
  79. package/dist/components/grid.js.map +1 -1
  80. package/dist/components/group.d.ts +6 -4
  81. package/dist/components/group.js +1 -1
  82. package/dist/components/group.js.map +1 -1
  83. package/dist/components/heading.d.ts +2 -4
  84. package/dist/components/heading.js.map +1 -1
  85. package/dist/components/hover-card.d.ts +3 -4
  86. package/dist/components/hover-card.js.map +1 -1
  87. package/dist/components/input-group.d.ts +8 -10
  88. package/dist/components/input-group.js.map +1 -1
  89. package/dist/components/input-otp.d.ts +4 -5
  90. package/dist/components/input-otp.js.map +1 -1
  91. package/dist/components/input.d.ts +1 -2
  92. package/dist/components/input.js.map +1 -1
  93. package/dist/components/kbd.d.ts +2 -3
  94. package/dist/components/kbd.js.map +1 -1
  95. package/dist/components/label.d.ts +1 -2
  96. package/dist/components/label.js.map +1 -1
  97. package/dist/components/list.d.ts +4 -6
  98. package/dist/components/list.js.map +1 -1
  99. package/dist/components/mask-input.d.ts +25 -0
  100. package/dist/components/mask-input.js +2 -0
  101. package/dist/components/mask-input.js.map +1 -0
  102. package/dist/components/native-select.d.ts +3 -4
  103. package/dist/components/native-select.js.map +1 -1
  104. package/dist/components/navigation-menu.d.ts +9 -11
  105. package/dist/components/navigation-menu.js.map +1 -1
  106. package/dist/components/pagination.d.ts +7 -8
  107. package/dist/components/pagination.js.map +1 -1
  108. package/dist/components/pie-chart.d.ts +58 -0
  109. package/dist/components/pie-chart.js +2 -0
  110. package/dist/components/pie-chart.js.map +1 -0
  111. package/dist/components/popover.d.ts +7 -8
  112. package/dist/components/popover.js.map +1 -1
  113. package/dist/components/progress.d.ts +1 -2
  114. package/dist/components/progress.js.map +1 -1
  115. package/dist/components/radio-group.d.ts +3 -5
  116. package/dist/components/radio-group.js.map +1 -1
  117. package/dist/components/resizable.d.ts +3 -4
  118. package/dist/components/resizable.js.map +1 -1
  119. package/dist/components/scroll-area.d.ts +2 -3
  120. package/dist/components/scroll-area.js.map +1 -1
  121. package/dist/components/select.d.ts +10 -11
  122. package/dist/components/select.js.map +1 -1
  123. package/dist/components/separator.d.ts +1 -2
  124. package/dist/components/separator.js.map +1 -1
  125. package/dist/components/sidebar.d.ts +24 -26
  126. package/dist/components/sidebar.js.map +1 -1
  127. package/dist/components/simple-grid.d.ts +1 -2
  128. package/dist/components/simple-grid.js.map +1 -1
  129. package/dist/components/skeleton.d.ts +1 -2
  130. package/dist/components/skeleton.js.map +1 -1
  131. package/dist/components/skeleton.spec.js +1 -1
  132. package/dist/components/skeleton.spec.js.map +1 -1
  133. package/dist/components/slider.d.ts +1 -2
  134. package/dist/components/slider.js.map +1 -1
  135. package/dist/components/sonner.d.ts +1 -2
  136. package/dist/components/sonner.js.map +1 -1
  137. package/dist/components/spinner.d.ts +2 -4
  138. package/dist/components/spinner.js.map +1 -1
  139. package/dist/components/stack.d.ts +6 -4
  140. package/dist/components/stack.js +1 -1
  141. package/dist/components/stack.js.map +1 -1
  142. package/dist/components/switch.d.ts +1 -2
  143. package/dist/components/switch.js.map +1 -1
  144. package/dist/components/table.d.ts +29 -11
  145. package/dist/components/table.js +1 -1
  146. package/dist/components/table.js.map +1 -1
  147. package/dist/components/tabs.d.ts +5 -7
  148. package/dist/components/tabs.js.map +1 -1
  149. package/dist/components/text.d.ts +2 -4
  150. package/dist/components/text.js.map +1 -1
  151. package/dist/components/textarea.d.ts +1 -2
  152. package/dist/components/textarea.js.map +1 -1
  153. package/dist/components/theme.d.ts +2 -3
  154. package/dist/components/theme.js.map +1 -1
  155. package/dist/components/timeline.d.ts +4 -5
  156. package/dist/components/timeline.js.map +1 -1
  157. package/dist/components/toggle-group.d.ts +2 -3
  158. package/dist/components/toggle-group.js.map +1 -1
  159. package/dist/components/toggle.d.ts +2 -4
  160. package/dist/components/toggle.js.map +1 -1
  161. package/dist/components/tooltip.d.ts +5 -6
  162. package/dist/components/tooltip.js.map +1 -1
  163. package/dist/components/unstyled-button.d.ts +1 -2
  164. package/dist/components/unstyled-button.js.map +1 -1
  165. package/dist/components/visibility.d.ts +2 -3
  166. package/dist/components/visibility.js.map +1 -1
  167. package/dist/{dist-QvsLCO1I.js → dist-C35vdEt2.js} +45 -25
  168. package/dist/dist-C35vdEt2.js.map +1 -0
  169. package/dist/hooks/use-breakpoint-value.js.map +1 -1
  170. package/dist/hooks/use-breakpoint-value.spec.js +1 -1
  171. package/dist/hooks/use-breakpoint-value.spec.js.map +1 -1
  172. package/dist/hooks/use-breakpoint.js.map +1 -1
  173. package/dist/hooks/use-breakpoint.spec.js +1 -1
  174. package/dist/hooks/use-breakpoint.spec.js.map +1 -1
  175. package/dist/hooks/use-disclosure.js.map +1 -1
  176. package/dist/hooks/use-disclosure.spec.js +1 -1
  177. package/dist/hooks/use-disclosure.spec.js.map +1 -1
  178. package/dist/hooks/use-document-visibility.js.map +1 -1
  179. package/dist/hooks/use-document-visibility.spec.js +1 -1
  180. package/dist/hooks/use-document-visibility.spec.js.map +1 -1
  181. package/dist/hooks/use-idle.js.map +1 -1
  182. package/dist/hooks/use-idle.spec.js +1 -1
  183. package/dist/hooks/use-idle.spec.js.map +1 -1
  184. package/dist/hooks/use-interval.js.map +1 -1
  185. package/dist/hooks/use-interval.spec.js +1 -1
  186. package/dist/hooks/use-interval.spec.js.map +1 -1
  187. package/dist/hooks/use-local-storage.js.map +1 -1
  188. package/dist/hooks/use-local-storage.spec.js +1 -1
  189. package/dist/hooks/use-local-storage.spec.js.map +1 -1
  190. package/dist/hooks/use-mask.d.ts +9 -0
  191. package/dist/hooks/use-mask.js +2 -0
  192. package/dist/hooks/use-mask.js.map +1 -0
  193. package/dist/hooks/use-mobile.js.map +1 -1
  194. package/dist/hooks/use-mobile.spec.js +1 -1
  195. package/dist/hooks/use-mobile.spec.js.map +1 -1
  196. package/dist/index.d.ts +10 -3
  197. package/dist/index.js +1 -1
  198. package/dist/lib/button.js.map +1 -1
  199. package/dist/lib/color.js.map +1 -1
  200. package/dist/lib/utils.js.map +1 -1
  201. package/dist/magic-string.es-C-uSEkg2.js.map +1 -1
  202. package/dist/postcss.config.js.map +1 -1
  203. package/dist/{react.esm-D_dNEsWM.js → react.esm-BagSCtI3.js} +11 -2
  204. package/dist/{react.esm-D_dNEsWM.js.map → react.esm-BagSCtI3.js.map} +1 -1
  205. package/dist/skills/uselets-ui/SKILL.md +1 -0
  206. package/dist/skills/uselets-ui/references/component-catalog.md +31 -4
  207. package/dist/skills/uselets-ui/references/package-guide.md +136 -1
  208. package/package.json +15 -13
  209. package/skills/uselets-ui/SKILL.md +1 -0
  210. package/skills/uselets-ui/references/component-catalog.md +31 -4
  211. package/skills/uselets-ui/references/package-guide.md +136 -1
  212. package/dist/dist-QvsLCO1I.js.map +0 -1
@@ -56,6 +56,7 @@ To set up a consumer project after installing `@uselets/ui`, run `bun uselets in
56
56
  - For toast usage, mount `Toaster` near the app root before calling `toast(...)`.
57
57
  - For package theme helpers, use `ThemeProvider`, `useTheme`, and `ModeToggle` from `@uselets/ui/theme` or the root barrel.
58
58
  - For form fields, `Field` automatically shares a generated control id with `FieldLabel` and labelable controls when explicit ids are omitted; pass explicit matching `htmlFor` and `id` only when a stable custom id is required.
59
+ - For client-side tabular data, use `DataTable` from `@uselets/ui/data-table`. Defaults are bare: `enableRowSelection`, `enableSorting`, and `showPagination` are all `false`. Set `enableRowSelection` to auto-prepend the selection checkbox column (no need to add `createDataTableSelectColumn` manually); set `enableSorting` to make `DataTableColumnHeader` clickable for sort. `filterColumns` accepts a single id or an array; with 2+ ids the toolbar renders an `InputGroup` with a column selector on the left. `toolbarActions` always sits at the right of the toolbar. All labels default to pt-BR; override any subset through `labels` (`Partial<DataTableLabels>`), with full defaults exposed as `defaultDataTableLabels`. Use `variant` on `DataTable` (or directly on the base `Table`) to switch chrome: `"line"` (default) renders only horizontal separators between rows; `"outline"` adds a rounded outer border around the table. The base `Table` from `@uselets/ui/table` renders only the `<table>` element and is striped by default (pass `striped={false}` to disable). Wrap it in `TableContainer` to control scroll; `minWidth` lives on `TableContainer`. Combine `stickyHeader` on `Table` with `max-h` + `overflow-y-auto` on the `TableContainer` to keep the `<thead>` visible during vertical scroll. `DataTable` wraps its `Table` in `TableContainer` automatically.
59
60
 
60
61
  ## Quick Routing
61
62
 
@@ -50,6 +50,10 @@ Unless noted otherwise, each module also exports its public `*Props` types from
50
50
  - Import path: `@uselets/ui/hooks/use-local-storage`
51
51
  - Exports: `useLocalStorage`, `UseLocalStorageOptions`
52
52
  - Notes: returns a `[value, setValue]` tuple, stores values with JSON serialization, and syncs storage changes between browser tabs by default (`sync: true`)
53
+ - `use-mask`
54
+ - Import path: `@uselets/ui/hooks/use-mask`
55
+ - Exports: `useMask`, `UseMaskOptions`, `MaskOptions`, `Replacement`, `Track`, `TrackingData`, `Modify`, `ModifiedData`
56
+ - Notes: thin wrapper over `@react-input/mask`'s `useMask` that defaults `replacement` to `{ 9: /\d/, a: /[A-Za-z]/, '*': /./ }`; returns a `MutableRefObject<HTMLInputElement>` to attach to any input. Consumers may override `replacement`.
53
57
  - `utils`
54
58
  - Import path: `@uselets/ui/utils`
55
59
  - Exports: `cn`, `randomId`
@@ -135,6 +139,9 @@ Unless noted otherwise, each module also exports its public `*Props` types from
135
139
  - Notes: compose controls with prefixes, suffixes, or inline actions
136
140
  - `input-otp`
137
141
  - Exports: `InputOTP`, `InputOTPGroup`, `InputOTPSeparator`, `InputOTPSlot`, `REGEXP_ONLY_CHARS`, `REGEXP_ONLY_DIGITS`, `REGEXP_ONLY_DIGITS_AND_CHARS`
142
+ - `mask-input`
143
+ - Exports: `MaskInput`
144
+ - Notes: formatted input backed by `@react-input/mask`. Required prop `mask`; optional `replacement` (string or `Record<string, RegExp>`), `showMask`, `separate`, `track`, `modify`. Default replacement tokens are `9` (digit), `a` (letter), `*` (any). Renders the standard `Input`, so `Field`/`FieldLabel` and all native `<input>` props work as usual.
138
145
  - `checkbox`
139
146
  - Exports: `Checkbox`
140
147
  - Notes: supports `size: 'sm' | 'md' | 'lg'`, `color: UIColor` (default `primary`), `label?: React.ReactNode`, `labelProps`, and automatic `id`/`htmlFor` wiring when a label is provided without an explicit `id`; composed label text follows checkbox size (`sm` 12px, `md` 14px, `lg` 20px)
@@ -213,7 +220,20 @@ Unless noted otherwise, each module also exports its public `*Props` types from
213
220
  - `center`
214
221
  - Exports: `Center`, `centerVariants`
215
222
  - `chart`
216
- - Exports: `Bar`, `BarChart`, `CartesianGrid`, `XAxis`, `ChartContainer`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`
223
+ - Exports: `ChartContainer`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`, `ChartConfig`
224
+ - Notes: low-level chart primitives. For app usage prefer the high-level `area-chart`, `bar-chart`, `pie-chart`, `donut-chart` modules. Import raw recharts primitives (`Bar`, `XAxis`, etc.) directly from `recharts` when composing with `ChartContainer`.
225
+ - `area-chart`
226
+ - Exports: `AreaChart`, `AreaChartSeries`, `AreaChartType`, `AreaChartCurveType`, `AreaChartGridAxis`
227
+ - Notes: high-level area chart. Pass `data`, `series` (array of `{ name, label?, color? }`), and `dataKey` (X axis field). Supports `type` (default/stacked/percent/split), `curveType`, `withXAxis`, `withYAxis`, `gridAxis`, `withDots`, `withTooltip`, `withLegend`. Defaults to chart colors `--chart-1..5`.
228
+ - `bar-chart`
229
+ - Exports: `BarChart`, `BarChartSeries`, `BarChartType`, `BarChartOrientation`, `BarChartGridAxis`
230
+ - Notes: high-level bar chart. Same shape as `area-chart` plus `orientation` (vertical/horizontal), `barSize`, `radius`. Supports stacked and percent types.
231
+ - `pie-chart`
232
+ - Exports: `PieChart`, `PieChartBase`, `PieChartDatum`, `buildPieConfig`
233
+ - Notes: high-level pie chart. Pass `data` as `Array<{ name, value, color? }>`. Supports `withLabels`, `labelsPosition`, `startAngle`, `endAngle`, `paddingAngle`, `withLegend`, `withTooltip`. `PieChartBase` is the shared internal renderer used by `donut-chart`.
234
+ - `donut-chart`
235
+ - Exports: `DonutChart`
236
+ - Notes: same data shape as `pie-chart`. Adds `thickness` (donut hole size in px) and `withTotal` + `totalLabel` (renders sum in the center).
217
237
  - `collapsible`
218
238
  - Exports: `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`
219
239
  - `container`
@@ -223,7 +243,7 @@ Unless noted otherwise, each module also exports its public `*Props` types from
223
243
  - Notes: generic flexbox layout primitive. Supports `direction: 'row' | 'row-reverse' | 'column' | 'column-reverse'`, `align: 'start' | 'end' | 'center' | 'baseline' | 'stretch'`, `justify: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'`, `wrap`, and `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`).
224
244
  - `group`
225
245
  - Exports: `Group`, `groupVariants`
226
- - Notes: supports `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`) and `wrap`
246
+ - Notes: horizontal row primitive. Supports `align: 'start' | 'end' | 'center' | 'baseline' | 'stretch'` (default `center`), `justify: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'`, `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`), and `wrap`
227
247
  - `kbd`
228
248
  - Exports: `Kbd`, `KbdGroup`
229
249
  - `resizable`
@@ -234,9 +254,16 @@ Unless noted otherwise, each module also exports its public `*Props` types from
234
254
  - Exports: `Separator`
235
255
  - `stack`
236
256
  - Exports: `Stack`, `stackVariants`
237
- - Notes: supports `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`)
257
+ - Notes: vertical column primitive. Supports `align: 'start' | 'end' | 'center' | 'baseline' | 'stretch'`, `justify: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'`, and `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`)
238
258
  - `table`
239
- - Exports: `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`, `TableCell`, `TableCaption`
259
+ - Exports: `Table`, `TableContainer`, `TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`, `TableCell`, `TableCaption`, `tableVariants`
260
+ - Notes: `Table` renders only the `<table>` element (no auto wrapper); striped by default (`striped` prop, default `true`) and supports `stickyHeader`. `variant` (`"line"` default | `"outline"`) controls outer chrome — `"line"` has only horizontal separators between rows; `"outline"` adds a rounded outer border. Outline works standalone (chrome on the `<table>`) or wrapped in `TableContainer` (container detects `data-variant=outline` via `:has()`, applies the chrome on itself, and zeroes it on the `<table>` — keeps sticky headers from painting over the border). Wrap in `TableContainer` for horizontal scroll; `TableContainer` accepts `minWidth` (number in px or any CSS length). For sticky header, set `max-h` + `overflow-y-auto` on the `TableContainer` (via `className` or `style`)
261
+ - `data-table`
262
+ - Exports: `DataTable`, `DataTableRoot`, `DataTableToolbar`, `DataTableHeader`, `DataTableBody`, `DataTablePagination`, `DataTableColumnHeader`, `createDataTableSelectColumn`, `useDataTable`, `useDataTableContext`, `defaultDataTableLabels`, `DataTableLabels`, `UseDataTableOptions`, `DataTableRootProps`, `DataTableToolbarProps`
263
+ - Notes: dual API — high-level `DataTable` (monolithic, simple cases) and compound (`DataTableRoot` + sub-parts, power cases including virtualization). Both share the same underlying logic via `useDataTable`.
264
+ - **High-level `DataTable`:** client-side TanStack Table composition. Defaults are bare — `enableRowSelection`, `enableSorting`, `showPagination` all default to `false` and must be explicitly enabled. Setting `enableRowSelection` auto-prepends the selection column; setting `enableSorting` activates sort buttons in `DataTableColumnHeader`. `filterColumns` accepts `string | string[]`; with 2+ ids the toolbar renders an `InputGroup` with a column selector at the left. `toolbarActions` always renders at the right of the toolbar. `variant` (`"line"` default | `"outline"`) is forwarded to the internal `Table` — pass `"outline"` to wrap the table in a rounded border. All user-facing labels default to pt-BR and are overridable through the `labels` prop (`Partial<DataTableLabels>`)
265
+ - **Compound API:** `DataTableRoot` accepts either `{ columns, data, ...options }` (uncontrolled — creates the `table` instance internally) or `{ table }` (controlled — table instance comes from `useDataTable` called by the consumer). `DataTableToolbar`, `DataTableHeader`, `DataTableBody`, and `DataTablePagination` read the `table` instance from context. `DataTableHeader` renders a full `<thead>`; `DataTableBody` renders a full `<tbody>` — to virtualize, omit `<DataTableBody />` and render your own `<tbody>` instead. `useDataTable(options)` is a thin wrapper around `useReactTable` that bakes in the same defaults `DataTable` uses (row models, selection auto-injection); pass `enablePagination: false` when virtualizing so `table.getRowModel()` returns all rows. `useDataTableContext<TData>()` retrieves the current `table` and merged `labels` inside a custom compound child
266
+ - **Virtualization:** the package is virtualizer-agnostic — it has zero runtime dependency on `@tanstack/react-virtual`. Bring your own virtualizer (TanStack Virtual, `react-window`, `virtua`, or manual). See the *Virtualized lists* section in `references/package-guide.md` for the canonical TanStack Virtual recipe
240
267
  - `timeline`
241
268
  - Exports: `Timeline`, `TimelineConnector`, `TimelineContent`, `TimelineDescription`, `TimelineEmpty`, `TimelineHeader`, `TimelineIcon`, `TimelineItem`, `TimelineTime`, `TimelineTitle`, `timelineVariants`
242
269
  - Notes: timeline flow with root `size`, inherited `iconSize`, item `status`, optional loading/error states, and semantic icon or connector colors
@@ -146,6 +146,7 @@ Use these patterns:
146
146
  ```tsx
147
147
  import { Alert, Button, UnstyledButton, cn, randomId } from '@uselets/ui'
148
148
  import type { UIColor } from '@uselets/ui'
149
+ import { DataTable } from '@uselets/ui/data-table'
149
150
  import { Dialog } from '@uselets/ui/dialog'
150
151
  import { useIsMobile } from '@uselets/ui/hooks/use-mobile'
151
152
  import { useDisclosure } from '@uselets/ui/hooks/use-disclosure'
@@ -161,7 +162,9 @@ Guidance:
161
162
  - Use `@uselets/ui/hooks/<hook-module>` for dedicated hook subpaths.
162
163
  - Do not invent undocumented subpaths.
163
164
 
164
- Public hook subpaths currently include `hooks/use-mobile`, `hooks/use-disclosure`, `hooks/use-interval`, `hooks/use-idle`, `hooks/use-document-visibility`, and `hooks/use-local-storage`. Legacy `@uselets/ui/use-*` paths are not public entrypoints. Utility helpers are available from `@uselets/ui/utils`, and shared UI color helpers are available from the root barrel or `@uselets/ui/lib/color`.
165
+ Public hook subpaths currently include `hooks/use-mobile`, `hooks/use-disclosure`, `hooks/use-interval`, `hooks/use-idle`, `hooks/use-document-visibility`, `hooks/use-local-storage`, and `hooks/use-mask`. Legacy `@uselets/ui/use-*` paths are not public entrypoints. Utility helpers are available from `@uselets/ui/utils`, and shared UI color helpers are available from the root barrel or `@uselets/ui/lib/color`.
166
+
167
+ Use `DataTable` from `@uselets/ui/data-table` for client-side tables with sorting, filtering, pagination, row selection, column visibility, formatted cells, and custom action columns. All user-facing labels default to Portuguese (pt-BR) and can be overridden through the `labels` prop (`Partial<DataTableLabels>`); the full defaults are exported as `defaultDataTableLabels`.
165
168
 
166
169
  `useLocalStorage` uses an options object and returns a tuple like `useState`:
167
170
 
@@ -290,6 +293,138 @@ The `RadioGroup` component supports `color: UIColor`, with `primary` as the defa
290
293
 
291
294
  The `DateRangePicker` component is a compound popover date range picker built on `Calendar` range mode. Use `DateRange | null` for controlled values; applying an empty draft or resetting emits `null` through `onValueChange`. Fixed presets, relative-day inputs, date inputs, and the calendar all edit the same draft, and `DateRangePickerPreset` displays a selected state when its range matches the current draft by calendar day.
292
295
 
296
+ ## DataTable compound API and virtualization
297
+
298
+ `DataTable` ships two complementary APIs:
299
+
300
+ - **High-level `DataTable`** — monolithic, ergonomic, covers the common case (pagination, sorting, row selection, filters, toolbar actions, custom labels). Public surface is unchanged from earlier versions.
301
+ - **Compound parts** — `DataTableRoot`, `DataTableToolbar`, `DataTableHeader`, `DataTableBody`, `DataTablePagination`. The high-level component is itself implemented in terms of these, so behavior never diverges.
302
+
303
+ Use the compound API when you need to:
304
+
305
+ - Virtualize the body (large datasets).
306
+ - Place the toolbar, pagination, or other actions in non-default positions (e.g. toolbar inside a card header, pagination in a footer slot).
307
+ - Share the same `table` instance with code that lives outside the `DataTable` subtree (e.g. an "Export selected" button rendered above the page).
308
+
309
+ ### Compound shape (non-virtualized)
310
+
311
+ ```tsx
312
+ import {
313
+ DataTableBody,
314
+ DataTableHeader,
315
+ DataTablePagination,
316
+ DataTableRoot,
317
+ DataTableToolbar
318
+ } from '@uselets/ui/data-table'
319
+ import { Table, TableContainer } from '@uselets/ui/table'
320
+
321
+ <DataTableRoot columns={columns} data={data} enableRowSelection enableSorting>
322
+ <DataTableToolbar filterColumns={['name', 'email']} />
323
+ <TableContainer className="rounded-lg border">
324
+ <Table>
325
+ <DataTableHeader />
326
+ <DataTableBody />
327
+ </Table>
328
+ </TableContainer>
329
+ <DataTablePagination />
330
+ </DataTableRoot>
331
+ ```
332
+
333
+ `DataTableRoot` accepts either `{ columns, data, ...options }` (uncontrolled — creates the `table` instance internally) or `{ table }` (controlled — table instance comes from `useDataTable` called by the consumer). Use the controlled form whenever you need access to the `table` instance in the same scope as the JSX (e.g. for virtualization or external action buttons).
334
+
335
+ ### Virtualized lists (bring your own virtualizer)
336
+
337
+ `@uselets/ui` has **zero runtime dependency** on any virtualization library. Pick the one you prefer (`@tanstack/react-virtual`, `react-window`, `virtua`, or even a manual implementation) and replace `<DataTableBody />` with a `<tbody>` that renders only the virtual rows.
338
+
339
+ Canonical recipe using `@tanstack/react-virtual` (the most common choice in the React ecosystem):
340
+
341
+ ```tsx
342
+ import { useVirtualizer } from '@tanstack/react-virtual'
343
+ import {
344
+ DataTableHeader,
345
+ DataTableRoot,
346
+ DataTableToolbar,
347
+ useDataTable
348
+ } from '@uselets/ui/data-table'
349
+ import { Table, TableCell, TableContainer, TableRow } from '@uselets/ui/table'
350
+ import * as React from 'react'
351
+
352
+ function VirtualizedPayments({ data }: { data: Payment[] }) {
353
+ const table = useDataTable<Payment>({
354
+ columns,
355
+ data,
356
+ enableRowSelection: true,
357
+ enableSorting: true,
358
+ enablePagination: false // IMPORTANT: returns all rows from getRowModel()
359
+ })
360
+
361
+ const parentRef = React.useRef<HTMLDivElement>(null)
362
+ const rows = table.getRowModel().rows
363
+ const colSpan = table.getVisibleLeafColumns().length
364
+
365
+ const virtualizer = useVirtualizer({
366
+ count: rows.length,
367
+ getScrollElement: () => parentRef.current,
368
+ estimateSize: () => 52,
369
+ overscan: 8
370
+ })
371
+
372
+ const virtualItems = virtualizer.getVirtualItems()
373
+ const totalSize = virtualizer.getTotalSize()
374
+ const paddingTop = virtualItems[0]?.start ?? 0
375
+ const paddingBottom = totalSize - (virtualItems.at(-1)?.end ?? 0)
376
+
377
+ return (
378
+ <DataTableRoot table={table}>
379
+ <DataTableToolbar filterColumns={['customer']} />
380
+ <TableContainer ref={parentRef} className="h-[600px] rounded-lg border">
381
+ <Table stickyHeader>
382
+ <DataTableHeader />
383
+ <tbody>
384
+ {paddingTop > 0 && (
385
+ <tr aria-hidden>
386
+ <td colSpan={colSpan} style={{ height: paddingTop, padding: 0, border: 0 }} />
387
+ </tr>
388
+ )}
389
+ {virtualItems.map((vi) => {
390
+ const row = rows[vi.index]
391
+ return (
392
+ <TableRow key={row.id} data-state={row.getIsSelected() ? 'selected' : undefined}>
393
+ {row.getVisibleCells().map((cell) => {
394
+ const renderer = cell.column.columnDef.cell
395
+ return (
396
+ <TableCell key={cell.id}>
397
+ {typeof renderer === 'function' ? renderer(cell.getContext()) : (cell.getValue() as React.ReactNode)}
398
+ </TableCell>
399
+ )
400
+ })}
401
+ </TableRow>
402
+ )
403
+ })}
404
+ {paddingBottom > 0 && (
405
+ <tr aria-hidden>
406
+ <td colSpan={colSpan} style={{ height: paddingBottom, padding: 0, border: 0 }} />
407
+ </tr>
408
+ )}
409
+ </tbody>
410
+ </Table>
411
+ </TableContainer>
412
+ </DataTableRoot>
413
+ )
414
+ }
415
+ ```
416
+
417
+ Key points and gotchas:
418
+
419
+ - **Bring your own virtualizer.** The example uses `@tanstack/react-virtual` for clarity, but any virtualizer works. Replace `useVirtualizer` and the `paddingTop`/`paddingBottom` spacer-row computation with the equivalent from `react-window`, `virtua`, or your own. The compounds (`DataTableRoot`, `DataTableToolbar`, `DataTableHeader`) and primitives (`Table`, `TableContainer`, `TableRow`, `TableCell`) do not change.
420
+ - **`enablePagination: false`** is required so `table.getRowModel()` returns the full filtered/sorted dataset instead of just the current page. Pagination and virtualization are mutually exclusive — pick one.
421
+ - **Sticky header.** Set `stickyHeader` on `<Table>` and a fixed height on `<TableContainer>`. The CSS-only sticky header in the primitive `Table` continues to work because `<thead>` stays in normal flow.
422
+ - **Row height.** `estimateSize` is fine when rows are uniform. For dynamic heights, pass `measureElement={virtualizer.measureElement}` on each `TableRow`'s `ref` to let TanStack Virtual measure them.
423
+ - **Spacer rows vs absolute positioning.** Two valid approaches exist: spacer rows (`paddingTop`/`paddingBottom` as shown — keeps rows in normal flow, simpler) or `position: absolute; transform: translateY(start)` on every row (requires `position: relative` on `<tbody>` and `height: totalSize`). The spacer-row approach is generally easier and works across virtualizers.
424
+ - **Row selection, sorting, and filtering all work** in the virtualized mode because they live on the `table` instance — only rendering of the body is virtualized.
425
+
426
+ A full working example lives in `apps/web/src/stories/data-table-virtualized.stories.tsx`.
427
+
293
428
  ## Component selection
294
429
 
295
430
  Choose components by intent:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uselets/ui",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "React component library for the LET'S design system.",
@@ -57,7 +57,9 @@
57
57
  "dependencies": {
58
58
  "@base-ui/react": "1.4.1",
59
59
  "@fontsource-variable/inter": "5.2.8",
60
- "@tabler/icons-react": "3.41.1",
60
+ "@react-input/mask": "2.0.4",
61
+ "@tabler/icons-react": "3.44.0",
62
+ "@tanstack/react-table": "8.21.3",
61
63
  "class-variance-authority": "0.7.1",
62
64
  "clsx": "2.1.1",
63
65
  "cmdk": "1.1.1",
@@ -69,11 +71,11 @@
69
71
  "radix-ui": "1.4.3",
70
72
  "react-day-picker": "9.14.0",
71
73
  "react-dropzone": "15.0.0",
72
- "react-resizable-panels": "4.10.0",
73
- "recharts": "2.15.4",
74
+ "react-resizable-panels": "4.11.1",
75
+ "recharts": "3.8.1",
74
76
  "shadcn": "4.6.0",
75
77
  "sonner": "2.0.7",
76
- "tailwind-merge": "3.5.0",
78
+ "tailwind-merge": "3.6.0",
77
79
  "tw-animate-css": "1.4.0",
78
80
  "vaul": "1.1.2"
79
81
  },
@@ -83,17 +85,17 @@
83
85
  },
84
86
  "devDependencies": {
85
87
  "@bosh-code/tsdown-plugin-tailwindcss": "1.0.1",
86
- "@tailwindcss/postcss": "4.2.4",
88
+ "@tailwindcss/postcss": "4.3.0",
87
89
  "@testing-library/react": "16.3.2",
88
- "@tsdown/css": "0.21.10",
89
- "@turbo/gen": "2.9.8",
90
- "@types/node": "25.6.0",
91
- "@types/react": "19.2.14",
90
+ "@tsdown/css": "0.22.0",
91
+ "@turbo/gen": "2.9.14",
92
+ "@types/node": "25.9.1",
93
+ "@types/react": "19.2.15",
92
94
  "@types/react-dom": "19.2.3",
93
95
  "jsdom": "29.1.1",
94
- "tailwindcss": "4.2.4",
95
- "tsdown": "0.21.10",
96
+ "tailwindcss": "4.3.0",
97
+ "tsdown": "0.22.0",
96
98
  "typescript": "6.0.3",
97
- "vitest": "4.1.5"
99
+ "vitest": "4.1.6"
98
100
  }
99
101
  }
@@ -56,6 +56,7 @@ To set up a consumer project after installing `@uselets/ui`, run `bun uselets in
56
56
  - For toast usage, mount `Toaster` near the app root before calling `toast(...)`.
57
57
  - For package theme helpers, use `ThemeProvider`, `useTheme`, and `ModeToggle` from `@uselets/ui/theme` or the root barrel.
58
58
  - For form fields, `Field` automatically shares a generated control id with `FieldLabel` and labelable controls when explicit ids are omitted; pass explicit matching `htmlFor` and `id` only when a stable custom id is required.
59
+ - For client-side tabular data, use `DataTable` from `@uselets/ui/data-table`. Defaults are bare: `enableRowSelection`, `enableSorting`, and `showPagination` are all `false`. Set `enableRowSelection` to auto-prepend the selection checkbox column (no need to add `createDataTableSelectColumn` manually); set `enableSorting` to make `DataTableColumnHeader` clickable for sort. `filterColumns` accepts a single id or an array; with 2+ ids the toolbar renders an `InputGroup` with a column selector on the left. `toolbarActions` always sits at the right of the toolbar. All labels default to pt-BR; override any subset through `labels` (`Partial<DataTableLabels>`), with full defaults exposed as `defaultDataTableLabels`. Use `variant` on `DataTable` (or directly on the base `Table`) to switch chrome: `"line"` (default) renders only horizontal separators between rows; `"outline"` adds a rounded outer border around the table. The base `Table` from `@uselets/ui/table` renders only the `<table>` element and is striped by default (pass `striped={false}` to disable). Wrap it in `TableContainer` to control scroll; `minWidth` lives on `TableContainer`. Combine `stickyHeader` on `Table` with `max-h` + `overflow-y-auto` on the `TableContainer` to keep the `<thead>` visible during vertical scroll. `DataTable` wraps its `Table` in `TableContainer` automatically.
59
60
 
60
61
  ## Quick Routing
61
62
 
@@ -50,6 +50,10 @@ Unless noted otherwise, each module also exports its public `*Props` types from
50
50
  - Import path: `@uselets/ui/hooks/use-local-storage`
51
51
  - Exports: `useLocalStorage`, `UseLocalStorageOptions`
52
52
  - Notes: returns a `[value, setValue]` tuple, stores values with JSON serialization, and syncs storage changes between browser tabs by default (`sync: true`)
53
+ - `use-mask`
54
+ - Import path: `@uselets/ui/hooks/use-mask`
55
+ - Exports: `useMask`, `UseMaskOptions`, `MaskOptions`, `Replacement`, `Track`, `TrackingData`, `Modify`, `ModifiedData`
56
+ - Notes: thin wrapper over `@react-input/mask`'s `useMask` that defaults `replacement` to `{ 9: /\d/, a: /[A-Za-z]/, '*': /./ }`; returns a `MutableRefObject<HTMLInputElement>` to attach to any input. Consumers may override `replacement`.
53
57
  - `utils`
54
58
  - Import path: `@uselets/ui/utils`
55
59
  - Exports: `cn`, `randomId`
@@ -135,6 +139,9 @@ Unless noted otherwise, each module also exports its public `*Props` types from
135
139
  - Notes: compose controls with prefixes, suffixes, or inline actions
136
140
  - `input-otp`
137
141
  - Exports: `InputOTP`, `InputOTPGroup`, `InputOTPSeparator`, `InputOTPSlot`, `REGEXP_ONLY_CHARS`, `REGEXP_ONLY_DIGITS`, `REGEXP_ONLY_DIGITS_AND_CHARS`
142
+ - `mask-input`
143
+ - Exports: `MaskInput`
144
+ - Notes: formatted input backed by `@react-input/mask`. Required prop `mask`; optional `replacement` (string or `Record<string, RegExp>`), `showMask`, `separate`, `track`, `modify`. Default replacement tokens are `9` (digit), `a` (letter), `*` (any). Renders the standard `Input`, so `Field`/`FieldLabel` and all native `<input>` props work as usual.
138
145
  - `checkbox`
139
146
  - Exports: `Checkbox`
140
147
  - Notes: supports `size: 'sm' | 'md' | 'lg'`, `color: UIColor` (default `primary`), `label?: React.ReactNode`, `labelProps`, and automatic `id`/`htmlFor` wiring when a label is provided without an explicit `id`; composed label text follows checkbox size (`sm` 12px, `md` 14px, `lg` 20px)
@@ -213,7 +220,20 @@ Unless noted otherwise, each module also exports its public `*Props` types from
213
220
  - `center`
214
221
  - Exports: `Center`, `centerVariants`
215
222
  - `chart`
216
- - Exports: `Bar`, `BarChart`, `CartesianGrid`, `XAxis`, `ChartContainer`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`
223
+ - Exports: `ChartContainer`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`, `ChartConfig`
224
+ - Notes: low-level chart primitives. For app usage prefer the high-level `area-chart`, `bar-chart`, `pie-chart`, `donut-chart` modules. Import raw recharts primitives (`Bar`, `XAxis`, etc.) directly from `recharts` when composing with `ChartContainer`.
225
+ - `area-chart`
226
+ - Exports: `AreaChart`, `AreaChartSeries`, `AreaChartType`, `AreaChartCurveType`, `AreaChartGridAxis`
227
+ - Notes: high-level area chart. Pass `data`, `series` (array of `{ name, label?, color? }`), and `dataKey` (X axis field). Supports `type` (default/stacked/percent/split), `curveType`, `withXAxis`, `withYAxis`, `gridAxis`, `withDots`, `withTooltip`, `withLegend`. Defaults to chart colors `--chart-1..5`.
228
+ - `bar-chart`
229
+ - Exports: `BarChart`, `BarChartSeries`, `BarChartType`, `BarChartOrientation`, `BarChartGridAxis`
230
+ - Notes: high-level bar chart. Same shape as `area-chart` plus `orientation` (vertical/horizontal), `barSize`, `radius`. Supports stacked and percent types.
231
+ - `pie-chart`
232
+ - Exports: `PieChart`, `PieChartBase`, `PieChartDatum`, `buildPieConfig`
233
+ - Notes: high-level pie chart. Pass `data` as `Array<{ name, value, color? }>`. Supports `withLabels`, `labelsPosition`, `startAngle`, `endAngle`, `paddingAngle`, `withLegend`, `withTooltip`. `PieChartBase` is the shared internal renderer used by `donut-chart`.
234
+ - `donut-chart`
235
+ - Exports: `DonutChart`
236
+ - Notes: same data shape as `pie-chart`. Adds `thickness` (donut hole size in px) and `withTotal` + `totalLabel` (renders sum in the center).
217
237
  - `collapsible`
218
238
  - Exports: `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`
219
239
  - `container`
@@ -223,7 +243,7 @@ Unless noted otherwise, each module also exports its public `*Props` types from
223
243
  - Notes: generic flexbox layout primitive. Supports `direction: 'row' | 'row-reverse' | 'column' | 'column-reverse'`, `align: 'start' | 'end' | 'center' | 'baseline' | 'stretch'`, `justify: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'`, `wrap`, and `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`).
224
244
  - `group`
225
245
  - Exports: `Group`, `groupVariants`
226
- - Notes: supports `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`) and `wrap`
246
+ - Notes: horizontal row primitive. Supports `align: 'start' | 'end' | 'center' | 'baseline' | 'stretch'` (default `center`), `justify: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'`, `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`), and `wrap`
227
247
  - `kbd`
228
248
  - Exports: `Kbd`, `KbdGroup`
229
249
  - `resizable`
@@ -234,9 +254,16 @@ Unless noted otherwise, each module also exports its public `*Props` types from
234
254
  - Exports: `Separator`
235
255
  - `stack`
236
256
  - Exports: `Stack`, `stackVariants`
237
- - Notes: supports `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`)
257
+ - Notes: vertical column primitive. Supports `align: 'start' | 'end' | 'center' | 'baseline' | 'stretch'`, `justify: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'`, and `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`)
238
258
  - `table`
239
- - Exports: `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`, `TableCell`, `TableCaption`
259
+ - Exports: `Table`, `TableContainer`, `TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`, `TableCell`, `TableCaption`, `tableVariants`
260
+ - Notes: `Table` renders only the `<table>` element (no auto wrapper); striped by default (`striped` prop, default `true`) and supports `stickyHeader`. `variant` (`"line"` default | `"outline"`) controls outer chrome — `"line"` has only horizontal separators between rows; `"outline"` adds a rounded outer border. Outline works standalone (chrome on the `<table>`) or wrapped in `TableContainer` (container detects `data-variant=outline` via `:has()`, applies the chrome on itself, and zeroes it on the `<table>` — keeps sticky headers from painting over the border). Wrap in `TableContainer` for horizontal scroll; `TableContainer` accepts `minWidth` (number in px or any CSS length). For sticky header, set `max-h` + `overflow-y-auto` on the `TableContainer` (via `className` or `style`)
261
+ - `data-table`
262
+ - Exports: `DataTable`, `DataTableRoot`, `DataTableToolbar`, `DataTableHeader`, `DataTableBody`, `DataTablePagination`, `DataTableColumnHeader`, `createDataTableSelectColumn`, `useDataTable`, `useDataTableContext`, `defaultDataTableLabels`, `DataTableLabels`, `UseDataTableOptions`, `DataTableRootProps`, `DataTableToolbarProps`
263
+ - Notes: dual API — high-level `DataTable` (monolithic, simple cases) and compound (`DataTableRoot` + sub-parts, power cases including virtualization). Both share the same underlying logic via `useDataTable`.
264
+ - **High-level `DataTable`:** client-side TanStack Table composition. Defaults are bare — `enableRowSelection`, `enableSorting`, `showPagination` all default to `false` and must be explicitly enabled. Setting `enableRowSelection` auto-prepends the selection column; setting `enableSorting` activates sort buttons in `DataTableColumnHeader`. `filterColumns` accepts `string | string[]`; with 2+ ids the toolbar renders an `InputGroup` with a column selector at the left. `toolbarActions` always renders at the right of the toolbar. `variant` (`"line"` default | `"outline"`) is forwarded to the internal `Table` — pass `"outline"` to wrap the table in a rounded border. All user-facing labels default to pt-BR and are overridable through the `labels` prop (`Partial<DataTableLabels>`)
265
+ - **Compound API:** `DataTableRoot` accepts either `{ columns, data, ...options }` (uncontrolled — creates the `table` instance internally) or `{ table }` (controlled — table instance comes from `useDataTable` called by the consumer). `DataTableToolbar`, `DataTableHeader`, `DataTableBody`, and `DataTablePagination` read the `table` instance from context. `DataTableHeader` renders a full `<thead>`; `DataTableBody` renders a full `<tbody>` — to virtualize, omit `<DataTableBody />` and render your own `<tbody>` instead. `useDataTable(options)` is a thin wrapper around `useReactTable` that bakes in the same defaults `DataTable` uses (row models, selection auto-injection); pass `enablePagination: false` when virtualizing so `table.getRowModel()` returns all rows. `useDataTableContext<TData>()` retrieves the current `table` and merged `labels` inside a custom compound child
266
+ - **Virtualization:** the package is virtualizer-agnostic — it has zero runtime dependency on `@tanstack/react-virtual`. Bring your own virtualizer (TanStack Virtual, `react-window`, `virtua`, or manual). See the *Virtualized lists* section in `references/package-guide.md` for the canonical TanStack Virtual recipe
240
267
  - `timeline`
241
268
  - Exports: `Timeline`, `TimelineConnector`, `TimelineContent`, `TimelineDescription`, `TimelineEmpty`, `TimelineHeader`, `TimelineIcon`, `TimelineItem`, `TimelineTime`, `TimelineTitle`, `timelineVariants`
242
269
  - Notes: timeline flow with root `size`, inherited `iconSize`, item `status`, optional loading/error states, and semantic icon or connector colors
@@ -146,6 +146,7 @@ Use these patterns:
146
146
  ```tsx
147
147
  import { Alert, Button, UnstyledButton, cn, randomId } from '@uselets/ui'
148
148
  import type { UIColor } from '@uselets/ui'
149
+ import { DataTable } from '@uselets/ui/data-table'
149
150
  import { Dialog } from '@uselets/ui/dialog'
150
151
  import { useIsMobile } from '@uselets/ui/hooks/use-mobile'
151
152
  import { useDisclosure } from '@uselets/ui/hooks/use-disclosure'
@@ -161,7 +162,9 @@ Guidance:
161
162
  - Use `@uselets/ui/hooks/<hook-module>` for dedicated hook subpaths.
162
163
  - Do not invent undocumented subpaths.
163
164
 
164
- Public hook subpaths currently include `hooks/use-mobile`, `hooks/use-disclosure`, `hooks/use-interval`, `hooks/use-idle`, `hooks/use-document-visibility`, and `hooks/use-local-storage`. Legacy `@uselets/ui/use-*` paths are not public entrypoints. Utility helpers are available from `@uselets/ui/utils`, and shared UI color helpers are available from the root barrel or `@uselets/ui/lib/color`.
165
+ Public hook subpaths currently include `hooks/use-mobile`, `hooks/use-disclosure`, `hooks/use-interval`, `hooks/use-idle`, `hooks/use-document-visibility`, `hooks/use-local-storage`, and `hooks/use-mask`. Legacy `@uselets/ui/use-*` paths are not public entrypoints. Utility helpers are available from `@uselets/ui/utils`, and shared UI color helpers are available from the root barrel or `@uselets/ui/lib/color`.
166
+
167
+ Use `DataTable` from `@uselets/ui/data-table` for client-side tables with sorting, filtering, pagination, row selection, column visibility, formatted cells, and custom action columns. All user-facing labels default to Portuguese (pt-BR) and can be overridden through the `labels` prop (`Partial<DataTableLabels>`); the full defaults are exported as `defaultDataTableLabels`.
165
168
 
166
169
  `useLocalStorage` uses an options object and returns a tuple like `useState`:
167
170
 
@@ -290,6 +293,138 @@ The `RadioGroup` component supports `color: UIColor`, with `primary` as the defa
290
293
 
291
294
  The `DateRangePicker` component is a compound popover date range picker built on `Calendar` range mode. Use `DateRange | null` for controlled values; applying an empty draft or resetting emits `null` through `onValueChange`. Fixed presets, relative-day inputs, date inputs, and the calendar all edit the same draft, and `DateRangePickerPreset` displays a selected state when its range matches the current draft by calendar day.
292
295
 
296
+ ## DataTable compound API and virtualization
297
+
298
+ `DataTable` ships two complementary APIs:
299
+
300
+ - **High-level `DataTable`** — monolithic, ergonomic, covers the common case (pagination, sorting, row selection, filters, toolbar actions, custom labels). Public surface is unchanged from earlier versions.
301
+ - **Compound parts** — `DataTableRoot`, `DataTableToolbar`, `DataTableHeader`, `DataTableBody`, `DataTablePagination`. The high-level component is itself implemented in terms of these, so behavior never diverges.
302
+
303
+ Use the compound API when you need to:
304
+
305
+ - Virtualize the body (large datasets).
306
+ - Place the toolbar, pagination, or other actions in non-default positions (e.g. toolbar inside a card header, pagination in a footer slot).
307
+ - Share the same `table` instance with code that lives outside the `DataTable` subtree (e.g. an "Export selected" button rendered above the page).
308
+
309
+ ### Compound shape (non-virtualized)
310
+
311
+ ```tsx
312
+ import {
313
+ DataTableBody,
314
+ DataTableHeader,
315
+ DataTablePagination,
316
+ DataTableRoot,
317
+ DataTableToolbar
318
+ } from '@uselets/ui/data-table'
319
+ import { Table, TableContainer } from '@uselets/ui/table'
320
+
321
+ <DataTableRoot columns={columns} data={data} enableRowSelection enableSorting>
322
+ <DataTableToolbar filterColumns={['name', 'email']} />
323
+ <TableContainer className="rounded-lg border">
324
+ <Table>
325
+ <DataTableHeader />
326
+ <DataTableBody />
327
+ </Table>
328
+ </TableContainer>
329
+ <DataTablePagination />
330
+ </DataTableRoot>
331
+ ```
332
+
333
+ `DataTableRoot` accepts either `{ columns, data, ...options }` (uncontrolled — creates the `table` instance internally) or `{ table }` (controlled — table instance comes from `useDataTable` called by the consumer). Use the controlled form whenever you need access to the `table` instance in the same scope as the JSX (e.g. for virtualization or external action buttons).
334
+
335
+ ### Virtualized lists (bring your own virtualizer)
336
+
337
+ `@uselets/ui` has **zero runtime dependency** on any virtualization library. Pick the one you prefer (`@tanstack/react-virtual`, `react-window`, `virtua`, or even a manual implementation) and replace `<DataTableBody />` with a `<tbody>` that renders only the virtual rows.
338
+
339
+ Canonical recipe using `@tanstack/react-virtual` (the most common choice in the React ecosystem):
340
+
341
+ ```tsx
342
+ import { useVirtualizer } from '@tanstack/react-virtual'
343
+ import {
344
+ DataTableHeader,
345
+ DataTableRoot,
346
+ DataTableToolbar,
347
+ useDataTable
348
+ } from '@uselets/ui/data-table'
349
+ import { Table, TableCell, TableContainer, TableRow } from '@uselets/ui/table'
350
+ import * as React from 'react'
351
+
352
+ function VirtualizedPayments({ data }: { data: Payment[] }) {
353
+ const table = useDataTable<Payment>({
354
+ columns,
355
+ data,
356
+ enableRowSelection: true,
357
+ enableSorting: true,
358
+ enablePagination: false // IMPORTANT: returns all rows from getRowModel()
359
+ })
360
+
361
+ const parentRef = React.useRef<HTMLDivElement>(null)
362
+ const rows = table.getRowModel().rows
363
+ const colSpan = table.getVisibleLeafColumns().length
364
+
365
+ const virtualizer = useVirtualizer({
366
+ count: rows.length,
367
+ getScrollElement: () => parentRef.current,
368
+ estimateSize: () => 52,
369
+ overscan: 8
370
+ })
371
+
372
+ const virtualItems = virtualizer.getVirtualItems()
373
+ const totalSize = virtualizer.getTotalSize()
374
+ const paddingTop = virtualItems[0]?.start ?? 0
375
+ const paddingBottom = totalSize - (virtualItems.at(-1)?.end ?? 0)
376
+
377
+ return (
378
+ <DataTableRoot table={table}>
379
+ <DataTableToolbar filterColumns={['customer']} />
380
+ <TableContainer ref={parentRef} className="h-[600px] rounded-lg border">
381
+ <Table stickyHeader>
382
+ <DataTableHeader />
383
+ <tbody>
384
+ {paddingTop > 0 && (
385
+ <tr aria-hidden>
386
+ <td colSpan={colSpan} style={{ height: paddingTop, padding: 0, border: 0 }} />
387
+ </tr>
388
+ )}
389
+ {virtualItems.map((vi) => {
390
+ const row = rows[vi.index]
391
+ return (
392
+ <TableRow key={row.id} data-state={row.getIsSelected() ? 'selected' : undefined}>
393
+ {row.getVisibleCells().map((cell) => {
394
+ const renderer = cell.column.columnDef.cell
395
+ return (
396
+ <TableCell key={cell.id}>
397
+ {typeof renderer === 'function' ? renderer(cell.getContext()) : (cell.getValue() as React.ReactNode)}
398
+ </TableCell>
399
+ )
400
+ })}
401
+ </TableRow>
402
+ )
403
+ })}
404
+ {paddingBottom > 0 && (
405
+ <tr aria-hidden>
406
+ <td colSpan={colSpan} style={{ height: paddingBottom, padding: 0, border: 0 }} />
407
+ </tr>
408
+ )}
409
+ </tbody>
410
+ </Table>
411
+ </TableContainer>
412
+ </DataTableRoot>
413
+ )
414
+ }
415
+ ```
416
+
417
+ Key points and gotchas:
418
+
419
+ - **Bring your own virtualizer.** The example uses `@tanstack/react-virtual` for clarity, but any virtualizer works. Replace `useVirtualizer` and the `paddingTop`/`paddingBottom` spacer-row computation with the equivalent from `react-window`, `virtua`, or your own. The compounds (`DataTableRoot`, `DataTableToolbar`, `DataTableHeader`) and primitives (`Table`, `TableContainer`, `TableRow`, `TableCell`) do not change.
420
+ - **`enablePagination: false`** is required so `table.getRowModel()` returns the full filtered/sorted dataset instead of just the current page. Pagination and virtualization are mutually exclusive — pick one.
421
+ - **Sticky header.** Set `stickyHeader` on `<Table>` and a fixed height on `<TableContainer>`. The CSS-only sticky header in the primitive `Table` continues to work because `<thead>` stays in normal flow.
422
+ - **Row height.** `estimateSize` is fine when rows are uniform. For dynamic heights, pass `measureElement={virtualizer.measureElement}` on each `TableRow`'s `ref` to let TanStack Virtual measure them.
423
+ - **Spacer rows vs absolute positioning.** Two valid approaches exist: spacer rows (`paddingTop`/`paddingBottom` as shown — keeps rows in normal flow, simpler) or `position: absolute; transform: translateY(start)` on every row (requires `position: relative` on `<tbody>` and `height: totalSize`). The spacer-row approach is generally easier and works across virtualizers.
424
+ - **Row selection, sorting, and filtering all work** in the virtualized mode because they live on the `table` instance — only rendering of the body is virtualized.
425
+
426
+ A full working example lives in `apps/web/src/stories/data-table-virtualized.stories.tsx`.
427
+
293
428
  ## Component selection
294
429
 
295
430
  Choose components by intent: