@inui/design-system 0.0.0-stage → 1.22.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 (312) hide show
  1. package/README.md +360 -2
  2. package/dist/components/ui/accordion.d.ts +44 -0
  3. package/dist/components/ui/accordion.d.ts.map +1 -0
  4. package/dist/components/ui/accordion.js +86 -0
  5. package/dist/components/ui/accordion.js.map +1 -0
  6. package/dist/components/ui/ai-avatar.d.ts +46 -0
  7. package/dist/components/ui/ai-avatar.d.ts.map +1 -0
  8. package/dist/components/ui/ai-avatar.js +43 -0
  9. package/dist/components/ui/ai-avatar.js.map +1 -0
  10. package/dist/components/ui/ai-label.d.ts +118 -0
  11. package/dist/components/ui/ai-label.d.ts.map +1 -0
  12. package/dist/components/ui/ai-label.js +184 -0
  13. package/dist/components/ui/ai-label.js.map +1 -0
  14. package/dist/components/ui/ai-sparkle.d.ts +91 -0
  15. package/dist/components/ui/ai-sparkle.d.ts.map +1 -0
  16. package/dist/components/ui/ai-sparkle.js +200 -0
  17. package/dist/components/ui/ai-sparkle.js.map +1 -0
  18. package/dist/components/ui/alert-dialog.d.ts +79 -0
  19. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  20. package/dist/components/ui/alert-dialog.js +147 -0
  21. package/dist/components/ui/alert-dialog.js.map +1 -0
  22. package/dist/components/ui/alert.d.ts +71 -0
  23. package/dist/components/ui/alert.d.ts.map +1 -0
  24. package/dist/components/ui/alert.js +108 -0
  25. package/dist/components/ui/alert.js.map +1 -0
  26. package/dist/components/ui/aspect-ratio.d.ts +30 -0
  27. package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
  28. package/dist/components/ui/aspect-ratio.js +40 -0
  29. package/dist/components/ui/aspect-ratio.js.map +1 -0
  30. package/dist/components/ui/attachment.d.ts +74 -0
  31. package/dist/components/ui/attachment.d.ts.map +1 -0
  32. package/dist/components/ui/attachment.js +142 -0
  33. package/dist/components/ui/attachment.js.map +1 -0
  34. package/dist/components/ui/avatar.d.ts +82 -0
  35. package/dist/components/ui/avatar.d.ts.map +1 -0
  36. package/dist/components/ui/avatar.js +123 -0
  37. package/dist/components/ui/avatar.js.map +1 -0
  38. package/dist/components/ui/badge.d.ts +130 -0
  39. package/dist/components/ui/badge.d.ts.map +1 -0
  40. package/dist/components/ui/badge.js +226 -0
  41. package/dist/components/ui/badge.js.map +1 -0
  42. package/dist/components/ui/breadcrumb.d.ts +43 -0
  43. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  44. package/dist/components/ui/breadcrumb.js +98 -0
  45. package/dist/components/ui/breadcrumb.js.map +1 -0
  46. package/dist/components/ui/browser-tabs.d.ts +218 -0
  47. package/dist/components/ui/browser-tabs.d.ts.map +1 -0
  48. package/dist/components/ui/browser-tabs.js +528 -0
  49. package/dist/components/ui/browser-tabs.js.map +1 -0
  50. package/dist/components/ui/bubble.d.ts +80 -0
  51. package/dist/components/ui/bubble.d.ts.map +1 -0
  52. package/dist/components/ui/bubble.js +235 -0
  53. package/dist/components/ui/bubble.js.map +1 -0
  54. package/dist/components/ui/button-group.d.ts +60 -0
  55. package/dist/components/ui/button-group.d.ts.map +1 -0
  56. package/dist/components/ui/button-group.js +89 -0
  57. package/dist/components/ui/button-group.js.map +1 -0
  58. package/dist/components/ui/button.d.ts +157 -0
  59. package/dist/components/ui/button.d.ts.map +1 -0
  60. package/dist/components/ui/button.js +144 -0
  61. package/dist/components/ui/button.js.map +1 -0
  62. package/dist/components/ui/calendar.d.ts +57 -0
  63. package/dist/components/ui/calendar.d.ts.map +1 -0
  64. package/dist/components/ui/calendar.js +163 -0
  65. package/dist/components/ui/calendar.js.map +1 -0
  66. package/dist/components/ui/card.d.ts +96 -0
  67. package/dist/components/ui/card.d.ts.map +1 -0
  68. package/dist/components/ui/card.js +131 -0
  69. package/dist/components/ui/card.js.map +1 -0
  70. package/dist/components/ui/carousel.d.ts +86 -0
  71. package/dist/components/ui/carousel.d.ts.map +1 -0
  72. package/dist/components/ui/carousel.js +207 -0
  73. package/dist/components/ui/carousel.js.map +1 -0
  74. package/dist/components/ui/checkbox.d.ts +54 -0
  75. package/dist/components/ui/checkbox.d.ts.map +1 -0
  76. package/dist/components/ui/checkbox.js +78 -0
  77. package/dist/components/ui/checkbox.js.map +1 -0
  78. package/dist/components/ui/collapsible.d.ts +45 -0
  79. package/dist/components/ui/collapsible.d.ts.map +1 -0
  80. package/dist/components/ui/collapsible.js +65 -0
  81. package/dist/components/ui/collapsible.js.map +1 -0
  82. package/dist/components/ui/combobox.d.ts +111 -0
  83. package/dist/components/ui/combobox.d.ts.map +1 -0
  84. package/dist/components/ui/combobox.js +264 -0
  85. package/dist/components/ui/combobox.js.map +1 -0
  86. package/dist/components/ui/command.d.ts +71 -0
  87. package/dist/components/ui/command.d.ts.map +1 -0
  88. package/dist/components/ui/command.js +133 -0
  89. package/dist/components/ui/command.js.map +1 -0
  90. package/dist/components/ui/context-menu.d.ts +70 -0
  91. package/dist/components/ui/context-menu.d.ts.map +1 -0
  92. package/dist/components/ui/context-menu.js +163 -0
  93. package/dist/components/ui/context-menu.js.map +1 -0
  94. package/dist/components/ui/count-badge.d.ts +40 -0
  95. package/dist/components/ui/count-badge.d.ts.map +1 -0
  96. package/dist/components/ui/count-badge.js +43 -0
  97. package/dist/components/ui/count-badge.js.map +1 -0
  98. package/dist/components/ui/data-table.d.ts +1026 -0
  99. package/dist/components/ui/data-table.d.ts.map +1 -0
  100. package/dist/components/ui/data-table.js +3423 -0
  101. package/dist/components/ui/data-table.js.map +1 -0
  102. package/dist/components/ui/dialog.d.ts +73 -0
  103. package/dist/components/ui/dialog.d.ts.map +1 -0
  104. package/dist/components/ui/dialog.js +145 -0
  105. package/dist/components/ui/dialog.js.map +1 -0
  106. package/dist/components/ui/direction.d.ts +35 -0
  107. package/dist/components/ui/direction.d.ts.map +1 -0
  108. package/dist/components/ui/direction.js +35 -0
  109. package/dist/components/ui/direction.js.map +1 -0
  110. package/dist/components/ui/drawer.d.ts +53 -0
  111. package/dist/components/ui/drawer.d.ts.map +1 -0
  112. package/dist/components/ui/drawer.js +115 -0
  113. package/dist/components/ui/drawer.js.map +1 -0
  114. package/dist/components/ui/dropdown-menu.d.ts +75 -0
  115. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  116. package/dist/components/ui/dropdown-menu.js +173 -0
  117. package/dist/components/ui/dropdown-menu.js.map +1 -0
  118. package/dist/components/ui/dropzone.d.ts +141 -0
  119. package/dist/components/ui/dropzone.d.ts.map +1 -0
  120. package/dist/components/ui/dropzone.js +257 -0
  121. package/dist/components/ui/dropzone.js.map +1 -0
  122. package/dist/components/ui/empty.d.ts +94 -0
  123. package/dist/components/ui/empty.d.ts.map +1 -0
  124. package/dist/components/ui/empty.js +149 -0
  125. package/dist/components/ui/empty.js.map +1 -0
  126. package/dist/components/ui/field.d.ts +122 -0
  127. package/dist/components/ui/field.d.ts.map +1 -0
  128. package/dist/components/ui/field.js +254 -0
  129. package/dist/components/ui/field.js.map +1 -0
  130. package/dist/components/ui/hover-card.d.ts +51 -0
  131. package/dist/components/ui/hover-card.d.ts.map +1 -0
  132. package/dist/components/ui/hover-card.js +101 -0
  133. package/dist/components/ui/hover-card.js.map +1 -0
  134. package/dist/components/ui/input-group.d.ts +62 -0
  135. package/dist/components/ui/input-group.d.ts.map +1 -0
  136. package/dist/components/ui/input-group.js +115 -0
  137. package/dist/components/ui/input-group.js.map +1 -0
  138. package/dist/components/ui/input-otp.d.ts +52 -0
  139. package/dist/components/ui/input-otp.d.ts.map +1 -0
  140. package/dist/components/ui/input-otp.js +89 -0
  141. package/dist/components/ui/input-otp.js.map +1 -0
  142. package/dist/components/ui/input.d.ts +47 -0
  143. package/dist/components/ui/input.d.ts.map +1 -0
  144. package/dist/components/ui/input.js +47 -0
  145. package/dist/components/ui/input.js.map +1 -0
  146. package/dist/components/ui/item.d.ts +74 -0
  147. package/dist/components/ui/item.d.ts.map +1 -0
  148. package/dist/components/ui/item.js +176 -0
  149. package/dist/components/ui/item.js.map +1 -0
  150. package/dist/components/ui/kbd.d.ts +41 -0
  151. package/dist/components/ui/kbd.d.ts.map +1 -0
  152. package/dist/components/ui/kbd.js +58 -0
  153. package/dist/components/ui/kbd.js.map +1 -0
  154. package/dist/components/ui/key-value-pair.d.ts +56 -0
  155. package/dist/components/ui/key-value-pair.d.ts.map +1 -0
  156. package/dist/components/ui/key-value-pair.js +110 -0
  157. package/dist/components/ui/key-value-pair.js.map +1 -0
  158. package/dist/components/ui/label.d.ts +83 -0
  159. package/dist/components/ui/label.d.ts.map +1 -0
  160. package/dist/components/ui/label.js +85 -0
  161. package/dist/components/ui/label.js.map +1 -0
  162. package/dist/components/ui/marker.d.ts +54 -0
  163. package/dist/components/ui/marker.d.ts.map +1 -0
  164. package/dist/components/ui/marker.js +79 -0
  165. package/dist/components/ui/marker.js.map +1 -0
  166. package/dist/components/ui/menubar.d.ts +72 -0
  167. package/dist/components/ui/menubar.d.ts.map +1 -0
  168. package/dist/components/ui/menubar.js +174 -0
  169. package/dist/components/ui/menubar.js.map +1 -0
  170. package/dist/components/ui/message-scroller.d.ts +53 -0
  171. package/dist/components/ui/message-scroller.d.ts.map +1 -0
  172. package/dist/components/ui/message-scroller.js +95 -0
  173. package/dist/components/ui/message-scroller.js.map +1 -0
  174. package/dist/components/ui/message.d.ts +63 -0
  175. package/dist/components/ui/message.d.ts.map +1 -0
  176. package/dist/components/ui/message.js +92 -0
  177. package/dist/components/ui/message.js.map +1 -0
  178. package/dist/components/ui/meta-list.d.ts +55 -0
  179. package/dist/components/ui/meta-list.d.ts.map +1 -0
  180. package/dist/components/ui/meta-list.js +75 -0
  181. package/dist/components/ui/meta-list.js.map +1 -0
  182. package/dist/components/ui/native-select.d.ts +51 -0
  183. package/dist/components/ui/native-select.d.ts.map +1 -0
  184. package/dist/components/ui/native-select.js +75 -0
  185. package/dist/components/ui/native-select.js.map +1 -0
  186. package/dist/components/ui/navigation-menu.d.ts +56 -0
  187. package/dist/components/ui/navigation-menu.d.ts.map +1 -0
  188. package/dist/components/ui/navigation-menu.js +116 -0
  189. package/dist/components/ui/navigation-menu.js.map +1 -0
  190. package/dist/components/ui/page-header.d.ts +223 -0
  191. package/dist/components/ui/page-header.d.ts.map +1 -0
  192. package/dist/components/ui/page-header.js +233 -0
  193. package/dist/components/ui/page-header.js.map +1 -0
  194. package/dist/components/ui/pagination.d.ts +68 -0
  195. package/dist/components/ui/pagination.d.ts.map +1 -0
  196. package/dist/components/ui/pagination.js +145 -0
  197. package/dist/components/ui/pagination.js.map +1 -0
  198. package/dist/components/ui/popover.d.ts +61 -0
  199. package/dist/components/ui/popover.d.ts.map +1 -0
  200. package/dist/components/ui/popover.js +144 -0
  201. package/dist/components/ui/popover.js.map +1 -0
  202. package/dist/components/ui/progress.d.ts +35 -0
  203. package/dist/components/ui/progress.d.ts.map +1 -0
  204. package/dist/components/ui/progress.js +46 -0
  205. package/dist/components/ui/progress.js.map +1 -0
  206. package/dist/components/ui/questionnaire.d.ts +92 -0
  207. package/dist/components/ui/questionnaire.d.ts.map +1 -0
  208. package/dist/components/ui/questionnaire.js +277 -0
  209. package/dist/components/ui/questionnaire.js.map +1 -0
  210. package/dist/components/ui/radio-group.d.ts +50 -0
  211. package/dist/components/ui/radio-group.d.ts.map +1 -0
  212. package/dist/components/ui/radio-group.js +65 -0
  213. package/dist/components/ui/radio-group.js.map +1 -0
  214. package/dist/components/ui/resizable.d.ts +44 -0
  215. package/dist/components/ui/resizable.d.ts.map +1 -0
  216. package/dist/components/ui/resizable.js +73 -0
  217. package/dist/components/ui/resizable.js.map +1 -0
  218. package/dist/components/ui/scroll-area.d.ts +55 -0
  219. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  220. package/dist/components/ui/scroll-area.js +72 -0
  221. package/dist/components/ui/scroll-area.js.map +1 -0
  222. package/dist/components/ui/select.d.ts +274 -0
  223. package/dist/components/ui/select.d.ts.map +1 -0
  224. package/dist/components/ui/select.js +609 -0
  225. package/dist/components/ui/select.js.map +1 -0
  226. package/dist/components/ui/separator.d.ts +31 -0
  227. package/dist/components/ui/separator.d.ts.map +1 -0
  228. package/dist/components/ui/separator.js +38 -0
  229. package/dist/components/ui/separator.js.map +1 -0
  230. package/dist/components/ui/sheet.d.ts +59 -0
  231. package/dist/components/ui/sheet.d.ts.map +1 -0
  232. package/dist/components/ui/sheet.js +128 -0
  233. package/dist/components/ui/sheet.js.map +1 -0
  234. package/dist/components/ui/sidebar.d.ts +166 -0
  235. package/dist/components/ui/sidebar.d.ts.map +1 -0
  236. package/dist/components/ui/sidebar.js +512 -0
  237. package/dist/components/ui/sidebar.js.map +1 -0
  238. package/dist/components/ui/skeleton.d.ts +35 -0
  239. package/dist/components/ui/skeleton.d.ts.map +1 -0
  240. package/dist/components/ui/skeleton.js +46 -0
  241. package/dist/components/ui/skeleton.js.map +1 -0
  242. package/dist/components/ui/slider.d.ts +39 -0
  243. package/dist/components/ui/slider.d.ts.map +1 -0
  244. package/dist/components/ui/slider.js +75 -0
  245. package/dist/components/ui/slider.js.map +1 -0
  246. package/dist/components/ui/sonner.d.ts +56 -0
  247. package/dist/components/ui/sonner.d.ts.map +1 -0
  248. package/dist/components/ui/sonner.js +96 -0
  249. package/dist/components/ui/sonner.js.map +1 -0
  250. package/dist/components/ui/spinner.d.ts +37 -0
  251. package/dist/components/ui/spinner.d.ts.map +1 -0
  252. package/dist/components/ui/spinner.js +51 -0
  253. package/dist/components/ui/spinner.js.map +1 -0
  254. package/dist/components/ui/stepper.d.ts +84 -0
  255. package/dist/components/ui/stepper.d.ts.map +1 -0
  256. package/dist/components/ui/stepper.js +145 -0
  257. package/dist/components/ui/stepper.js.map +1 -0
  258. package/dist/components/ui/switch.d.ts +36 -0
  259. package/dist/components/ui/switch.d.ts.map +1 -0
  260. package/dist/components/ui/switch.js +44 -0
  261. package/dist/components/ui/switch.js.map +1 -0
  262. package/dist/components/ui/table.d.ts +80 -0
  263. package/dist/components/ui/table.d.ts.map +1 -0
  264. package/dist/components/ui/table.js +130 -0
  265. package/dist/components/ui/table.js.map +1 -0
  266. package/dist/components/ui/tabs.d.ts +87 -0
  267. package/dist/components/ui/tabs.d.ts.map +1 -0
  268. package/dist/components/ui/tabs.js +128 -0
  269. package/dist/components/ui/tabs.js.map +1 -0
  270. package/dist/components/ui/textarea.d.ts +35 -0
  271. package/dist/components/ui/textarea.d.ts.map +1 -0
  272. package/dist/components/ui/textarea.js +38 -0
  273. package/dist/components/ui/textarea.js.map +1 -0
  274. package/dist/components/ui/time-picker.d.ts +58 -0
  275. package/dist/components/ui/time-picker.d.ts.map +1 -0
  276. package/dist/components/ui/time-picker.js +188 -0
  277. package/dist/components/ui/time-picker.js.map +1 -0
  278. package/dist/components/ui/toggle-group.d.ts +52 -0
  279. package/dist/components/ui/toggle-group.d.ts.map +1 -0
  280. package/dist/components/ui/toggle-group.js +94 -0
  281. package/dist/components/ui/toggle-group.js.map +1 -0
  282. package/dist/components/ui/toggle.d.ts +50 -0
  283. package/dist/components/ui/toggle.d.ts.map +1 -0
  284. package/dist/components/ui/toggle.js +70 -0
  285. package/dist/components/ui/toggle.js.map +1 -0
  286. package/dist/components/ui/tooltip.d.ts +51 -0
  287. package/dist/components/ui/tooltip.d.ts.map +1 -0
  288. package/dist/components/ui/tooltip.js +78 -0
  289. package/dist/components/ui/tooltip.js.map +1 -0
  290. package/dist/hooks/use-dark-mode.d.ts +2 -0
  291. package/dist/hooks/use-dark-mode.d.ts.map +1 -0
  292. package/dist/hooks/use-dark-mode.js +36 -0
  293. package/dist/hooks/use-dark-mode.js.map +1 -0
  294. package/dist/hooks/use-mobile.d.ts +2 -0
  295. package/dist/hooks/use-mobile.d.ts.map +1 -0
  296. package/dist/hooks/use-mobile.js +20 -0
  297. package/dist/hooks/use-mobile.js.map +1 -0
  298. package/dist/index.d.ts +88 -0
  299. package/dist/index.d.ts.map +1 -0
  300. package/dist/index.js +77 -0
  301. package/dist/lib/scope.d.ts +64 -0
  302. package/dist/lib/scope.d.ts.map +1 -0
  303. package/dist/lib/scope.js +74 -0
  304. package/dist/lib/scope.js.map +1 -0
  305. package/dist/lib/utils.d.ts +3 -0
  306. package/dist/lib/utils.d.ts.map +1 -0
  307. package/dist/lib/utils.js +11 -0
  308. package/dist/lib/utils.js.map +1 -0
  309. package/dist/styles.css +2 -0
  310. package/dist/theme-tokens.css +537 -0
  311. package/dist/theme.css +417 -0
  312. package/package.json +103 -3
@@ -0,0 +1,1026 @@
1
+ /**
2
+ * DataTable — Table plus sorting, filtering, search, row selection,
3
+ * row actions, expandable rows, AI affordances, column management, and
4
+ * pagination.
5
+ *
6
+ * A composite over the existing `Table` primitive (unmodified) — state is
7
+ * owned by `@tanstack/react-table`, a headless logic-only library with no
8
+ * rendered markup of its own (same "headless engine, MMDS renders every
9
+ * pixel" shape as RichTextEditor's use of draft-js), not hand-rolled here.
10
+ * Every visible piece (checkboxes, buttons, the search field, filter
11
+ * popovers, the column menu, pagination, loading/empty states) is an
12
+ * existing MMDS primitive — Checkbox, Button, InputGroup, Input, Select,
13
+ * Popover, DropdownMenu, Skeleton, Empty, AISparkle.
14
+ */
15
+ import * as React from "react";
16
+ import { type ColumnDef, type Column, type Row, type SortingState, type VisibilityState, type RowSelectionState, type ColumnFiltersState, type ColumnOrderState, type ColumnPinningState, type ColumnSizingState, type ExpandedState, type PaginationState, type Table as TanstackTable } from "@tanstack/react-table";
17
+ /** One resolved option for a `select`/`singleSelect`/`range` filter — see `meta.filterOptions`. */
18
+ type FilterOption = {
19
+ value: string;
20
+ label?: React.ReactNode;
21
+ /** Plain-text stand-in for `label` — read by the trigger's comma-list and the search recipe below when `label` isn't already a plain string (an icon, a Badge, anything non-text). */
22
+ textValue?: string;
23
+ icon?: React.ComponentType<{
24
+ className?: string;
25
+ }>;
26
+ /** A second, muted line under the label — a code, a subtitle, whatever extra context makes one option distinguishable from another that isn't worth its own column. Plain text only (unlike `label`), since it renders inside the same comma-list/search-text machinery `textValue` does. */
27
+ description?: string;
28
+ /** Options sharing a `group` render together under a `SelectMultipleLabel`/muted heading, in whatever order groups first appear in the resolved list. Ungrouped options (no `group` on any option in the list) render as one flat list — grouping only activates once at least one option opts in. */
29
+ group?: string;
30
+ disabled?: boolean;
31
+ /** Set internally when `showFilterCounts` is on — not meant to be supplied on a `filterOptions` entry yourself, since it's overwritten from `getFacetedUniqueValues()` regardless. */
32
+ count?: number;
33
+ };
34
+ declare module "@tanstack/react-table" {
35
+ interface ColumnMeta<TData, TValue> {
36
+ /**
37
+ * "text" (default): substring filter — pass a custom `filterFn` directly
38
+ * on the `ColumnDef` (checked first, ahead of everything here) for
39
+ * anything other than a case-insensitive substring match, e.g. TanStack's
40
+ * own `"equalsString"`/`"includesStringSensitive"` presets, or your own.
41
+ * "select": multi-select checklist of the column's unique values (via getFacetedUniqueValues).
42
+ * "singleSelect": same source, but only one value at a time.
43
+ * "bucket" (`"range"` also still accepted — see `rangeBucketSize`/`bucketSize`, deprecated
44
+ * in favor of it): multi-select checklist of fixed-size numeric buckets ("1-3", "4-6", ...),
45
+ * computed from the trailing number in each unique value — for id-like columns (invoice
46
+ * numbers, ticket numbers) where an exact substring match isn't the point.
47
+ */
48
+ filterVariant?: "text" | "select" | "singleSelect" | "range" | "bucket";
49
+ /** Bucket size for `filterVariant: "bucket"` — how many consecutive numeric values group into one bucket. Default 3. Deprecated alias: `rangeBucketSize`. */
50
+ bucketSize?: number;
51
+ /** @deprecated Use `bucketSize` — same option, `filterVariant: "bucket"`'s original name back when the variant itself was still called `"range"`. */
52
+ rangeBucketSize?: number;
53
+ /**
54
+ * Explicit options for a `select`/`singleSelect`/`range` column instead
55
+ * of deriving them from the column's own unique values — labels, icons,
56
+ * groups, and a fixed order none of that can express on its own. A
57
+ * function receives `column` (e.g. to read `getFacetedUniqueValues()`
58
+ * counts yourself, or vary options off something outside the table
59
+ * entirely). Every option's own `value` must still match what actually
60
+ * appears in the data for the filter to match anything.
61
+ */
62
+ filterOptions?: FilterOption[] | ((column: Column<TData, TValue>) => FilterOption[]);
63
+ /** Renders `Skeleton` rows in place of the option list while an async `filterOptions` source is still loading — DataTable never fetches anything itself, this only controls what renders meanwhile. */
64
+ filterOptionsLoading?: boolean;
65
+ /** How the resolved option list orders itself — default `"auto"`: a numeric-aware collator (so "2" sorts before "10"), applied whether the options came from facets or your own `filterOptions`. `"none"` keeps `filterOptions`' own array order (meaningless for facet-derived options, which have no other order). A comparator gets full control. */
66
+ filterOptionsSort?: "auto" | "none" | ((a: FilterOption, b: FilterOption) => number);
67
+ /**
68
+ * Per-column override of `DataTable`'s own `filterApplyMode` — e.g. one
69
+ * text column that should defer to an explicit Apply while the rest of
70
+ * the table applies immediately, or vice versa. Doesn't affect
71
+ * `select`/`range`: those are always deferred regardless (see
72
+ * `isMultiSelectFilterVariant`) — this can't force them immediate. Also
73
+ * meaningless for `singleSelect`, which closes on pick before an Apply
74
+ * step could ever matter.
75
+ */
76
+ filterApplyMode?: "immediate" | "deferred";
77
+ /**
78
+ * Escape hatch for a filter UI none of the built-in `filterVariant`s
79
+ * cover — a date-range picker, a numeric slider, a three-state
80
+ * toggle, a multi-field filter, anything a specific table's own
81
+ * use case calls for. This is the deliberate answer for those, not a
82
+ * gap: MMDS ships the primitives (`Calendar`, `Slider`, `ToggleGroup`,
83
+ * `Input`, ...) a designer composes into whatever shape that use case
84
+ * actually needs — a number-range Slider, a date-range Calendar, a
85
+ * Yes/No ToggleGroup — rather than DataTable guessing at one fixed
86
+ * shape/labeling/styling for everyone. Checked first, ahead of
87
+ * `filterVariant`, in every filter surface (header-icon popover,
88
+ * inline toolbar button, and the "All filters" pane) — same
89
+ * `value`/`onValueChange` shape those surfaces already pass to the
90
+ * built-in variants, and `value`/`onValueChange` carry a pending draft
91
+ * under `filterApplyMode="deferred"` in all three the same way, not
92
+ * just two of them: the inline toolbar button wraps a deferred custom
93
+ * render in a Popover + the shared Cancel/Apply footer of its own (it
94
+ * has no such popover otherwise, unlike `select`/`range`/text, which
95
+ * always get one); the pane — which has no popover for *any* variant,
96
+ * custom or built-in — gets an inline Cancel/Apply row directly under
97
+ * the rendered content instead. Immediate (the default), every surface
98
+ * renders it directly with no extra chrome, same as always. Read and
99
+ * write the real filter state yourself via `column` (e.g.
100
+ * `column.getFacetedUniqueValues()`, `column.getFacetedMinMaxValues()`
101
+ * — that one needs `getFacetedMinMaxValues` passed to the table's own
102
+ * options too, since DataTable only wires it up automatically for its
103
+ * own built-in variants) if the custom UI needs it beyond the one
104
+ * value being edited.
105
+ */
106
+ filterRender?: (props: {
107
+ column: Column<TData, TValue>;
108
+ value: unknown;
109
+ onValueChange: (value: unknown) => void;
110
+ }) => React.ReactNode;
111
+ /**
112
+ * Defines a column purely to filter on a data point that isn't shown
113
+ * anywhere in the table — the column itself (its `header`/`cell`) never
114
+ * renders, permanently, with no user-facing toggle to show it: it's
115
+ * excluded from the Columns menu entirely (not just its checkbox — from
116
+ * the reorder/pin list too, since there's no header cell to reorder or
117
+ * pin) rather than merely defaulting hidden the way a plain hideable
118
+ * column can. It still needs a real `accessorKey`/`accessorFn` (that's
119
+ * what its filter reads) and still shows up as a normal filterable
120
+ * column in the "All filters" pane — but never inline in the toolbar
121
+ * (no user-facing pin toggle for it there either, same reasoning as
122
+ * the Columns menu exclusion — there's no header cell for a toolbar
123
+ * chip to point back at), and its presence alone forces the pane to
124
+ * exist at all even if 3 or fewer *other* columns are filterable,
125
+ * since the pane is its only access point. Give it a `header` anyway
126
+ * (used as its filter's label) even though it's never rendered.
127
+ */
128
+ filterOnly?: boolean;
129
+ /**
130
+ * Merges other columns' filter fields into *this* column's own
131
+ * header-icon filter popover instead of each getting its own separate
132
+ * entry in the toolbar/pane — for a cell that already displays more
133
+ * than one filterable value at once (e.g. a "Patient" column whose
134
+ * custom `cell` renders a name plus an MRN below it), so filtering
135
+ * either reads as one decision made from one icon, not two unrelated
136
+ * ones. Each id listed here is excluded from `filterableColumns`
137
+ * (so it never *also* shows up on its own) and instead renders as its
138
+ * own labeled section inside this column's popover, stacked under a
139
+ * divider — typically `meta: { filterOnly: true }` columns, since the
140
+ * value they filter is usually already folded into this column's own
141
+ * `cell` rather than shown in a column of its own, but that's a
142
+ * convention, not a requirement: any filterable column id works. This
143
+ * column's own filter field always renders first, unlabeled; merged
144
+ * ones below it are labeled with their own `header` so it's clear
145
+ * which value each field filters. The header icon itself shows filled
146
+ * (and the Cancel/Apply footer under `filterApplyMode="deferred"`
147
+ * commits/discards) if *any* of them — this column or a merged one —
148
+ * has an active filter, not just this column's own.
149
+ */
150
+ filterMergeWith?: string[];
151
+ /**
152
+ * Overrides this column's position in the filter order — both the
153
+ * toolbar's pinned chips and the "All filters" pane render filterable
154
+ * columns sorted by this (ascending; ties broken by column order, then
155
+ * lower first), independent of the table's own column order. Omit to
156
+ * keep the default (this column's own position in column order, as
157
+ * today) — only set it on columns that actually need to jump out of
158
+ * that order.
159
+ */
160
+ filterOrder?: number;
161
+ /**
162
+ * Aligns this column's header and cell content — default `"left"`,
163
+ * the right fit for text. Set `"right"` for quantitative content
164
+ * (amounts, counts, percentages, anything meant to be scanned/summed
165
+ * down the column) — numbers read fastest when their digits line up
166
+ * on a shared right edge, which is why this is a per-column opt-in
167
+ * rather than something DataTable guesses from the data: it has no
168
+ * reliable way to tell "123 Main St" apart from "123" apart from a
169
+ * custom cell rendering a styled duration, so the convention is
170
+ * yours to apply, not DataTable's to infer. `"right"` also mirrors
171
+ * the header itself — the sort button's label right-aligns and the
172
+ * filter icon/`headerActions` cluster moves to the *left* of it,
173
+ * instead of both sitting to its right — so the controls stay off to
174
+ * the side the numbers don't occupy.
175
+ */
176
+ align?: "left" | "right";
177
+ /**
178
+ * This column's name wherever one is needed that isn't the rendered
179
+ * `header` itself — the Columns menu, the "All filters" pane, header
180
+ * tooltips/`aria-label`s, applied-filter copy. Only matters when
181
+ * `header` isn't a plain string (an icon-only header, a custom
182
+ * component) — DataTable has no reliable way to derive a name from
183
+ * arbitrary JSX, and falls back to the column's raw `id` (a dev-only
184
+ * console warning, once per column) rather than guessing.
185
+ */
186
+ label?: string;
187
+ /**
188
+ * Collapses this column's cell into one `rowSpan`-merged cell across
189
+ * however many consecutive rendered rows (post-sort/filter/page — the
190
+ * rows actually adjacent on screen, not the raw data) share the same
191
+ * `column.getValue()` — a spreadsheet-style vertical merge instead of
192
+ * repeating the value on every row. Only meaningful when those rows
193
+ * are actually adjacent because the table is sorted by (or the data is
194
+ * pre-grouped by) this column; otherwise every run is length 1 and
195
+ * nothing visibly merges. Equality is plain `===` — no custom
196
+ * comparator. Content is top-aligned inside the merged cell so it
197
+ * reads like a label for the rows below it, not vertically centered;
198
+ * override via the column's own `cell` if centered fits better.
199
+ */
200
+ mergeRepeatedValues?: boolean;
201
+ /** Sticks this column to the left/right edge during horizontal scroll. Seeds the *initial* pinned side only — the user can toggle it live from there (e.g. via `columnMenuActions` in `headerActions`), unless `pinLocked` is set. */
202
+ pin?: "left" | "right";
203
+ /** Suppresses `columnMenuActions`' Pin left/Pin right for this column, so pinning stays fully dev-controlled with no user-facing toggle. Doesn't stop a custom `headerActions` entry from calling `column.pin(...)` itself. */
204
+ pinLocked?: boolean;
205
+ /**
206
+ * Custom actions rendered in this column's header cell, next to the
207
+ * sort button and filter icon — hidden until the header cell is
208
+ * hovered or keyboard-focused, collapsing into an overflow menu past
209
+ * 2 actions (same `RowActions` component row actions use). A
210
+ * function, not a static array, so labels/icons can reflect live
211
+ * per-column state (e.g. "Pin left" flipping to "Unpin"). Takes the
212
+ * table too, since some actions (`columnMenuActions`' "Fit to
213
+ * content") need it. See `columnMenuActions`.
214
+ */
215
+ headerActions?: (column: Column<TData, unknown>, table: TanstackTable<TData>) => RowAction[];
216
+ }
217
+ interface TableMeta<TData> {
218
+ /** Internal — backs `columnMenuActions`' "Fit to content" measurement. Not meant to be set by consumers. */
219
+ getColumnCellElements?: (columnId: string) => HTMLElement[];
220
+ }
221
+ }
222
+ /**
223
+ * Wraps the substring of `text` matching `query` in a `<mark>`. Exported so a
224
+ * column with a custom `cell` can highlight matches itself — read the live
225
+ * query off `info.table.getState().globalFilter` inside that `cell`. Pass
226
+ * `startsWith` when the table's `searchMatch` is `"startsWith"`, so only
227
+ * word-start matches are marked.
228
+ */
229
+ declare function HighlightMatch({ text, query, startsWith, }: {
230
+ text: string;
231
+ query: string;
232
+ startsWith?: boolean;
233
+ }): React.JSX.Element;
234
+ /** The committed (not live-dragging/previewing) column layout `onColumnLayoutChange` hands back — pair with `defaultColumnVisibility`/`Order`/`Pinning`/`Sizing` to persist and restore it. */
235
+ type DataTableColumnLayout = {
236
+ visibility: VisibilityState;
237
+ order: ColumnOrderState;
238
+ pinning: ColumnPinningState;
239
+ sizing: ColumnSizingState;
240
+ };
241
+ type RowAction = {
242
+ label: string;
243
+ icon?: React.ComponentType<{
244
+ className?: string;
245
+ }>;
246
+ onClick: () => void;
247
+ variant?: "default" | "destructive";
248
+ /**
249
+ * Passed straight through to the rendered control (an inline icon Button
250
+ * or, past the overflow threshold, a DropdownMenuItem) — both already
251
+ * forward arbitrary DOM props, so a hover-preview, focus-driven tooltip,
252
+ * or any other non-click interaction works the same regardless of which
253
+ * one ends up rendering this action.
254
+ */
255
+ onMouseEnter?: React.MouseEventHandler;
256
+ onMouseLeave?: React.MouseEventHandler;
257
+ onFocus?: React.FocusEventHandler;
258
+ onBlur?: React.FocusEventHandler;
259
+ };
260
+ /**
261
+ * Renders `actions` as inline icon buttons (≤2 actions) or an overflow
262
+ * menu (>2) — `mode` overrides the auto pick. Stops its own click from
263
+ * bubbling, so it composes safely inside a row that also has `onRowClick`.
264
+ */
265
+ declare function RowActions({ actions, mode, revealClassName, onOpenChange, }: {
266
+ actions: RowAction[];
267
+ mode?: "inline" | "overflow" | "auto";
268
+ /**
269
+ * Hover/keyboard-focus-reveal classes for this cluster's own visibility
270
+ * (e.g. `revealColumnControlsOnHover`'s computed class) — applied here
271
+ * rather than by a wrapping `<div>` at the call site, so the overflow
272
+ * branch can force itself visible (via real React state, not a CSS
273
+ * selector) while its own menu is open, however the pointer got there.
274
+ * A modal Radix menu sets `pointer-events: none` on the rest of the
275
+ * page while open, which breaks *any* CSS-only `:hover` tracking for
276
+ * this cluster regardless of where the cursor physically is — hence
277
+ * `open` state, not `group-hover`, driving the override. Omit to
278
+ * always render visible (no hover-gating at all).
279
+ */
280
+ revealClassName?: string;
281
+ /** Fires when the overflow menu opens/closes — a sibling control in the
282
+ * same header cell (the filter icon) needs to know too, since it can't
283
+ * detect this itself: this menu is modal, so it sets `pointer-events:
284
+ * none` on the rest of the header while open, breaking that sibling's
285
+ * own `:hover` tracking regardless of where the pointer actually is. */
286
+ onOpenChange?: (open: boolean) => void;
287
+ }): React.JSX.Element;
288
+ /**
289
+ * A ready-made column-menu bundle for `headerActions` — Pin left, Pin
290
+ * right (via `pinActions`), Hide column, and Fit to content — each
291
+ * omitted when the column can't do it (`getCanPin()`/`getCanHide()`/
292
+ * `getCanResize()`, the last only true once `enableColumnResizing` is
293
+ * set). Typically 3-4 actions, so `RowActions`' own auto mode already
294
+ * renders it as a single overflow "more" menu — no need to force
295
+ * `mode="overflow"`.
296
+ */
297
+ declare function columnMenuActions<TData>(column: Column<TData, unknown>, table: TanstackTable<TData>): RowAction[];
298
+ declare function DataTable<TData, TValue = unknown>({ columns: userColumns, data, loading, error, errorDescription, emptyTitle, emptyDescription, searchPlaceholder, searchableColumnIds, searchMatch, filterApplyMode, defaultSorting, defaultColumnFilters, defaultGlobalFilter, defaultPageIndex, defaultColumnVisibility, defaultColumnOrder, defaultColumnPinning, defaultColumnSizing, defaultExpanded, onExpandedChange, tableRef, onColumnLayoutChange, defaultPinnedFilterIds, allowFilterPinning, showFilterCounts, stickyFilterSelectAll, clearFiltersOnHide, enableRowSelection, selectionScope, maxSelectedRows, defaultRowSelection, selectionBarPlacement, onRowSelectionChange, onStateChange, renderBulkActions, onRowClick, renderExpandedRow, getRowId, lockedColumnIds, enableColumnResizing, enableMultiSort, revealColumnControlsOnHover, striped, cellDividers, aiAction, actionButtons, aiRowPredicate, aiColumnIds, aiCellPredicate, pageSize, pageSizeOptions, showPageSizeSelector, paginationStyle, onLoadMore, loadMoreOffset, hasMore, showColumnToggle, loadingMore, maxHeight, stickyHeader, caption, captionHidden, size, className, "aria-label": ariaLabel, }: {
299
+ columns: ColumnDef<TData, TValue>[];
300
+ data: TData[];
301
+ /** Renders skeleton rows shaped like the real columns instead of `data`. */
302
+ loading?: boolean;
303
+ /** True renders the failure state (an Empty with `role="alert"`) instead of rows. Passing a string/node here still works but is deprecated — put the copy in `errorDescription` instead, so "is errored" and "what to say" are separate. */
304
+ error?: boolean | React.ReactNode;
305
+ /** Description shown in the failure state; defaults to "Something went wrong. Try again." Only read while `error` is truthy. */
306
+ errorDescription?: React.ReactNode;
307
+ emptyTitle?: string;
308
+ emptyDescription?: string;
309
+ searchPlaceholder?: string;
310
+ /** Column ids the search box matches against. Defaults to the first two entries in `columns`. `"all"` matches every column with a real accessor (`accessorKey`/`accessorFn`) — an injected column (selection checkbox, expand toggle, AI marker) has neither, so this never accidentally includes those. */
311
+ searchableColumnIds?: string[] | "all";
312
+ /** How the search box matches — default `"contains"` (anywhere in the value). `"startsWith"` only matches where a word starts, so "nair" finds "Priya Nair" but not "Bernair". Matches are highlighted the same way. */
313
+ searchMatch?: "contains" | "startsWith";
314
+ /**
315
+ * Only affects the header-icon popover and inline toolbar's plain-text
316
+ * field — a multiselect filter (`"select"`/`"range"`, a checklist of
317
+ * several values at once) always requires Apply regardless of this
318
+ * prop, in every surface (see `isMultiSelectFilterVariant`), and
319
+ * `"singleSelect"` never does anywhere (closes on pick, before a footer
320
+ * could matter). `"immediate"` (default): a text pick applies to the
321
+ * real table as it's made, no confirmation step. `"deferred"` adds a
322
+ * Cancel/Apply step to that same text field instead — picks edit a
323
+ * local draft only, with no effect on the real table until Apply
324
+ * commits it; Cancel just closes and discards it. Table-wide, not
325
+ * per-column — a mix of immediate and deferred text filters in one
326
+ * toolbar would be confusing about which picks need a second step. Has
327
+ * no effect on the "All filters" pane at all — every field there is
328
+ * either always live (text, singleSelect) or always deferred with its
329
+ * own individual popover Apply/Cancel (select/range), never driven by
330
+ * this prop.
331
+ */
332
+ filterApplyMode?: "immediate" | "deferred";
333
+ /** Sort applied on first render — same shape as `@tanstack/react-table`'s `SortingState`. Uncontrolled from there on; the user's own header clicks take over. */
334
+ defaultSorting?: SortingState;
335
+ /** Column filters applied on first render — same shape as `@tanstack/react-table`'s `ColumnFiltersState` (`[{ id: "status", value: ["Paid"] }, ...]`, `value` shaped however that column's own filter variant expects it). Uncontrolled from there on; the user's own picks take over. Paired with `defaultGlobalFilter` and `defaultSorting`, this is what makes restoring table state after a round trip (e.g. from a URL query param, on the way back from a detail page) possible — `onStateChange` reads that same state back out live, but had nothing to feed a value back in with until these existed. */
336
+ defaultColumnFilters?: ColumnFiltersState;
337
+ /** Global search value applied on first render. Uncontrolled from there on — the user's own typing into the search box takes over. See `defaultColumnFilters`. */
338
+ defaultGlobalFilter?: string;
339
+ /** Page index applied on first render (0-based). Uncontrolled from there on — the user's own pagination clicks take over. Ignored with `onLoadMore` (infinite scroll has no page concept). */
340
+ defaultPageIndex?: number;
341
+ /** Column visibility applied on first render — same shape as `@tanstack/react-table`'s `VisibilityState`. Uncontrolled from there on; the Columns menu's own checkboxes take over. Also becomes the baseline "Reset to defaults" restores and `hasColumnCustomization` compares against, in place of "everything visible." */
342
+ defaultColumnVisibility?: VisibilityState;
343
+ /** Column order applied on first render — same shape as `@tanstack/react-table`'s `ColumnOrderState`, center (unpinned) columns only. Uncontrolled from there on; drag-reordering in the Columns menu takes over. Also becomes the "Reset to defaults" baseline in place of `columns`' own order. */
344
+ defaultColumnOrder?: ColumnOrderState;
345
+ /** Column pinning applied on first render — same shape as `@tanstack/react-table`'s `ColumnPinningState`. Wins over any per-column `meta.pin` when provided; uncontrolled from there on, same as `defaultColumnOrder`. Also becomes the "Reset to defaults" baseline in place of `meta.pin`-derived pinning. */
346
+ defaultColumnPinning?: ColumnPinningState;
347
+ /** Column widths applied on first render — same shape as `@tanstack/react-table`'s `ColumnSizingState`. Uncontrolled from there on; the user's own drag-resizing takes over. Also becomes what "Reset to defaults" restores widths to, in place of each column's own `size`. */
348
+ defaultColumnSizing?: ColumnSizingState;
349
+ /** Expanded rows applied on first render — same shape as `@tanstack/react-table`'s `ExpandedState`. Uncontrolled from there on; the row's own expand toggle takes over. Only meaningful alongside `renderExpandedRow` or `getSubRows`. */
350
+ defaultExpanded?: ExpandedState;
351
+ /** Fires after a row's expanded state changes, live — the `renderExpandedRow`/tree-data counterpart to `onRowSelectionChange`. Not controlled: there's no live two-way binding, but paired with `defaultExpanded` it round-trips the same way `onStateChange`/`defaultColumnFilters` do. */
352
+ onExpandedChange?: (expanded: ExpandedState) => void;
353
+ /** A read-mostly escape hatch onto the underlying `@tanstack/react-table` instance — for anything DataTable doesn't already expose a prop for (e.g. a CSV export reading `table.getFilteredRowModel()`, or `table.setColumnFilters(...)` from an external "saved views" picker). A callback, not a ref object — assign it to a ref of your own inside the callback (`tableRef={(table) => (myRef.current = table)}`) if you need to hold onto it; called after mount, and again with `null` on unmount. Treat table methods called from outside as one-off imperative actions, not a second source of truth to render from. */
354
+ tableRef?: (table: TanstackTable<TData> | null) => void;
355
+ /** Fires whenever committed (not live-dragging/live-previewing) column visibility, order, pinning, or sizing changes — after a resize ends, or a Columns-menu Apply/Reset, not on every intermediate drag pixel or menu preview tick. Pair with `defaultColumnVisibility`/`Order`/`Pinning`/`Sizing` to persist a user's layout (e.g. to `localStorage`) and restore it next session. */
356
+ onColumnLayoutChange?: (layout: DataTableColumnLayout) => void;
357
+ /** Which filterable columns start pinned/inline in the toolbar (with a pin toggle in the "All filters" pane to change it live, unless `allowFilterPinning` is off). Defaults to the first two filterable entries in `columns`, in column order — set this instead when the columns you want inline aren't already first. */
358
+ defaultPinnedFilterIds?: string[];
359
+ /** Whether the "All filters" pane's per-row pin/unpin toggle renders at all — default `true`. Set `false` for a designer-fixed toolbar: `defaultPinnedFilterIds` (or its first-two-columns default) becomes the permanent, non-user-changeable inline set, and every other filterable column lives only in the pane, with no way to move it out. The toggle itself is hover/keyboard-focus-revealed by default, same as the header cell's own controls — see `revealColumnControlsOnHover`. */
360
+ allowFilterPinning?: boolean;
361
+ /** Shows each option's `getFacetedUniqueValues()` count next to its label ("Paid (124)") across every `select`/`singleSelect`/`range` filter surface — the header-icon popover, the inline toolbar chip, and the pane. Off by default: a count only means something once a consumer actually wants "how many rows have this value" surfaced, and it costs a facet lookup per option on every render otherwise skipped. */
362
+ showFilterCounts?: boolean;
363
+ /** Whether "Select all" in a multi-select filter's option list (once past its own, lower option-count threshold — independent of the search box's) stays pinned above the list while it scrolls — default `false`: it's the first row inside the same scrolling area as the options themselves, same as a plain checkbox list where nothing else stays fixed either. Set `true` to keep it visible regardless of scroll position instead. */
364
+ stickyFilterSelectAll?: boolean;
365
+ /** Clears a column's filter the moment it's hidden via the Columns menu — default `false` (a hidden column's filter stays active, still visible in the pane with a muted "(hidden)" suffix, since hiding a column isn't the same action as clearing what it's filtered to). Set `true` if hiding a column should mean "I'm done filtering by this" instead. */
366
+ clearFiltersOnHide?: boolean;
367
+ /** Turns on the row-selection checkbox column. A function instead of a plain `true` makes specific rows unselectable — e.g. `(row) => row.original.status !== "Completed"` — the cell checkbox `disabled`s for those rows and neither the header nor "Select all" checkbox counts them. */
368
+ enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
369
+ /** What "selected" means once a filter hides some of the checked rows — default `"filtered"`: selection state itself never loses those rows (clearing the filter shows them checked again), but the count, `renderBulkActions`, and `onRowSelectionChange` all act only on rows that currently pass every filter, so a bulk action can never silently reach past what's on screen. `"all"` restores pre-1.x behavior, where a bulk action acts on every checked row regardless of the active filter — opt into this only if that's a deliberate multi-pass selection workflow. */
370
+ selectionScope?: "filtered" | "all";
371
+ /** Caps how many rows can be checked at once — every unselected checkbox (row, header, "Select all") disables once the cap is hit, so a user has to uncheck something before adding another. Already-checked rows stay checkable (to uncheck) past the cap. */
372
+ maxSelectedRows?: number;
373
+ /** Rows checked on first render — same shape as `@tanstack/react-table`'s `RowSelectionState` (`{ [rowId]: true }`). Uncontrolled from there on. Meaningless without `getRowId`: row identity otherwise defaults to render index, which this has no way to match against your data ahead of the first render. */
374
+ defaultRowSelection?: RowSelectionState;
375
+ /** Where the selection UI (count, "Select all", `renderBulkActions`) lives in paginated mode — default `"toolbar"` swaps search/filters out for it there; `"floating"` keeps search/filters mounted and shows the same floating pill infinite-scroll tables always use instead. No effect with `onLoadMore` set — infinite scroll has no footer to swap into, so it's always floating. */
376
+ selectionBarPlacement?: "toolbar" | "floating";
377
+ /** `details.hiddenByFilterCount` is how many additional rows are checked but excluded from `selectedRows` by `selectionScope="filtered"` (always `0` under `"all"`); `details.allSelectedRows` is the unfiltered full set regardless of scope. */
378
+ onRowSelectionChange?: (selectedRows: TData[], details: {
379
+ allSelectedRows: TData[];
380
+ hiddenByFilterCount: number;
381
+ }) => void;
382
+ /** Fires after sorting, column filters, search, pagination, expansion, or pinned-filter changes, live — a read-only escape hatch for syncing this internal state elsewhere (URL params, analytics, a server-driven refetch). Not a controlled-mode prop — there's no live two-way binding — but paired with `defaultSorting`/`defaultColumnFilters`/`defaultGlobalFilter`/`defaultPageIndex`/`defaultExpanded`/`defaultPinnedFilterIds` it's enough to round-trip state out to a URL and back in on next mount. */
383
+ onStateChange?: (state: {
384
+ sorting: SortingState;
385
+ columnFilters: ColumnFiltersState;
386
+ globalFilter: string;
387
+ pagination: PaginationState;
388
+ expanded: ExpandedState;
389
+ pinnedFilterIds: string[];
390
+ }) => void;
391
+ /** Product-configured actions shown while rows are selected — on an infinite-scroll table (`onLoadMore`), a pill that hovers over the table's own bottom edge (count + Select all on the left, this on the right of a divider, a close button after another); on a paginated table, search/filters in the toolbar are swapped out for the count, Select all, this, and a close button instead — no separate bar. Omit either way for just the count/Select all/close with no actions. */
392
+ renderBulkActions?: (selectedRows: TData[], clearSelection: () => void) => React.ReactNode;
393
+ /** Makes every row clickable/keyboard-activatable — e.g. navigating to a detail view. Combine with `enableRowSelection` freely; clicking the row checkbox never triggers this. */
394
+ onRowClick?: (row: TData) => void;
395
+ /** Renders below a row when expanded — a detail card, or more rows of its own. Injects a leading expand/collapse toggle on every row. */
396
+ renderExpandedRow?: (row: TData) => React.ReactNode;
397
+ /** A stable per-row id (e.g. `(row) => row.id`), passed straight through to `@tanstack/react-table`. Without it, a row's identity defaults to its index — fine for rendering, but meaningless across a sort/filter/pagination change, where identity is what row-selection/expansion state tracks against. */
398
+ getRowId?: (originalRow: TData, index: number) => string;
399
+ /** Column ids the Columns menu won't let the user reorder. Injected columns are always locked. */
400
+ lockedColumnIds?: string[];
401
+ /** Adds a drag handle to every resizable column's right border (injected columns never resize). Also required for `columnMenuActions`' "Fit to content" to render at all — it checks `column.getCanResize()`, same as the handle. Set `enableResizing: false` on a `ColumnDef` to exempt a specific column. */
402
+ enableColumnResizing?: boolean;
403
+ /** Shift-click a sortable header to add it to the existing sort instead of replacing it — `sorting` then holds more than one entry, in click order, and each sorted header shows a small 1/2/3 badge next to its arrow marking that order. `aria-sort` on any one header still only ever reflects that column's own direction (the attribute has no concept of rank), so the badge is the only place the *order* is actually conveyed. Default `false`. */
404
+ enableMultiSort?: boolean;
405
+ /** Whether the header-cell filter icon, `headerActions`/`columnMenuActions` menu, and the Columns-menu popover's own pin/move controls stay hidden until hovered or keyboard-focused, or render always-visible. Default `true` — set `false` for a consumer that wants every control visible up front instead of discovered on hover. */
406
+ revealColumnControlsOnHover?: boolean;
407
+ /** Tints every other body row for readability on wide/dense tables. */
408
+ striped?: boolean;
409
+ /** Adds a vertical rule between columns, for tables where column boundaries need to read clearly. */
410
+ cellDividers?: boolean;
411
+ /** Renders a `variant="ai"` action in the toolbar — table-level AI presence (e.g. "Summarize", "Ask AI"). There's no `ai` boolean/whole-table glow prop — DataTable's own outer surface never takes a border/shadow of its own; wrap it in a Card instead and put the glow + an `AILabel` on that, same as any other AI-marked surface. See the "WithAIWholeTable" story. */
412
+ aiAction?: {
413
+ label: string;
414
+ onClick: () => void;
415
+ };
416
+ /** Product-configured toolbar actions, rendered in order right before the Columns menu — persistent, unlike `renderBulkActions` (selection-scoped) or `aiAction` (always `variant="ai"`, rendered separately, and the one toolbar control that stays at `size` itself instead of the notch-below `controlSize`/`controlIconSize` every other toolbar control — including this one, every variant — renders at). Cap at 3: this is a designer call per table, not a user preference, and a toolbar reads as noise past that. `"primary"` maps to a filled `variant="default"` Button (keep this the artifact's only filled button — never pair two), `"secondary"` to `variant="outline"`, `"icon"` to an icon-only `variant="outline"` Button (needs `icon` set; `label` still required, used as its `aria-label` since there's no visible text). */
417
+ actionButtons?: {
418
+ label: string;
419
+ onClick: () => void;
420
+ variant?: "primary" | "secondary" | "icon";
421
+ icon?: React.ReactNode;
422
+ disabled?: boolean;
423
+ }[];
424
+ /** Rows this returns true for get a colored left edge (an inset box-shadow on the row's first cell — the table is `border-separate`, so a `<tr>` itself can never paint a border) plus a soft inward AI glow bleeding in from that same edge (`bg-ai-glow`, not a solid fill — echoes `aiColumnIds`' colored top rule, just on the row's left edge instead) and a leading `AILabel` — the very first thing in the row, ahead of the expand toggle and selection checkbox — row-level AI presence. */
425
+ aiRowPredicate?: (row: TData) => boolean;
426
+ /** Every cell in a column whose id is in this list gets the same left-glowing inward glow, tiling seamlessly down the whole column since every cell in it shares the same width; that column's header also gets a colored top rule and a leading `AILabel` (generic explanatory copy — compose `AILabel` directly into that column's own `header` instead of using this if you need column-specific text) — column-level AI presence. */
427
+ aiColumnIds?: string[];
428
+ /** Individual cells this returns true for get a trailing `AILabel` inline after the value — no glow at all, unlike row/column level — cell-level AI presence, for when only specific values (not a whole row or column) are AI-generated. */
429
+ aiCellPredicate?: (row: TData, columnId: string) => boolean;
430
+ pageSize?: number;
431
+ /** Choices shown in the footer's "items per page" control. */
432
+ pageSizeOptions?: number[];
433
+ /** Whether the "Items per page" control renders in the footer at all — a designer's call per table, not a user preference. Default true. */
434
+ showPageSizeSelector?: boolean;
435
+ /** How the footer's page indicator renders — default `"compact"`: a page-jump control (a `Select` past a plain click-through, or a number `Input` once page count passes `PAGE_SELECT_THRESHOLD`) plus "of N pages" text, next to Previous/Next icon buttons. `"numbered"`: `Pagination`'s own numbered links with ellipsis windowing around the current page, first/last page always reachable directly. */
436
+ paginationStyle?: "compact" | "numbered";
437
+ /** Switches to infinite scroll: every row in `data` renders (no client pagination, no footer at all), the table scrolls internally with a locked header (and locked pinned columns) instead of the page, and this is called when the user scrolls near the last row. Append the next page to `data` yourself; pair with `hasMore`/`loadingMore`. */
438
+ onLoadMore?: () => void;
439
+ /** How far (in px) from the bottom of the scroll area the next page starts loading — default `200`. Raise it to prefetch earlier on slow endpoints, `0` to load only when the last row is actually reached. Only relevant with `onLoadMore`. */
440
+ loadMoreOffset?: number;
441
+ /** Whether there's more to load — when false, the scroll trigger stops rendering (and observing) entirely. Only relevant with `onLoadMore`. */
442
+ hasMore?: boolean;
443
+ /** Shows the toolbar's "Columns" button (visibility, order and pinning) — default `true`. Set `false` for tables whose column set is fixed. */
444
+ showColumnToggle?: boolean;
445
+ /** Shows skeleton rows below the last real row while the next page is in flight, same shape as `loading`. Only relevant with `onLoadMore`. */
446
+ loadingMore?: boolean;
447
+ /** Caps how tall the internal scroll container can grow (any CSS size). In infinite-scroll mode (`onLoadMore`) this is the container's real height, defaulting to `"32rem"` — it has no other height source. In paginated mode it's an upper bound only, layered on top of whatever height the surrounding layout already gives the rows area (e.g. a fixed-height Card) — omit it there and nothing changes. */
448
+ maxHeight?: string;
449
+ /** Whether the header row sticks to the top of the scroll container instead of scrolling away with the body — default `true`. */
450
+ stickyHeader?: boolean;
451
+ /** An accessible-name fallback rendered as a real `TableCaption` — set this (or `aria-label`) so the table has *some* accessible name; a dev-only console warning fires when neither is given. `captionHidden` (default `true`) keeps it visually `sr-only` while still counting as the table's name; set `false` to actually show it above the rows. */
452
+ caption?: React.ReactNode;
453
+ captionHidden?: boolean;
454
+ size?: "default" | "sm" | "xs";
455
+ className?: string;
456
+ "aria-label"?: string;
457
+ }): React.JSX.Element;
458
+ export { DataTable, HighlightMatch, RowActions, columnMenuActions };
459
+ export type { RowAction, DataTableColumnLayout, FilterOption };
460
+ /**
461
+ * WHEN TO USE
462
+ * - Tabular data that needs sorting, search, column filters, selectable
463
+ * rows with bulk actions, row actions, expandable detail rows, row-click
464
+ * navigation, AI affordances, column management, or pagination handled
465
+ * for you — an admin list, a resource picker, anything past "just render
466
+ * some rows."
467
+ *
468
+ * NOT WHEN
469
+ * - A fixed, small table with no interaction beyond display — use Table
470
+ * directly, it's lighter and this adds nothing over it.
471
+ * - You need virtualization or server-driven async fetching — neither is
472
+ * built in this pass. Virtualization has no existing MMDS primitive to
473
+ * lean on and would need a further headless dependency (e.g.
474
+ * `@tanstack/react-virtual`) — out of scope here. (Column resize is
475
+ * already built — `enableColumnResizing` — this isn't that.)
476
+ *
477
+ * KEY RULES
478
+ * - `columns`/`data` follow `@tanstack/react-table`'s `ColumnDef<TData>[]`
479
+ * shape directly — this component doesn't wrap or reinvent that API, it
480
+ * renders it. Read TanStack Table's own docs for `accessorKey`, custom
481
+ * `cell` renderers, `enableSorting: false` per column, etc.
482
+ * - Sorting, filtering, search, selection, expansion, column order/
483
+ * visibility, and pagination state all live inside DataTable (via
484
+ * `useReactTable`) — an exception to MMDS's usual fully-uncontrolled
485
+ * convention, because the whole point of this component is to own that
486
+ * state so you don't have to. `onRowSelectionChange` is one escape
487
+ * hatch, for wiring selection to something outside (a bulk delete call, a
488
+ * parent form); `onStateChange` is another, firing sorting/columnFilters/
489
+ * globalFilter live for the same reason (a URL param, analytics, a
490
+ * server-driven refetch) — read-only, no live two-way binding, but
491
+ * paired with `defaultSorting`/`defaultColumnFilters`/`defaultGlobalFilter`
492
+ * (seeding each on first render) it's enough to round-trip state out to
493
+ * a URL and back in on next mount. `defaultPinnedFilterIds` seeds the
494
+ * toolbar's inline-filter set the same way — all four still fully
495
+ * uncontrolled after first render, same as everything else here.
496
+ * - Columns render at a fixed width (`table-fixed` + a `<colgroup>` driven by
497
+ * each column's `ColumnDef.size`, default 150) instead of auto-fitting to
498
+ * content — set `size` per column for anything narrower/wider than that.
499
+ * This trades content-driven auto-sizing for widths that stay put across
500
+ * sorting, filtering, and pagination.
501
+ * - The toolbar carries the same edge-to-edge treatment as `Table` itself
502
+ * (see Table's own KEY RULES) — its outer box matches the table's wider
503
+ * footprint instead of looking narrower next to it, and its own padding
504
+ * keeps search/filter/Columns content at the same inset as the table's
505
+ * cell content. Automatic, no prop for it.
506
+ * - Toolbar controls (search, inline/"All filters" filters, Columns) and
507
+ * footer controls (items-per-page, page-jump, Previous/Next) render one
508
+ * size notch below `size` itself — "default" gets "sm" controls, "sm"
509
+ * gets "xs", "xs" stays "xs" (already the floor). They're always-visible,
510
+ * repetitive chrome, not row content competing for the same emphasis —
511
+ * automatic, not a prop, and it scales with `size` rather than being
512
+ * fixed at one density. The `aiAction` button is the deliberate exception
513
+ * — it's meant to stand out, so it stays at `size` itself.
514
+ * - `loading` swaps every cell for a `Skeleton` shaped to the real columns —
515
+ * don't also filter `data` to empty while loading, pass the real (stale
516
+ * or previous) data and let `loading` cover it, or column widths jump.
517
+ * - `error` (a boolean) swaps the whole body for an Empty-based failure
518
+ * state — same composition the error-state pattern uses elsewhere
519
+ * (Patterns/UI States), just pre-wired here — and `errorDescription`
520
+ * supplies its copy (default "Something went wrong. Try again."). Two
521
+ * props on purpose: whether it failed and what to say are different
522
+ * things; passing the copy as `error` itself still works but is
523
+ * deprecated. A plain empty `data` array
524
+ * (not loading, no error) renders the "no results" Empty state instead.
525
+ * - `enableRowSelection` injects a checkbox column automatically — don't
526
+ * add your own selection column to `columns`.
527
+ * - Row actions are just a normal column whose `cell` renders the exported
528
+ * `RowActions` helper (inline icon buttons for ≤2 actions, an overflow
529
+ * menu past that) — there's no dedicated `actions` prop, this is the same
530
+ * "put a Button in a cell" technique any custom `cell` already uses.
531
+ * `RowActions` stops its own clicks from bubbling, so it's safe inside a
532
+ * row that also sets `onRowClick`.
533
+ * - `renderExpandedRow` adds a leading expand/collapse toggle to every row;
534
+ * expanding inserts a full-width row below it with whatever you return —
535
+ * a detail card, or a nested table of its own. The toggle stops its click
536
+ * from bubbling, so it composes with `onRowClick` too.
537
+ * - `onRowClick` makes the whole row clickable and keyboard-activatable
538
+ * (Enter/Space), for row→detail navigation. It composes with
539
+ * `enableRowSelection` — the selection checkbox stops its own click from
540
+ * bubbling up, so checking a row never also triggers `onRowClick`. Any
541
+ * other interactive element you put inside a cell (a button, a link)
542
+ * needs its own `stopPropagation` the same way if it shouldn't also
543
+ * trigger the row click.
544
+ * - `striped` and `cellDividers` are independent visual modifiers — zebra
545
+ * row tinting and vertical column rules, respectively — for tables where
546
+ * plain row/column boundaries are hard to scan.
547
+ * - AI presence has a distinct answer at each level, but DataTable's own
548
+ * outer surface never takes a border/shadow of its own — there's no `ai`
549
+ * boolean prop. For whole-table AI presence, the table just sits inside
550
+ * an AI Card — the exact same recipe AI/Card's "BoundaryTreatments" story uses (a
551
+ * `bg-gradient-to-r from-ai-wash to-ai-grad-from p-px` wrapper div, not
552
+ * a one-off DataTable-specific border) — plus a soft inward glow rising
553
+ * from the Card's own bottom edge (`bg-ai-glow-bottom`, fading out at 15%
554
+ * of the Card's own height) as a plain Card className — a background-image,
555
+ * so it layers over Card's own `bg-card` (`cn` registers the glow classes
556
+ * as background-image utilities so tailwind-merge keeps both).
557
+ * `AILabel` sits in the
558
+ * Card's top corner (a `CardAction`) — see the "WithAIWholeTable" story.
559
+ * The whole-table treatment is a boundary (gradient outline), an inward
560
+ * bottom glow and a corner marker — not the row/column glow used below.
561
+ * Below that: `aiAction` (table level)
562
+ * renders a `variant="ai"` toolbar button; `aiRowPredicate` (row level)
563
+ * puts a leading `AILabel` as the very first thing in the row — ahead
564
+ * of the expand toggle and selection checkbox, its own leading slot —
565
+ * plus a colored left edge (an inset box-shadow on the row's first
566
+ * cell, not `border-l` on the `<tr>` — the table is `border-separate`
567
+ * for pinned columns, and in that model a `<tr>` never paints borders)
568
+ * and a soft inward glow (`bg-ai-glow`, fading out at 20% of the row's own
569
+ * width, a background-image, not a solid fill) bleeding in from that
570
+ * same left edge — plus a second copy of that same glow painted directly
571
+ * on every left-pinned cell (however many — the injected AI column and a
572
+ * genuinely pinned column can both land in that same sticky group, see
573
+ * `effectiveColumnPinning` above), needed because pinning makes each
574
+ * `position: sticky` with its own opaque background, so the `<tr>`'s copy
575
+ * (anchored to the row's original, unscrolled left edge) would otherwise
576
+ * end up hidden behind them once the table scrolls horizontally. Each
577
+ * copy is sized via an explicit `backgroundSize` (the row's real total
578
+ * width, from `table.getTotalSize()`, not that cell's own narrower one)
579
+ * and shifted by `backgroundPositionX: -column.getStart("left")` so it
580
+ * shows the exact slice of that same gradient that falls at its actual
581
+ * position in the row — the whole pinned group reads as one continuous
582
+ * glow, pixel-identical to the unpinned case, rather than a smaller
583
+ * version of it; `aiColumnIds` (column level)
584
+ * adds the same left-edge glow down the whole column, header cell
585
+ * included (every cell in a column shares the same width, so 20% of it
586
+ * resolves to the same px value down the whole column, tiling into one
587
+ * continuous glow with no visible seam), a colored top rule on that
588
+ * column's header cell
589
+ * (mirrored by the row's own left edge above), and a leading
590
+ * `AILabel` (generic copy) automatically in that header;
591
+ * `aiCellPredicate` (cell level) adds just a trailing `AILabel` inline
592
+ * after the value — no glow, no border, since a single AI-touched value
593
+ * doesn't need its own boundary treatment, only the explainability
594
+ * entry point. Every `AILabelTrigger` here follows DataTable's own
595
+ * density prop — "default" at default density, "sm" at both sm and xs
596
+ * — never AILabel's `iconOnly`, which is opt-in only for when a
597
+ * designer explicitly asks for it. Give an AI-marked column enough
598
+ * width for its title plus the visible "AI" marker (the header keeps
599
+ * both; the title truncates first). Row/column backgrounds use
600
+ * `background-image` rather than `box-shadow` specifically for the row
601
+ * case — `box-shadow` doesn't paint reliably on `<tr>` in every
602
+ * browser, `background-image` does. Compose `AILabel` directly into a
603
+ * column's own `header`/`cell` instead of `aiColumnIds` when a column
604
+ * needs its own specific explanation text.
605
+ * - `meta: { align: "left" | "right" }` (default `"left"`) aligns both a
606
+ * column's header and its cell content — the guideline is quantitative
607
+ * content (amounts, counts, percentages, anything meant to be compared
608
+ * or summed down the column) gets `"right"`, text stays `"left"`. Cell
609
+ * alignment uses `flex justify-end`, not just `text-align`, so it also
610
+ * right-aligns a custom `cell` that renders its own Badge/icon cluster,
611
+ * not only plain text. `"right"` mirrors the header too — the sort
612
+ * button's label moves to that side and the filter icon/`headerActions`
613
+ * cluster moves to the *other* side (`flex-row-reverse` on the same row,
614
+ * not a second layout to maintain), so a numeric column's controls sit
615
+ * off to the side its digits don't occupy.
616
+ * - Search matches only `searchableColumnIds` (default: the first two
617
+ * entries in `columns`) — set it explicitly to widen/narrow the scope.
618
+ * Matches highlight automatically for plain columns (no custom `cell`);
619
+ * for a column with a custom `cell`, highlight it yourself with the
620
+ * exported `HighlightMatch`, reading the query off
621
+ * `info.table.getState().globalFilter`.
622
+ * - Every column gets a filter icon in its header cell. In the toolbar,
623
+ * filters show inline right after search — all of them if there are 3 or
624
+ * fewer filterable columns, otherwise just the pinned ones (the first two
625
+ * by default, and however many the user pins from the pane after that —
626
+ * uncapped, the row wraps onto more lines rather than overflowing) plus
627
+ * an "All filters" button, shown whenever there are more than 3
628
+ * filterable columns at all — regardless of how many are currently
629
+ * pinned inline, even every one of them, since the pane is still the
630
+ * only place to *unpin* one. That button opens a docked pane, not a
631
+ * floating overlay — the table area (`flex-1`) shrinks to make room for
632
+ * it on the surface DataTable sits on, instead of the pane covering the
633
+ * table, and reaches that surface's true top/right/bottom edge (measured
634
+ * in JS against the nearest Card, since a header/footer's height isn't a
635
+ * fixed token) while never growing taller than that surface — its own
636
+ * filter-list area is a `min-h-0` flex child so a long list scrolls
637
+ * internally instead of pushing the pane past the surface's edge. While
638
+ * the pane is open, the inline pinned filter buttons and the "All
639
+ * filters" trigger both hide — every filterable column and its current
640
+ * value already lives in the pane itself, each with a pin/unpin toggle
641
+ * (hover/keyboard-focus-revealed by default, like the header cell's own
642
+ * controls — see `revealColumnControlsOnHover`), so there's one place to
643
+ * look, not two copies of the same control. `allowFilterPinning` (default
644
+ * `true`) turns that toggle off entirely for a designer-fixed toolbar —
645
+ * `defaultPinnedFilterIds` (or its first-two default) becomes the
646
+ * permanent inline set, with no user-facing way to change which columns
647
+ * those are; every other filterable column still lives in the pane, just
648
+ * with nothing to move it out. Both the inline pinned chips and the pane
649
+ * list order their filterable columns by the table's own column order by
650
+ * default; set `meta: { filterOrder: N }` on a `ColumnDef` to decouple a
651
+ * filter's display position from where its column currently sits — handy
652
+ * when drag-and-drop/the Columns menu can reorder columns but a filter
653
+ * should stay put regardless. Once
654
+ * any filter is set, the toolbar shows "N filters applied" (every
655
+ * applied filter, inline pinned ones included, not just pane-only ones)
656
+ * next to a "Clear filters" button that clears all of them the same way
657
+ * — the pane's own footer "Clear filters" does the same, just from
658
+ * inside the pane. Default filter is text-contains; set
659
+ * `meta: { filterVariant: "select" }` on a `ColumnDef` for a
660
+ * multi-select filter instead — a checklist in the header icon's own
661
+ * popover; a `SelectMultiple` for the inline pinned button (its
662
+ * trigger's placeholder is the column label, its value only ever the
663
+ * first selected value's label, `maxLabelChars={12}` ellipsis-truncating
664
+ * that one past 12 characters even with just one selected, plus "+N"
665
+ * once more than one is — so neither a single long value nor a long list
666
+ * can run the toolbar out of room) and for the pane (which holds every
667
+ * filterable column at once). A multiselect filter (`"select"` or
668
+ * `"range"`) always requires a deliberate Apply — in every surface,
669
+ * regardless of `filterApplyMode` — since checking several boxes is a
670
+ * set to review before committing, not a single complete thought; picks
671
+ * still show as checked/selected immediately, on the trigger and in the
672
+ * list both — only the real filtering of the table's rows waits for
673
+ * Apply, not the visible selection itself. Closed (nothing open to
674
+ * preview into), the trigger always reflects whatever's actually
675
+ * committed. Its Cancel/Apply footer always lives inside that one
676
+ * field's own popover — the header icon's, the inline pinned button's,
677
+ * or (in the pane) that field's own, not a pane-wide batch, even though
678
+ * the pane holds every filterable column open at once; each field there
679
+ * resolves independently, exactly like the toolbar's. `filterVariant:
680
+ * "singleSelect"` picks exactly one value from that same kind of list —
681
+ * `SelectMultiple` capped at one value (`closeOnSelect`), in every
682
+ * surface including the pane, not a native-feeling dropdown — and,
683
+ * since it closes the instant a pick is made, is never itself deferred
684
+ * regardless of `filterApplyMode` (no open popover left by the time a
685
+ * footer could matter). `meta: { filterRender: (props) => node }` is
686
+ * the escape hatch past those four built-in shapes — a date-range
687
+ * picker, a slider, a radio group, colored selection badges, anything a
688
+ * designer's own filter needs — given the same `{ column, value,
689
+ * onValueChange }` every built-in variant already receives. Deferred
690
+ * under `filterApplyMode="deferred"` in every surface, not just the
691
+ * header-icon popover: the inline toolbar button wraps it in a Popover +
692
+ * the same shared Cancel/Apply footer text filters get there; the pane —
693
+ * which has no popover for any variant — adds an inline Cancel/Apply row
694
+ * directly under the rendered content instead. Immediate, every surface
695
+ * renders it with no extra chrome, same as always.
696
+ * - `meta: { filterOnly: true }` defines a column purely to filter a data
697
+ * point that isn't shown anywhere in the table — its `header`/`cell`
698
+ * never render, and unlike a plain hidden column it has no checkbox
699
+ * anywhere to show it (excluded from the Columns menu entirely, not just
700
+ * defaulted off), while still showing up as a normal filterable column in
701
+ * the "All filters" pane — never inline in the toolbar, and never
702
+ * pinnable there either (no header cell for either to point back at).
703
+ * Its presence alone forces the pane to exist even when 3 or fewer
704
+ * *other* columns are filterable, since the pane is its only access
705
+ * point.
706
+ * - `meta: { filterMergeWith: [...ids] }` folds other columns' filter
707
+ * fields into *this* column's own header-icon popover instead of each
708
+ * getting a separate toolbar/pane entry — for a cell whose custom `cell`
709
+ * already displays more than one filterable value at once (e.g. a name
710
+ * plus an MRN below it), so filtering either reads as one decision from
711
+ * one icon. Pairs naturally with `filterOnly` on the merged column(s),
712
+ * since the value being merged in is usually already folded into this
713
+ * column's own `cell` rather than shown in a column of its own — but
714
+ * that's a convention, not a requirement. Each merged field renders
715
+ * below this column's own (always first, unlabeled), under a divider,
716
+ * labeled with its own `header`. The icon fills, and the
717
+ * `filterApplyMode="deferred"` footer commits/discards, based on any of
718
+ * them having an active filter — not just this column's own.
719
+ * - Column reordering lives only in the Columns menu, not the header
720
+ * cells themselves — drag a row there (a thin primary-colored line
721
+ * marks exactly where it'll land) or use its Move left/Move right
722
+ * buttons — real, individually focusable/clickable buttons, so no
723
+ * custom keyboard handling is needed for them to be keyboard-operable,
724
+ * unlike the mouse-only drag. They say left/right, not up/down, even
725
+ * though the list itself is vertical — that's what the button actually
726
+ * does to the real table, which is what the label should describe, not
727
+ * the popover's own incidental layout. Every one of these buttons —
728
+ * Move left/right, and (once a column can pin) Pin left/Pin right or
729
+ * "Unpin" using the same icons `columnMenuActions` renders in the
730
+ * header — is hover/keyboard-focus-revealed by default (see
731
+ * `revealColumnControlsOnHover`) and carries its own Tooltip labelling
732
+ * it. Once any column is pinned, the list splits into up to three
733
+ * sections — pinned left, unpinned/center, pinned right, each separated
734
+ * by its own divider — mirroring the same grouping the real header/body
735
+ * cells render in; reordering (drag or the Move buttons) stays confined
736
+ * to one section at a time, matching what a plain visual drag could
737
+ * mean anyway. Moving a column *into* or *out of* a pinned section is
738
+ * what the Pin left/Pin right/Unpin buttons are for instead. Past 10
739
+ * manageable columns the list grows a search box and scrolls instead of
740
+ * pushing the menu taller. Visibility, order, and pinning changes apply
741
+ * live as you make them (so the menu's own rows and the real table
742
+ * never disagree), but the menu's footer still offers Cancel (reverts
743
+ * all three to whatever was committed when the menu opened, including
744
+ * if you dismiss it by clicking outside or Escape) and Apply (disabled
745
+ * until at least one of the three has actually changed since the menu
746
+ * opened) — so the whole session reads as one reviewable batch of
747
+ * changes, not silent live edits. "Reset to defaults" sits in that same
748
+ * footer, only rendered once order, hidden columns, or pinning have
749
+ * actually changed from default — a different comparison than Apply's
750
+ * (against the dev-configured default, not against this session's own
751
+ * starting point).
752
+ * - `lockedColumnIds` marks a column as fully static: the user can't
753
+ * reorder it (shows a lock glyph instead of a drag handle, and isn't a
754
+ * valid drop target) or hide it (the show/hide checkbox doesn't even
755
+ * render — `enableHiding: false` is forced regardless of what the
756
+ * column's own `ColumnDef` says). Every injected column (selection,
757
+ * expand, AI) is always locked. Column-visibility control (the checkbox
758
+ * on each row) also only lists columns that don't set
759
+ * `enableHiding: false` themselves — non-hideable columns still show up
760
+ * for reordering.
761
+ * - Column pinning: set `meta: { pin: "left" | "right" }` on a `ColumnDef`
762
+ * to seed a column's *initial* pinned side. From there it's live,
763
+ * user-togglable `@tanstack/react-table` state — wire the exported
764
+ * `columnMenuActions` into `meta.headerActions` for a ready-made Pin
765
+ * left/Pin right toggle (see below), or call `column.pin(...)` from any
766
+ * custom action of your own. Any number of columns can pin to the same
767
+ * side at once — they render as a contiguous group at that edge, in the
768
+ * order they were pinned (reorderable afterward from the Columns menu,
769
+ * confined to that group — see above), with a divider towards whichever
770
+ * edge actually borders the scrollable/center columns (the left group's
771
+ * right edge, the right group's left edge) on top of the solid
772
+ * background every pinned cell already gets so scrolled content doesn't
773
+ * show through it. Set `meta: { pinLocked: true }` to keep a column's
774
+ * pin fully dev-controlled with no user-facing toggle (`columnMenuActions`
775
+ * omits both Pin actions for it — a custom `headerActions` entry could
776
+ * still call `column.pin()` on it directly, since this only suppresses
777
+ * the built-in affordance, not the underlying state). A pinned column
778
+ * renders `position: sticky` at the correct cumulative offset, so it
779
+ * stays in view while the rest of the table scrolls horizontally —
780
+ * hover/selected/striped row tinting won't show through a pinned cell
781
+ * either, a known trade-off of frozen columns. Pinning a real column left
782
+ * also pulls the injected AI/expand/selection columns (whichever are
783
+ * present) into that same left-pinned group ahead of it, live — so they
784
+ * stay genuinely first and sticky too, matching their own "leading slot"
785
+ * documentation, instead of being pushed behind a real pinned column the
786
+ * moment one exists. Only kicks in once something's actually pinned left;
787
+ * otherwise these render exactly as before (unpinned, first in the
788
+ * unpinned/center group by definition order — no sticky, no divider).
789
+ * Nothing to configure — this is automatic and not itself user-togglable.
790
+ * - "Sticky first column" isn't its own prop — it's this same pinning
791
+ * recipe, combined with `lockedColumnIds` so the user can't unpin or
792
+ * reorder it away from that edge: `defaultColumnPinning={{ left: ["id"] }}`
793
+ * plus `lockedColumnIds={["id"]}` (see `ColumnManagement`).
794
+ * - `meta.headerActions` adds custom actions to a column's header cell,
795
+ * next to its sort button and filter icon — hidden until that header
796
+ * cell is hovered or keyboard-focused (see `revealColumnControlsOnHover`
797
+ * below), and rendered with the same exported `RowActions` helper row
798
+ * actions use (inline for ≤2 actions, an overflow menu past that),
799
+ * uniformly for every column including pinned ones. It's a function of
800
+ * `(column, table)`, not a static array, so it can reflect live
801
+ * per-column state. The exported `columnMenuActions` is the ready-made
802
+ * bundle for this slot — Pin left/Pin right (each becomes "Unpin" once
803
+ * pinned to that side, and both are omitted for a `pinLocked` column),
804
+ * Hide column, and Fit to content, each omitted when the column can't
805
+ * do it. Being 3-4 actions, it renders as a single overflow "more" menu
806
+ * in practice — the same shape as a plain custom `headerActions`
807
+ * function returning your own `RowAction[]` would, if you need
808
+ * something `columnMenuActions` doesn't cover.
809
+ * - `revealColumnControlsOnHover` (default `true`) is the one knob for
810
+ * whether the filter icon, `headerActions`/`columnMenuActions`, and the
811
+ * Columns menu's own pin/move controls stay hidden until hovered or
812
+ * keyboard-focused, or render always-visible — set `false` for a
813
+ * consumer whose final UX calls for every control visible up front.
814
+ * - `enableColumnResizing` adds a drag handle to every resizable column's
815
+ * right border (`ColumnDef.enableResizing: false` exempts one; injected
816
+ * columns already are). It's also what unlocks `columnMenuActions`' "Fit
817
+ * to content" — that action measures every currently-rendered cell in
818
+ * the column (this page's rows, not the whole dataset) via `scrollWidth`
819
+ * and sets the column to the widest one found.
820
+ * - Bulk actions while any row is selected land in one of two places,
821
+ * never both:
822
+ * - `onLoadMore` (infinite scroll, no footer): the toolbar stays
823
+ * mounted and interactive (search, filters) — searching/filtering
824
+ * the rows loaded so far while some of them are selected is exactly
825
+ * the "load more, then narrow down what's loaded" flow this mode is
826
+ * for. Instead, a floating bar hovers over the table's own bottom
827
+ * edge (never the raw viewport). Three sections divided by a line:
828
+ * count + "Select all" (every filtered row across everything loaded
829
+ * so far, not just what's rendered), `renderBulkActions`'s own
830
+ * product-configured actions in the middle, and a close button that
831
+ * clears the selection. Scrolling to the last row while any row is
832
+ * selected reveals extra bottom padding sized to the bar's own
833
+ * height — otherwise the last row(s) would stay hidden behind it
834
+ * permanently.
835
+ * - Paginated (a real footer): no floating bar, and the toolbar's own
836
+ * search/filter controls are swapped out in place for the selection
837
+ * itself — count, "Select all" (every filtered row across every
838
+ * page, not just the current one — same as infinite scroll's own),
839
+ * `renderBulkActions`'s actions, then a close button. Search/filters
840
+ * act on rows that may not even be the ones selected once paging is
841
+ * involved, so showing both the selection and a still-live search
842
+ * box read as two competing toolbars — this is the table's original
843
+ * selection behavior, kept for exactly that reason.
844
+ * - `getRowId` (e.g. `(row) => row.id`) gives each row a stable identity
845
+ * across a sort/filter/pagination change, passed straight through to
846
+ * `@tanstack/react-table` — without it, a row's identity defaults to its
847
+ * index, which is meaningless the moment the row set changes underneath
848
+ * it (filtering 100 rows down to 30 shouldn't treat "row 3" before and
849
+ * after as the same thing unless it actually is).
850
+ * - Footer is a full-width bar with the current range/total first, then
851
+ * "Items per page" (`pageSizeOptions`, default `[10, 20, 30]`; set
852
+ * `showPageSizeSelector={false}` to drop it — a design call for tables
853
+ * where the consumer shouldn't be choosing page size, not a user
854
+ * preference), and — past one page — a jump-to-page select plus
855
+ * Previous/Next. It carries the same
856
+ * edge-to-edge treatment as the toolbar and `Table` itself, but on all
857
+ * three sides it borders — left, right, *and* bottom — so on a Card it
858
+ * reads as that Card's own footer (same border-t/bg-muted/rounded-b-xl
859
+ * look as a real CardFooter, and it borrows CardFooter's own
860
+ * `data-slot` so a surrounding Card zeroes its bottom padding for it
861
+ * too).
862
+ * - `onLoadMore` switches to infinite scroll instead of pagination: every
863
+ * row in `data` renders (no client-side page slicing), there's no footer
864
+ * at all, and the table scrolls inside its own `maxHeight`-bounded
865
+ * container (default `32rem`) instead of the page — the header (and any
866
+ * pinned columns) stay locked in place via `position: sticky` while the
867
+ * body scrolls. `onLoadMore` fires once an invisible sentinel below the
868
+ * last row scrolls near-into view. Fetch the next page and append it to
869
+ * `data` yourself; `hasMore` stops rendering the sentinel once there's
870
+ * nothing left, and `loadingMore` renders 3 skeleton rows below the last
871
+ * real one — same shape as `loading`, not a spinner — while a fetch is
872
+ * in flight. Sorting/filtering still run client-side over whatever's
873
+ * currently loaded — an inherent limitation with partial data, not
874
+ * something this mode solves.
875
+ *
876
+ * USAGE
877
+ * const columns: ColumnDef<Invoice>[] = [
878
+ * { accessorKey: "id", header: "Invoice" },
879
+ * { accessorKey: "amount", header: "Amount" },
880
+ * { accessorKey: "status", header: "Status", meta: { filterVariant: "select" } },
881
+ * ]
882
+ *
883
+ * <DataTable columns={columns} data={invoices} aria-label="Invoices" />
884
+ *
885
+ * // Selectable rows with a bulk-delete action — the count (swapped
886
+ * // into the toolbar here, since this table paginates), "Select all",
887
+ * // and the close/clear button are all built in; this only fills the
888
+ * // actions themselves
889
+ * <DataTable
890
+ * columns={columns}
891
+ * data={invoices}
892
+ * enableRowSelection
893
+ * renderBulkActions={(selected, clear) => (
894
+ * <Button size="sm" variant="ghost" onClick={() => { onDelete(selected); clear() }}>
895
+ * <TrashIcon />
896
+ * Delete
897
+ * </Button>
898
+ * )}
899
+ * />
900
+ *
901
+ * // Row actions (inline for ≤2, overflow menu for >2)
902
+ * const withActions: ColumnDef<Invoice>[] = [
903
+ * ...columns,
904
+ * {
905
+ * id: "actions",
906
+ * header: "",
907
+ * size: 72,
908
+ * enableSorting: false,
909
+ * enableHiding: false,
910
+ * enableColumnFilter: false,
911
+ * cell: ({ row }) => (
912
+ * <RowActions
913
+ * actions={[
914
+ * { label: "Edit", icon: PencilIcon, onClick: () => onEdit(row.original) },
915
+ * { label: "Delete", icon: TrashIcon, variant: "destructive", onClick: () => onDelete(row.original) },
916
+ * ]}
917
+ * />
918
+ * ),
919
+ * },
920
+ * ]
921
+ *
922
+ * // Header-cell actions: a fully custom one, for anything columnMenuActions
923
+ * // doesn't cover
924
+ * const withHeaderActions: ColumnDef<Invoice>[] = columns.map((col) =>
925
+ * col.id === "status"
926
+ * ? {
927
+ * ...col,
928
+ * meta: {
929
+ * ...col.meta,
930
+ * headerActions: () => [
931
+ * { label: "Search", icon: SearchIcon, onClick: () => openColumnSearch("status") },
932
+ * ],
933
+ * },
934
+ * }
935
+ * : col
936
+ * )
937
+ *
938
+ * // Column resizing (drag handles) plus the ready-made Pin left/Pin
939
+ * // right/Hide/Fit-to-content menu on every column
940
+ * const withColumnMenu: ColumnDef<Invoice>[] = columns.map((col) => ({
941
+ * ...col,
942
+ * meta: { ...col.meta, headerActions: columnMenuActions },
943
+ * }))
944
+ * <DataTable columns={withColumnMenu} data={invoices} enableColumnResizing />
945
+ *
946
+ * // Right-align a quantitative column — header and cells both flip
947
+ * const withAlignedAmount: ColumnDef<Invoice>[] = columns.map((col) =>
948
+ * col.id === "amount"
949
+ * ? { ...col, meta: { ...col.meta, align: "right" } }
950
+ * : col
951
+ * )
952
+ *
953
+ * // Expandable detail row, striping, and column dividers
954
+ * <DataTable
955
+ * columns={columns}
956
+ * data={invoices}
957
+ * renderExpandedRow={(invoice) => <InvoiceDetailCard invoice={invoice} />}
958
+ * striped
959
+ * cellDividers
960
+ * />
961
+ *
962
+ * // AI presence at every level (except whole-table — wrap in a Card for that, see below)
963
+ * <DataTable
964
+ * columns={columns}
965
+ * data={invoices}
966
+ * aiAction={{ label: "Summarize", onClick: () => runAISummary(invoices) }}
967
+ * aiRowPredicate={(invoice) => invoice.flaggedByAI}
968
+ * aiColumnIds={["amount"]}
969
+ * aiCellPredicate={(invoice, columnId) => columnId === "customer" && invoice.customerSuggestedByAI}
970
+ * />
971
+ *
972
+ * // Whole-table AI presence: the table just sits inside an AI Card (the
973
+ * // same recipe AI/Card's "BoundaryTreatments" story uses) plus a soft glow rising from
974
+ * // the Card's own bottom edge (bg-ai-glow-bottom, a plain Card className) —
975
+ * // AILabel in its top corner, never on DataTable's own surface
976
+ * <div className="rounded-xl bg-gradient-to-r from-ai-wash to-ai-grad-from p-px">
977
+ * <Card className="w-full bg-ai-glow-bottom">
978
+ * <CardHeader>
979
+ * <CardTitle>Invoices</CardTitle>
980
+ * <CardAction>
981
+ * <AILabel>
982
+ * <AILabelTrigger />
983
+ * <AILabelContent>...</AILabelContent>
984
+ * </AILabel>
985
+ * </CardAction>
986
+ * </CardHeader>
987
+ * <CardContent>
988
+ * <DataTable columns={columns} data={invoices} />
989
+ * </CardContent>
990
+ * </Card>
991
+ * </div>
992
+ *
993
+ * // Loading and error states
994
+ * <DataTable columns={columns} data={[]} loading />
995
+ * <DataTable columns={columns} data={[]} error errorDescription="Couldn't reach the server." />
996
+ *
997
+ * // Clickable rows (e.g. navigate to a detail view), a locked first column,
998
+ * // and a wider items-per-page range
999
+ * <DataTable
1000
+ * columns={columns}
1001
+ * data={invoices}
1002
+ * onRowClick={(invoice) => router.push(`/invoices/${invoice.id}`)}
1003
+ * lockedColumnIds={["id"]}
1004
+ * pageSizeOptions={[10, 25, 50]}
1005
+ * />
1006
+ *
1007
+ * // Drop the "Items per page" control from the static footer — page size
1008
+ * // stays fixed at whatever `pageSize` is, not something the user picks
1009
+ * <DataTable columns={columns} data={invoices} showPageSizeSelector={false} />
1010
+ *
1011
+ * // Infinite scroll instead of pagination — no footer
1012
+ * const [invoices, setInvoices] = useState(firstPage)
1013
+ * const [loadingMore, setLoadingMore] = useState(false)
1014
+ * <DataTable
1015
+ * columns={columns}
1016
+ * data={invoices}
1017
+ * hasMore={invoices.length < total}
1018
+ * loadingMore={loadingMore}
1019
+ * onLoadMore={async () => {
1020
+ * setLoadingMore(true)
1021
+ * setInvoices((current) => [...current, ...await fetchNextPage(current.length)])
1022
+ * setLoadingMore(false)
1023
+ * }}
1024
+ * />
1025
+ */
1026
+ //# sourceMappingURL=data-table.d.ts.map