@uselets/ui 1.3.0 → 1.5.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/README.md +92 -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 +3 -5
  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 +7 -5
  18. package/dist/components/badge.js +1 -1
  19. package/dist/components/badge.js.map +1 -1
  20. package/dist/components/bar-chart.d.ts +54 -0
  21. package/dist/components/bar-chart.js +2 -0
  22. package/dist/components/bar-chart.js.map +1 -0
  23. package/dist/components/breadcrumb.d.ts +7 -8
  24. package/dist/components/breadcrumb.js.map +1 -1
  25. package/dist/components/button-group.d.ts +5 -7
  26. package/dist/components/button-group.js.map +1 -1
  27. package/dist/components/button.d.ts +4 -6
  28. package/dist/components/button.js +1 -1
  29. package/dist/components/button.js.map +1 -1
  30. package/dist/components/calendar.d.ts +2 -3
  31. package/dist/components/calendar.js.map +1 -1
  32. package/dist/components/card.d.ts +7 -8
  33. package/dist/components/card.js.map +1 -1
  34. package/dist/components/carousel.d.ts +6 -7
  35. package/dist/components/carousel.js.map +1 -1
  36. package/dist/components/carousel.spec.js +1 -1
  37. package/dist/components/carousel.spec.js.map +1 -1
  38. package/dist/components/center.d.ts +4 -6
  39. package/dist/components/center.js.map +1 -1
  40. package/dist/components/chart.d.ts +2 -263
  41. package/dist/components/chart.js +1 -1
  42. package/dist/components/chart.js.map +1 -1
  43. package/dist/components/checkbox.d.ts +2 -4
  44. package/dist/components/checkbox.js +1 -1
  45. package/dist/components/checkbox.js.map +1 -1
  46. package/dist/components/code.d.ts +1 -2
  47. package/dist/components/code.js.map +1 -1
  48. package/dist/components/collapsible.d.ts +3 -4
  49. package/dist/components/collapsible.js.map +1 -1
  50. package/dist/components/combobox.d.ts +15 -16
  51. package/dist/components/combobox.js +1 -1
  52. package/dist/components/combobox.js.map +1 -1
  53. package/dist/components/command.d.ts +9 -10
  54. package/dist/components/command.js.map +1 -1
  55. package/dist/components/container.d.ts +2 -4
  56. package/dist/components/container.js.map +1 -1
  57. package/dist/components/data-table.d.ts +164 -0
  58. package/dist/components/data-table.js +2 -0
  59. package/dist/components/data-table.js.map +1 -0
  60. package/dist/components/date-range-picker.d.ts +11 -12
  61. package/dist/components/date-range-picker.js.map +1 -1
  62. package/dist/components/dialog.d.ts +11 -13
  63. package/dist/components/dialog.js.map +1 -1
  64. package/dist/components/donut-chart.d.ts +22 -0
  65. package/dist/components/donut-chart.js +2 -0
  66. package/dist/components/donut-chart.js.map +1 -0
  67. package/dist/components/drawer.d.ts +12 -14
  68. package/dist/components/drawer.js.map +1 -1
  69. package/dist/components/dropdown-menu.d.ts +15 -16
  70. package/dist/components/dropdown-menu.js.map +1 -1
  71. package/dist/components/dropzone.d.ts +1 -2
  72. package/dist/components/dropzone.js.map +1 -1
  73. package/dist/components/empty.d.ts +8 -10
  74. package/dist/components/empty.js.map +1 -1
  75. package/dist/components/field.d.ts +12 -14
  76. package/dist/components/field.js.map +1 -1
  77. package/dist/components/field.spec.js +1 -1
  78. package/dist/components/field.spec.js.map +1 -1
  79. package/dist/components/flex.d.ts +3 -5
  80. package/dist/components/flex.js.map +1 -1
  81. package/dist/components/grid.d.ts +2 -3
  82. package/dist/components/grid.js.map +1 -1
  83. package/dist/components/group.d.ts +7 -5
  84. package/dist/components/group.js +1 -1
  85. package/dist/components/group.js.map +1 -1
  86. package/dist/components/heading.d.ts +3 -5
  87. package/dist/components/heading.js.map +1 -1
  88. package/dist/components/hover-card.d.ts +3 -4
  89. package/dist/components/hover-card.js.map +1 -1
  90. package/dist/components/input-group.d.ts +9 -11
  91. package/dist/components/input-group.js.map +1 -1
  92. package/dist/components/input-otp.d.ts +4 -5
  93. package/dist/components/input-otp.js.map +1 -1
  94. package/dist/components/input.d.ts +1 -2
  95. package/dist/components/input.js.map +1 -1
  96. package/dist/components/kbd.d.ts +2 -3
  97. package/dist/components/kbd.js.map +1 -1
  98. package/dist/components/label.d.ts +1 -2
  99. package/dist/components/label.js.map +1 -1
  100. package/dist/components/list.d.ts +6 -8
  101. package/dist/components/list.js.map +1 -1
  102. package/dist/components/mask-input.d.ts +25 -0
  103. package/dist/components/mask-input.js +2 -0
  104. package/dist/components/mask-input.js.map +1 -0
  105. package/dist/components/native-select.d.ts +3 -4
  106. package/dist/components/native-select.js.map +1 -1
  107. package/dist/components/navigation-menu.d.ts +9 -11
  108. package/dist/components/navigation-menu.js.map +1 -1
  109. package/dist/components/pagination.d.ts +7 -8
  110. package/dist/components/pagination.js.map +1 -1
  111. package/dist/components/pie-chart.d.ts +58 -0
  112. package/dist/components/pie-chart.js +2 -0
  113. package/dist/components/pie-chart.js.map +1 -0
  114. package/dist/components/popover.d.ts +7 -8
  115. package/dist/components/popover.js.map +1 -1
  116. package/dist/components/progress.d.ts +1 -2
  117. package/dist/components/progress.js.map +1 -1
  118. package/dist/components/radio-group.d.ts +3 -5
  119. package/dist/components/radio-group.js.map +1 -1
  120. package/dist/components/resizable.d.ts +3 -4
  121. package/dist/components/resizable.js.map +1 -1
  122. package/dist/components/scroll-area.d.ts +2 -3
  123. package/dist/components/scroll-area.js.map +1 -1
  124. package/dist/components/select.d.ts +10 -11
  125. package/dist/components/select.js.map +1 -1
  126. package/dist/components/separator.d.ts +1 -2
  127. package/dist/components/separator.js.map +1 -1
  128. package/dist/components/sidebar.d.ts +25 -27
  129. package/dist/components/sidebar.js.map +1 -1
  130. package/dist/components/simple-grid.d.ts +1 -2
  131. package/dist/components/simple-grid.js.map +1 -1
  132. package/dist/components/skeleton.d.ts +1 -2
  133. package/dist/components/skeleton.js.map +1 -1
  134. package/dist/components/skeleton.spec.js +1 -1
  135. package/dist/components/skeleton.spec.js.map +1 -1
  136. package/dist/components/slider.d.ts +1 -2
  137. package/dist/components/slider.js.map +1 -1
  138. package/dist/components/sonner.d.ts +1 -2
  139. package/dist/components/sonner.js.map +1 -1
  140. package/dist/components/spinner.d.ts +2 -4
  141. package/dist/components/spinner.js.map +1 -1
  142. package/dist/components/stack.d.ts +7 -5
  143. package/dist/components/stack.js +1 -1
  144. package/dist/components/stack.js.map +1 -1
  145. package/dist/components/switch.d.ts +1 -2
  146. package/dist/components/switch.js.map +1 -1
  147. package/dist/components/table.d.ts +29 -11
  148. package/dist/components/table.js +1 -1
  149. package/dist/components/table.js.map +1 -1
  150. package/dist/components/tabs.d.ts +5 -7
  151. package/dist/components/tabs.js.map +1 -1
  152. package/dist/components/text.d.ts +3 -5
  153. package/dist/components/text.js.map +1 -1
  154. package/dist/components/textarea.d.ts +1 -2
  155. package/dist/components/textarea.js.map +1 -1
  156. package/dist/components/theme.d.ts +2 -3
  157. package/dist/components/theme.js.map +1 -1
  158. package/dist/components/timeline.d.ts +6 -7
  159. package/dist/components/timeline.js.map +1 -1
  160. package/dist/components/toggle-group.d.ts +2 -3
  161. package/dist/components/toggle-group.js.map +1 -1
  162. package/dist/components/toggle.d.ts +2 -4
  163. package/dist/components/toggle.js.map +1 -1
  164. package/dist/components/tooltip.d.ts +5 -6
  165. package/dist/components/tooltip.js.map +1 -1
  166. package/dist/components/unstyled-button.d.ts +1 -2
  167. package/dist/components/unstyled-button.js.map +1 -1
  168. package/dist/components/visibility.d.ts +2 -3
  169. package/dist/components/visibility.js.map +1 -1
  170. package/dist/{dist-QvsLCO1I.js → dist-C35vdEt2.js} +45 -25
  171. package/dist/dist-C35vdEt2.js.map +1 -0
  172. package/dist/hooks/use-breakpoint-value.js.map +1 -1
  173. package/dist/hooks/use-breakpoint-value.spec.js +1 -1
  174. package/dist/hooks/use-breakpoint-value.spec.js.map +1 -1
  175. package/dist/hooks/use-breakpoint.js.map +1 -1
  176. package/dist/hooks/use-breakpoint.spec.js +1 -1
  177. package/dist/hooks/use-breakpoint.spec.js.map +1 -1
  178. package/dist/hooks/use-disclosure.js.map +1 -1
  179. package/dist/hooks/use-disclosure.spec.js +1 -1
  180. package/dist/hooks/use-disclosure.spec.js.map +1 -1
  181. package/dist/hooks/use-document-visibility.js.map +1 -1
  182. package/dist/hooks/use-document-visibility.spec.js +1 -1
  183. package/dist/hooks/use-document-visibility.spec.js.map +1 -1
  184. package/dist/hooks/use-idle.js.map +1 -1
  185. package/dist/hooks/use-idle.spec.js +1 -1
  186. package/dist/hooks/use-idle.spec.js.map +1 -1
  187. package/dist/hooks/use-interval.js.map +1 -1
  188. package/dist/hooks/use-interval.spec.js +1 -1
  189. package/dist/hooks/use-interval.spec.js.map +1 -1
  190. package/dist/hooks/use-local-storage.js.map +1 -1
  191. package/dist/hooks/use-local-storage.spec.js +1 -1
  192. package/dist/hooks/use-local-storage.spec.js.map +1 -1
  193. package/dist/hooks/use-mask.d.ts +9 -0
  194. package/dist/hooks/use-mask.js +2 -0
  195. package/dist/hooks/use-mask.js.map +1 -0
  196. package/dist/hooks/use-mobile.js.map +1 -1
  197. package/dist/hooks/use-mobile.spec.js +1 -1
  198. package/dist/hooks/use-mobile.spec.js.map +1 -1
  199. package/dist/index.d.ts +11 -3
  200. package/dist/index.js +1 -1
  201. package/dist/lib/badge.d.ts +20 -0
  202. package/dist/lib/badge.js +2 -0
  203. package/dist/lib/badge.js.map +1 -0
  204. package/dist/lib/button.js.map +1 -1
  205. package/dist/lib/color.js.map +1 -1
  206. package/dist/lib/utils.js.map +1 -1
  207. package/dist/magic-string.es-C-uSEkg2.js.map +1 -1
  208. package/dist/postcss.config.js.map +1 -1
  209. package/dist/{react.esm-D_dNEsWM.js → react.esm-BagSCtI3.js} +11 -2
  210. package/dist/{react.esm-D_dNEsWM.js.map → react.esm-BagSCtI3.js.map} +1 -1
  211. package/dist/skills/uselets-ui/SKILL.md +1 -0
  212. package/dist/skills/uselets-ui/references/component-catalog.md +32 -4
  213. package/dist/skills/uselets-ui/references/package-guide.md +136 -1
  214. package/dist/styles/theme.css +4 -0
  215. package/package.json +15 -13
  216. package/skills/uselets-ui/SKILL.md +1 -0
  217. package/skills/uselets-ui/references/component-catalog.md +32 -4
  218. package/skills/uselets-ui/references/package-guide.md +136 -1
  219. 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`
@@ -97,6 +101,7 @@ Unless noted otherwise, each module also exports its public `*Props` types from
97
101
  - Notes: confirmation dialog composition
98
102
  - `badge`
99
103
  - Exports: `Badge`, `badgeVariants`
104
+ - Notes: pill-shaped tag with `size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (default `md`), `solid`/`outline`/`subtle`/`ghost`/`link` variants, `color: UIColor` (`primary` default), and `asChild`
100
105
  - `button`
101
106
  - Exports: `Button`, `buttonVariants`
102
107
  - Notes: action button with `solid`, `outline`, `subtle`, `ghost`, and `link` variants, `color: UIColor` (`primary` default), sizes from `xs` through `xl` plus icon sizes through `icon-xl`, and `loading`
@@ -135,6 +140,9 @@ Unless noted otherwise, each module also exports its public `*Props` types from
135
140
  - Notes: compose controls with prefixes, suffixes, or inline actions
136
141
  - `input-otp`
137
142
  - Exports: `InputOTP`, `InputOTPGroup`, `InputOTPSeparator`, `InputOTPSlot`, `REGEXP_ONLY_CHARS`, `REGEXP_ONLY_DIGITS`, `REGEXP_ONLY_DIGITS_AND_CHARS`
143
+ - `mask-input`
144
+ - Exports: `MaskInput`
145
+ - 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
146
  - `checkbox`
139
147
  - Exports: `Checkbox`
140
148
  - 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 +221,20 @@ Unless noted otherwise, each module also exports its public `*Props` types from
213
221
  - `center`
214
222
  - Exports: `Center`, `centerVariants`
215
223
  - `chart`
216
- - Exports: `Bar`, `BarChart`, `CartesianGrid`, `XAxis`, `ChartContainer`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`
224
+ - Exports: `ChartContainer`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`, `ChartConfig`
225
+ - 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`.
226
+ - `area-chart`
227
+ - Exports: `AreaChart`, `AreaChartSeries`, `AreaChartType`, `AreaChartCurveType`, `AreaChartGridAxis`
228
+ - 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`.
229
+ - `bar-chart`
230
+ - Exports: `BarChart`, `BarChartSeries`, `BarChartType`, `BarChartOrientation`, `BarChartGridAxis`
231
+ - Notes: high-level bar chart. Same shape as `area-chart` plus `orientation` (vertical/horizontal), `barSize`, `radius`. Supports stacked and percent types.
232
+ - `pie-chart`
233
+ - Exports: `PieChart`, `PieChartBase`, `PieChartDatum`, `buildPieConfig`
234
+ - 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`.
235
+ - `donut-chart`
236
+ - Exports: `DonutChart`
237
+ - Notes: same data shape as `pie-chart`. Adds `thickness` (donut hole size in px) and `withTotal` + `totalLabel` (renders sum in the center).
217
238
  - `collapsible`
218
239
  - Exports: `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`
219
240
  - `container`
@@ -223,7 +244,7 @@ Unless noted otherwise, each module also exports its public `*Props` types from
223
244
  - 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
245
  - `group`
225
246
  - Exports: `Group`, `groupVariants`
226
- - Notes: supports `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`) and `wrap`
247
+ - 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
248
  - `kbd`
228
249
  - Exports: `Kbd`, `KbdGroup`
229
250
  - `resizable`
@@ -234,9 +255,16 @@ Unless noted otherwise, each module also exports its public `*Props` types from
234
255
  - Exports: `Separator`
235
256
  - `stack`
236
257
  - Exports: `Stack`, `stackVariants`
237
- - Notes: supports `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`)
258
+ - 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
259
  - `table`
239
- - Exports: `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`, `TableCell`, `TableCaption`
260
+ - Exports: `Table`, `TableContainer`, `TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`, `TableCell`, `TableCaption`, `tableVariants`
261
+ - 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`)
262
+ - `data-table`
263
+ - Exports: `DataTable`, `DataTableRoot`, `DataTableToolbar`, `DataTableHeader`, `DataTableBody`, `DataTablePagination`, `DataTableColumnHeader`, `createDataTableSelectColumn`, `useDataTable`, `useDataTableContext`, `defaultDataTableLabels`, `DataTableLabels`, `UseDataTableOptions`, `DataTableRootProps`, `DataTableToolbarProps`
264
+ - 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`.
265
+ - **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>`)
266
+ - **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
267
+ - **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
268
  - `timeline`
241
269
  - Exports: `Timeline`, `TimelineConnector`, `TimelineContent`, `TimelineDescription`, `TimelineEmpty`, `TimelineHeader`, `TimelineIcon`, `TimelineItem`, `TimelineTime`, `TimelineTitle`, `timelineVariants`
242
270
  - 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:
@@ -193,3 +193,7 @@
193
193
  --radius-3xl: calc(var(--radius) + 12px);
194
194
  --radius-4xl: calc(var(--radius) + 16px);
195
195
  }
196
+
197
+ @utility bg-gradient-primary {
198
+ background-image: var(--gradient-primary);
199
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uselets/ui",
3
- "version": "1.3.0",
3
+ "version": "1.5.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`
@@ -97,6 +101,7 @@ Unless noted otherwise, each module also exports its public `*Props` types from
97
101
  - Notes: confirmation dialog composition
98
102
  - `badge`
99
103
  - Exports: `Badge`, `badgeVariants`
104
+ - Notes: pill-shaped tag with `size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (default `md`), `solid`/`outline`/`subtle`/`ghost`/`link` variants, `color: UIColor` (`primary` default), and `asChild`
100
105
  - `button`
101
106
  - Exports: `Button`, `buttonVariants`
102
107
  - Notes: action button with `solid`, `outline`, `subtle`, `ghost`, and `link` variants, `color: UIColor` (`primary` default), sizes from `xs` through `xl` plus icon sizes through `icon-xl`, and `loading`
@@ -135,6 +140,9 @@ Unless noted otherwise, each module also exports its public `*Props` types from
135
140
  - Notes: compose controls with prefixes, suffixes, or inline actions
136
141
  - `input-otp`
137
142
  - Exports: `InputOTP`, `InputOTPGroup`, `InputOTPSeparator`, `InputOTPSlot`, `REGEXP_ONLY_CHARS`, `REGEXP_ONLY_DIGITS`, `REGEXP_ONLY_DIGITS_AND_CHARS`
143
+ - `mask-input`
144
+ - Exports: `MaskInput`
145
+ - 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
146
  - `checkbox`
139
147
  - Exports: `Checkbox`
140
148
  - 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 +221,20 @@ Unless noted otherwise, each module also exports its public `*Props` types from
213
221
  - `center`
214
222
  - Exports: `Center`, `centerVariants`
215
223
  - `chart`
216
- - Exports: `Bar`, `BarChart`, `CartesianGrid`, `XAxis`, `ChartContainer`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`
224
+ - Exports: `ChartContainer`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`, `ChartConfig`
225
+ - 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`.
226
+ - `area-chart`
227
+ - Exports: `AreaChart`, `AreaChartSeries`, `AreaChartType`, `AreaChartCurveType`, `AreaChartGridAxis`
228
+ - 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`.
229
+ - `bar-chart`
230
+ - Exports: `BarChart`, `BarChartSeries`, `BarChartType`, `BarChartOrientation`, `BarChartGridAxis`
231
+ - Notes: high-level bar chart. Same shape as `area-chart` plus `orientation` (vertical/horizontal), `barSize`, `radius`. Supports stacked and percent types.
232
+ - `pie-chart`
233
+ - Exports: `PieChart`, `PieChartBase`, `PieChartDatum`, `buildPieConfig`
234
+ - 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`.
235
+ - `donut-chart`
236
+ - Exports: `DonutChart`
237
+ - Notes: same data shape as `pie-chart`. Adds `thickness` (donut hole size in px) and `withTotal` + `totalLabel` (renders sum in the center).
217
238
  - `collapsible`
218
239
  - Exports: `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`
219
240
  - `container`
@@ -223,7 +244,7 @@ Unless noted otherwise, each module also exports its public `*Props` types from
223
244
  - 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
245
  - `group`
225
246
  - Exports: `Group`, `groupVariants`
226
- - Notes: supports `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`) and `wrap`
247
+ - 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
248
  - `kbd`
228
249
  - Exports: `Kbd`, `KbdGroup`
229
250
  - `resizable`
@@ -234,9 +255,16 @@ Unless noted otherwise, each module also exports its public `*Props` types from
234
255
  - Exports: `Separator`
235
256
  - `stack`
236
257
  - Exports: `Stack`, `stackVariants`
237
- - Notes: supports `gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (`4/8/16/24/32px`)
258
+ - 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
259
  - `table`
239
- - Exports: `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`, `TableCell`, `TableCaption`
260
+ - Exports: `Table`, `TableContainer`, `TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`, `TableCell`, `TableCaption`, `tableVariants`
261
+ - 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`)
262
+ - `data-table`
263
+ - Exports: `DataTable`, `DataTableRoot`, `DataTableToolbar`, `DataTableHeader`, `DataTableBody`, `DataTablePagination`, `DataTableColumnHeader`, `createDataTableSelectColumn`, `useDataTable`, `useDataTableContext`, `defaultDataTableLabels`, `DataTableLabels`, `UseDataTableOptions`, `DataTableRootProps`, `DataTableToolbarProps`
264
+ - 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`.
265
+ - **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>`)
266
+ - **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
267
+ - **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
268
  - `timeline`
241
269
  - Exports: `Timeline`, `TimelineConnector`, `TimelineContent`, `TimelineDescription`, `TimelineEmpty`, `TimelineHeader`, `TimelineIcon`, `TimelineItem`, `TimelineTime`, `TimelineTitle`, `timelineVariants`
242
270
  - 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: