@krizaka/ui 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/README.md +44 -6
  2. package/dist/audio-player.js +1 -1
  3. package/dist/blocks.css +247 -0
  4. package/dist/catalogue-page.d.ts +34 -0
  5. package/dist/catalogue-page.js +26 -0
  6. package/dist/catalogue-page.js.map +1 -0
  7. package/dist/changelog-page.d.ts +50 -0
  8. package/dist/changelog-page.js +27 -0
  9. package/dist/changelog-page.js.map +1 -0
  10. package/dist/{chunk-EUPTXKH7.js → chunk-BRKMQ6JV.js} +2 -1
  11. package/dist/chunk-FDCMBWUU.js +168 -0
  12. package/dist/chunk-FDCMBWUU.js.map +1 -0
  13. package/dist/chunk-HCNOOPZE.js +210 -0
  14. package/dist/chunk-HCNOOPZE.js.map +1 -0
  15. package/dist/{chunk-NOVUREEC.js → chunk-JZPVUJIX.js} +25 -3
  16. package/dist/chunk-JZPVUJIX.js.map +1 -0
  17. package/dist/chunk-KNBYZGSQ.js +11 -0
  18. package/dist/chunk-KNBYZGSQ.js.map +1 -0
  19. package/dist/{chunk-X6TW3X6Y.js → chunk-LCZXS6AK.js} +2 -2
  20. package/dist/chunk-OGJI5X4W.js +157 -0
  21. package/dist/chunk-OGJI5X4W.js.map +1 -0
  22. package/dist/combobox.js +2 -2
  23. package/dist/component-page.d.ts +41 -0
  24. package/dist/component-page.js +17 -0
  25. package/dist/component-page.js.map +1 -0
  26. package/dist/contact-page.d.ts +31 -0
  27. package/dist/contact-page.js +26 -0
  28. package/dist/contact-page.js.map +1 -0
  29. package/dist/data-table.d.ts +284 -24
  30. package/dist/data-table.js +914 -121
  31. package/dist/data-table.js.map +1 -1
  32. package/dist/date-picker.js +1 -1
  33. package/dist/docs-article-page.d.ts +54 -0
  34. package/dist/docs-article-page.js +29 -0
  35. package/dist/docs-article-page.js.map +1 -0
  36. package/dist/docs-hub-page.d.ts +72 -0
  37. package/dist/docs-hub-page.js +45 -0
  38. package/dist/docs-hub-page.js.map +1 -0
  39. package/dist/error-page.d.ts +22 -0
  40. package/dist/error-page.js +23 -0
  41. package/dist/error-page.js.map +1 -0
  42. package/dist/filter-bar.d.ts +12 -12
  43. package/dist/filter-bar.js +9 -145
  44. package/dist/filter-bar.js.map +1 -1
  45. package/dist/gallery-page.d.ts +50 -0
  46. package/dist/gallery-page.js +30 -0
  47. package/dist/gallery-page.js.map +1 -0
  48. package/dist/{geometry-CXrAW34x.d.ts → geometry-CbOU4rtL.d.ts} +4 -4
  49. package/dist/how-it-works-page.d.ts +48 -0
  50. package/dist/how-it-works-page.js +28 -0
  51. package/dist/how-it-works-page.js.map +1 -0
  52. package/dist/image-cropper.js +1 -1
  53. package/dist/index.d.ts +11 -2
  54. package/dist/index.js +93 -5
  55. package/dist/index.js.map +1 -1
  56. package/dist/landing-page.d.ts +58 -0
  57. package/dist/landing-page.js +42 -0
  58. package/dist/landing-page.js.map +1 -0
  59. package/dist/legal-page.d.ts +34 -0
  60. package/dist/legal-page.js +18 -0
  61. package/dist/legal-page.js.map +1 -0
  62. package/dist/longform-page.d.ts +42 -0
  63. package/dist/longform-page.js +29 -0
  64. package/dist/longform-page.js.map +1 -0
  65. package/dist/mascot-quote.d.ts +4 -4
  66. package/dist/mascot-quote.js +23 -1
  67. package/dist/mascot-quote.js.map +1 -1
  68. package/dist/mascot-state.d.ts +2 -2
  69. package/dist/mascot-state.js +1 -1
  70. package/dist/mascot-state.js.map +1 -1
  71. package/dist/mascots.d.ts +4 -4
  72. package/dist/mascots.js +1 -1
  73. package/dist/media-capture.js +1 -1
  74. package/dist/native/list.d.ts +95 -0
  75. package/dist/native/list.js +135 -0
  76. package/dist/native/list.js.map +1 -0
  77. package/dist/native.d.ts +16 -9
  78. package/dist/native.js +183 -74
  79. package/dist/native.js.map +1 -1
  80. package/dist/pricing-page.d.ts +62 -0
  81. package/dist/pricing-page.js +71 -0
  82. package/dist/pricing-page.js.map +1 -0
  83. package/dist/product-page.d.ts +83 -0
  84. package/dist/product-page.js +60 -0
  85. package/dist/product-page.js.map +1 -0
  86. package/dist/search-field.js +6 -160
  87. package/dist/search-field.js.map +1 -1
  88. package/dist/status-page.d.ts +42 -0
  89. package/dist/status-page.js +28 -0
  90. package/dist/status-page.js.map +1 -0
  91. package/dist/tag-input.js +2 -2
  92. package/dist/video-player.js +1 -1
  93. package/dist/video-trimmer.js +1 -1
  94. package/dist/virtual.d.ts +140 -0
  95. package/dist/virtual.js +19 -0
  96. package/dist/virtual.js.map +1 -0
  97. package/package.json +296 -10
  98. package/registry/achievement.json +1 -1
  99. package/registry/action-bar.json +1 -1
  100. package/registry/alert.json +1 -1
  101. package/registry/app-shell.json +1 -1
  102. package/registry/argument-card.json +1 -1
  103. package/registry/audio-player.json +1 -1
  104. package/registry/auth-layout.json +1 -1
  105. package/registry/avatar.json +1 -1
  106. package/registry/badge.json +1 -1
  107. package/registry/button.json +1 -1
  108. package/registry/card-grid.json +1 -1
  109. package/registry/card.json +1 -1
  110. package/registry/catalogue-page.json +240 -0
  111. package/registry/changelog-page.json +226 -0
  112. package/registry/checkbox.json +1 -1
  113. package/registry/chip-link.json +1 -1
  114. package/registry/chip.json +1 -1
  115. package/registry/choice-grid.json +1 -1
  116. package/registry/code-block.json +1 -1
  117. package/registry/combobox.json +1 -1
  118. package/registry/command.json +1 -1
  119. package/registry/component-page.json +227 -0
  120. package/registry/confirm-button.json +1 -1
  121. package/registry/contact-page.json +212 -0
  122. package/registry/continue-prompt.json +1 -1
  123. package/registry/countdown.json +1 -1
  124. package/registry/cta-panel.json +1 -1
  125. package/registry/dashboard-layout.json +1 -1
  126. package/registry/data-table.json +643 -49
  127. package/registry/date-picker.json +1 -1
  128. package/registry/decision-list.json +1 -1
  129. package/registry/detail-layout.json +1 -1
  130. package/registry/dialog.json +1 -1
  131. package/registry/docs-article-page.json +242 -0
  132. package/registry/docs-hub-page.json +270 -0
  133. package/registry/docs-layout.json +1 -1
  134. package/registry/dropdown-menu.json +1 -1
  135. package/registry/empty-state.json +1 -1
  136. package/registry/error-page.json +183 -0
  137. package/registry/examples/catalogue-page/components.d.ts +4 -0
  138. package/registry/examples/catalogue-page/components.js +33 -0
  139. package/registry/examples/catalogue-page/components.tsx +36 -0
  140. package/registry/examples/changelog-page/releases.d.ts +4 -0
  141. package/registry/examples/changelog-page/releases.js +44 -0
  142. package/registry/examples/changelog-page/releases.tsx +39 -0
  143. package/registry/examples/component-page/button.d.ts +4 -0
  144. package/registry/examples/component-page/button.js +33 -0
  145. package/registry/examples/component-page/button.tsx +30 -0
  146. package/registry/examples/contact-page/team.d.ts +4 -0
  147. package/registry/examples/contact-page/team.js +39 -0
  148. package/registry/examples/contact-page/team.tsx +38 -0
  149. package/registry/examples/data-table/details.d.ts +4 -0
  150. package/registry/examples/data-table/details.js +33 -0
  151. package/registry/examples/data-table/details.tsx +31 -0
  152. package/registry/examples/data-table/filters.d.ts +4 -0
  153. package/registry/examples/data-table/filters.js +40 -0
  154. package/registry/examples/data-table/filters.tsx +40 -0
  155. package/registry/examples/data-table/grouped.d.ts +4 -0
  156. package/registry/examples/data-table/grouped.js +24 -0
  157. package/registry/examples/data-table/grouped.tsx +23 -0
  158. package/registry/examples/data-table/paginated.d.ts +4 -0
  159. package/registry/examples/data-table/paginated.js +23 -0
  160. package/registry/examples/data-table/paginated.tsx +22 -0
  161. package/registry/examples/data-table/pinned.d.ts +4 -0
  162. package/registry/examples/data-table/pinned.js +26 -0
  163. package/registry/examples/data-table/pinned.tsx +25 -0
  164. package/registry/examples/data-table/server.d.ts +4 -0
  165. package/registry/examples/data-table/server.js +40 -0
  166. package/registry/examples/data-table/server.tsx +40 -0
  167. package/registry/examples/data-table/virtualized.js +2 -2
  168. package/registry/examples/data-table/virtualized.tsx +3 -3
  169. package/registry/examples/docs-article-page/cli.d.ts +4 -0
  170. package/registry/examples/docs-article-page/cli.js +33 -0
  171. package/registry/examples/docs-article-page/cli.tsx +38 -0
  172. package/registry/examples/docs-hub-page/krizaka.d.ts +4 -0
  173. package/registry/examples/docs-hub-page/krizaka.js +46 -0
  174. package/registry/examples/docs-hub-page/krizaka.tsx +37 -0
  175. package/registry/examples/error-page/not-found.d.ts +4 -0
  176. package/registry/examples/error-page/not-found.js +24 -0
  177. package/registry/examples/error-page/not-found.tsx +19 -0
  178. package/registry/examples/gallery-page/made-here.d.ts +4 -0
  179. package/registry/examples/gallery-page/made-here.js +34 -0
  180. package/registry/examples/gallery-page/made-here.tsx +32 -0
  181. package/registry/examples/how-it-works-page/orazaka.d.ts +4 -0
  182. package/registry/examples/how-it-works-page/orazaka.js +30 -0
  183. package/registry/examples/how-it-works-page/orazaka.tsx +26 -0
  184. package/registry/examples/landing-page/krizaka.d.ts +4 -0
  185. package/registry/examples/landing-page/krizaka.js +44 -0
  186. package/registry/examples/landing-page/krizaka.tsx +44 -0
  187. package/registry/examples/legal-page/privacy.d.ts +4 -0
  188. package/registry/examples/legal-page/privacy.js +24 -0
  189. package/registry/examples/legal-page/privacy.tsx +19 -0
  190. package/registry/examples/list/native/default.d.ts +4 -0
  191. package/registry/examples/list/native/default.dark.png +0 -0
  192. package/registry/examples/list/native/default.js +33 -0
  193. package/registry/examples/list/native/default.light.png +0 -0
  194. package/registry/examples/list/native/default.tsx +31 -0
  195. package/registry/examples/list/native/infinite.d.ts +4 -0
  196. package/registry/examples/list/native/infinite.dark.png +0 -0
  197. package/registry/examples/list/native/infinite.js +30 -0
  198. package/registry/examples/list/native/infinite.light.png +0 -0
  199. package/registry/examples/list/native/infinite.tsx +35 -0
  200. package/registry/examples/list/native/states.d.ts +4 -0
  201. package/registry/examples/list/native/states.dark.png +0 -0
  202. package/registry/examples/list/native/states.js +26 -0
  203. package/registry/examples/list/native/states.light.png +0 -0
  204. package/registry/examples/list/native/states.tsx +21 -0
  205. package/registry/examples/longform-page/story.d.ts +4 -0
  206. package/registry/examples/longform-page/story.js +38 -0
  207. package/registry/examples/longform-page/story.tsx +35 -0
  208. package/registry/examples/mascots/crane.d.ts +4 -0
  209. package/registry/examples/mascots/crane.js +19 -0
  210. package/registry/examples/mascots/crane.tsx +18 -0
  211. package/registry/examples/mascots/flock.tsx +2 -1
  212. package/registry/examples/mascots/native/crane.d.ts +4 -0
  213. package/registry/examples/mascots/native/crane.dark.png +0 -0
  214. package/registry/examples/mascots/native/crane.js +21 -0
  215. package/registry/examples/mascots/native/crane.light.png +0 -0
  216. package/registry/examples/mascots/native/crane.tsx +18 -0
  217. package/registry/examples/pricing-page/credits.d.ts +4 -0
  218. package/registry/examples/pricing-page/credits.js +41 -0
  219. package/registry/examples/pricing-page/credits.tsx +33 -0
  220. package/registry/examples/product-page/orazaka.d.ts +4 -0
  221. package/registry/examples/product-page/orazaka.js +44 -0
  222. package/registry/examples/product-page/orazaka.tsx +36 -0
  223. package/registry/examples/status-page/building.d.ts +4 -0
  224. package/registry/examples/status-page/building.js +29 -0
  225. package/registry/examples/status-page/building.tsx +25 -0
  226. package/registry/examples/status-page/operational.d.ts +4 -0
  227. package/registry/examples/status-page/operational.js +26 -0
  228. package/registry/examples/status-page/operational.tsx +20 -0
  229. package/registry/examples/virtual/feed.d.ts +4 -0
  230. package/registry/examples/virtual/feed.js +26 -0
  231. package/registry/examples/virtual/feed.tsx +24 -0
  232. package/registry/examples/virtual/gallery.d.ts +4 -0
  233. package/registry/examples/virtual/gallery.js +31 -0
  234. package/registry/examples/virtual/gallery.tsx +28 -0
  235. package/registry/examples/virtual/messages.d.ts +4 -0
  236. package/registry/examples/virtual/messages.js +28 -0
  237. package/registry/examples/virtual/messages.tsx +28 -0
  238. package/registry/field.json +1 -1
  239. package/registry/file-upload.json +1 -1
  240. package/registry/filter-bar.json +1 -1
  241. package/registry/gallery-page.json +256 -0
  242. package/registry/glow-card.json +1 -1
  243. package/registry/goal-gauge.json +1 -1
  244. package/registry/how-it-works-page.json +242 -0
  245. package/registry/image-cropper.json +1 -1
  246. package/registry/index.json +356 -9
  247. package/registry/iso.json +1 -1
  248. package/registry/kbd.json +1 -1
  249. package/registry/landing-page.json +242 -0
  250. package/registry/landscape.json +1 -1
  251. package/registry/legal-page.json +199 -0
  252. package/registry/link-card.json +1 -1
  253. package/registry/list.json +249 -0
  254. package/registry/loader.json +1 -1
  255. package/registry/longform-page.json +213 -0
  256. package/registry/marketing-layout.json +1 -1
  257. package/registry/marquee.json +1 -1
  258. package/registry/mascot-quote.json +6 -6
  259. package/registry/mascot-state.json +34 -34
  260. package/registry/mascots.json +41 -21
  261. package/registry/media-capture.json +1 -1
  262. package/registry/meter.json +1 -1
  263. package/registry/nav-bar.json +1 -1
  264. package/registry/number-ticker.json +1 -1
  265. package/registry/page-header.json +1 -1
  266. package/registry/page-hero.json +1 -1
  267. package/registry/page.json +1 -1
  268. package/registry/pagination.json +1 -1
  269. package/registry/pointer-light.json +1 -1
  270. package/registry/popover.json +1 -1
  271. package/registry/pricing-page.json +245 -0
  272. package/registry/product-page.json +319 -0
  273. package/registry/profile-card.json +1 -1
  274. package/registry/progress.json +1 -1
  275. package/registry/promise-card.json +1 -1
  276. package/registry/radio-group.json +1 -1
  277. package/registry/scroll-stage.json +1 -1
  278. package/registry/search-field.json +1 -1
  279. package/registry/section-backdrop.json +1 -1
  280. package/registry/separator.json +1 -1
  281. package/registry/settings-layout.json +1 -1
  282. package/registry/skeleton.json +1 -1
  283. package/registry/slider.json +1 -1
  284. package/registry/slot.json +1 -1
  285. package/registry/spinner.json +1 -1
  286. package/registry/split-view.json +1 -1
  287. package/registry/stage-banner.json +1 -1
  288. package/registry/stat.json +1 -1
  289. package/registry/status-page.json +263 -0
  290. package/registry/stepper.json +1 -1
  291. package/registry/switch.json +1 -1
  292. package/registry/tabs.json +1 -1
  293. package/registry/tag-input.json +1 -1
  294. package/registry/terminal-replay.json +1 -1
  295. package/registry/theme.json +1 -1
  296. package/registry/toast.json +1 -1
  297. package/registry/toc.json +1 -1
  298. package/registry/tooltip.json +1 -1
  299. package/registry/video-player.json +1 -1
  300. package/registry/video-trimmer.json +1 -1
  301. package/registry/virtual.json +569 -0
  302. package/skills/data-table/SKILL.md +56 -0
  303. package/skills/native-list/SKILL.md +39 -0
  304. package/skills/virtual/SKILL.md +35 -0
  305. package/dist/chunk-NOVUREEC.js.map +0 -1
  306. /package/dist/{chunk-EUPTXKH7.js.map → chunk-BRKMQ6JV.js.map} +0 -0
  307. /package/dist/{chunk-X6TW3X6Y.js.map → chunk-LCZXS6AK.js.map} +0 -0
@@ -1,14 +1,46 @@
1
1
  "use client";
2
+ import {
3
+ useLayoutFallbacks,
4
+ useVirtualizer
5
+ } from "./chunk-HCNOOPZE.js";
6
+ import {
7
+ DropdownMenu,
8
+ FilterBar,
9
+ FilterMenu
10
+ } from "./chunk-OGJI5X4W.js";
11
+ import "./chunk-YH4LMWRU.js";
12
+ import "./chunk-2USC2YT6.js";
13
+ import {
14
+ SearchField
15
+ } from "./chunk-FDCMBWUU.js";
16
+ import "./chunk-ZIXB33X7.js";
2
17
  import {
3
18
  useControllable
4
- } from "./chunk-EUPTXKH7.js";
19
+ } from "./chunk-BRKMQ6JV.js";
5
20
  import {
21
+ ChevronGlyph,
6
22
  SortGlyph
7
23
  } from "./chunk-CPA5CF35.js";
8
24
 
25
+ // src/data-table/csv.ts
26
+ var FORMULA = /^[=+\-@\t\r]/;
27
+ function field(value) {
28
+ let text = value == null ? "" : value instanceof Date ? value.toISOString() : String(value);
29
+ if (typeof value === "string" && FORMULA.test(text)) text = `'${text}`;
30
+ return /[",\n\r;]/.test(text) || text !== text.trim() ? `"${text.replace(/"/g, '""')}"` : text;
31
+ }
32
+ function dataTableCsv(rows, columns, separator = ",") {
33
+ const lines = [columns.map((column) => field(column.header)).join(separator)];
34
+ for (const row of rows) {
35
+ lines.push(columns.map((column) => field(column.value ? column.value(row) : row[column.id])).join(separator));
36
+ }
37
+ return lines.join("\r\n");
38
+ }
39
+
9
40
  // src/data-table/data-table.tsx
41
+ import { useTable } from "@tanstack/react-table";
10
42
  import * as React from "react";
11
- import { tv as tv2 } from "tailwind-variants";
43
+ import { tv as tv3 } from "tailwind-variants";
12
44
 
13
45
  // src/checkbox/checkbox.tsx
14
46
  import { Checkbox as CheckboxPrimitive } from "radix-ui";
@@ -28,58 +60,270 @@ function Dash() {
28
60
  return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-3.5 w-3.5", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M5 12h14" }) });
29
61
  }
30
62
  function Checkbox({ invalid, className, children, ...props }) {
31
- const s = checkbox();
63
+ const s2 = checkbox();
32
64
  const box = /* @__PURE__ */ jsx(
33
65
  CheckboxPrimitive.Root,
34
66
  {
35
67
  "aria-invalid": invalid || void 0,
36
68
  "data-invalid": invalid ? "" : void 0,
37
- className: s.root({ className: children ? "mt-0.5" : className }),
69
+ className: s2.root({ className: children ? "mt-0.5" : className }),
38
70
  ...props,
39
- children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { className: s.indicator(), children: props.checked === "indeterminate" ? /* @__PURE__ */ jsx(Dash, {}) : /* @__PURE__ */ jsx(Check, {}) })
71
+ children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { className: s2.indicator(), children: props.checked === "indeterminate" ? /* @__PURE__ */ jsx(Dash, {}) : /* @__PURE__ */ jsx(Check, {}) })
40
72
  }
41
73
  );
42
74
  if (!children) return box;
43
- return /* @__PURE__ */ jsxs("label", { className: s.label({ className }), children: [
75
+ return /* @__PURE__ */ jsxs("label", { className: s2.label({ className }), children: [
44
76
  box,
45
77
  /* @__PURE__ */ jsx("span", { className: "min-w-0", children })
46
78
  ] });
47
79
  }
48
80
 
81
+ // src/pagination/pagination.tsx
82
+ import { tv as tv2 } from "tailwind-variants";
83
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
84
+ var pagination = tv2({
85
+ slots: {
86
+ root: "@container flex w-full flex-wrap items-center justify-between gap-3 text-sm",
87
+ list: "flex items-center gap-1",
88
+ pages: "hidden items-center gap-1 @md:flex",
89
+ compact: "px-2 text-xs font-semibold tabular-nums text-fg-secondary @md:hidden",
90
+ item: "inline-flex h-9 min-w-9 select-none items-center justify-center gap-1.5 rounded-lg px-2.5 text-sm font-semibold tabular-nums text-fg-secondary transition-colors hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:bg-accent aria-[current=page]:text-on-accent aria-[current=page]:hover:bg-accent aria-disabled:pointer-events-none aria-disabled:opacity-40 disabled:pointer-events-none disabled:opacity-40",
91
+ edge: "border border-border-default bg-surface-1 px-3",
92
+ ellipsis: "inline-flex h-9 min-w-6 items-end justify-center pb-2 text-fg-muted",
93
+ status: "text-xs text-fg-secondary tabular-nums",
94
+ size: "flex items-center gap-2 text-xs text-fg-secondary",
95
+ select: "h-9 rounded-lg border border-border-default bg-surface-2 px-2 text-sm text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
96
+ }
97
+ });
98
+ var paginationLabels = {
99
+ nav: "Pagination",
100
+ previous: "Previous",
101
+ next: "Next",
102
+ page: (page) => `Page ${page}`,
103
+ compact: (page, count) => `Page ${page} of ${count}`,
104
+ pageSize: "Rows per page"
105
+ };
106
+ function pageRange(page, pageCount, siblings = 1, boundaries = 1) {
107
+ if (pageCount <= 2 * boundaries + 2 * siblings + 3) return Array.from({ length: Math.max(0, pageCount) }, (_, i) => i + 1);
108
+ const pages = /* @__PURE__ */ new Set();
109
+ for (let i = 1; i <= Math.min(boundaries, pageCount); i += 1) pages.add(i);
110
+ for (let i = Math.max(1, pageCount - boundaries + 1); i <= pageCount; i += 1) pages.add(i);
111
+ for (let i = Math.max(1, page - siblings); i <= Math.min(pageCount, page + siblings); i += 1) pages.add(i);
112
+ const sorted = [...pages].sort((a, b) => a - b);
113
+ const out = [];
114
+ sorted.forEach((n, i) => {
115
+ const prev = sorted[i - 1];
116
+ if (prev !== void 0 && n - prev === 2) out.push(prev + 1);
117
+ else if (prev !== void 0 && n - prev > 2) out.push("\u2026");
118
+ out.push(n);
119
+ });
120
+ return out;
121
+ }
122
+ function Item({ href, onClick, disabled, current, label, className, children }) {
123
+ const common = { "aria-label": label, "aria-current": current ? "page" : void 0, className };
124
+ if (href && !disabled) {
125
+ return /* @__PURE__ */ jsx2("a", { href, onClick, ...common, children });
126
+ }
127
+ if (href) {
128
+ return /* @__PURE__ */ jsx2("a", { role: "link", "aria-disabled": true, ...common, children });
129
+ }
130
+ return /* @__PURE__ */ jsx2("button", { type: "button", onClick, disabled, ...common, children });
131
+ }
132
+ function Pagination({
133
+ page = 1,
134
+ pageCount,
135
+ onPageChange,
136
+ getHref,
137
+ hasPrevious,
138
+ hasNext,
139
+ onPrevious,
140
+ onNext,
141
+ siblings = 1,
142
+ boundaries = 1,
143
+ status,
144
+ pageSize,
145
+ pageSizeOptions,
146
+ onPageSizeChange,
147
+ disabled,
148
+ labels: given,
149
+ className,
150
+ ...props
151
+ }) {
152
+ const s2 = pagination();
153
+ const labels = { ...paginationLabels, ...given };
154
+ const paged = pageCount !== void 0;
155
+ const count = Math.max(1, pageCount ?? 1);
156
+ const current = Math.min(Math.max(1, page), count);
157
+ const canPrevious = paged ? current > 1 : Boolean(hasPrevious);
158
+ const canNext = paged ? current < count : Boolean(hasNext);
159
+ const go = (target) => () => onPageChange?.(target);
160
+ const href = (target) => paged && getHref ? getHref(target) : void 0;
161
+ return /* @__PURE__ */ jsxs2("nav", { "aria-label": labels.nav, className: s2.root({ className }), ...props, children: [
162
+ status !== void 0 || pageSizeOptions ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-4", children: [
163
+ status !== void 0 && /* @__PURE__ */ jsx2("p", { className: s2.status(), children: status }),
164
+ pageSizeOptions && pageSize !== void 0 && /* @__PURE__ */ jsxs2("label", { className: s2.size(), children: [
165
+ labels.pageSize,
166
+ /* @__PURE__ */ jsx2(
167
+ "select",
168
+ {
169
+ className: s2.select(),
170
+ value: pageSize,
171
+ disabled,
172
+ onChange: (event) => onPageSizeChange?.(Number(event.target.value)),
173
+ children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx2("option", { value: size, children: size }, size))
174
+ }
175
+ )
176
+ ] })
177
+ ] }) : null,
178
+ /* @__PURE__ */ jsxs2("ul", { className: s2.list(), children: [
179
+ /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsxs2(
180
+ Item,
181
+ {
182
+ href: href(current - 1),
183
+ onClick: paged ? go(current - 1) : onPrevious,
184
+ disabled: disabled || !canPrevious,
185
+ label: labels.previous,
186
+ className: s2.item({ className: s2.edge() }),
187
+ children: [
188
+ /* @__PURE__ */ jsx2(ChevronGlyph, { direction: "left" }),
189
+ /* @__PURE__ */ jsx2("span", { className: "hidden @lg:inline", children: labels.previous })
190
+ ]
191
+ }
192
+ ) }),
193
+ paged && /* @__PURE__ */ jsxs2(Fragment, { children: [
194
+ /* @__PURE__ */ jsx2("li", { className: s2.pages(), children: /* @__PURE__ */ jsx2("ul", { className: "contents", children: pageRange(current, count, siblings, boundaries).map(
195
+ (n, i) => n === "\u2026" ? /* @__PURE__ */ jsx2("li", { "aria-hidden": true, className: s2.ellipsis(), children: "\u2026" }, `gap-${i}`) : /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(Item, { href: href(n), onClick: go(n), disabled, current: n === current, label: labels.page(n), className: s2.item(), children: n }) }, n)
196
+ ) }) }),
197
+ /* @__PURE__ */ jsx2("li", { className: s2.compact(), children: labels.compact(current, count) })
198
+ ] }),
199
+ /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsxs2(
200
+ Item,
201
+ {
202
+ href: href(current + 1),
203
+ onClick: paged ? go(current + 1) : onNext,
204
+ disabled: disabled || !canNext,
205
+ label: labels.next,
206
+ className: s2.item({ className: s2.edge() }),
207
+ children: [
208
+ /* @__PURE__ */ jsx2("span", { className: "hidden @lg:inline", children: labels.next }),
209
+ /* @__PURE__ */ jsx2(ChevronGlyph, { direction: "right" })
210
+ ]
211
+ }
212
+ ) })
213
+ ] })
214
+ ] });
215
+ }
216
+
217
+ // src/data-table/engine.ts
218
+ import {
219
+ columnFacetingFeature,
220
+ columnFilteringFeature,
221
+ columnGroupingFeature,
222
+ columnPinningFeature,
223
+ columnResizingFeature,
224
+ columnSizingFeature,
225
+ columnVisibilityFeature,
226
+ createExpandedRowModel,
227
+ createFacetedMinMaxValues,
228
+ createFacetedRowModel,
229
+ createFacetedUniqueValues,
230
+ createFilteredRowModel,
231
+ createGroupedRowModel,
232
+ createPaginatedRowModel,
233
+ createSortedRowModel,
234
+ rowExpandingFeature,
235
+ rowPaginationFeature,
236
+ rowSelectionFeature,
237
+ rowSortingFeature,
238
+ tableFeatures
239
+ } from "@tanstack/react-table";
240
+ var features = tableFeatures({
241
+ columnFilteringFeature,
242
+ filteredRowModel: createFilteredRowModel(),
243
+ columnFacetingFeature,
244
+ facetedRowModel: createFacetedRowModel(),
245
+ facetedUniqueValues: createFacetedUniqueValues(),
246
+ facetedMinMaxValues: createFacetedMinMaxValues(),
247
+ columnGroupingFeature,
248
+ groupedRowModel: createGroupedRowModel(),
249
+ rowSortingFeature,
250
+ sortedRowModel: createSortedRowModel(),
251
+ rowExpandingFeature,
252
+ expandedRowModel: createExpandedRowModel(),
253
+ rowPaginationFeature,
254
+ paginatedRowModel: createPaginatedRowModel(),
255
+ rowSelectionFeature,
256
+ columnVisibilityFeature,
257
+ columnPinningFeature,
258
+ columnSizingFeature,
259
+ columnResizingFeature
260
+ });
261
+ var empty = (value) => value == null || value === "";
262
+ function compareValues(a, b) {
263
+ if (empty(a)) return empty(b) ? 0 : 1;
264
+ if (empty(b)) return -1;
265
+ if (a instanceof Date || b instanceof Date) return Number(a) - Number(b);
266
+ if (typeof a === "number" && typeof b === "number") return a - b;
267
+ return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
268
+ }
269
+ var sortByValue = (a, b, id) => compareValues(a.getValue(id), b.getValue(id));
270
+ var filterText = (row, id, query) => String(row.getValue(id) ?? "").normalize("NFD").toLocaleLowerCase().includes(query.normalize("NFD").toLocaleLowerCase().trim());
271
+ filterText.autoRemove = (value) => typeof value !== "string" || value.trim() === "";
272
+ var filterFacet = (row, id, chosen) => chosen.includes(String(row.getValue(id) ?? ""));
273
+ filterFacet.autoRemove = (value) => !Array.isArray(value) || value.length === 0;
274
+ var filterRange = (row, id, [min, max]) => {
275
+ const raw = row.getValue(id);
276
+ if (empty(raw)) return false;
277
+ const value = Number(raw);
278
+ return (min == null || value >= min) && (max == null || value <= max);
279
+ };
280
+ filterRange.autoRemove = (value) => !Array.isArray(value) || value[0] == null && value[1] == null;
281
+ var FILTERS = { text: filterText, facet: filterFacet, range: filterRange };
282
+
49
283
  // src/data-table/data-table.tsx
50
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
51
- var dataTable = tv2({
284
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
285
+ var dataTable = tv3({
52
286
  slots: {
53
287
  root: "@container w-full overflow-hidden rounded-xl border border-border-default bg-surface-1",
288
+ toolbar: "border-b border-border-default p-3",
54
289
  scroller: "w-full overflow-x-auto",
55
290
  table: "w-full border-collapse text-left text-sm text-fg",
56
291
  caption: "px-4 pt-4 pb-2 text-left text-sm font-semibold text-fg",
57
292
  thead: "bg-surface-2/60",
58
293
  headRow: "border-b border-border-default",
59
- th: "whitespace-nowrap px-4 py-3 text-xs font-semibold text-fg-secondary",
294
+ th: "relative whitespace-nowrap px-4 py-3 text-xs font-semibold text-fg-secondary",
60
295
  sortButton: "-mx-1.5 inline-flex items-center gap-1 rounded-md px-1.5 py-1 hover:bg-surface-3 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[pressed=true]:text-fg",
296
+ resizer: "absolute top-0 right-0 h-full w-1.5 cursor-col-resize touch-none select-none bg-transparent hover:bg-border-strong focus-visible:bg-accent focus-visible:outline-hidden data-[resizing]:bg-accent",
61
297
  tbody: "",
62
298
  tr: "border-b border-border-subtle transition-colors last:border-b-0 hover:bg-surface-2/50 data-[state=selected]:bg-accent-soft",
299
+ groupRow: "bg-surface-2/40 font-semibold",
63
300
  td: "px-4 py-3 align-middle",
64
301
  select: "w-10",
302
+ expander: "w-10",
303
+ toggle: "inline-flex h-7 min-w-7 items-center justify-center gap-1.5 rounded-md text-fg-secondary hover:bg-surface-3 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [&[aria-expanded=true]>svg]:rotate-90 [&>svg]:transition-transform motion-reduce:[&>svg]:transition-none",
304
+ detail: "bg-surface-0 px-4 py-4",
65
305
  message: "px-4 py-10 text-center text-sm text-fg-secondary",
66
306
  skeleton: "h-4 w-full max-w-40 animate-pulse rounded bg-surface-3 motion-reduce:animate-none",
67
307
  footer: "border-t border-border-default px-4 py-3"
68
308
  },
69
309
  variants: {
70
310
  layout: {
71
- table: {},
311
+ table: {
312
+ th: "data-[pin]:sticky data-[pin]:z-[2] data-[pin]:bg-surface-2 data-[pin=start]:left-(--dt-pin) data-[pin=end]:right-(--dt-pin)",
313
+ td: "data-[pin]:sticky data-[pin]:z-[1] data-[pin]:bg-surface-1 data-[pin=start]:left-(--dt-pin) data-[pin=end]:right-(--dt-pin)"
314
+ },
72
315
  // Below 36rem of container: cards. The sortable headers stay, as a row of sort buttons above the cards.
73
316
  auto: {
74
317
  table: "@max-xl:block",
75
318
  caption: "@max-xl:block",
76
319
  thead: "@max-xl:sr-only @max-xl:bg-transparent @max-xl:has-[[data-keep]]:not-sr-only @max-xl:has-[[data-keep]]:block",
77
320
  headRow: "@max-xl:flex @max-xl:flex-wrap @max-xl:items-center @max-xl:gap-2 @max-xl:px-3 @max-xl:py-2",
78
- th: "@max-xl:sr-only @max-xl:data-[keep]:not-sr-only @max-xl:data-[keep]:block @max-xl:p-0",
321
+ th: "@max-xl:sr-only @max-xl:data-[keep]:not-sr-only @max-xl:data-[keep]:block @max-xl:p-0 @xl:data-[pin]:sticky @xl:data-[pin]:z-[2] @xl:data-[pin]:bg-surface-2 @xl:data-[pin=start]:left-(--dt-pin) @xl:data-[pin=end]:right-(--dt-pin)",
79
322
  tbody: "@max-xl:grid @max-xl:gap-3 @max-xl:p-3",
80
323
  tr: "@max-xl:relative @max-xl:grid @max-xl:gap-1.5 @max-xl:rounded-xl @max-xl:border @max-xl:border-border-default @max-xl:bg-surface-0 @max-xl:p-4 @max-xl:last:border-b",
81
- td: "@max-xl:flex @max-xl:items-baseline @max-xl:justify-between @max-xl:gap-4 @max-xl:p-0 @max-xl:text-right @max-xl:before:shrink-0 @max-xl:before:text-xs @max-xl:before:font-semibold @max-xl:before:text-fg-secondary @max-xl:before:content-[attr(data-label)] @max-xl:data-[card=title]:order-first @max-xl:data-[card=title]:pr-8 @max-xl:data-[card=title]:text-left @max-xl:data-[card=title]:text-base @max-xl:data-[card=title]:font-semibold @max-xl:data-[card=title]:before:content-none @max-xl:data-[card=hidden]:hidden @max-xl:data-[card=message]:block @max-xl:data-[card=message]:text-center @max-xl:data-[card=message]:before:content-none",
82
- select: "@max-xl:absolute @max-xl:right-3 @max-xl:top-3 @max-xl:w-auto @max-xl:before:content-none"
324
+ td: "@max-xl:flex @max-xl:items-baseline @max-xl:justify-between @max-xl:gap-4 @max-xl:p-0 @max-xl:text-right @max-xl:before:shrink-0 @max-xl:before:text-xs @max-xl:before:font-semibold @max-xl:before:text-fg-secondary @max-xl:before:content-[attr(data-label)] @max-xl:data-[card=title]:order-first @max-xl:data-[card=title]:pr-8 @max-xl:data-[card=title]:text-left @max-xl:data-[card=title]:text-base @max-xl:data-[card=title]:font-semibold @max-xl:data-[card=title]:before:content-none @max-xl:data-[card=hidden]:hidden @max-xl:data-[card=message]:block @max-xl:data-[card=message]:text-center @max-xl:data-[card=message]:before:content-none @xl:data-[pin]:sticky @xl:data-[pin]:z-[1] @xl:data-[pin]:bg-surface-1 @xl:data-[pin=start]:left-(--dt-pin) @xl:data-[pin=end]:right-(--dt-pin)",
325
+ select: "@max-xl:absolute @max-xl:right-3 @max-xl:top-3 @max-xl:w-auto @max-xl:before:content-none",
326
+ expander: "@max-xl:before:content-none"
83
327
  },
84
328
  cards: {
85
329
  table: "block",
@@ -90,7 +334,8 @@ var dataTable = tv2({
90
334
  tbody: "grid gap-3 p-3",
91
335
  tr: "relative grid gap-1.5 rounded-xl border border-border-default bg-surface-0 p-4 last:border-b",
92
336
  td: "flex items-baseline justify-between gap-4 p-0 text-right before:shrink-0 before:text-xs before:font-semibold before:text-fg-secondary before:content-[attr(data-label)] data-[card=title]:order-first data-[card=title]:pr-8 data-[card=title]:text-left data-[card=title]:text-base data-[card=title]:font-semibold data-[card=title]:before:content-none data-[card=hidden]:hidden data-[card=message]:block data-[card=message]:text-center data-[card=message]:before:content-none",
93
- select: "absolute right-3 top-3 w-auto before:content-none"
337
+ select: "absolute right-3 top-3 w-auto before:content-none",
338
+ expander: "before:content-none"
94
339
  }
95
340
  },
96
341
  density: {
@@ -104,17 +349,34 @@ var dataTableLabels = {
104
349
  selectAll: "Select all rows",
105
350
  selectRow: (name) => `Select ${name}`,
106
351
  loading: "Loading",
107
- empty: "Nothing to show"
352
+ empty: "Nothing to show",
353
+ details: "Details",
354
+ expandRow: (name) => `Show details of ${name}`,
355
+ group: (value, count) => `${value}, ${count} ${count === 1 ? "row" : "rows"}`,
356
+ resize: (header) => `Resize ${header}`,
357
+ results: (count) => `${count.toLocaleString("en")} ${count === 1 ? "row" : "rows"}`,
358
+ filters: "Filter the table",
359
+ clearFilters: "Clear filters",
360
+ removeFilter: (label) => `Remove ${label}`,
361
+ activeFilter: (header, value) => `${header}: ${value}`,
362
+ search: (header) => `Search ${header.toLocaleLowerCase()}`,
363
+ rangeFrom: (header) => `${header} from`,
364
+ rangeTo: (header) => `${header} to`,
365
+ columns: "Columns",
366
+ export: "Export CSV"
108
367
  };
109
- function compare(a, b) {
110
- if (a == null || a === "") return b == null || b === "" ? 0 : 1;
111
- if (b == null || b === "") return -1;
112
- if (a instanceof Date || b instanceof Date) return Number(a) - Number(b);
113
- if (typeof a === "number" && typeof b === "number") return a - b;
114
- return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
368
+ var DataTableContext = React.createContext(null);
369
+ function useDataTable() {
370
+ const context = React.useContext(DataTableContext);
371
+ if (!context) throw new Error("useDataTable: call it inside a DataTable (its `toolbar`, `footer` or a cell)");
372
+ return context;
115
373
  }
116
- var nextSort = (sort, column) => sort?.column !== column ? { column, direction: "ascending" } : sort.direction === "ascending" ? { column, direction: "descending" } : null;
117
374
  var ALIGN = { start: "text-left", center: "text-center", end: "text-right" };
375
+ var NONE = [];
376
+ var NO_RECORD = {};
377
+ var apply = (updater, old) => typeof updater === "function" ? updater(old) : updater;
378
+ var valueOf = (column, row) => column.value ? column.value(row) : row[column.id];
379
+ var display = (value) => value instanceof Date ? value.toISOString() : String(value ?? "");
118
380
  function DataTable({
119
381
  rows,
120
382
  columns,
@@ -124,167 +386,698 @@ function DataTable({
124
386
  sort: sortProp,
125
387
  defaultSort = null,
126
388
  onSortChange,
127
- manualSort,
389
+ filters: filtersProp,
390
+ defaultFilters = NO_RECORD,
391
+ onFiltersChange,
128
392
  selectable,
129
393
  selected: selectedProp,
130
- defaultSelected = [],
394
+ defaultSelected = NONE,
131
395
  onSelectedChange,
132
396
  rowLabel,
397
+ groupBy: groupByProp,
398
+ defaultGroupBy = NONE,
399
+ onGroupByChange,
400
+ columnVisibility: visibilityProp,
401
+ defaultColumnVisibility = NO_RECORD,
402
+ onColumnVisibilityChange,
403
+ pinning,
404
+ resizing,
405
+ getSubRows,
406
+ renderDetail,
407
+ expanded: expandedProp,
408
+ defaultExpanded = NO_RECORD,
409
+ onExpandedChange,
410
+ pageSize: pageSizeProp,
411
+ defaultPageSize,
412
+ onPageSizeChange,
413
+ pageSizeOptions,
414
+ page: pageProp,
415
+ defaultPage = 1,
416
+ onPageChange,
417
+ manual,
418
+ rowCount,
419
+ onStateChange,
133
420
  loading,
134
421
  loadingRows = 5,
135
422
  error,
136
- empty,
423
+ empty: empty2,
137
424
  layout = "auto",
138
425
  density,
139
- virtualize,
426
+ virtualized,
427
+ toolbar,
140
428
  footer,
141
429
  labels: given,
142
430
  className
143
431
  }) {
144
432
  const labels = { ...dataTableLabels, ...given };
145
- const s = dataTable({ layout: virtualize ? "table" : layout, density });
433
+ const effectiveLayout = virtualized ? "table" : layout;
434
+ const s2 = dataTable({ layout: effectiveLayout, density });
146
435
  const [sort, setSort] = useControllable(sortProp, defaultSort, onSortChange);
436
+ const [filters, setFiltersState] = useControllable(filtersProp, defaultFilters, onFiltersChange);
147
437
  const [selected, setSelected] = useControllable(selectedProp, defaultSelected, onSelectedChange);
148
- const [scrollTop, setScrollTop] = React.useState(0);
149
- const valueOf = (column, row) => column.value ? column.value(row) : row[column.id];
150
- const sorted = React.useMemo(() => {
151
- const column = sort && columns.find((c) => c.id === sort.column);
152
- if (!column || manualSort) return rows;
153
- const factor = sort.direction === "ascending" ? 1 : -1;
154
- return [...rows].sort((a, b) => factor * compare(valueOf(column, a), valueOf(column, b)));
155
- }, [rows, columns, sort, manualSort]);
156
- const ids = sorted.map(getRowId);
438
+ const [groupBy, setGroupBy] = useControllable(groupByProp, defaultGroupBy, onGroupByChange);
439
+ const [visibility, setVisibility] = useControllable(visibilityProp, defaultColumnVisibility, onColumnVisibilityChange);
440
+ const [expanded, setExpanded] = useControllable(expandedProp, defaultExpanded, onExpandedChange);
441
+ const paginated = pageSizeProp !== void 0 || defaultPageSize !== void 0;
442
+ const [pageSize, setPageSize] = useControllable(pageSizeProp, defaultPageSize ?? Number.POSITIVE_INFINITY, onPageSizeChange);
443
+ const [page, setPage] = useControllable(pageProp, defaultPage, onPageChange);
444
+ const [sizing, setSizing] = React.useState({});
445
+ const [resizeState, setResizeState] = React.useState(void 0);
446
+ const announce = (next) => onStateChange?.({ sort, filters, page: paginated ? page : 1, pageSize: paginated ? pageSize : Number.POSITIVE_INFINITY, ...next });
447
+ const changeSort = (next) => {
448
+ setSort(next);
449
+ if (paginated && page !== 1) setPage(1);
450
+ announce({ sort: next, page: 1 });
451
+ };
452
+ const changeFilters = (next) => {
453
+ setFiltersState(next);
454
+ if (paginated && page !== 1) setPage(1);
455
+ announce({ filters: next, page: 1 });
456
+ };
457
+ const changePage = (next) => {
458
+ setPage(next);
459
+ announce({ page: next });
460
+ };
461
+ const changePageSize = (next) => {
462
+ setPageSize(next);
463
+ setPage(1);
464
+ announce({ pageSize: next, page: 1 });
465
+ };
466
+ const hasExpander = Boolean(renderDetail || getSubRows);
467
+ const defs = React.useMemo(
468
+ () => columns.map((column) => ({
469
+ id: column.id,
470
+ accessorFn: (row) => valueOf(column, row),
471
+ header: column.header,
472
+ enableSorting: Boolean(column.sortable),
473
+ sortFn: sortByValue,
474
+ sortUndefined: "last",
475
+ enableColumnFilter: Boolean(column.filter),
476
+ filterFn: column.filter ? FILTERS[column.filter] : FILTERS.text,
477
+ enableGrouping: Boolean(column.groupable),
478
+ enableHiding: column.hideable !== false,
479
+ enableResizing: Boolean(resizing),
480
+ ...column.width ? { size: column.width } : {},
481
+ minSize: column.minWidth ?? 64
482
+ })),
483
+ [columns, resizing]
484
+ );
485
+ const pageIndex = Math.max(0, page - 1);
486
+ const table = useTable({
487
+ features,
488
+ columns: defs,
489
+ data: rows,
490
+ getRowId: (row) => getRowId(row),
491
+ getSubRows: getSubRows ? (row) => getSubRows(row) : void 0,
492
+ getRowCanExpand: renderDetail ? () => true : void 0,
493
+ state: {
494
+ sorting: sort ? [{ id: sort.column, desc: sort.direction === "descending" }] : [],
495
+ columnFilters: Object.entries(filters).map(([id, value]) => ({ id, value })),
496
+ rowSelection: Object.fromEntries(selected.map((id) => [id, true])),
497
+ grouping: [...groupBy],
498
+ columnVisibility: { ...visibility },
499
+ expanded: { ...expanded },
500
+ pagination: { pageIndex, pageSize: paginated ? pageSize : Math.max(1, rows.length) },
501
+ columnPinning: { start: [...pinning?.start ?? []], end: [...pinning?.end ?? []] },
502
+ columnSizing: sizing,
503
+ ...resizeState ? { columnResizing: resizeState } : {}
504
+ },
505
+ onSortingChange: (updater) => {
506
+ const [first] = apply(updater, sort ? [{ id: sort.column, desc: sort.direction === "descending" }] : []);
507
+ changeSort(first ? { column: first.id, direction: first.desc ? "descending" : "ascending" } : null);
508
+ },
509
+ onColumnFiltersChange: (updater) => {
510
+ const next = apply(updater, Object.entries(filters).map(([id, value]) => ({ id, value })));
511
+ changeFilters(Object.fromEntries(next.map((f) => [f.id, f.value])));
512
+ },
513
+ onRowSelectionChange: (updater) => {
514
+ const next = apply(updater, Object.fromEntries(selected.map((id) => [id, true])));
515
+ setSelected(Object.keys(next).filter((id) => next[id]));
516
+ },
517
+ onGroupingChange: (updater) => setGroupBy(apply(updater, [...groupBy])),
518
+ onColumnVisibilityChange: (updater) => setVisibility(apply(updater, { ...visibility })),
519
+ onExpandedChange: (updater) => {
520
+ const next = apply(updater, { ...expanded });
521
+ setExpanded(next === true ? Object.fromEntries(table.getRowModel().rows.map((r) => [r.id, true])) : next);
522
+ },
523
+ onColumnSizingChange: (updater) => setSizing((old) => apply(updater, old)),
524
+ onColumnResizingChange: (updater) => setResizeState((old) => apply(updater, old)),
525
+ enableSortingRemoval: true,
526
+ sortDescFirst: false,
527
+ manualSorting: manual?.sorting,
528
+ manualFiltering: manual?.filtering,
529
+ manualPagination: manual?.pagination || !paginated,
530
+ rowCount: manual?.pagination ? rowCount : void 0,
531
+ autoResetPageIndex: false,
532
+ autoResetExpanded: false,
533
+ groupedColumnMode: false,
534
+ columnResizeMode: "onChange",
535
+ enableRowSelection: (row) => !row.getIsGrouped()
536
+ });
537
+ const visibleColumns = table.getVisibleLeafColumns();
538
+ const byId = new Map(columns.map((c) => [c.id, c]));
539
+ const pageRows = table.getRowModel().rows;
540
+ const leafCount = (manual?.pagination || manual?.filtering) && rowCount !== void 0 ? rowCount : table.getFilteredRowModel().rows.length;
541
+ const pageCount = paginated ? Math.max(1, manual?.pagination ? Math.ceil((rowCount ?? rows.length) / pageSize) : table.getPageCount()) : 1;
542
+ React.useEffect(() => {
543
+ if (paginated && !loading && page > pageCount) changePage(pageCount);
544
+ }, [pageCount]);
545
+ const selectableIds = pageRows.filter((r) => !r.getIsGrouped()).map((r) => r.id);
157
546
  const chosen = new Set(selected);
158
- const allChosen = ids.length > 0 && ids.every((id) => chosen.has(id));
159
- const someChosen = ids.some((id) => chosen.has(id));
160
- const toggleAll = () => setSelected(allChosen ? selected.filter((id) => !ids.includes(id)) : [.../* @__PURE__ */ new Set([...selected, ...ids])]);
161
- const toggle = (id) => setSelected(chosen.has(id) ? selected.filter((x) => x !== id) : [...selected, id]);
162
- const span = columns.length + (selectable ? 1 : 0);
163
- let windowed = sorted;
164
- let before = 0;
165
- let after = 0;
166
- let offset = 0;
167
- if (virtualize && !loading) {
168
- const overscan = virtualize.overscan ?? 6;
169
- const start = Math.max(0, Math.floor(scrollTop / virtualize.rowHeight) - overscan);
170
- const end = Math.min(sorted.length, Math.ceil((scrollTop + virtualize.height) / virtualize.rowHeight) + overscan);
171
- windowed = sorted.slice(start, end);
172
- before = start * virtualize.rowHeight;
173
- after = (sorted.length - end) * virtualize.rowHeight;
174
- offset = start;
175
- }
176
- const message = (content, alert) => /* @__PURE__ */ jsx2("tr", { role: "row", className: s.tr({ className: "hover:bg-transparent" }), children: /* @__PURE__ */ jsx2("td", { role: "cell", colSpan: span, "data-card": "message", className: s.td({ className: s.message() }), children: alert ? /* @__PURE__ */ jsx2("div", { role: "alert", children: content }) : content }) });
547
+ const allChosen = selectableIds.length > 0 && selectableIds.every((id) => chosen.has(id));
548
+ const someChosen = selectableIds.some((id) => chosen.has(id));
549
+ const toggleAll = () => setSelected(allChosen ? selected.filter((id) => !selectableIds.includes(id)) : [.../* @__PURE__ */ new Set([...selected, ...selectableIds])]);
550
+ const span = visibleColumns.length + (selectable ? 1 : 0) + (hasExpander ? 1 : 0);
551
+ const fixedLayout = Boolean(resizing || pinning?.start?.length || pinning?.end?.length);
552
+ const nameOf = (row) => rowLabel ? rowLabel(row.original) : row.id;
553
+ const scroller = React.useRef(null);
554
+ const fallbacks = useLayoutFallbacks(virtualized?.height ?? 0, virtualized?.estimateRowHeight ?? 44);
555
+ const virtualizer = useVirtualizer({
556
+ ...fallbacks,
557
+ count: pageRows.length,
558
+ getScrollElement: () => scroller.current,
559
+ estimateSize: () => virtualized?.estimateRowHeight ?? 44,
560
+ overscan: virtualized?.overscan ?? 6,
561
+ initialRect: { width: 0, height: virtualized?.height ?? 0 },
562
+ getItemKey: (index) => pageRows[index]?.id ?? index,
563
+ enabled: Boolean(virtualized) && !loading
564
+ });
565
+ const windowItems = virtualized && !loading ? virtualizer.getVirtualItems() : null;
566
+ const before = windowItems?.[0]?.start ?? 0;
567
+ const after = windowItems && windowItems.length > 0 ? virtualizer.getTotalSize() - (windowItems.at(-1)?.end ?? 0) : 0;
568
+ const pin = (column) => {
569
+ const position = column.getIsPinned();
570
+ if (!position) return {};
571
+ const offset = position === "start" ? column.getStart("start") : column.getAfter("end");
572
+ return { "data-pin": position, style: { ["--dt-pin"]: `${offset}px` } };
573
+ };
574
+ const width = (column) => fixedLayout || byId.get(column.id)?.width ? { width: column.getSize() } : void 0;
575
+ const message = (content, alert) => /* @__PURE__ */ jsx3("tr", { role: "row", className: s2.tr({ className: "hover:bg-transparent" }), children: /* @__PURE__ */ jsx3("td", { role: "cell", colSpan: span, "data-card": "message", className: s2.td({ className: s2.message() }), children: alert ? /* @__PURE__ */ jsx3("div", { role: "alert", children: content }) : content }) });
576
+ const cardOf = (column) => column?.card === "title" ? "title" : column?.card === "hidden" ? "hidden" : void 0;
577
+ const dataCell = (row, cell, first) => {
578
+ const column = byId.get(cell.column.id);
579
+ if (!column) return null;
580
+ const { style, ...pinned } = pin(cell.column);
581
+ let content;
582
+ if (row.getIsGrouped()) {
583
+ const leaves = row.getLeafRows().filter((r) => !r.getIsGrouped()).map((r) => r.original);
584
+ if (cell.column.id === row.groupingColumnId) {
585
+ const value = display(row.groupingValue);
586
+ content = /* @__PURE__ */ jsxs3("button", { type: "button", className: s2.toggle({ className: "px-1.5 font-semibold text-fg" }), "aria-expanded": row.getIsExpanded(), onClick: row.getToggleExpandedHandler(), children: [
587
+ /* @__PURE__ */ jsx3(ChevronGlyph, { direction: "right", className: "h-4 w-4" }),
588
+ labels.group(value, leaves.length)
589
+ ] });
590
+ } else content = column.aggregate ? column.aggregate(leaves) : null;
591
+ } else content = column.cell ? column.cell(row.original) : display(valueOf(column, row.original));
592
+ return /* @__PURE__ */ jsx3(
593
+ "td",
594
+ {
595
+ role: "cell",
596
+ "data-label": column.header,
597
+ "data-card": row.getIsGrouped() ? cell.column.id === row.groupingColumnId ? "title" : column.aggregate ? void 0 : "hidden" : cardOf(column),
598
+ ...pinned,
599
+ className: s2.td({ className: ALIGN[column.align ?? "start"] }),
600
+ style: { ...style, ...first && row.depth > 0 && !row.getIsGrouped() ? { paddingInlineStart: `${1 + row.depth * 1.25}rem` } : {} },
601
+ children: content
602
+ },
603
+ cell.id
604
+ );
605
+ };
606
+ const renderRow = (row, index) => {
607
+ const isChosen = chosen.has(row.id);
608
+ const grouped = row.getIsGrouped();
609
+ const canExpand = !grouped && (renderDetail ? true : row.getCanExpand());
610
+ const cells = row.getVisibleCells();
611
+ const main = /* @__PURE__ */ jsxs3(
612
+ "tr",
613
+ {
614
+ role: "row",
615
+ "data-index": windowItems ? index : void 0,
616
+ ref: windowItems ? virtualizer.measureElement : void 0,
617
+ "aria-rowindex": virtualized ? index + 2 : void 0,
618
+ "data-state": isChosen ? "selected" : void 0,
619
+ "data-group": grouped ? "" : void 0,
620
+ className: s2.tr({ className: grouped ? s2.groupRow() : void 0 }),
621
+ children: [
622
+ selectable && /* @__PURE__ */ jsx3("td", { role: "cell", className: s2.td({ className: s2.select() }), children: grouped ? null : /* @__PURE__ */ jsx3(Checkbox, { checked: isChosen, onCheckedChange: () => row.toggleSelected(), "aria-label": labels.selectRow(nameOf(row)) }) }),
623
+ hasExpander && /* @__PURE__ */ jsx3("td", { role: "cell", className: s2.td({ className: s2.expander() }), children: canExpand ? /* @__PURE__ */ jsx3("button", { type: "button", className: s2.toggle(), "aria-expanded": row.getIsExpanded(), "aria-label": labels.expandRow(nameOf(row)), onClick: row.getToggleExpandedHandler(), children: /* @__PURE__ */ jsx3(ChevronGlyph, { direction: "right", className: "h-4 w-4" }) }) : null }),
624
+ cells.map((cell, i) => dataCell(row, cell, i === 0))
625
+ ]
626
+ },
627
+ row.id
628
+ );
629
+ if (!renderDetail || grouped || !row.getIsExpanded() || virtualized) return main;
630
+ return /* @__PURE__ */ jsxs3(React.Fragment, { children: [
631
+ main,
632
+ /* @__PURE__ */ jsx3("tr", { role: "row", className: s2.tr({ className: "hover:bg-transparent" }), children: /* @__PURE__ */ jsx3("td", { role: "cell", colSpan: span, "data-card": "message", className: s2.td({ className: s2.detail() }), children: renderDetail(row.original) }) })
633
+ ] }, row.id);
634
+ };
177
635
  let body;
178
636
  if (loading) {
179
- body = Array.from({ length: loadingRows }, (_, i) => /* @__PURE__ */ jsxs2("tr", { role: "row", "aria-hidden": true, className: s.tr(), children: [
180
- selectable && /* @__PURE__ */ jsx2("td", { role: "cell", className: s.td({ className: s.select() }) }),
181
- columns.map((column) => /* @__PURE__ */ jsx2("td", { role: "cell", "data-card": column.card === "title" ? "title" : column.card === "hidden" ? "hidden" : void 0, className: s.td(), children: /* @__PURE__ */ jsx2("div", { className: s.skeleton() }) }, column.id))
637
+ body = Array.from({ length: loadingRows }, (_, i) => /* @__PURE__ */ jsxs3("tr", { role: "row", "aria-hidden": true, className: s2.tr(), children: [
638
+ selectable && /* @__PURE__ */ jsx3("td", { role: "cell", className: s2.td({ className: s2.select() }) }),
639
+ hasExpander && /* @__PURE__ */ jsx3("td", { role: "cell", className: s2.td({ className: s2.expander() }) }),
640
+ visibleColumns.map((column) => /* @__PURE__ */ jsx3("td", { role: "cell", "data-card": cardOf(byId.get(column.id)), className: s2.td(), children: /* @__PURE__ */ jsx3("div", { className: s2.skeleton() }) }, column.id))
182
641
  ] }, `loading-${i}`));
183
642
  } else if (error) body = message(error, true);
184
- else if (sorted.length === 0) body = message(empty ?? labels.empty);
185
- else {
186
- body = windowed.map((row, i) => {
187
- const id = getRowId(row);
188
- const isChosen = chosen.has(id);
189
- return /* @__PURE__ */ jsxs2(
190
- "tr",
191
- {
192
- role: "row",
193
- "aria-rowindex": virtualize ? offset + i + 2 : void 0,
194
- "data-state": isChosen ? "selected" : void 0,
195
- className: s.tr(),
196
- style: virtualize ? { height: virtualize.rowHeight } : void 0,
197
- children: [
198
- selectable && /* @__PURE__ */ jsx2("td", { role: "cell", className: s.td({ className: s.select() }), children: /* @__PURE__ */ jsx2(Checkbox, { checked: isChosen, onCheckedChange: () => toggle(id), "aria-label": labels.selectRow(rowLabel ? rowLabel(row) : id) }) }),
199
- columns.map((column) => /* @__PURE__ */ jsx2(
200
- "td",
201
- {
202
- role: "cell",
203
- "data-label": column.header,
204
- "data-card": column.card === "title" ? "title" : column.card === "hidden" ? "hidden" : void 0,
205
- className: s.td({ className: ALIGN[column.align ?? "start"] }),
206
- children: column.cell ? column.cell(row) : String(valueOf(column, row) ?? "")
207
- },
208
- column.id
209
- ))
210
- ]
211
- },
212
- id
213
- );
214
- });
215
- }
216
- const table = /* @__PURE__ */ jsxs2(
643
+ else if (pageRows.length === 0) body = message(empty2 ?? labels.empty);
644
+ else if (windowItems) body = windowItems.map((item) => renderRow(pageRows[item.index], item.index));
645
+ else body = pageRows.map((row, i) => renderRow(row, i));
646
+ const headers = table.getHeaderGroups().at(-1)?.headers ?? [];
647
+ const tableElement = /* @__PURE__ */ jsxs3(
217
648
  "table",
218
649
  {
219
650
  role: "table",
220
651
  "aria-busy": loading || void 0,
221
- "aria-rowcount": virtualize ? sorted.length + 1 : void 0,
222
- className: s.table(),
652
+ "aria-rowcount": virtualized ? pageRows.length + 1 : void 0,
653
+ className: s2.table({ className: fixedLayout ? "table-fixed" : void 0 }),
654
+ style: fixedLayout ? { minWidth: table.getTotalSize() + (selectable ? 40 : 0) + (hasExpander ? 40 : 0) } : void 0,
223
655
  children: [
224
- /* @__PURE__ */ jsxs2("caption", { className: showCaption ? s.caption() : "sr-only", children: [
656
+ /* @__PURE__ */ jsxs3("caption", { className: showCaption ? s2.caption() : "sr-only", children: [
225
657
  caption,
226
- loading ? /* @__PURE__ */ jsx2("span", { className: "sr-only", children: ` \u2014 ${labels.loading}` }) : null
658
+ loading ? /* @__PURE__ */ jsx3("span", { className: "sr-only", children: ` \u2014 ${labels.loading}` }) : null
227
659
  ] }),
228
- /* @__PURE__ */ jsx2("thead", { role: "rowgroup", className: s.thead({ className: virtualize ? "sticky top-0 z-10 bg-surface-2" : void 0 }), children: /* @__PURE__ */ jsxs2("tr", { role: "row", "aria-rowindex": virtualize ? 1 : void 0, className: s.headRow(), children: [
229
- selectable && /* @__PURE__ */ jsx2("th", { role: "columnheader", scope: "col", "data-keep": "", className: s.th({ className: s.select() }), children: /* @__PURE__ */ jsx2(
660
+ /* @__PURE__ */ jsx3("thead", { role: "rowgroup", className: s2.thead({ className: virtualized ? "sticky top-0 z-10 bg-surface-2" : void 0 }), children: /* @__PURE__ */ jsxs3("tr", { role: "row", "aria-rowindex": virtualized ? 1 : void 0, className: s2.headRow(), children: [
661
+ selectable && /* @__PURE__ */ jsx3("th", { role: "columnheader", scope: "col", "data-keep": "", className: s2.th({ className: s2.select() }), children: /* @__PURE__ */ jsx3(
230
662
  Checkbox,
231
663
  {
232
664
  checked: allChosen ? true : someChosen ? "indeterminate" : false,
233
665
  onCheckedChange: toggleAll,
234
- disabled: loading || ids.length === 0,
666
+ disabled: loading || selectableIds.length === 0,
235
667
  "aria-label": labels.selectAll
236
668
  }
237
669
  ) }),
238
- columns.map((column) => {
239
- const direction = sort?.column === column.id ? sort.direction : void 0;
240
- return /* @__PURE__ */ jsx2(
670
+ hasExpander && /* @__PURE__ */ jsx3("th", { role: "columnheader", scope: "col", className: s2.th({ className: s2.expander() }), children: /* @__PURE__ */ jsx3("span", { className: "sr-only", children: labels.details }) }),
671
+ headers.map((header) => {
672
+ const column = byId.get(header.column.id);
673
+ if (!column) return null;
674
+ const sorted = header.column.getIsSorted();
675
+ const direction = sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : void 0;
676
+ const { style, ...pinned } = pin(header.column);
677
+ return /* @__PURE__ */ jsxs3(
241
678
  "th",
242
679
  {
243
680
  role: "columnheader",
244
681
  scope: "col",
245
682
  "aria-sort": column.sortable ? direction ?? "none" : void 0,
246
683
  "data-keep": column.sortable ? "" : void 0,
247
- className: s.th({ className: ALIGN[column.align ?? "start"] }),
248
- style: column.width ? { width: column.width } : void 0,
249
- children: column.sortable ? /* @__PURE__ */ jsxs2("button", { type: "button", "aria-pressed": Boolean(direction), className: s.sortButton(), onClick: () => setSort(nextSort(sort, column.id)), children: [
250
- column.header,
251
- /* @__PURE__ */ jsx2(SortGlyph, { direction, className: "h-3.5 w-3.5" })
252
- ] }) : column.headerHidden ? /* @__PURE__ */ jsx2("span", { className: "sr-only", children: column.header }) : column.header
684
+ ...pinned,
685
+ className: s2.th({ className: ALIGN[column.align ?? "start"] }),
686
+ style: { ...style, ...width(header.column) },
687
+ children: [
688
+ column.sortable ? /* @__PURE__ */ jsxs3("button", { type: "button", "aria-pressed": Boolean(direction), className: s2.sortButton(), onClick: () => header.column.toggleSorting(void 0, false), children: [
689
+ column.header,
690
+ /* @__PURE__ */ jsx3(SortGlyph, { direction, className: "h-3.5 w-3.5" })
691
+ ] }) : column.headerHidden ? /* @__PURE__ */ jsx3("span", { className: "sr-only", children: column.header }) : column.header,
692
+ resizing && header.column.getCanResize() ? /* @__PURE__ */ jsx3(
693
+ ResizeHandle,
694
+ {
695
+ label: labels.resize(column.header),
696
+ size: header.column.getSize(),
697
+ min: column.minWidth ?? 64,
698
+ resizing: header.column.getIsResizing(),
699
+ className: s2.resizer(),
700
+ onPointerStart: header.getResizeHandler(),
701
+ onSize: (size) => setSizing((old) => ({ ...old, [header.column.id]: size }))
702
+ }
703
+ ) : null
704
+ ]
253
705
  },
254
- column.id
706
+ header.id
255
707
  );
256
708
  })
257
709
  ] }) }),
258
- /* @__PURE__ */ jsxs2("tbody", { role: "rowgroup", className: s.tbody(), children: [
259
- before > 0 && /* @__PURE__ */ jsx2("tr", { "aria-hidden": true, style: { height: before }, children: /* @__PURE__ */ jsx2("td", { colSpan: span }) }),
710
+ /* @__PURE__ */ jsxs3("tbody", { role: "rowgroup", className: s2.tbody(), children: [
711
+ before > 0 && /* @__PURE__ */ jsx3("tr", { "aria-hidden": true, style: { height: before }, children: /* @__PURE__ */ jsx3("td", { colSpan: span }) }),
260
712
  body,
261
- after > 0 && /* @__PURE__ */ jsx2("tr", { "aria-hidden": true, style: { height: after }, children: /* @__PURE__ */ jsx2("td", { colSpan: span }) })
713
+ after > 0 && /* @__PURE__ */ jsx3("tr", { "aria-hidden": true, style: { height: after }, children: /* @__PURE__ */ jsx3("td", { colSpan: span }) })
262
714
  ] })
263
715
  ]
264
716
  }
265
717
  );
266
- return /* @__PURE__ */ jsxs2("div", { className: s.root({ className }), children: [
267
- virtualize ? (
718
+ const context = {
719
+ table,
720
+ columns,
721
+ labels,
722
+ filters,
723
+ setFilters: changeFilters,
724
+ resultCount: leafCount,
725
+ exportRows: () => table.getSortedRowModel().flatRows.filter((r) => !r.getIsGrouped()).map((r) => r.original),
726
+ manualFiltering: Boolean(manual?.filtering)
727
+ };
728
+ const pagination2 = paginated ? /* @__PURE__ */ jsx3(
729
+ Pagination,
730
+ {
731
+ page: Math.min(page, pageCount),
732
+ pageCount,
733
+ onPageChange: changePage,
734
+ pageSize: pageSizeOptions ? pageSize : void 0,
735
+ pageSizeOptions,
736
+ onPageSizeChange: pageSizeOptions ? changePageSize : void 0,
737
+ disabled: loading,
738
+ labels: labels.pagination
739
+ }
740
+ ) : null;
741
+ return /* @__PURE__ */ jsx3(DataTableContext.Provider, { value: context, children: /* @__PURE__ */ jsxs3("div", { className: s2.root({ className }), children: [
742
+ toolbar ? /* @__PURE__ */ jsx3("div", { className: s2.toolbar(), children: toolbar }) : null,
743
+ virtualized ? (
268
744
  // A scrolling region the keyboard can reach: Tab lands on it, the arrows scroll it.
269
- /* @__PURE__ */ jsx2(
745
+ /* @__PURE__ */ jsx3(
270
746
  "div",
271
747
  {
748
+ ref: scroller,
272
749
  tabIndex: 0,
273
750
  role: "region",
274
751
  "aria-label": caption,
275
- className: s.scroller({ className: "overflow-y-auto focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" }),
276
- style: { height: virtualize.height },
277
- onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
278
- children: table
752
+ className: s2.scroller({ className: "overflow-y-auto focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" }),
753
+ style: { height: virtualized.height },
754
+ children: tableElement
279
755
  }
280
756
  )
281
- ) : /* @__PURE__ */ jsx2("div", { className: s.scroller(), children: table }),
282
- footer ? /* @__PURE__ */ jsx2("div", { className: s.footer(), children: footer }) : null
757
+ ) : /* @__PURE__ */ jsx3("div", { className: s2.scroller(), children: tableElement }),
758
+ pagination2 || footer ? /* @__PURE__ */ jsxs3("div", { className: s2.footer({ className: "flex flex-col gap-3" }), children: [
759
+ pagination2,
760
+ footer
761
+ ] }) : null
762
+ ] }) });
763
+ }
764
+ function ResizeHandle({
765
+ label,
766
+ size,
767
+ min,
768
+ resizing,
769
+ className,
770
+ onPointerStart,
771
+ onSize
772
+ }) {
773
+ return (
774
+ // A focusable separator is interactive (WAI-ARIA "window splitter"): its value is the width, the arrows change it.
775
+ /* @__PURE__ */ jsx3(
776
+ "div",
777
+ {
778
+ role: "separator",
779
+ "aria-orientation": "vertical",
780
+ "aria-label": label,
781
+ "aria-valuenow": Math.round(size),
782
+ "aria-valuemin": min,
783
+ tabIndex: 0,
784
+ "data-resizing": resizing ? "" : void 0,
785
+ className,
786
+ onMouseDown: onPointerStart,
787
+ onTouchStart: onPointerStart,
788
+ onClick: (event) => event.stopPropagation(),
789
+ onKeyDown: (event) => {
790
+ const step = event.shiftKey ? 48 : 16;
791
+ if (event.key === "ArrowLeft") onSize(Math.max(min, size - step));
792
+ else if (event.key === "ArrowRight") onSize(size + step);
793
+ else return;
794
+ event.preventDefault();
795
+ }
796
+ }
797
+ )
798
+ );
799
+ }
800
+
801
+ // src/button/button.tsx
802
+ import { Slot } from "radix-ui";
803
+ import { tv as tv4 } from "tailwind-variants";
804
+
805
+ // src/spinner/glyph.tsx
806
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
807
+ var HEX = "M12 3 19.79 7.5 19.79 16.5 12 21 4.21 16.5 4.21 7.5Z";
808
+ function LoaderGlyph({ state = "loading", value, ...props }) {
809
+ const known = typeof value === "number" && Number.isFinite(value);
810
+ const fill = state !== "loading" ? 100 : known ? Math.round(Math.min(1, Math.max(0, value)) * 100) : null;
811
+ return /* @__PURE__ */ jsxs4("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, "data-state": state, "data-progress": fill === null ? void 0 : fill, ...props, children: [
812
+ /* @__PURE__ */ jsx4("path", { d: HEX, stroke: "currentColor", strokeOpacity: "0.22", strokeWidth: "2.2", strokeLinejoin: "round" }),
813
+ /* @__PURE__ */ jsx4(
814
+ "path",
815
+ {
816
+ d: HEX,
817
+ pathLength: 100,
818
+ stroke: "currentColor",
819
+ strokeWidth: "2.2",
820
+ strokeLinejoin: "round",
821
+ strokeLinecap: "round",
822
+ className: "kz-loader-trace",
823
+ strokeDasharray: fill === null ? void 0 : `${fill} ${100 - fill}`
824
+ }
825
+ ),
826
+ state === "loading" && /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "2.6", fill: "currentColor", className: "kz-loader-node" }),
827
+ state === "success" && /* @__PURE__ */ jsx4("path", { d: "M8.3 12.3l2.6 2.6 4.9-5.3", pathLength: 20, stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", className: "kz-loader-mark" }),
828
+ state === "error" && /* @__PURE__ */ jsx4("path", { d: "M9.4 9.4l5.2 5.2M14.6 9.4l-5.2 5.2", pathLength: 20, stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", className: "kz-loader-mark" })
283
829
  ] });
284
830
  }
831
+
832
+ // src/button/button.tsx
833
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
834
+ var buttonVariants = tv4({
835
+ base: "inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 disabled:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98] [&[data-loading]]:opacity-100",
836
+ variants: {
837
+ variant: {
838
+ primary: "bg-accent text-on-accent shadow-sm hover:bg-accent-hover",
839
+ // The brand's signature: accent → accent-2 (on-accent reads on both stops, tested in @krizaka/tokens).
840
+ gradient: "bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110",
841
+ secondary: "border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3",
842
+ outline: "border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent",
843
+ ghost: "text-fg-secondary hover:bg-surface-2 hover:text-fg",
844
+ // The border and the tint carry the danger; the label stays the primary text role, legible on the tint at rest
845
+ // and hovered over every surface (`text-fg-danger` is for text set directly on a surface).
846
+ danger: "border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20"
847
+ },
848
+ size: { sm: "h-8 px-3 text-xs", md: "h-10 px-4 text-sm", lg: "h-12 px-6 text-sm", icon: "h-10 w-10" },
849
+ // `cut`: the Krizaka chamfer — 45° corners (CSS corner-shape, where supported; rounded elsewhere). The border, the
850
+ // focus ring and the hover follow the cut, unlike a clip-path.
851
+ shape: { rounded: "rounded-lg", pill: "rounded-full", cut: "rounded-[10px] [corner-shape:bevel]" }
852
+ },
853
+ defaultVariants: { variant: "secondary", size: "md", shape: "rounded" }
854
+ });
855
+ var STATUS_TEXT = { success: "text-fg-success", error: "text-fg-danger" };
856
+ function Button({
857
+ asChild,
858
+ loading,
859
+ progress,
860
+ status,
861
+ announce,
862
+ indicator = true,
863
+ shimmer,
864
+ magnetic,
865
+ variant,
866
+ size,
867
+ shape,
868
+ className,
869
+ disabled,
870
+ children,
871
+ ...props
872
+ }) {
873
+ const Comp = asChild ? Slot.Root : "button";
874
+ const known = loading && typeof progress === "number" && Number.isFinite(progress);
875
+ const ratio = known ? Math.min(1, Math.max(0, progress)) : 0;
876
+ const glyph = indicator && !asChild && (loading || status) ? status ?? "loading" : null;
877
+ const filled = variant === "primary" || variant === "gradient";
878
+ const button = /* @__PURE__ */ jsx5(
879
+ Comp,
880
+ {
881
+ type: asChild ? void 0 : "button",
882
+ "aria-busy": loading || void 0,
883
+ "data-loading": loading ? "" : void 0,
884
+ "data-variant": variant ?? "secondary",
885
+ "data-kz-status": status ?? void 0,
886
+ "data-kz-magnetic": magnetic ? "" : void 0,
887
+ disabled: asChild ? void 0 : disabled || loading,
888
+ "aria-disabled": asChild && (disabled || loading) ? true : void 0,
889
+ className: buttonVariants({
890
+ variant,
891
+ size,
892
+ shape,
893
+ className: [known && "relative isolate overflow-hidden", shimmer && "kz-sheen", className].filter(Boolean).join(" ") || void 0
894
+ }),
895
+ ...props,
896
+ children: asChild ? children : /* @__PURE__ */ jsxs5(Fragment3, { children: [
897
+ known && /* @__PURE__ */ jsx5("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 -z-10 bg-current opacity-15 transition-[width] duration-500", style: { width: `${ratio * 100}%` } }),
898
+ glyph && /* @__PURE__ */ jsx5(
899
+ "span",
900
+ {
901
+ "aria-hidden": true,
902
+ "data-slot": "indicator",
903
+ className: "-my-1 grid grid-cols-[1fr] transition-[grid-template-columns,margin,opacity] duration-300 starting:-mr-2 starting:grid-cols-[0fr] starting:opacity-0",
904
+ children: /* @__PURE__ */ jsx5("span", { className: "overflow-hidden", children: /* @__PURE__ */ jsx5(LoaderGlyph, { state: glyph, value: known ? ratio : null, className: ["h-4 w-4", glyph !== "loading" && !filled && STATUS_TEXT[glyph]].filter(Boolean).join(" ") }) })
905
+ }
906
+ ),
907
+ children
908
+ ] })
909
+ }
910
+ );
911
+ if (announce === void 0 || asChild) return button;
912
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
913
+ button,
914
+ /* @__PURE__ */ jsx5("span", { role: "status", className: "sr-only", children: announce })
915
+ ] });
916
+ }
917
+
918
+ // src/field/field.tsx
919
+ import { tv as tv5 } from "tailwind-variants";
920
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
921
+ var field2 = tv5({
922
+ slots: {
923
+ root: "flex flex-col gap-1.5",
924
+ label: "text-xs font-semibold text-fg-secondary",
925
+ control: "flex h-11 w-full rounded-lg border border-border-default bg-surface-2 px-3 text-sm text-fg placeholder:text-fg-muted transition-[border-color,box-shadow] focus-visible:border-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger aria-invalid:ring-danger/30",
926
+ hint: "text-xs text-fg-secondary",
927
+ // The message reads in the danger text role (≥ 4.5:1 on every surface, both themes); the dot is the danger fill.
928
+ error: "flex items-center gap-1.5 text-xs font-medium text-fg-danger before:h-1.5 before:w-1.5 before:shrink-0 before:rounded-full before:bg-danger",
929
+ selectWrap: "relative flex w-full",
930
+ chevron: "pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-fg-secondary"
931
+ }
932
+ });
933
+ var s = field2();
934
+ var invalidProps = (invalid) => invalid ? { "aria-invalid": true, "data-invalid": "" } : {};
935
+ function Input({ invalid, className, ...props }) {
936
+ return /* @__PURE__ */ jsx6("input", { ...invalidProps(invalid), className: s.control({ className }), ...props });
937
+ }
938
+
939
+ // src/data-table/toolbar.tsx
940
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
941
+ var asRange = (value) => Array.isArray(value) && value.length === 2 && (typeof value[0] !== "string" || typeof value[1] !== "string") ? [value[0], value[1]] : [null, null];
942
+ var asList = (value) => Array.isArray(value) ? value : [];
943
+ var number = (text) => text.trim() === "" || Number.isNaN(Number(text)) ? null : Number(text);
944
+ function DataTableFilters({ debounce = 250, children, className }) {
945
+ const { table, columns, labels, filters, setFilters, resultCount, manualFiltering } = useDataTable();
946
+ const set = (id, value) => {
947
+ const next = { ...filters };
948
+ if (value === null || value === "" || Array.isArray(value) && (value.length === 0 || value.length === 2 && value[0] == null && value[1] == null)) delete next[id];
949
+ else next[id] = value;
950
+ setFilters(next);
951
+ };
952
+ const filterable = columns.filter((column) => column.filter);
953
+ const optionsOf = (column) => {
954
+ if (column.options) return column.options;
955
+ const facets = manualFiltering ? /* @__PURE__ */ new Map() : table.getColumn(column.id)?.getFacetedUniqueValues() ?? /* @__PURE__ */ new Map();
956
+ return [...facets.entries()].filter(([value]) => value != null && value !== "").map(([value, count]) => ({ value: String(value), label: String(value), count })).sort((a, b) => a.label.localeCompare(b.label, void 0, { numeric: true }));
957
+ };
958
+ const active = filterable.flatMap((column) => {
959
+ const value = filters[column.id];
960
+ if (value === void 0) return [];
961
+ let text;
962
+ if (column.filter === "facet") {
963
+ const options = optionsOf(column);
964
+ text = asList(value).map((v) => options.find((o) => o.value === v)?.label ?? v).join(", ");
965
+ } else if (column.filter === "range") {
966
+ const [min, max] = asRange(value);
967
+ text = `${min ?? "\u2026"}\u2013${max ?? "\u2026"}`;
968
+ } else text = String(value);
969
+ const label = labels.activeFilter(column.header, text);
970
+ return [{ id: column.id, label, onRemove: () => set(column.id, null) }];
971
+ });
972
+ return /* @__PURE__ */ jsxs7(
973
+ FilterBar,
974
+ {
975
+ label: labels.filters,
976
+ active,
977
+ onClearAll: () => setFilters({}),
978
+ clearLabel: labels.clearFilters,
979
+ removeLabel: labels.removeFilter,
980
+ results: labels.results(resultCount),
981
+ className,
982
+ children: [
983
+ filterable.map((column) => {
984
+ if (column.filter === "text")
985
+ return /* @__PURE__ */ jsx7(
986
+ SearchField,
987
+ {
988
+ label: labels.search(column.header),
989
+ placeholder: labels.search(column.header),
990
+ value: typeof filters[column.id] === "string" ? void 0 : "",
991
+ defaultValue: typeof filters[column.id] === "string" ? filters[column.id] : void 0,
992
+ debounce,
993
+ onSearch: (query) => set(column.id, query),
994
+ landmark: false,
995
+ size: "sm",
996
+ className: "min-w-48 flex-1"
997
+ },
998
+ column.id
999
+ );
1000
+ if (column.filter === "facet")
1001
+ return /* @__PURE__ */ jsx7(FilterMenu, { label: column.header, options: optionsOf(column), value: asList(filters[column.id]), onValueChange: (value) => set(column.id, value) }, column.id);
1002
+ const [min, max] = asRange(filters[column.id]);
1003
+ const bounds = manualFiltering ? void 0 : table.getColumn(column.id)?.getFacetedMinMaxValues();
1004
+ return /* @__PURE__ */ jsxs7("span", { className: "inline-flex items-center gap-1.5", children: [
1005
+ /* @__PURE__ */ jsx7(
1006
+ Input,
1007
+ {
1008
+ type: "number",
1009
+ inputMode: "decimal",
1010
+ "aria-label": labels.rangeFrom(column.header),
1011
+ placeholder: bounds ? String(bounds[0]) : labels.rangeFrom(column.header),
1012
+ value: min ?? "",
1013
+ onChange: (event) => set(column.id, [number(event.currentTarget.value), max]),
1014
+ className: "h-11 w-28"
1015
+ }
1016
+ ),
1017
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": true, className: "text-fg-secondary", children: "\u2013" }),
1018
+ /* @__PURE__ */ jsx7(
1019
+ Input,
1020
+ {
1021
+ type: "number",
1022
+ inputMode: "decimal",
1023
+ "aria-label": labels.rangeTo(column.header),
1024
+ placeholder: bounds ? String(bounds[1]) : labels.rangeTo(column.header),
1025
+ value: max ?? "",
1026
+ onChange: (event) => set(column.id, [min, number(event.currentTarget.value)]),
1027
+ className: "h-11 w-28"
1028
+ }
1029
+ )
1030
+ ] }, column.id);
1031
+ }),
1032
+ children
1033
+ ]
1034
+ }
1035
+ );
1036
+ }
1037
+ function DataTableColumnsMenu({ defaultOpen, className }) {
1038
+ const { table, columns, labels } = useDataTable();
1039
+ return /* @__PURE__ */ jsxs7(DropdownMenu.Root, { defaultOpen, modal: false, children: [
1040
+ /* @__PURE__ */ jsx7(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs7(Button, { variant: "outline", size: "sm", className, children: [
1041
+ labels.columns,
1042
+ /* @__PURE__ */ jsx7(ChevronGlyph, { className: "h-4 w-4" })
1043
+ ] }) }),
1044
+ /* @__PURE__ */ jsx7(DropdownMenu.Content, { align: "end", "aria-label": labels.columns, children: columns.filter((column) => column.hideable !== false).map((column) => {
1045
+ const instance = table.getColumn(column.id);
1046
+ return /* @__PURE__ */ jsx7(
1047
+ DropdownMenu.CheckboxItem,
1048
+ {
1049
+ checked: instance?.getIsVisible() ?? true,
1050
+ onSelect: (event) => event.preventDefault(),
1051
+ onCheckedChange: (checked) => instance?.toggleVisibility(Boolean(checked)),
1052
+ children: column.header
1053
+ },
1054
+ column.id
1055
+ );
1056
+ }) })
1057
+ ] });
1058
+ }
1059
+ function DataTableExport({ filename = "export.csv", onExport, className }) {
1060
+ const { table, columns, labels, exportRows } = useDataTable();
1061
+ const run = () => {
1062
+ const visible = columns.filter((column) => table.getColumn(column.id)?.getIsVisible() !== false);
1063
+ const csv = dataTableCsv(exportRows(), visible);
1064
+ if (onExport) return onExport(csv);
1065
+ const url = URL.createObjectURL(new Blob(["\uFEFF", csv], { type: "text/csv;charset=utf-8" }));
1066
+ const link = Object.assign(document.createElement("a"), { href: url, download: filename });
1067
+ link.click();
1068
+ setTimeout(() => URL.revokeObjectURL(url), 0);
1069
+ };
1070
+ return /* @__PURE__ */ jsx7(Button, { variant: "outline", size: "sm", className, onClick: run, children: labels.export });
1071
+ }
285
1072
  export {
286
1073
  DataTable,
1074
+ DataTableColumnsMenu,
1075
+ DataTableExport,
1076
+ DataTableFilters,
1077
+ compareValues,
287
1078
  dataTable,
288
- dataTableLabels
1079
+ dataTableCsv,
1080
+ dataTableLabels,
1081
+ useDataTable
289
1082
  };
290
1083
  //# sourceMappingURL=data-table.js.map