@thangnqdev/ui 0.1.2 → 0.2.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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -206
  3. package/dist/bento/index.cjs.map +1 -1
  4. package/dist/bento/index.js +2 -2
  5. package/dist/bento/styles.css +2 -46
  6. package/dist/buttons/index.cjs +111 -18
  7. package/dist/buttons/index.cjs.map +1 -1
  8. package/dist/buttons/index.d.cts +1 -1
  9. package/dist/buttons/index.d.ts +1 -1
  10. package/dist/buttons/index.js +3 -3
  11. package/dist/buttons/styles.css +2 -6
  12. package/dist/{chunk-4S6L5V7X.js → chunk-2YJBW4E2.js} +2 -2
  13. package/dist/chunk-2YJBW4E2.js.map +1 -0
  14. package/dist/{chunk-F5Q7BTMH.js → chunk-4CRFNALF.js} +1011 -556
  15. package/dist/chunk-4CRFNALF.js.map +1 -0
  16. package/dist/{chunk-LWFRUJRU.js → chunk-4MS6ZHFQ.js} +225 -41
  17. package/dist/chunk-4MS6ZHFQ.js.map +1 -0
  18. package/dist/{chunk-2OXZK6IP.js → chunk-77KTYOMQ.js} +3 -3
  19. package/dist/chunk-77KTYOMQ.js.map +1 -0
  20. package/dist/{chunk-IABPUCAQ.js → chunk-ADO3SVUA.js} +2 -2
  21. package/dist/chunk-ADO3SVUA.js.map +1 -0
  22. package/dist/{chunk-GXI2H5XA.js → chunk-D7FB3TCA.js} +3 -3
  23. package/dist/chunk-D7FB3TCA.js.map +1 -0
  24. package/dist/chunk-DSEL7ELZ.js +314 -0
  25. package/dist/chunk-DSEL7ELZ.js.map +1 -0
  26. package/dist/{chunk-KJRBRTQE.js → chunk-EXFR2BFJ.js} +37 -28
  27. package/dist/chunk-EXFR2BFJ.js.map +1 -0
  28. package/dist/{chunk-PF4ZHGVW.js → chunk-GXWB6BSY.js} +116 -23
  29. package/dist/chunk-GXWB6BSY.js.map +1 -0
  30. package/dist/{chunk-66VWUJEI.js → chunk-I2FQBMVW.js} +3 -3
  31. package/dist/chunk-I2FQBMVW.js.map +1 -0
  32. package/dist/{chunk-EU2LPUYP.js → chunk-I5O2WCGF.js} +3 -3
  33. package/dist/chunk-I5O2WCGF.js.map +1 -0
  34. package/dist/{chunk-ATYGDTMK.js → chunk-ME4XILG5.js} +5 -5
  35. package/dist/chunk-ME4XILG5.js.map +1 -0
  36. package/dist/{chunk-5FZ6ZFII.js → chunk-PC5SZFZC.js} +373 -150
  37. package/dist/chunk-PC5SZFZC.js.map +1 -0
  38. package/dist/{chunk-C4SUXMI3.js → chunk-S6XJMYME.js} +3 -3
  39. package/dist/chunk-S6XJMYME.js.map +1 -0
  40. package/dist/{chunk-A7P3O5Z5.js → chunk-V52JOSDF.js} +3 -3
  41. package/dist/chunk-V52JOSDF.js.map +1 -0
  42. package/dist/{chunk-NFVONJUK.js → chunk-W5WNWTQC.js} +455 -211
  43. package/dist/chunk-W5WNWTQC.js.map +1 -0
  44. package/dist/{chunk-WAFHC4DQ.js → chunk-YJ64RI7J.js} +3 -3
  45. package/dist/chunk-YJ64RI7J.js.map +1 -0
  46. package/dist/chunk-YOCGBORV.js +155 -0
  47. package/dist/chunk-YOCGBORV.js.map +1 -0
  48. package/dist/chunk-ZEDXARXW.js +96 -0
  49. package/dist/chunk-ZEDXARXW.js.map +1 -0
  50. package/dist/components.css +2 -0
  51. package/dist/core/index.cjs +33 -24
  52. package/dist/core/index.cjs.map +1 -1
  53. package/dist/core/index.js +2 -2
  54. package/dist/core/styles.css +2 -134
  55. package/dist/data-display/index.cjs +770 -247
  56. package/dist/data-display/index.cjs.map +1 -1
  57. package/dist/data-display/index.d.cts +1 -1
  58. package/dist/data-display/index.d.ts +1 -1
  59. package/dist/data-display/index.js +5 -4
  60. package/dist/data-display/styles.css +2 -4
  61. package/dist/feedback/index.cjs.map +1 -1
  62. package/dist/feedback/index.js +2 -2
  63. package/dist/feedback/styles.css +2 -4
  64. package/dist/forms/index.cjs +1211 -459
  65. package/dist/forms/index.cjs.map +1 -1
  66. package/dist/forms/index.d.cts +1 -1
  67. package/dist/forms/index.d.ts +1 -1
  68. package/dist/forms/index.js +8 -8
  69. package/dist/forms/styles.css +2 -6
  70. package/dist/glass/index.cjs.map +1 -1
  71. package/dist/glass/index.js +2 -2
  72. package/dist/glass/styles.css +2 -81
  73. package/dist/{index-D6fE7w-b.d.cts → index-BGt_hecO.d.cts} +1 -0
  74. package/dist/{index-D6fE7w-b.d.ts → index-BGt_hecO.d.ts} +1 -0
  75. package/dist/{index-DzcWh1Hs.d.cts → index-BtDvpZCf.d.cts} +7 -0
  76. package/dist/{index-DzcWh1Hs.d.ts → index-BtDvpZCf.d.ts} +7 -0
  77. package/dist/{index-Cle6QzQl.d.ts → index-COX1sBUN.d.ts} +27 -3
  78. package/dist/{index-BkRqGNG1.d.ts → index-Cg8MuP7u.d.cts} +40 -13
  79. package/dist/{index-BkRqGNG1.d.cts → index-Cg8MuP7u.d.ts} +40 -13
  80. package/dist/{index-CenuF_P2.d.ts → index-DHkPRTvE.d.cts} +39 -8
  81. package/dist/{index-CenuF_P2.d.cts → index-DHkPRTvE.d.ts} +39 -8
  82. package/dist/{index-TyK-Ldpy.d.cts → index-Du_oht9k.d.cts} +27 -3
  83. package/dist/index.cjs +2481 -917
  84. package/dist/index.cjs.map +1 -1
  85. package/dist/index.d.cts +5 -5
  86. package/dist/index.d.ts +5 -5
  87. package/dist/index.js +19 -19
  88. package/dist/layout/index.cjs.map +1 -1
  89. package/dist/layout/index.js +2 -2
  90. package/dist/layout/styles.css +2 -8
  91. package/dist/navigation/index.cjs +288 -48
  92. package/dist/navigation/index.cjs.map +1 -1
  93. package/dist/navigation/index.d.cts +1 -1
  94. package/dist/navigation/index.d.ts +1 -1
  95. package/dist/navigation/index.js +6 -6
  96. package/dist/navigation/styles.css +2 -4
  97. package/dist/overlays/index.cjs +566 -229
  98. package/dist/overlays/index.cjs.map +1 -1
  99. package/dist/overlays/index.d.cts +1 -1
  100. package/dist/overlays/index.d.ts +1 -1
  101. package/dist/overlays/index.js +7 -7
  102. package/dist/overlays/styles.css +2 -8
  103. package/dist/reset.css +45 -0
  104. package/dist/styles.css +1 -1
  105. package/dist/tokens.css +11 -0
  106. package/dist/typography/index.cjs.map +1 -1
  107. package/dist/typography/index.js +4 -4
  108. package/dist/typography/styles.css +2 -5
  109. package/llms.txt +211 -113
  110. package/package.json +160 -116
  111. package/src/bento/BentoCard.tsx +140 -140
  112. package/src/bento/BentoGrid.tsx +50 -50
  113. package/src/bento/index.ts +2 -2
  114. package/src/bento/tokens.css +46 -46
  115. package/src/buttons/Button.tsx +104 -104
  116. package/src/buttons/ButtonGroup.tsx +43 -43
  117. package/src/buttons/IconButton.tsx +79 -79
  118. package/src/buttons/SplitButton.tsx +186 -86
  119. package/src/buttons/ToggleButton.tsx +157 -157
  120. package/src/buttons/index.ts +5 -5
  121. package/src/buttons/tokens.css +6 -6
  122. package/src/core/ThemeProvider.tsx +135 -123
  123. package/src/core/index.ts +2 -2
  124. package/src/core/tokens.css +134 -134
  125. package/src/data-display/Accordion.tsx +147 -147
  126. package/src/data-display/Avatar.tsx +134 -134
  127. package/src/data-display/Card.tsx +112 -112
  128. package/src/data-display/Chip.tsx +74 -74
  129. package/src/data-display/DataTablePro.tsx +299 -121
  130. package/src/data-display/StatCard.tsx +60 -60
  131. package/src/data-display/Table.tsx +74 -74
  132. package/src/data-display/Timeline.tsx +62 -62
  133. package/src/data-display/TreeView.tsx +243 -104
  134. package/src/data-display/tokens.css +4 -4
  135. package/src/feedback/Alert.tsx +80 -80
  136. package/src/feedback/EmptyState.tsx +45 -45
  137. package/src/feedback/Skeleton.tsx +66 -66
  138. package/src/feedback/Spinner.tsx +65 -65
  139. package/src/feedback/index.ts +5 -5
  140. package/src/feedback/tokens.css +4 -4
  141. package/src/forms/Checkbox.tsx +149 -69
  142. package/src/forms/CheckboxGroup.tsx +76 -29
  143. package/src/forms/Combobox.tsx +172 -28
  144. package/src/forms/DatePicker.tsx +385 -81
  145. package/src/forms/FileUpload.tsx +51 -7
  146. package/src/forms/FormField.tsx +115 -115
  147. package/src/forms/Input.tsx +151 -151
  148. package/src/forms/NumberInput.tsx +34 -20
  149. package/src/forms/PasswordInput.tsx +35 -35
  150. package/src/forms/PinInput.tsx +139 -125
  151. package/src/forms/Radio.tsx +142 -142
  152. package/src/forms/RangeSlider.tsx +237 -111
  153. package/src/forms/SearchInput.tsx +34 -34
  154. package/src/forms/Select.tsx +103 -103
  155. package/src/forms/Slider.tsx +85 -85
  156. package/src/forms/Textarea.tsx +114 -114
  157. package/src/forms/tokens.css +6 -6
  158. package/src/glass/GlassButton.tsx +59 -59
  159. package/src/glass/GlassCard.tsx +71 -71
  160. package/src/glass/index.ts +3 -3
  161. package/src/glass/tokens.css +81 -81
  162. package/src/index.ts +26 -26
  163. package/src/layout/AspectRatio.tsx +39 -39
  164. package/src/layout/Container.tsx +53 -53
  165. package/src/layout/Divider.tsx +61 -61
  166. package/src/layout/Flex.tsx +79 -79
  167. package/src/layout/Grid.tsx +124 -124
  168. package/src/layout/PageLayout.tsx +59 -59
  169. package/src/layout/Section.tsx +47 -47
  170. package/src/layout/Spacer.tsx +30 -30
  171. package/src/layout/Stack.tsx +88 -88
  172. package/src/layout/tokens.css +8 -8
  173. package/src/navigation/Breadcrumb.tsx +73 -73
  174. package/src/navigation/CommandPalette.tsx +142 -142
  175. package/src/navigation/DropdownMenu.tsx +244 -160
  176. package/src/navigation/Pagination.tsx +114 -114
  177. package/src/navigation/Stepper.tsx +93 -93
  178. package/src/navigation/Tabs.tsx +265 -133
  179. package/src/navigation/index.ts +6 -6
  180. package/src/navigation/tokens.css +4 -4
  181. package/src/overlays/AlertDialog.tsx +63 -54
  182. package/src/overlays/BottomSheet.tsx +30 -10
  183. package/src/overlays/ContextMenu.tsx +51 -9
  184. package/src/overlays/Drawer.tsx +160 -145
  185. package/src/overlays/FocusTrap.tsx +122 -49
  186. package/src/overlays/Modal.tsx +296 -219
  187. package/src/overlays/Popover.tsx +55 -25
  188. package/src/overlays/Portal.tsx +23 -23
  189. package/src/overlays/Toast.tsx +175 -137
  190. package/src/overlays/Tooltip.tsx +125 -100
  191. package/src/overlays/tokens.css +8 -8
  192. package/src/registry/components.ts +1368 -0
  193. package/src/styles/components.css +39 -0
  194. package/src/styles/index.css +2 -93
  195. package/src/styles/reset.css +45 -0
  196. package/src/styles/tokens.css +11 -2
  197. package/src/typography/Blockquote.tsx +32 -32
  198. package/src/typography/Code.tsx +89 -89
  199. package/src/typography/Heading.tsx +56 -56
  200. package/src/typography/Kbd.tsx +25 -25
  201. package/src/typography/Label.tsx +47 -47
  202. package/src/typography/Link.tsx +56 -56
  203. package/src/typography/Text.tsx +62 -62
  204. package/src/typography/index.ts +7 -7
  205. package/src/typography/tokens.css +5 -5
  206. package/src/utils/cn.ts +6 -6
  207. package/src/utils/scroll-lock.ts +19 -0
  208. package/src/utils/useControllableState.ts +54 -0
  209. package/ui-contract.json +192 -35
  210. package/dist/chunk-2OXZK6IP.js.map +0 -1
  211. package/dist/chunk-4S6L5V7X.js.map +0 -1
  212. package/dist/chunk-5FZ6ZFII.js.map +0 -1
  213. package/dist/chunk-66VWUJEI.js.map +0 -1
  214. package/dist/chunk-A7P3O5Z5.js.map +0 -1
  215. package/dist/chunk-ATYGDTMK.js.map +0 -1
  216. package/dist/chunk-C4SUXMI3.js.map +0 -1
  217. package/dist/chunk-EU2LPUYP.js.map +0 -1
  218. package/dist/chunk-F5Q7BTMH.js.map +0 -1
  219. package/dist/chunk-GXI2H5XA.js.map +0 -1
  220. package/dist/chunk-H3LCNPMC.js +0 -101
  221. package/dist/chunk-H3LCNPMC.js.map +0 -1
  222. package/dist/chunk-IABPUCAQ.js.map +0 -1
  223. package/dist/chunk-KJRBRTQE.js.map +0 -1
  224. package/dist/chunk-LBUBXUYQ.js +0 -40
  225. package/dist/chunk-LBUBXUYQ.js.map +0 -1
  226. package/dist/chunk-LWFRUJRU.js.map +0 -1
  227. package/dist/chunk-NFVONJUK.js.map +0 -1
  228. package/dist/chunk-PF4ZHGVW.js.map +0 -1
  229. package/dist/chunk-WAFHC4DQ.js.map +0 -1
  230. package/dist/chunk-X76KBH4Y.js +0 -71
  231. package/dist/chunk-X76KBH4Y.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -33,25 +33,32 @@ function ThemeProvider({
33
33
  attribute = "data-theme",
34
34
  enableSystem = true
35
35
  }) {
36
- const [theme, setThemeState] = React66.useState(() => {
37
- if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
38
- try {
39
- const stored = window.localStorage.getItem(storageKey);
40
- if (stored) return stored;
41
- } catch {
42
- }
43
- }
44
- return defaultTheme;
45
- });
46
- const [resolvedTheme, setResolvedTheme] = React66.useState("dark");
47
- React66.useEffect(() => {
48
- const root = document.documentElement;
49
- const computeResolved = (t) => {
36
+ const [theme, setThemeState] = React66.useState(defaultTheme);
37
+ const [resolvedTheme, setResolvedTheme] = React66.useState(
38
+ defaultTheme === "light" ? "light" : "dark"
39
+ );
40
+ const computeResolved = React66.useCallback(
41
+ (t) => {
50
42
  if (t === "system" && enableSystem && typeof window !== "undefined" && window.matchMedia) {
51
43
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
52
44
  }
53
45
  return t === "dark" ? "dark" : "light";
54
- };
46
+ },
47
+ [enableSystem]
48
+ );
49
+ React66.useEffect(() => {
50
+ if (typeof window === "undefined") return;
51
+ try {
52
+ const stored = window.localStorage?.getItem(storageKey);
53
+ if (stored && stored !== theme) {
54
+ setThemeState(stored);
55
+ }
56
+ } catch {
57
+ }
58
+ }, [storageKey, theme]);
59
+ React66.useEffect(() => {
60
+ if (typeof document === "undefined") return;
61
+ const root = document.documentElement;
55
62
  const currentResolved = computeResolved(theme);
56
63
  setResolvedTheme(currentResolved);
57
64
  root.setAttribute(attribute, currentResolved);
@@ -68,20 +75,22 @@ function ThemeProvider({
68
75
  } catch {
69
76
  }
70
77
  }
71
- }, [theme, attribute, enableSystem, storageKey]);
78
+ }, [theme, attribute, computeResolved, storageKey]);
72
79
  React66.useEffect(() => {
73
- if (theme !== "system" || !enableSystem) return;
80
+ if (theme !== "system" || !enableSystem || typeof window === "undefined" || !window.matchMedia) return;
74
81
  const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
75
82
  const handleChange = () => {
76
83
  const newResolved = mediaQuery.matches ? "dark" : "light";
77
84
  setResolvedTheme(newResolved);
78
- document.documentElement.setAttribute(attribute, newResolved);
79
- if (newResolved === "dark") {
80
- document.documentElement.classList.add("dark");
81
- document.documentElement.classList.remove("light");
82
- } else {
83
- document.documentElement.classList.add("light");
84
- document.documentElement.classList.remove("dark");
85
+ if (typeof document !== "undefined") {
86
+ document.documentElement.setAttribute(attribute, newResolved);
87
+ if (newResolved === "dark") {
88
+ document.documentElement.classList.add("dark");
89
+ document.documentElement.classList.remove("light");
90
+ } else {
91
+ document.documentElement.classList.add("light");
92
+ document.documentElement.classList.remove("dark");
93
+ }
85
94
  }
86
95
  };
87
96
  mediaQuery.addEventListener("change", handleChange);
@@ -1207,9 +1216,24 @@ var ToggleGroup = React66.forwardRef(
1207
1216
  );
1208
1217
  ToggleGroup.displayName = "ToggleGroup";
1209
1218
  var SplitButton = React66.forwardRef(
1210
- ({ className, items, onMainClick, variant = "primary", size = "md", children, disabled, ...props }, ref) => {
1219
+ ({
1220
+ className,
1221
+ items,
1222
+ onMainClick,
1223
+ variant = "primary",
1224
+ size = "md",
1225
+ children,
1226
+ disabled,
1227
+ menuAriaLabel = "More actions",
1228
+ ...props
1229
+ }, ref) => {
1211
1230
  const [isOpen, setIsOpen] = React66.useState(false);
1231
+ const [focusedIndex, setFocusedIndex] = React66.useState(0);
1212
1232
  const containerRef = React66.useRef(null);
1233
+ const triggerBtnRef = React66.useRef(null);
1234
+ const menuRef = React66.useRef(null);
1235
+ const menuId = `split-button-menu-${React66.useId()}`;
1236
+ const enabledItemsIndices = items.map((item, idx) => !item.disabled ? idx : -1).filter((idx) => idx !== -1);
1213
1237
  React66.useEffect(() => {
1214
1238
  const handleOutside = (e) => {
1215
1239
  if (containerRef.current && !containerRef.current.contains(e.target)) {
@@ -1221,6 +1245,56 @@ var SplitButton = React66.forwardRef(
1221
1245
  }
1222
1246
  return () => document.removeEventListener("mousedown", handleOutside);
1223
1247
  }, [isOpen]);
1248
+ React66.useEffect(() => {
1249
+ if (isOpen && enabledItemsIndices.length > 0) {
1250
+ setFocusedIndex(enabledItemsIndices[0]);
1251
+ const timer = setTimeout(() => {
1252
+ const itemEl = menuRef.current?.querySelector('[role="menuitem"]:not([aria-disabled="true"])');
1253
+ itemEl?.focus();
1254
+ }, 10);
1255
+ return () => clearTimeout(timer);
1256
+ }
1257
+ }, [isOpen]);
1258
+ const handleMenuKeyDown = (e) => {
1259
+ if (!isOpen) return;
1260
+ if (e.key === "ArrowDown") {
1261
+ e.preventDefault();
1262
+ const currentPos = enabledItemsIndices.indexOf(focusedIndex);
1263
+ const nextPos = (currentPos + 1) % enabledItemsIndices.length;
1264
+ const nextIdx = enabledItemsIndices[nextPos];
1265
+ setFocusedIndex(nextIdx);
1266
+ const buttons = menuRef.current?.querySelectorAll('[role="menuitem"]');
1267
+ buttons?.[nextIdx]?.focus();
1268
+ } else if (e.key === "ArrowUp") {
1269
+ e.preventDefault();
1270
+ const currentPos = enabledItemsIndices.indexOf(focusedIndex);
1271
+ const prevPos = (currentPos - 1 + enabledItemsIndices.length) % enabledItemsIndices.length;
1272
+ const prevIdx = enabledItemsIndices[prevPos];
1273
+ setFocusedIndex(prevIdx);
1274
+ const buttons = menuRef.current?.querySelectorAll('[role="menuitem"]');
1275
+ buttons?.[prevIdx]?.focus();
1276
+ } else if (e.key === "Home") {
1277
+ e.preventDefault();
1278
+ if (enabledItemsIndices.length > 0) {
1279
+ const firstIdx = enabledItemsIndices[0];
1280
+ setFocusedIndex(firstIdx);
1281
+ const buttons = menuRef.current?.querySelectorAll('[role="menuitem"]');
1282
+ buttons?.[firstIdx]?.focus();
1283
+ }
1284
+ } else if (e.key === "End") {
1285
+ e.preventDefault();
1286
+ if (enabledItemsIndices.length > 0) {
1287
+ const lastIdx = enabledItemsIndices[enabledItemsIndices.length - 1];
1288
+ setFocusedIndex(lastIdx);
1289
+ const buttons = menuRef.current?.querySelectorAll('[role="menuitem"]');
1290
+ buttons?.[lastIdx]?.focus();
1291
+ }
1292
+ } else if (e.key === "Escape") {
1293
+ e.preventDefault();
1294
+ setIsOpen(false);
1295
+ triggerBtnRef.current?.focus();
1296
+ }
1297
+ };
1224
1298
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: cn("relative inline-flex", className), children: [
1225
1299
  /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: "inline-flex rounded-[var(--ui-radius-md)] shadow-sm", children: [
1226
1300
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1238,34 +1312,62 @@ var SplitButton = React66.forwardRef(
1238
1312
  /* @__PURE__ */ jsxRuntime.jsx(
1239
1313
  Button,
1240
1314
  {
1315
+ ref: triggerBtnRef,
1241
1316
  variant,
1242
1317
  size,
1243
1318
  disabled,
1244
1319
  onClick: () => setIsOpen(!isOpen),
1320
+ onKeyDown: (e) => {
1321
+ if (e.key === "ArrowDown") {
1322
+ e.preventDefault();
1323
+ setIsOpen(true);
1324
+ }
1325
+ },
1245
1326
  "aria-expanded": isOpen,
1246
1327
  "aria-haspopup": "menu",
1247
- className: "rounded-l-none px-2",
1328
+ "aria-controls": isOpen ? menuId : void 0,
1329
+ "aria-label": menuAriaLabel,
1330
+ className: "rounded-l-none px-2 cursor-pointer",
1248
1331
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn("w-4 h-4 transition-transform duration-200", isOpen && "rotate-180") })
1249
1332
  }
1250
1333
  )
1251
1334
  ] }),
1252
- isOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-full right-0 mt-1 w-48 rounded-[var(--ui-radius-md)] bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] shadow-lg py-1 z-50 animate-in fade-in zoom-in-95", children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1253
- "button",
1335
+ isOpen && /* @__PURE__ */ jsxRuntime.jsx(
1336
+ "div",
1254
1337
  {
1255
- type: "button",
1256
- disabled: item.disabled,
1257
- onClick: () => {
1258
- item.onClick();
1259
- setIsOpen(false);
1260
- },
1261
- className: "w-full flex items-center gap-2 px-3 py-2 text-left text-sm text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)] disabled:opacity-50 disabled:pointer-events-none transition-colors",
1262
- children: [
1263
- item.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: item.icon }),
1264
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: item.label })
1265
- ]
1266
- },
1267
- i
1268
- )) })
1338
+ ref: menuRef,
1339
+ id: menuId,
1340
+ role: "menu",
1341
+ "aria-orientation": "vertical",
1342
+ tabIndex: -1,
1343
+ onKeyDown: handleMenuKeyDown,
1344
+ className: "absolute top-full right-0 mt-1 w-48 rounded-[var(--ui-radius-md)] bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] shadow-lg py-1 z-50 animate-in fade-in zoom-in-95 focus:outline-none",
1345
+ children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1346
+ "button",
1347
+ {
1348
+ type: "button",
1349
+ role: "menuitem",
1350
+ tabIndex: focusedIndex === i ? 0 : -1,
1351
+ "aria-disabled": item.disabled,
1352
+ disabled: item.disabled,
1353
+ onClick: () => {
1354
+ item.onClick();
1355
+ setIsOpen(false);
1356
+ triggerBtnRef.current?.focus();
1357
+ },
1358
+ className: cn(
1359
+ "w-full flex items-center gap-2 px-3 py-2 text-left text-sm text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)] focus:bg-[var(--ui-bg-tertiary)] focus:outline-none disabled:opacity-50 disabled:pointer-events-none transition-colors cursor-pointer",
1360
+ focusedIndex === i && "bg-[var(--ui-bg-tertiary)]"
1361
+ ),
1362
+ children: [
1363
+ item.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: item.icon }),
1364
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: item.label })
1365
+ ]
1366
+ },
1367
+ i
1368
+ ))
1369
+ }
1370
+ )
1269
1371
  ] });
1270
1372
  }
1271
1373
  );
@@ -1276,6 +1378,7 @@ var forms_exports = {};
1276
1378
  __export(forms_exports, {
1277
1379
  Checkbox: () => Checkbox,
1278
1380
  CheckboxGroup: () => CheckboxGroup,
1381
+ CheckboxGroupContext: () => CheckboxGroupContext,
1279
1382
  Combobox: () => Combobox,
1280
1383
  CurrencyInput: () => CurrencyInput,
1281
1384
  DatePicker: () => DatePicker,
@@ -1312,6 +1415,7 @@ __export(forms_exports, {
1312
1415
  switchThumbVariants: () => switchThumbVariants,
1313
1416
  switchVariants: () => switchVariants,
1314
1417
  textareaVariants: () => textareaVariants,
1418
+ useCheckboxGroup: () => useCheckboxGroup,
1315
1419
  useFormField: () => useFormField
1316
1420
  });
1317
1421
  var FormFieldContext = React66.createContext(null);
@@ -1710,8 +1814,59 @@ var Select = React66.forwardRef(
1710
1814
  }
1711
1815
  );
1712
1816
  Select.displayName = "Select";
1817
+ var CheckboxGroupContext = React66.createContext(null);
1818
+ var useCheckboxGroup = () => React66.useContext(CheckboxGroupContext);
1819
+ var CheckboxGroup = React66.forwardRef(
1820
+ ({
1821
+ className,
1822
+ orientation = "vertical",
1823
+ children,
1824
+ value: controlledValue,
1825
+ defaultValue = [],
1826
+ onChange,
1827
+ disabled,
1828
+ name,
1829
+ ...props
1830
+ }, ref) => {
1831
+ const [internalValue, setInternalValue] = React66.useState(defaultValue);
1832
+ const isControlled = controlledValue !== void 0;
1833
+ const value = isControlled ? controlledValue : internalValue;
1834
+ const handleValueChange = (nextValue) => {
1835
+ if (!isControlled) {
1836
+ setInternalValue(nextValue);
1837
+ }
1838
+ onChange?.(nextValue);
1839
+ };
1840
+ return /* @__PURE__ */ jsxRuntime.jsx(
1841
+ CheckboxGroupContext.Provider,
1842
+ {
1843
+ value: {
1844
+ value,
1845
+ onChange: handleValueChange,
1846
+ disabled,
1847
+ name
1848
+ },
1849
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1850
+ "div",
1851
+ {
1852
+ ref,
1853
+ role: "group",
1854
+ className: cn(
1855
+ "flex gap-2",
1856
+ orientation === "vertical" ? "flex-col" : "flex-row flex-wrap items-center",
1857
+ className
1858
+ ),
1859
+ ...props,
1860
+ children
1861
+ }
1862
+ )
1863
+ }
1864
+ );
1865
+ }
1866
+ );
1867
+ CheckboxGroup.displayName = "CheckboxGroup";
1713
1868
  var checkboxVariants = classVarianceAuthority.cva(
1714
- "peer shrink-0 rounded-[var(--ui-radius-sm)] border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:cursor-not-allowed disabled:opacity-50 transition-all cursor-pointer flex items-center justify-center text-[var(--ui-primary-fg)]",
1869
+ "peer shrink-0 rounded-[var(--ui-radius-sm)] border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] transition-all flex items-center justify-center text-[var(--ui-primary-fg)] peer-focus-visible:ring-2 peer-focus-visible:ring-[var(--ui-primary)] peer-focus-visible:ring-offset-2",
1715
1870
  {
1716
1871
  variants: {
1717
1872
  size: {
@@ -1726,61 +1881,96 @@ var checkboxVariants = classVarianceAuthority.cva(
1726
1881
  }
1727
1882
  );
1728
1883
  var Checkbox = React66.forwardRef(
1729
- ({ className, size, indeterminate = false, label, checked, disabled, id, onChange, ...props }, ref) => {
1884
+ ({
1885
+ className,
1886
+ size,
1887
+ indeterminate = false,
1888
+ label,
1889
+ checked,
1890
+ defaultChecked = false,
1891
+ disabled,
1892
+ id,
1893
+ name,
1894
+ value: propValue,
1895
+ onChange,
1896
+ onCheckedChange,
1897
+ ...props
1898
+ }, ref) => {
1730
1899
  const formField = useFormField();
1731
- const isDisabled = disabled ?? formField?.isDisabled ?? false;
1900
+ const group = useCheckboxGroup();
1901
+ const isGroupMember = group !== null && propValue !== void 0;
1902
+ const isDisabled = disabled ?? group?.disabled ?? formField?.isDisabled ?? false;
1732
1903
  const checkboxId = id || formField?.id;
1733
- return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: cn("inline-flex items-center gap-2 select-none cursor-pointer", isDisabled && "cursor-not-allowed opacity-50"), children: [
1734
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
1735
- /* @__PURE__ */ jsxRuntime.jsx(
1736
- "input",
1737
- {
1738
- ref,
1739
- type: "checkbox",
1740
- id: checkboxId,
1741
- disabled: isDisabled,
1742
- checked,
1743
- onChange,
1744
- className: "sr-only peer",
1745
- ...props
1746
- }
1747
- ),
1748
- /* @__PURE__ */ jsxRuntime.jsx(
1749
- "div",
1750
- {
1751
- className: cn(
1752
- checkboxVariants({ size }),
1753
- (checked || indeterminate) && "bg-[var(--ui-primary)] border-[var(--ui-primary)]",
1754
- className
1755
- ),
1756
- children: indeterminate ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { className: "w-3.5 h-3.5 stroke-[3]" }) : checked && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-3.5 h-3.5 stroke-[3]" })
1757
- }
1758
- )
1759
- ] }),
1760
- label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-[var(--ui-text-primary)]", children: label })
1761
- ] });
1762
- }
1763
- );
1764
- Checkbox.displayName = "Checkbox";
1765
- var CheckboxGroup = React66.forwardRef(
1766
- ({ className, orientation = "vertical", children, value, onChange, ...props }, ref) => {
1767
- return /* @__PURE__ */ jsxRuntime.jsx(
1768
- "div",
1904
+ const checkboxName = name || group?.name;
1905
+ const isControlled = checked !== void 0;
1906
+ const [internalChecked, setInternalChecked] = React66.useState(defaultChecked);
1907
+ const isChecked = isGroupMember ? group.value.includes(String(propValue)) : isControlled ? Boolean(checked) : internalChecked;
1908
+ const innerRef = React66.useRef(null);
1909
+ React66.useImperativeHandle(ref, () => innerRef.current);
1910
+ React66.useEffect(() => {
1911
+ if (innerRef.current) {
1912
+ innerRef.current.indeterminate = Boolean(indeterminate);
1913
+ }
1914
+ }, [indeterminate]);
1915
+ const handleChange = (e) => {
1916
+ if (isDisabled) return;
1917
+ const newChecked = e.target.checked;
1918
+ if (!isControlled && !isGroupMember) {
1919
+ setInternalChecked(newChecked);
1920
+ }
1921
+ if (isGroupMember && group) {
1922
+ const val = String(propValue);
1923
+ const next = newChecked ? [...group.value, val] : group.value.filter((v) => v !== val);
1924
+ group.onChange(next);
1925
+ }
1926
+ onChange?.(e);
1927
+ onCheckedChange?.(newChecked);
1928
+ };
1929
+ const isVisualActive = isChecked || indeterminate;
1930
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1931
+ "label",
1769
1932
  {
1770
- ref,
1771
- role: "group",
1772
1933
  className: cn(
1773
- "flex gap-2",
1774
- orientation === "vertical" ? "flex-col" : "flex-row flex-wrap items-center",
1775
- className
1934
+ "inline-flex items-center gap-2 select-none cursor-pointer group",
1935
+ isDisabled && "cursor-not-allowed opacity-50"
1776
1936
  ),
1777
- ...props,
1778
- children
1937
+ children: [
1938
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
1939
+ /* @__PURE__ */ jsxRuntime.jsx(
1940
+ "input",
1941
+ {
1942
+ ref: innerRef,
1943
+ type: "checkbox",
1944
+ id: checkboxId,
1945
+ name: checkboxName,
1946
+ value: propValue,
1947
+ disabled: isDisabled,
1948
+ checked: isChecked,
1949
+ onChange: handleChange,
1950
+ "aria-checked": indeterminate ? "mixed" : isChecked,
1951
+ className: "sr-only peer",
1952
+ ...props
1953
+ }
1954
+ ),
1955
+ /* @__PURE__ */ jsxRuntime.jsx(
1956
+ "div",
1957
+ {
1958
+ className: cn(
1959
+ checkboxVariants({ size }),
1960
+ isVisualActive ? "bg-[var(--ui-primary)] border-[var(--ui-primary)] text-white" : "hover:border-[var(--ui-border-strong)]",
1961
+ className
1962
+ ),
1963
+ children: indeterminate ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { className: "w-3.5 h-3.5 stroke-[3]" }) : isChecked && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-3.5 h-3.5 stroke-[3]" })
1964
+ }
1965
+ )
1966
+ ] }),
1967
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-[var(--ui-text-primary)]", children: label })
1968
+ ]
1779
1969
  }
1780
1970
  );
1781
1971
  }
1782
1972
  );
1783
- CheckboxGroup.displayName = "CheckboxGroup";
1973
+ Checkbox.displayName = "Checkbox";
1784
1974
  var RadioGroupContext = React66.createContext(null);
1785
1975
  var radioVariants = classVarianceAuthority.cva(
1786
1976
  "peer shrink-0 rounded-full border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:cursor-not-allowed disabled:opacity-50 transition-all cursor-pointer flex items-center justify-center",
@@ -2040,118 +2230,228 @@ var RangeSlider = React66.forwardRef(
2040
2230
  showValue = false,
2041
2231
  formatValue = (v) => `${v}`,
2042
2232
  className,
2043
- disabled = false
2044
- }, ref) => {
2045
- const [stateValue, setStateValue] = React66.useState(defaultValue);
2046
- const [minVal, maxVal] = controlledValue || stateValue;
2047
- const minPercentage = Math.min(100, Math.max(0, (minVal - min) / (max - min) * 100));
2048
- const maxPercentage = Math.min(100, Math.max(0, (maxVal - min) / (max - min) * 100));
2049
- const handleMinChange = (e) => {
2050
- const val = Math.min(Number(e.target.value), maxVal - step);
2051
- setStateValue([val, maxVal]);
2052
- onChange?.([val, maxVal]);
2053
- };
2054
- const handleMaxChange = (e) => {
2055
- const val = Math.max(Number(e.target.value), minVal + step);
2056
- setStateValue([minVal, val]);
2057
- onChange?.([minVal, val]);
2058
- };
2059
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn("w-full flex items-center gap-4", className), children: [
2060
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full flex items-center h-6 select-none touch-none", children: [
2061
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-2 rounded-full bg-[var(--ui-border-default)] overflow-hidden relative", children: /* @__PURE__ */ jsxRuntime.jsx(
2062
- "div",
2063
- {
2064
- className: "absolute h-full bg-[var(--ui-primary)]",
2065
- style: {
2066
- left: `${minPercentage}%`,
2067
- width: `${maxPercentage - minPercentage}%`
2068
- }
2069
- }
2070
- ) }),
2071
- /* @__PURE__ */ jsxRuntime.jsx(
2072
- "input",
2073
- {
2074
- type: "range",
2075
- min,
2076
- max,
2077
- step,
2078
- value: minVal,
2079
- disabled,
2080
- onChange: handleMinChange,
2081
- className: "absolute inset-0 w-full h-full opacity-0 pointer-events-auto cursor-pointer z-20"
2082
- }
2083
- ),
2084
- /* @__PURE__ */ jsxRuntime.jsx(
2085
- "input",
2086
- {
2087
- type: "range",
2088
- min,
2089
- max,
2090
- step,
2091
- value: maxVal,
2092
- disabled,
2093
- onChange: handleMaxChange,
2094
- className: "absolute inset-0 w-full h-full opacity-0 pointer-events-auto cursor-pointer z-30"
2095
- }
2096
- ),
2097
- /* @__PURE__ */ jsxRuntime.jsx(
2098
- "div",
2099
- {
2100
- className: "absolute w-5 h-5 rounded-full bg-white border-2 border-[var(--ui-primary)] shadow-md pointer-events-none -translate-x-1/2 z-10",
2101
- style: { left: `${minPercentage}%` }
2102
- }
2103
- ),
2104
- /* @__PURE__ */ jsxRuntime.jsx(
2105
- "div",
2106
- {
2107
- className: "absolute w-5 h-5 rounded-full bg-white border-2 border-[var(--ui-primary)] shadow-md pointer-events-none -translate-x-1/2 z-10",
2108
- style: { left: `${maxPercentage}%` }
2109
- }
2110
- )
2111
- ] }),
2112
- showValue && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-semibold font-mono text-[var(--ui-text-secondary)] min-w-[5rem] text-right", children: [
2113
- formatValue(minVal),
2114
- " - ",
2115
- formatValue(maxVal)
2116
- ] })
2117
- ] });
2118
- }
2119
- );
2120
- RangeSlider.displayName = "RangeSlider";
2121
- var PinInput = React66.forwardRef(
2122
- ({
2123
- className,
2124
- length = 4,
2125
- value: controlledValue,
2126
- defaultValue = "",
2127
- onChange,
2128
- onComplete,
2129
- mask = false,
2130
2233
  disabled = false,
2131
- isInvalid = false,
2132
- autoFocus = false,
2133
- ...props
2234
+ name,
2235
+ "aria-label": ariaLabel = "Range slider"
2134
2236
  }, ref) => {
2135
- const [pins, setPins] = React66.useState(() => {
2136
- const initial = (controlledValue !== void 0 ? controlledValue : defaultValue).slice(0, length);
2137
- return Array.from({ length }, (_, i) => initial[i] || "");
2138
- });
2139
- const inputsRef = React66.useRef([]);
2140
- React66.useEffect(() => {
2141
- if (controlledValue !== void 0) {
2142
- setPins(Array.from({ length }, (_, i) => controlledValue[i] || ""));
2237
+ const isControlled = controlledValue !== void 0;
2238
+ const [internalValue, setInternalValue] = React66.useState(defaultValue);
2239
+ const currentValue = isControlled ? controlledValue : internalValue;
2240
+ const [minVal, maxVal] = [
2241
+ Math.max(min, Math.min(currentValue[0], currentValue[1])),
2242
+ Math.min(max, Math.max(currentValue[0], currentValue[1]))
2243
+ ];
2244
+ const trackRef = React66.useRef(null);
2245
+ const draggingThumbRef = React66.useRef(null);
2246
+ const updateValues = React66.useCallback(
2247
+ (newMin, newMax) => {
2248
+ const clampedMin = Math.max(min, Math.min(newMin, newMax));
2249
+ const clampedMax = Math.min(max, Math.max(clampedMin, newMax));
2250
+ const next = [clampedMin, clampedMax];
2251
+ if (!isControlled) {
2252
+ setInternalValue(next);
2253
+ }
2254
+ onChange?.(next);
2255
+ },
2256
+ [min, max, isControlled, onChange]
2257
+ );
2258
+ const getValueFromPointer = React66.useCallback(
2259
+ (clientX) => {
2260
+ if (!trackRef.current) return min;
2261
+ const rect = trackRef.current.getBoundingClientRect();
2262
+ const percent = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
2263
+ const rawValue = min + percent * (max - min);
2264
+ const steppedValue = Math.round((rawValue - min) / step) * step + min;
2265
+ return Math.min(max, Math.max(min, Number(steppedValue.toFixed(4))));
2266
+ },
2267
+ [min, max, step]
2268
+ );
2269
+ const handlePointerDown = (e) => {
2270
+ if (disabled) return;
2271
+ const clickedVal = getValueFromPointer(e.clientX);
2272
+ const distToMin = Math.abs(clickedVal - minVal);
2273
+ const distToMax = Math.abs(clickedVal - maxVal);
2274
+ const targetThumb = distToMin <= distToMax ? "min" : "max";
2275
+ draggingThumbRef.current = targetThumb;
2276
+ if (targetThumb === "min") {
2277
+ updateValues(Math.min(clickedVal, maxVal), maxVal);
2278
+ } else {
2279
+ updateValues(minVal, Math.max(clickedVal, minVal));
2143
2280
  }
2144
- }, [controlledValue, length]);
2145
- React66.useEffect(() => {
2146
- if (autoFocus && inputsRef.current[0]) {
2147
- inputsRef.current[0].focus();
2281
+ e.currentTarget.setPointerCapture(e.pointerId);
2282
+ };
2283
+ const handlePointerMove = (e) => {
2284
+ if (disabled || !draggingThumbRef.current) return;
2285
+ const moveVal = getValueFromPointer(e.clientX);
2286
+ if (draggingThumbRef.current === "min") {
2287
+ updateValues(Math.min(moveVal, maxVal), maxVal);
2288
+ } else {
2289
+ updateValues(minVal, Math.max(moveVal, minVal));
2148
2290
  }
2149
- }, [autoFocus]);
2150
- const handleDigitChange = (index, val) => {
2151
- const sanitized = val.replace(/[^0-9a-zA-Z]/g, "").slice(-1);
2291
+ };
2292
+ const handlePointerUp = (e) => {
2293
+ if (draggingThumbRef.current) {
2294
+ draggingThumbRef.current = null;
2295
+ try {
2296
+ e.currentTarget.releasePointerCapture(e.pointerId);
2297
+ } catch {
2298
+ }
2299
+ }
2300
+ };
2301
+ const handleKeyDownMin = (e) => {
2302
+ if (disabled) return;
2303
+ let nextMin = minVal;
2304
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
2305
+ e.preventDefault();
2306
+ nextMin = Math.min(minVal + step, maxVal);
2307
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
2308
+ e.preventDefault();
2309
+ nextMin = Math.max(minVal - step, min);
2310
+ } else if (e.key === "Home") {
2311
+ e.preventDefault();
2312
+ nextMin = min;
2313
+ } else if (e.key === "End") {
2314
+ e.preventDefault();
2315
+ nextMin = maxVal;
2316
+ }
2317
+ if (nextMin !== minVal) {
2318
+ updateValues(nextMin, maxVal);
2319
+ }
2320
+ };
2321
+ const handleKeyDownMax = (e) => {
2322
+ if (disabled) return;
2323
+ let nextMax = maxVal;
2324
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
2325
+ e.preventDefault();
2326
+ nextMax = Math.min(maxVal + step, max);
2327
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
2328
+ e.preventDefault();
2329
+ nextMax = Math.max(maxVal - step, minVal);
2330
+ } else if (e.key === "Home") {
2331
+ e.preventDefault();
2332
+ nextMax = minVal;
2333
+ } else if (e.key === "End") {
2334
+ e.preventDefault();
2335
+ nextMax = max;
2336
+ }
2337
+ if (nextMax !== maxVal) {
2338
+ updateValues(minVal, nextMax);
2339
+ }
2340
+ };
2341
+ const minPercentage = Math.min(100, Math.max(0, (minVal - min) / (max - min || 1) * 100));
2342
+ const maxPercentage = Math.min(100, Math.max(0, (maxVal - min) / (max - min || 1) * 100));
2343
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2344
+ "div",
2345
+ {
2346
+ ref,
2347
+ className: cn("w-full flex items-center gap-4", disabled && "opacity-50 cursor-not-allowed", className),
2348
+ children: [
2349
+ /* @__PURE__ */ jsxRuntime.jsxs(
2350
+ "div",
2351
+ {
2352
+ ref: trackRef,
2353
+ onPointerDown: handlePointerDown,
2354
+ onPointerMove: handlePointerMove,
2355
+ onPointerUp: handlePointerUp,
2356
+ className: "relative w-full flex items-center h-7 select-none touch-none cursor-pointer",
2357
+ children: [
2358
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-2 rounded-full bg-[var(--ui-border-default)] overflow-hidden relative", children: /* @__PURE__ */ jsxRuntime.jsx(
2359
+ "div",
2360
+ {
2361
+ className: "absolute h-full bg-[var(--ui-primary)]",
2362
+ style: {
2363
+ left: `${minPercentage}%`,
2364
+ width: `${Math.max(0, maxPercentage - minPercentage)}%`
2365
+ }
2366
+ }
2367
+ ) }),
2368
+ name && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2369
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: `${name}_min`, value: minVal }),
2370
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: `${name}_max`, value: maxVal })
2371
+ ] }),
2372
+ /* @__PURE__ */ jsxRuntime.jsx(
2373
+ "div",
2374
+ {
2375
+ role: "slider",
2376
+ tabIndex: disabled ? -1 : 0,
2377
+ "aria-label": `${ariaLabel} minimum`,
2378
+ "aria-valuemin": min,
2379
+ "aria-valuemax": maxVal,
2380
+ "aria-valuenow": minVal,
2381
+ "aria-disabled": disabled,
2382
+ onKeyDown: handleKeyDownMin,
2383
+ className: cn(
2384
+ "absolute w-5 h-5 rounded-full bg-white border-2 border-[var(--ui-primary)] shadow-md -translate-x-1/2 z-20 cursor-grab active:cursor-grabbing transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] focus-visible:ring-offset-2",
2385
+ disabled && "pointer-events-none"
2386
+ ),
2387
+ style: { left: `${minPercentage}%` }
2388
+ }
2389
+ ),
2390
+ /* @__PURE__ */ jsxRuntime.jsx(
2391
+ "div",
2392
+ {
2393
+ role: "slider",
2394
+ tabIndex: disabled ? -1 : 0,
2395
+ "aria-label": `${ariaLabel} maximum`,
2396
+ "aria-valuemin": minVal,
2397
+ "aria-valuemax": max,
2398
+ "aria-valuenow": maxVal,
2399
+ "aria-disabled": disabled,
2400
+ onKeyDown: handleKeyDownMax,
2401
+ className: cn(
2402
+ "absolute w-5 h-5 rounded-full bg-white border-2 border-[var(--ui-primary)] shadow-md -translate-x-1/2 z-20 cursor-grab active:cursor-grabbing transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] focus-visible:ring-offset-2",
2403
+ disabled && "pointer-events-none"
2404
+ ),
2405
+ style: { left: `${maxPercentage}%` }
2406
+ }
2407
+ )
2408
+ ]
2409
+ }
2410
+ ),
2411
+ showValue && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-semibold font-mono text-[var(--ui-text-secondary)] min-w-[5rem] text-right", children: [
2412
+ formatValue(minVal),
2413
+ " - ",
2414
+ formatValue(maxVal)
2415
+ ] })
2416
+ ]
2417
+ }
2418
+ );
2419
+ }
2420
+ );
2421
+ RangeSlider.displayName = "RangeSlider";
2422
+ var PinInput = React66.forwardRef(
2423
+ ({
2424
+ className,
2425
+ length = 4,
2426
+ value: controlledValue,
2427
+ defaultValue = "",
2428
+ onChange,
2429
+ onComplete,
2430
+ mask = false,
2431
+ disabled = false,
2432
+ isInvalid = false,
2433
+ autoFocus = false,
2434
+ ...props
2435
+ }, ref) => {
2436
+ const isControlled = controlledValue !== void 0;
2437
+ const [internalPins, setInternalPins] = React66.useState(() => {
2438
+ const initial = (controlledValue !== void 0 ? controlledValue : defaultValue).slice(0, length);
2439
+ return Array.from({ length }, (_, i) => initial[i] || "");
2440
+ });
2441
+ const pins = isControlled ? Array.from({ length }, (_, i) => controlledValue[i] || "") : internalPins;
2442
+ const inputsRef = React66.useRef([]);
2443
+ React66.useEffect(() => {
2444
+ if (autoFocus && inputsRef.current[0]) {
2445
+ inputsRef.current[0].focus();
2446
+ }
2447
+ }, [autoFocus]);
2448
+ const handleDigitChange = (index, val) => {
2449
+ const sanitized = val.replace(/[^0-9a-zA-Z]/g, "").slice(-1);
2152
2450
  const newPins = [...pins];
2153
2451
  newPins[index] = sanitized;
2154
- setPins(newPins);
2452
+ if (!isControlled) {
2453
+ setInternalPins(newPins);
2454
+ }
2155
2455
  const combined = newPins.join("");
2156
2456
  onChange?.(combined);
2157
2457
  if (sanitized && index < length - 1) {
@@ -2162,16 +2462,28 @@ var PinInput = React66.forwardRef(
2162
2462
  }
2163
2463
  };
2164
2464
  const handleKeyDown = (index, e) => {
2165
- if (e.key === "Backspace" && !pins[index] && index > 0) {
2465
+ if (e.key === "Backspace") {
2466
+ if (!pins[index] && index > 0) {
2467
+ const newPins = [...pins];
2468
+ newPins[index - 1] = "";
2469
+ if (!isControlled) setInternalPins(newPins);
2470
+ onChange?.(newPins.join(""));
2471
+ inputsRef.current[index - 1]?.focus();
2472
+ }
2473
+ } else if (e.key === "ArrowLeft" && index > 0) {
2474
+ e.preventDefault();
2166
2475
  inputsRef.current[index - 1]?.focus();
2476
+ } else if (e.key === "ArrowRight" && index < length - 1) {
2477
+ e.preventDefault();
2478
+ inputsRef.current[index + 1]?.focus();
2167
2479
  }
2168
2480
  };
2169
2481
  const handlePaste = (e) => {
2170
2482
  e.preventDefault();
2171
- const pasted = e.clipboardData.getData("text").slice(0, length);
2483
+ const pasted = e.clipboardData.getData("text").replace(/[^0-9a-zA-Z]/g, "").slice(0, length);
2172
2484
  if (!pasted) return;
2173
2485
  const newPins = Array.from({ length }, (_, i) => pasted[i] || "");
2174
- setPins(newPins);
2486
+ if (!isControlled) setInternalPins(newPins);
2175
2487
  const combined = newPins.join("");
2176
2488
  onChange?.(combined);
2177
2489
  const nextEmptyIndex = newPins.findIndex((p) => !p);
@@ -2191,6 +2503,7 @@ var PinInput = React66.forwardRef(
2191
2503
  inputMode: "numeric",
2192
2504
  maxLength: 1,
2193
2505
  disabled,
2506
+ "aria-label": `Digit ${index + 1} of ${length}`,
2194
2507
  value: pins[index],
2195
2508
  onChange: (e) => handleDigitChange(index, e.target.value),
2196
2509
  onKeyDown: (e) => handleKeyDown(index, e),
@@ -2216,20 +2529,67 @@ function Portal({ children, container }) {
2216
2529
  if (!target) return null;
2217
2530
  return reactDom.createPortal(children, target);
2218
2531
  }
2532
+ function useControllableState({
2533
+ value: controlledValue,
2534
+ defaultValue,
2535
+ onChange,
2536
+ name
2537
+ }) {
2538
+ const isControlled = controlledValue !== void 0;
2539
+ const isControlledRef = React66.useRef(isControlled);
2540
+ React66.useEffect(() => {
2541
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && isControlledRef.current !== isControlled) {
2542
+ console.warn(
2543
+ `[${name || "Component"}] is changing from ${isControlledRef.current ? "controlled" : "uncontrolled"} to ${isControlled ? "controlled" : "uncontrolled"}. Check the value/defaultValue props.`
2544
+ );
2545
+ }
2546
+ isControlledRef.current = isControlled;
2547
+ }, [isControlled, name]);
2548
+ const [internalState, setInternalState] = React66.useState(defaultValue);
2549
+ const value = isControlled ? controlledValue : internalState;
2550
+ const setValue = React66.useCallback(
2551
+ (next) => {
2552
+ const nextValue = typeof next === "function" ? next(value) : next;
2553
+ if (!isControlled) {
2554
+ setInternalState(nextValue);
2555
+ }
2556
+ onChange?.(nextValue);
2557
+ },
2558
+ [isControlled, value, onChange]
2559
+ );
2560
+ return [value, setValue, isControlled];
2561
+ }
2219
2562
  var Popover = ({
2220
2563
  trigger,
2221
2564
  children,
2222
- isOpen: controlledIsOpen,
2565
+ open: controlledOpen,
2566
+ isOpen: legacyIsOpen,
2567
+ defaultOpen = false,
2568
+ onOpenChange,
2223
2569
  onClose,
2224
2570
  placement = "bottom",
2225
2571
  className
2226
2572
  }) => {
2227
- const [internalIsOpen, setInternalIsOpen] = React66.useState(false);
2228
- const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
2573
+ const activeControlled = controlledOpen !== void 0 ? controlledOpen : legacyIsOpen;
2574
+ const handleStateChange = React66.useCallback(
2575
+ (nextOpen) => {
2576
+ onOpenChange?.(nextOpen);
2577
+ if (!nextOpen) {
2578
+ onClose?.();
2579
+ }
2580
+ },
2581
+ [onOpenChange, onClose]
2582
+ );
2583
+ const [isOpen, setIsOpen] = useControllableState({
2584
+ value: activeControlled,
2585
+ defaultValue: defaultOpen,
2586
+ onChange: handleStateChange,
2587
+ name: "Popover"
2588
+ });
2229
2589
  const [coords, setCoords] = React66.useState({ top: 0, left: 0 });
2230
2590
  const triggerRef = React66.useRef(null);
2231
2591
  const popoverRef = React66.useRef(null);
2232
- const calculatePosition = () => {
2592
+ const calculatePosition = React66.useCallback(() => {
2233
2593
  if (triggerRef.current) {
2234
2594
  const rect = triggerRef.current.getBoundingClientRect();
2235
2595
  let top = 0;
@@ -2249,45 +2609,52 @@ var Popover = ({
2249
2609
  }
2250
2610
  setCoords({ top, left });
2251
2611
  }
2252
- };
2612
+ }, [placement]);
2253
2613
  const handleToggle = () => {
2254
2614
  if (!isOpen) {
2255
2615
  calculatePosition();
2256
2616
  }
2257
- if (controlledIsOpen !== void 0) {
2258
- if (isOpen) onClose?.();
2259
- } else {
2260
- setInternalIsOpen(!internalIsOpen);
2261
- }
2617
+ setIsOpen(!isOpen);
2262
2618
  };
2263
2619
  React66.useEffect(() => {
2264
2620
  if (isOpen) {
2265
2621
  calculatePosition();
2266
2622
  }
2267
- }, [isOpen]);
2623
+ }, [isOpen, calculatePosition]);
2268
2624
  React66.useEffect(() => {
2625
+ if (!isOpen) return;
2269
2626
  const handleOutside = (e) => {
2270
2627
  if (popoverRef.current && !popoverRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) {
2271
- if (controlledIsOpen !== void 0) {
2272
- onClose?.();
2273
- } else {
2274
- setInternalIsOpen(false);
2275
- }
2628
+ setIsOpen(false);
2276
2629
  }
2277
2630
  };
2278
- if (isOpen) {
2279
- document.addEventListener("mousedown", handleOutside);
2280
- window.addEventListener("resize", calculatePosition);
2281
- window.addEventListener("scroll", calculatePosition);
2282
- }
2631
+ const handleKeyDown = (e) => {
2632
+ if (e.key === "Escape") {
2633
+ setIsOpen(false);
2634
+ }
2635
+ };
2636
+ document.addEventListener("mousedown", handleOutside);
2637
+ document.addEventListener("keydown", handleKeyDown);
2638
+ window.addEventListener("resize", calculatePosition);
2639
+ window.addEventListener("scroll", calculatePosition);
2283
2640
  return () => {
2284
2641
  document.removeEventListener("mousedown", handleOutside);
2642
+ document.removeEventListener("keydown", handleKeyDown);
2285
2643
  window.removeEventListener("resize", calculatePosition);
2286
2644
  window.removeEventListener("scroll", calculatePosition);
2287
2645
  };
2288
- }, [isOpen, controlledIsOpen, onClose]);
2646
+ }, [isOpen, setIsOpen, calculatePosition]);
2289
2647
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2290
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: triggerRef, onClick: handleToggle, className: "w-full inline-block cursor-pointer", children: trigger }),
2648
+ /* @__PURE__ */ jsxRuntime.jsx(
2649
+ "div",
2650
+ {
2651
+ ref: triggerRef,
2652
+ onClick: handleToggle,
2653
+ className: "w-full inline-block cursor-pointer",
2654
+ "aria-expanded": isOpen,
2655
+ children: trigger
2656
+ }
2657
+ ),
2291
2658
  isOpen && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
2292
2659
  "div",
2293
2660
  {
@@ -2322,7 +2689,7 @@ function formatDisplayDate(d) {
2322
2689
  var DatePicker = React66.forwardRef(
2323
2690
  ({
2324
2691
  value: controlledValue,
2325
- defaultValue,
2692
+ defaultValue = null,
2326
2693
  onChange,
2327
2694
  placeholder = "Select date...",
2328
2695
  minDate,
@@ -2332,26 +2699,28 @@ var DatePicker = React66.forwardRef(
2332
2699
  className,
2333
2700
  presets
2334
2701
  }, ref) => {
2335
- const [internalDate, setInternalDate] = React66.useState(
2336
- defaultValue !== void 0 ? defaultValue : null
2337
- );
2338
- const selectedDate = controlledValue !== void 0 ? controlledValue : internalDate;
2702
+ const [selectedDate, setSelectedDate] = useControllableState({
2703
+ value: controlledValue,
2704
+ defaultValue,
2705
+ onChange,
2706
+ name: "DatePicker"
2707
+ });
2339
2708
  const [viewDate, setViewDate] = React66.useState(selectedDate || /* @__PURE__ */ new Date());
2340
2709
  const [isOpen, setIsOpen] = React66.useState(false);
2710
+ const [focusedDate, setFocusedDate] = React66.useState(selectedDate || /* @__PURE__ */ new Date());
2341
2711
  React66.useEffect(() => {
2342
2712
  if (selectedDate) {
2343
2713
  setViewDate(selectedDate);
2714
+ setFocusedDate(selectedDate);
2344
2715
  }
2345
2716
  }, [selectedDate]);
2346
2717
  const handleSelectDate = (d) => {
2347
- setInternalDate(d);
2348
- onChange?.(d);
2718
+ setSelectedDate(d);
2349
2719
  setIsOpen(false);
2350
2720
  };
2351
2721
  const handleClear = (e) => {
2352
2722
  e.stopPropagation();
2353
- setInternalDate(null);
2354
- onChange?.(null);
2723
+ setSelectedDate(null);
2355
2724
  };
2356
2725
  const prevMonth = () => {
2357
2726
  setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() - 1, 1));
@@ -2384,20 +2753,68 @@ var DatePicker = React66.forwardRef(
2384
2753
  }
2385
2754
  const todayStr = formatDate(/* @__PURE__ */ new Date());
2386
2755
  const selectedStr = selectedDate ? formatDate(selectedDate) : "";
2756
+ const focusedStr = formatDate(focusedDate);
2757
+ const handleGridKeyDown = (e) => {
2758
+ let nextDate = new Date(focusedDate);
2759
+ let handled = false;
2760
+ if (e.key === "ArrowLeft") {
2761
+ nextDate.setDate(nextDate.getDate() - 1);
2762
+ handled = true;
2763
+ } else if (e.key === "ArrowRight") {
2764
+ nextDate.setDate(nextDate.getDate() + 1);
2765
+ handled = true;
2766
+ } else if (e.key === "ArrowUp") {
2767
+ nextDate.setDate(nextDate.getDate() - 7);
2768
+ handled = true;
2769
+ } else if (e.key === "ArrowDown") {
2770
+ nextDate.setDate(nextDate.getDate() + 7);
2771
+ handled = true;
2772
+ } else if (e.key === "PageUp") {
2773
+ nextDate.setMonth(nextDate.getMonth() - 1);
2774
+ handled = true;
2775
+ } else if (e.key === "PageDown") {
2776
+ nextDate.setMonth(nextDate.getMonth() + 1);
2777
+ handled = true;
2778
+ } else if (e.key === "Home") {
2779
+ nextDate.setDate(1);
2780
+ handled = true;
2781
+ } else if (e.key === "End") {
2782
+ const lastDay = new Date(nextDate.getFullYear(), nextDate.getMonth() + 1, 0).getDate();
2783
+ nextDate.setDate(lastDay);
2784
+ handled = true;
2785
+ } else if (e.key === "Enter" || e.key === " ") {
2786
+ e.preventDefault();
2787
+ handleSelectDate(focusedDate);
2788
+ return;
2789
+ } else if (e.key === "Escape") {
2790
+ e.preventDefault();
2791
+ setIsOpen(false);
2792
+ return;
2793
+ }
2794
+ if (handled) {
2795
+ e.preventDefault();
2796
+ setFocusedDate(nextDate);
2797
+ if (nextDate.getMonth() !== viewDate.getMonth() || nextDate.getFullYear() !== viewDate.getFullYear()) {
2798
+ setViewDate(new Date(nextDate.getFullYear(), nextDate.getMonth(), 1));
2799
+ }
2800
+ }
2801
+ };
2387
2802
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("relative inline-block w-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
2388
2803
  Popover,
2389
2804
  {
2390
- isOpen,
2391
- onClose: () => setIsOpen(false),
2805
+ open: isOpen,
2806
+ onOpenChange: setIsOpen,
2392
2807
  trigger: /* @__PURE__ */ jsxRuntime.jsxs(
2393
2808
  "button",
2394
2809
  {
2395
2810
  type: "button",
2396
2811
  disabled,
2397
- onClick: () => !disabled && setIsOpen(!isOpen),
2812
+ "aria-haspopup": "dialog",
2813
+ "aria-expanded": isOpen,
2814
+ "aria-label": selectedDate ? `Selected date: ${formatDisplayDate(selectedDate)}` : placeholder,
2398
2815
  className: cn(
2399
- "w-full h-10 px-3.5 rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] text-left flex items-center justify-between text-sm transition-all shadow-inner cursor-pointer",
2400
- disabled ? "opacity-50 cursor-not-allowed border-[var(--ui-border-subtle)]" : "border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)] focus:ring-2 focus:ring-[var(--ui-primary)]/50"
2816
+ "w-full h-10 px-3.5 rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] text-left flex items-center justify-between text-sm transition-all shadow-inner cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
2817
+ disabled ? "opacity-50 cursor-not-allowed border-[var(--ui-border-subtle)]" : "border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)]"
2401
2818
  ),
2402
2819
  children: [
2403
2820
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 truncate", children: [
@@ -2407,7 +2824,11 @@ var DatePicker = React66.forwardRef(
2407
2824
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1 text-[var(--ui-text-muted)]", children: clearable && selectedDate && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
2408
2825
  "span",
2409
2826
  {
2827
+ role: "button",
2828
+ tabIndex: 0,
2829
+ "aria-label": "Clear date",
2410
2830
  onClick: handleClear,
2831
+ onKeyDown: (e) => e.key === "Enter" && handleClear(e),
2411
2832
  className: "p-1 rounded-full hover:bg-[var(--ui-bg-tertiary)] hover:text-[var(--ui-text-primary)] transition-colors",
2412
2833
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
2413
2834
  }
@@ -2415,74 +2836,131 @@ var DatePicker = React66.forwardRef(
2415
2836
  ]
2416
2837
  }
2417
2838
  ),
2418
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 w-72 rounded-2xl bg-[var(--ui-bg-surface)] backdrop-blur-2xl border border-[var(--ui-border-default)] shadow-2xl space-y-3", children: [
2419
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2420
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-bold text-[var(--ui-text-primary)] font-mono uppercase tracking-wider", children: viewDate.toLocaleString("default", { month: "long", year: "numeric" }) }),
2421
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2422
- /* @__PURE__ */ jsxRuntime.jsx(
2839
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2840
+ "div",
2841
+ {
2842
+ role: "dialog",
2843
+ "aria-label": "Choose date",
2844
+ className: "p-4 w-72 rounded-2xl bg-[var(--ui-bg-surface)] backdrop-blur-2xl border border-[var(--ui-border-default)] shadow-2xl space-y-3",
2845
+ onKeyDown: handleGridKeyDown,
2846
+ children: [
2847
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2848
+ /* @__PURE__ */ jsxRuntime.jsx(
2849
+ "span",
2850
+ {
2851
+ id: "calendar-month-heading",
2852
+ className: "text-xs font-bold text-[var(--ui-text-primary)] font-mono uppercase tracking-wider",
2853
+ "aria-live": "polite",
2854
+ children: viewDate.toLocaleString("default", { month: "long", year: "numeric" })
2855
+ }
2856
+ ),
2857
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2858
+ /* @__PURE__ */ jsxRuntime.jsx(
2859
+ "button",
2860
+ {
2861
+ type: "button",
2862
+ onClick: prevMonth,
2863
+ "aria-label": "Previous month",
2864
+ className: "p-1 rounded-lg hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors cursor-pointer",
2865
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "w-4 h-4" })
2866
+ }
2867
+ ),
2868
+ /* @__PURE__ */ jsxRuntime.jsx(
2869
+ "button",
2870
+ {
2871
+ type: "button",
2872
+ onClick: nextMonth,
2873
+ "aria-label": "Next month",
2874
+ className: "p-1 rounded-lg hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors cursor-pointer",
2875
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-4 h-4" })
2876
+ }
2877
+ )
2878
+ ] })
2879
+ ] }),
2880
+ presets && presets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 pb-2 border-b border-[var(--ui-border-subtle)]", children: presets.map((preset, idx) => /* @__PURE__ */ jsxRuntime.jsx(
2423
2881
  "button",
2424
2882
  {
2425
2883
  type: "button",
2426
- onClick: prevMonth,
2427
- className: "p-1 rounded-lg hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors",
2428
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "w-4 h-4" })
2884
+ onClick: () => handleSelectDate(preset.date),
2885
+ className: "px-2 py-0.5 rounded-md text-[10px] font-semibold bg-[var(--ui-bg-secondary)] hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-secondary)] transition-colors cursor-pointer",
2886
+ children: preset.label
2887
+ },
2888
+ idx
2889
+ )) }),
2890
+ /* @__PURE__ */ jsxRuntime.jsx(
2891
+ "div",
2892
+ {
2893
+ role: "row",
2894
+ className: "grid grid-cols-7 text-center text-[10px] font-bold text-[var(--ui-text-muted)] font-mono",
2895
+ children: [
2896
+ { short: "Su", full: "Sunday" },
2897
+ { short: "Mo", full: "Monday" },
2898
+ { short: "Tu", full: "Tuesday" },
2899
+ { short: "We", full: "Wednesday" },
2900
+ { short: "Th", full: "Thursday" },
2901
+ { short: "Fr", full: "Friday" },
2902
+ { short: "Sa", full: "Saturday" }
2903
+ ].map((d) => /* @__PURE__ */ jsxRuntime.jsx("div", { role: "columnheader", "aria-label": d.full, className: "py-1", children: d.short }, d.short))
2429
2904
  }
2430
2905
  ),
2431
2906
  /* @__PURE__ */ jsxRuntime.jsx(
2432
- "button",
2907
+ "div",
2433
2908
  {
2434
- type: "button",
2435
- onClick: nextMonth,
2436
- className: "p-1 rounded-lg hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors",
2437
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-4 h-4" })
2909
+ role: "grid",
2910
+ "aria-labelledby": "calendar-month-heading",
2911
+ className: "grid grid-cols-7 gap-1",
2912
+ children: days.map((item, idx) => {
2913
+ const itemStr = formatDate(item.date);
2914
+ const isSelected = itemStr === selectedStr;
2915
+ const isToday = itemStr === todayStr;
2916
+ const isFocused = itemStr === focusedStr;
2917
+ const fullDateLabel = item.date.toLocaleDateString("en-US", {
2918
+ weekday: "long",
2919
+ year: "numeric",
2920
+ month: "long",
2921
+ day: "numeric"
2922
+ });
2923
+ return /* @__PURE__ */ jsxRuntime.jsx(
2924
+ "button",
2925
+ {
2926
+ type: "button",
2927
+ role: "gridcell",
2928
+ disabled: item.disabled,
2929
+ "aria-selected": isSelected,
2930
+ "aria-current": isToday ? "date" : void 0,
2931
+ "aria-label": fullDateLabel,
2932
+ tabIndex: isFocused ? 0 : -1,
2933
+ onClick: () => handleSelectDate(item.date),
2934
+ className: cn(
2935
+ "h-8 w-8 rounded-full text-xs font-semibold flex items-center justify-center transition-all cursor-pointer outline-none",
2936
+ item.disabled && "opacity-25 cursor-not-allowed pointer-events-none",
2937
+ !item.currentMonth && "text-[var(--ui-text-muted)] opacity-50",
2938
+ isFocused && "ring-2 ring-[var(--ui-primary)]",
2939
+ isSelected ? "bg-[var(--ui-primary)] text-white font-bold shadow-md shadow-blue-500/30" : isToday ? "border border-[var(--ui-primary)] text-[var(--ui-primary)]" : "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
2940
+ ),
2941
+ children: item.date.getDate()
2942
+ },
2943
+ idx
2944
+ );
2945
+ })
2438
2946
  }
2439
2947
  )
2440
- ] })
2441
- ] }),
2442
- presets && presets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 pb-2 border-b border-[var(--ui-border-subtle)]", children: presets.map((preset, idx) => /* @__PURE__ */ jsxRuntime.jsx(
2443
- "button",
2444
- {
2445
- type: "button",
2446
- onClick: () => handleSelectDate(preset.date),
2447
- className: "px-2 py-0.5 rounded-md text-[10px] font-semibold bg-[var(--ui-bg-secondary)] hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-secondary)] transition-colors",
2448
- children: preset.label
2449
- },
2450
- idx
2451
- )) }),
2452
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 text-center text-[10px] font-bold text-[var(--ui-text-muted)] font-mono", children: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((d) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: d }, d)) }),
2453
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1", children: days.map((item, idx) => {
2454
- const itemStr = formatDate(item.date);
2455
- const isSelected = itemStr === selectedStr;
2456
- const isToday = itemStr === todayStr;
2457
- return /* @__PURE__ */ jsxRuntime.jsx(
2458
- "button",
2459
- {
2460
- type: "button",
2461
- disabled: item.disabled,
2462
- onClick: () => handleSelectDate(item.date),
2463
- className: cn(
2464
- "h-8 w-8 rounded-full text-xs font-semibold flex items-center justify-center transition-all cursor-pointer",
2465
- item.disabled && "opacity-25 cursor-not-allowed pointer-events-none",
2466
- !item.currentMonth && "text-[var(--ui-text-muted)] opacity-50",
2467
- isSelected ? "bg-[var(--ui-primary)] text-white font-bold shadow-md shadow-blue-500/30" : isToday ? "border border-[var(--ui-primary)] text-[var(--ui-primary)]" : "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
2468
- ),
2469
- children: item.date.getDate()
2470
- },
2471
- idx
2472
- );
2473
- }) })
2474
- ] })
2948
+ ]
2949
+ }
2950
+ )
2475
2951
  }
2476
2952
  ) });
2477
2953
  }
2478
2954
  );
2479
2955
  DatePicker.displayName = "DatePicker";
2480
2956
  var DateRangePicker = React66.forwardRef(
2481
- ({ value: controlledVal, defaultValue, onChange, placeholder = "Select date range...", className, disabled = false }, ref) => {
2482
- const [internalRange, setInternalRange] = React66.useState(
2483
- defaultValue || { from: null, to: null }
2484
- );
2485
- const range = controlledVal || internalRange;
2957
+ ({ value: controlledVal, defaultValue = { from: null, to: null }, onChange, placeholder = "Select date range...", className, disabled = false }, ref) => {
2958
+ const [range, setRange] = useControllableState({
2959
+ value: controlledVal,
2960
+ defaultValue,
2961
+ onChange,
2962
+ name: "DateRangePicker"
2963
+ });
2486
2964
  const [viewDate, setViewDate] = React66.useState(range.from || /* @__PURE__ */ new Date());
2487
2965
  const [isOpen, setIsOpen] = React66.useState(false);
2488
2966
  const handleDayClick = (d) => {
@@ -2497,8 +2975,7 @@ var DateRangePicker = React66.forwardRef(
2497
2975
  setIsOpen(false);
2498
2976
  }
2499
2977
  }
2500
- setInternalRange(next);
2501
- onChange?.(next);
2978
+ setRange(next);
2502
2979
  };
2503
2980
  const displayText = range.from && range.to ? `${formatDisplayDate(range.from)} \u2013 ${formatDisplayDate(range.to)}` : range.from ? `${formatDisplayDate(range.from)} \u2013 ...` : placeholder;
2504
2981
  const year = viewDate.getFullYear();
@@ -2512,146 +2989,345 @@ var DateRangePicker = React66.forwardRef(
2512
2989
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("relative inline-block w-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
2513
2990
  Popover,
2514
2991
  {
2515
- isOpen,
2516
- onClose: () => setIsOpen(false),
2992
+ open: isOpen,
2993
+ onOpenChange: setIsOpen,
2517
2994
  trigger: /* @__PURE__ */ jsxRuntime.jsx(
2518
2995
  "button",
2519
2996
  {
2520
2997
  type: "button",
2521
2998
  disabled,
2522
- onClick: () => !disabled && setIsOpen(!isOpen),
2523
- className: "w-full h-10 px-3.5 rounded-[var(--ui-radius-md)] border border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)] bg-[var(--ui-bg-surface)] text-left flex items-center justify-between text-sm shadow-inner cursor-pointer",
2999
+ "aria-haspopup": "dialog",
3000
+ "aria-expanded": isOpen,
3001
+ "aria-label": displayText,
3002
+ className: "w-full h-10 px-3.5 rounded-[var(--ui-radius-md)] border border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)] bg-[var(--ui-bg-surface)] text-left flex items-center justify-between text-sm shadow-inner cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
2524
3003
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 truncate", children: [
2525
3004
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "w-4 h-4 text-[var(--ui-text-muted)] shrink-0" }),
2526
3005
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: range.from ? "text-[var(--ui-text-primary)] font-medium" : "text-[var(--ui-text-muted)]", children: displayText })
2527
3006
  ] })
2528
3007
  }
2529
3008
  ),
2530
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 w-76 rounded-2xl bg-[var(--ui-bg-surface)] backdrop-blur-2xl border border-[var(--ui-border-default)] shadow-2xl space-y-3", children: [
2531
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2532
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-bold text-[var(--ui-text-primary)] font-mono uppercase tracking-wider", children: viewDate.toLocaleString("default", { month: "long", year: "numeric" }) }),
2533
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2534
- /* @__PURE__ */ jsxRuntime.jsx(
2535
- "button",
2536
- {
2537
- type: "button",
2538
- onClick: () => setViewDate(new Date(year, month - 1, 1)),
2539
- className: "p-1 rounded-lg hover:bg-[var(--ui-bg-tertiary)]",
2540
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "w-4 h-4" })
2541
- }
2542
- ),
2543
- /* @__PURE__ */ jsxRuntime.jsx(
2544
- "button",
3009
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
3010
+ "div",
3011
+ {
3012
+ role: "dialog",
3013
+ "aria-label": "Choose date range",
3014
+ className: "p-4 w-76 rounded-2xl bg-[var(--ui-bg-surface)] backdrop-blur-2xl border border-[var(--ui-border-default)] shadow-2xl space-y-3",
3015
+ children: [
3016
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3017
+ /* @__PURE__ */ jsxRuntime.jsx("span", { id: "range-month-heading", className: "text-xs font-bold text-[var(--ui-text-primary)] font-mono uppercase tracking-wider", children: viewDate.toLocaleString("default", { month: "long", year: "numeric" }) }),
3018
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
3019
+ /* @__PURE__ */ jsxRuntime.jsx(
3020
+ "button",
3021
+ {
3022
+ type: "button",
3023
+ onClick: () => setViewDate(new Date(year, month - 1, 1)),
3024
+ "aria-label": "Previous month",
3025
+ className: "p-1 rounded-lg hover:bg-[var(--ui-bg-tertiary)] cursor-pointer",
3026
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "w-4 h-4" })
3027
+ }
3028
+ ),
3029
+ /* @__PURE__ */ jsxRuntime.jsx(
3030
+ "button",
3031
+ {
3032
+ type: "button",
3033
+ onClick: () => setViewDate(new Date(year, month + 1, 1)),
3034
+ "aria-label": "Next month",
3035
+ className: "p-1 rounded-lg hover:bg-[var(--ui-bg-tertiary)] cursor-pointer",
3036
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-4 h-4" })
3037
+ }
3038
+ )
3039
+ ] })
3040
+ ] }),
3041
+ /* @__PURE__ */ jsxRuntime.jsxs(
3042
+ "div",
2545
3043
  {
2546
- type: "button",
2547
- onClick: () => setViewDate(new Date(year, month + 1, 1)),
2548
- className: "p-1 rounded-lg hover:bg-[var(--ui-bg-tertiary)]",
2549
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-4 h-4" })
3044
+ role: "grid",
3045
+ "aria-labelledby": "range-month-heading",
3046
+ className: "grid grid-cols-7 gap-1 text-center",
3047
+ children: [
3048
+ Array.from({ length: firstDayIndex }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", {}, `empty-${i}`)),
3049
+ days.map((d, idx) => {
3050
+ const dStr = formatDate(d);
3051
+ const fromStr = range.from ? formatDate(range.from) : "";
3052
+ const toStr = range.to ? formatDate(range.to) : "";
3053
+ const isFrom = dStr === fromStr;
3054
+ const isTo = dStr === toStr;
3055
+ const isInRange = range.from && range.to && d > range.from && d < range.to;
3056
+ return /* @__PURE__ */ jsxRuntime.jsx(
3057
+ "button",
3058
+ {
3059
+ type: "button",
3060
+ role: "gridcell",
3061
+ "aria-selected": Boolean(isFrom || isTo || isInRange),
3062
+ "aria-label": d.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" }),
3063
+ onClick: () => handleDayClick(d),
3064
+ className: cn(
3065
+ "h-8 w-8 rounded-full text-xs font-semibold flex items-center justify-center transition-all cursor-pointer",
3066
+ (isFrom || isTo) && "bg-[var(--ui-primary)] text-white shadow-md",
3067
+ isInRange && "bg-[var(--ui-primary)]/20 text-[var(--ui-primary)] rounded-none",
3068
+ !isFrom && !isTo && !isInRange && "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
3069
+ ),
3070
+ children: d.getDate()
3071
+ },
3072
+ idx
3073
+ );
3074
+ })
3075
+ ]
2550
3076
  }
2551
3077
  )
2552
- ] })
2553
- ] }),
2554
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-7 gap-1 text-center", children: [
2555
- Array.from({ length: firstDayIndex }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", {}, `empty-${i}`)),
2556
- days.map((d, idx) => {
2557
- const dStr = formatDate(d);
2558
- const fromStr = range.from ? formatDate(range.from) : "";
2559
- const toStr = range.to ? formatDate(range.to) : "";
2560
- const isFrom = dStr === fromStr;
2561
- const isTo = dStr === toStr;
2562
- const isInRange = range.from && range.to && d > range.from && d < range.to;
2563
- return /* @__PURE__ */ jsxRuntime.jsx(
2564
- "button",
2565
- {
2566
- type: "button",
2567
- onClick: () => handleDayClick(d),
2568
- className: cn(
2569
- "h-8 w-8 rounded-full text-xs font-semibold flex items-center justify-center transition-all cursor-pointer",
2570
- (isFrom || isTo) && "bg-[var(--ui-primary)] text-white shadow-md",
2571
- isInRange && "bg-[var(--ui-primary)]/20 text-[var(--ui-primary)] rounded-none",
2572
- !isFrom && !isTo && !isInRange && "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
2573
- ),
2574
- children: d.getDate()
2575
- },
2576
- idx
2577
- );
2578
- })
2579
- ] })
2580
- ] })
3078
+ ]
3079
+ }
3080
+ )
2581
3081
  }
2582
3082
  ) });
2583
3083
  }
2584
3084
  );
2585
3085
  DateRangePicker.displayName = "DateRangePicker";
2586
3086
  var TimePicker = React66.forwardRef(
2587
- ({ value: controlledVal, defaultValue = "12:00", onChange, format = "24h", className, disabled = false }, ref) => {
2588
- const [internalTime, setInternalTime] = React66.useState(defaultValue);
2589
- const time = controlledVal !== void 0 ? controlledVal : internalTime;
2590
- const [hours, setHours] = React66.useState(time.split(":")[0] || "12");
2591
- const [minutes, setMinutes] = React66.useState(time.split(":")[1] || "00");
3087
+ ({
3088
+ value: controlledVal,
3089
+ defaultValue,
3090
+ onChange,
3091
+ format = "24h",
3092
+ className,
3093
+ disabled = false,
3094
+ placeholder = "Select time..."
3095
+ }, ref) => {
3096
+ const is12h = format === "12h";
3097
+ const fallbackDefault = defaultValue ?? (is12h ? "12:00 PM" : "12:00");
3098
+ const [time, setTime] = useControllableState({
3099
+ value: controlledVal,
3100
+ defaultValue: fallbackDefault,
3101
+ onChange,
3102
+ name: "TimePicker"
3103
+ });
2592
3104
  const [isOpen, setIsOpen] = React66.useState(false);
2593
- const updateTime = (newH, newM) => {
2594
- const full = `${newH.padStart(2, "0")}:${newM.padStart(2, "0")}`;
2595
- setHours(newH);
2596
- setMinutes(newM);
2597
- setInternalTime(full);
2598
- onChange?.(full);
3105
+ const parseTime = (str, is12) => {
3106
+ if (!str) return { hours: is12 ? "12" : "12", minutes: "00", period: "AM" };
3107
+ const trimmed = str.trim();
3108
+ let period2 = "AM";
3109
+ let cleanTime = trimmed;
3110
+ if (trimmed.toLowerCase().includes("pm")) {
3111
+ period2 = "PM";
3112
+ cleanTime = trimmed.replace(/pm/i, "").trim();
3113
+ } else if (trimmed.toLowerCase().includes("am")) {
3114
+ period2 = "AM";
3115
+ cleanTime = trimmed.replace(/am/i, "").trim();
3116
+ }
3117
+ const parts = cleanTime.split(":");
3118
+ let h = parts[0] ? parts[0].padStart(2, "0") : is12 ? "12" : "00";
3119
+ const m = parts[1] ? parts[1].padStart(2, "0") : "00";
3120
+ return { hours: h, minutes: m, period: period2 };
3121
+ };
3122
+ const { hours, minutes, period } = parseTime(time, is12h);
3123
+ const updateTime = (newH, newM, newPeriod) => {
3124
+ let formatted;
3125
+ if (is12h) {
3126
+ const p = newPeriod ?? period;
3127
+ formatted = `${newH.padStart(2, "0")}:${newM.padStart(2, "0")} ${p}`;
3128
+ } else {
3129
+ formatted = `${newH.padStart(2, "0")}:${newM.padStart(2, "0")}`;
3130
+ }
3131
+ setTime(formatted);
3132
+ };
3133
+ const hourOptions = is12h ? Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, "0")) : Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
3134
+ const minuteOptions = ["00", "05", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55"];
3135
+ const hourListRef = React66.useRef(null);
3136
+ const minuteListRef = React66.useRef(null);
3137
+ const periodListRef = React66.useRef(null);
3138
+ React66.useEffect(() => {
3139
+ if (isOpen) {
3140
+ const timer = setTimeout(() => {
3141
+ const activeHourEl = hourListRef.current?.querySelector('[aria-selected="true"]');
3142
+ activeHourEl?.focus();
3143
+ }, 10);
3144
+ return () => clearTimeout(timer);
3145
+ }
3146
+ }, [isOpen]);
3147
+ const handleHourKeyDown = (e) => {
3148
+ const idx = hourOptions.indexOf(hours);
3149
+ if (e.key === "ArrowDown") {
3150
+ e.preventDefault();
3151
+ const next = hourOptions[(idx + 1) % hourOptions.length];
3152
+ updateTime(next, minutes, period);
3153
+ } else if (e.key === "ArrowUp") {
3154
+ e.preventDefault();
3155
+ const prev = hourOptions[(idx - 1 + hourOptions.length) % hourOptions.length];
3156
+ updateTime(prev, minutes, period);
3157
+ } else if (e.key === "ArrowRight") {
3158
+ e.preventDefault();
3159
+ const activeMinEl = minuteListRef.current?.querySelector('[aria-selected="true"]') || minuteListRef.current?.querySelector('[role="option"]');
3160
+ activeMinEl?.focus();
3161
+ } else if (e.key === "Escape") {
3162
+ e.preventDefault();
3163
+ setIsOpen(false);
3164
+ }
3165
+ };
3166
+ const handleMinuteKeyDown = (e) => {
3167
+ const idx = minuteOptions.indexOf(minutes);
3168
+ if (e.key === "ArrowDown") {
3169
+ e.preventDefault();
3170
+ const next = minuteOptions[(idx + 1) % minuteOptions.length];
3171
+ updateTime(hours, next, period);
3172
+ } else if (e.key === "ArrowUp") {
3173
+ e.preventDefault();
3174
+ const prev = minuteOptions[(idx - 1 + minuteOptions.length) % minuteOptions.length];
3175
+ updateTime(hours, prev, period);
3176
+ } else if (e.key === "ArrowLeft") {
3177
+ e.preventDefault();
3178
+ const activeHourEl = hourListRef.current?.querySelector('[aria-selected="true"]');
3179
+ activeHourEl?.focus();
3180
+ } else if (e.key === "ArrowRight" && is12h) {
3181
+ e.preventDefault();
3182
+ const activePeriodEl = periodListRef.current?.querySelector('[aria-selected="true"]');
3183
+ activePeriodEl?.focus();
3184
+ } else if (e.key === "Escape") {
3185
+ e.preventDefault();
3186
+ setIsOpen(false);
3187
+ }
3188
+ };
3189
+ const handlePeriodKeyDown = (e) => {
3190
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
3191
+ e.preventDefault();
3192
+ const next = period === "AM" ? "PM" : "AM";
3193
+ updateTime(hours, minutes, next);
3194
+ } else if (e.key === "ArrowLeft") {
3195
+ e.preventDefault();
3196
+ const activeMinEl = minuteListRef.current?.querySelector('[aria-selected="true"]');
3197
+ activeMinEl?.focus();
3198
+ } else if (e.key === "Escape") {
3199
+ e.preventDefault();
3200
+ setIsOpen(false);
3201
+ }
2599
3202
  };
2600
3203
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("relative inline-block w-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
2601
3204
  Popover,
2602
3205
  {
2603
- isOpen,
2604
- onClose: () => setIsOpen(false),
3206
+ open: isOpen,
3207
+ onOpenChange: setIsOpen,
2605
3208
  trigger: /* @__PURE__ */ jsxRuntime.jsx(
2606
3209
  "button",
2607
3210
  {
2608
3211
  type: "button",
2609
3212
  disabled,
2610
- onClick: () => !disabled && setIsOpen(!isOpen),
2611
- className: "w-full h-10 px-3.5 rounded-[var(--ui-radius-md)] border border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)] bg-[var(--ui-bg-surface)] text-left flex items-center justify-between text-sm shadow-inner cursor-pointer",
3213
+ "aria-haspopup": "dialog",
3214
+ "aria-expanded": isOpen,
3215
+ "aria-label": `Selected time: ${time}`,
3216
+ className: cn(
3217
+ "w-full h-10 px-3.5 rounded-[var(--ui-radius-md)] border border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)] bg-[var(--ui-bg-surface)] text-left flex items-center justify-between text-sm shadow-inner transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
3218
+ disabled && "opacity-50 cursor-not-allowed"
3219
+ ),
2612
3220
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
2613
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "w-4 h-4 text-[var(--ui-text-muted)]" }),
2614
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-sm font-semibold text-[var(--ui-text-primary)]", children: time })
3221
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "w-4 h-4 text-[var(--ui-text-muted)] shrink-0" }),
3222
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-sm font-semibold text-[var(--ui-text-primary)]", children: time || placeholder })
2615
3223
  ] })
2616
3224
  }
2617
3225
  ),
2618
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 w-52 rounded-2xl bg-[var(--ui-bg-surface)] backdrop-blur-2xl border border-[var(--ui-border-default)] shadow-2xl space-y-3", children: [
2619
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-bold text-[var(--ui-text-muted)] uppercase tracking-wider font-mono", children: [
2620
- "Select Time (",
2621
- format,
2622
- ")"
2623
- ] }),
2624
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
2625
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-36 overflow-y-auto border border-[var(--ui-border-subtle)] rounded-xl p-1 w-16 text-center space-y-1", children: Array.from({ length: 24 }).map((_, i) => {
2626
- const h = String(i).padStart(2, "0");
2627
- return /* @__PURE__ */ jsxRuntime.jsx(
2628
- "div",
2629
- {
2630
- onClick: () => updateTime(h, minutes),
2631
- className: cn(
2632
- "text-xs py-1 rounded-lg font-mono font-semibold cursor-pointer transition-colors",
2633
- hours === h ? "bg-[var(--ui-primary)] text-white" : "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
2634
- ),
2635
- children: h
2636
- },
2637
- h
2638
- );
2639
- }) }),
2640
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-bold", children: ":" }),
2641
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-36 overflow-y-auto border border-[var(--ui-border-subtle)] rounded-xl p-1 w-16 text-center space-y-1", children: ["00", "05", "10", "15", "20", "25", "30", "35", "40", "41", "45", "50", "55"].map((m) => /* @__PURE__ */ jsxRuntime.jsx(
2642
- "div",
2643
- {
2644
- onClick: () => updateTime(hours, m),
2645
- className: cn(
2646
- "text-xs py-1 rounded-lg font-mono font-semibold cursor-pointer transition-colors",
2647
- minutes === m ? "bg-[var(--ui-primary)] text-white" : "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
3226
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
3227
+ "div",
3228
+ {
3229
+ role: "dialog",
3230
+ "aria-label": "Choose time",
3231
+ className: "p-4 rounded-2xl bg-[var(--ui-bg-surface)] backdrop-blur-2xl border border-[var(--ui-border-default)] shadow-2xl space-y-3",
3232
+ children: [
3233
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-bold text-[var(--ui-text-muted)] uppercase tracking-wider font-mono", children: [
3234
+ "Select Time (",
3235
+ format,
3236
+ ")"
3237
+ ] }),
3238
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
3239
+ /* @__PURE__ */ jsxRuntime.jsx(
3240
+ "div",
3241
+ {
3242
+ ref: hourListRef,
3243
+ tabIndex: 0,
3244
+ onKeyDown: handleHourKeyDown,
3245
+ className: "h-40 overflow-y-auto border border-[var(--ui-border-subtle)] rounded-xl p-1 w-16 text-center space-y-1 focus:outline-none focus:ring-1 focus:ring-[var(--ui-primary)]",
3246
+ role: "listbox",
3247
+ "aria-label": "Hour",
3248
+ children: hourOptions.map((h) => {
3249
+ const isSelected = hours === h;
3250
+ return /* @__PURE__ */ jsxRuntime.jsx(
3251
+ "div",
3252
+ {
3253
+ tabIndex: isSelected ? 0 : -1,
3254
+ role: "option",
3255
+ "aria-selected": isSelected,
3256
+ onClick: () => updateTime(h, minutes, period),
3257
+ className: cn(
3258
+ "text-xs py-1 rounded-lg font-mono font-semibold cursor-pointer transition-colors select-none outline-none focus-visible:ring-1 focus-visible:ring-[var(--ui-primary)]",
3259
+ isSelected ? "bg-[var(--ui-primary)] text-white" : "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
3260
+ ),
3261
+ children: h
3262
+ },
3263
+ h
3264
+ );
3265
+ })
3266
+ }
2648
3267
  ),
2649
- children: m
2650
- },
2651
- m
2652
- )) })
2653
- ] })
2654
- ] })
3268
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-bold", children: ":" }),
3269
+ /* @__PURE__ */ jsxRuntime.jsx(
3270
+ "div",
3271
+ {
3272
+ ref: minuteListRef,
3273
+ tabIndex: 0,
3274
+ onKeyDown: handleMinuteKeyDown,
3275
+ className: "h-40 overflow-y-auto border border-[var(--ui-border-subtle)] rounded-xl p-1 w-16 text-center space-y-1 focus:outline-none focus:ring-1 focus:ring-[var(--ui-primary)]",
3276
+ role: "listbox",
3277
+ "aria-label": "Minute",
3278
+ children: minuteOptions.map((m) => {
3279
+ const isSelected = minutes === m;
3280
+ return /* @__PURE__ */ jsxRuntime.jsx(
3281
+ "div",
3282
+ {
3283
+ tabIndex: isSelected ? 0 : -1,
3284
+ role: "option",
3285
+ "aria-selected": isSelected,
3286
+ onClick: () => updateTime(hours, m, period),
3287
+ className: cn(
3288
+ "text-xs py-1 rounded-lg font-mono font-semibold cursor-pointer transition-colors select-none outline-none focus-visible:ring-1 focus-visible:ring-[var(--ui-primary)]",
3289
+ isSelected ? "bg-[var(--ui-primary)] text-white" : "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
3290
+ ),
3291
+ children: m
3292
+ },
3293
+ m
3294
+ );
3295
+ })
3296
+ }
3297
+ ),
3298
+ is12h && /* @__PURE__ */ jsxRuntime.jsx(
3299
+ "div",
3300
+ {
3301
+ ref: periodListRef,
3302
+ tabIndex: 0,
3303
+ onKeyDown: handlePeriodKeyDown,
3304
+ className: "h-40 border border-[var(--ui-border-subtle)] rounded-xl p-1 w-14 text-center space-y-1 flex flex-col justify-center focus:outline-none focus:ring-1 focus:ring-[var(--ui-primary)]",
3305
+ role: "listbox",
3306
+ "aria-label": "Period",
3307
+ children: ["AM", "PM"].map((p) => {
3308
+ const isSelected = period === p;
3309
+ return /* @__PURE__ */ jsxRuntime.jsx(
3310
+ "div",
3311
+ {
3312
+ tabIndex: isSelected ? 0 : -1,
3313
+ role: "option",
3314
+ "aria-selected": isSelected,
3315
+ onClick: () => updateTime(hours, minutes, p),
3316
+ className: cn(
3317
+ "text-xs py-2 rounded-lg font-mono font-semibold cursor-pointer transition-colors select-none outline-none focus-visible:ring-1 focus-visible:ring-[var(--ui-primary)]",
3318
+ isSelected ? "bg-[var(--ui-primary)] text-white" : "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
3319
+ ),
3320
+ children: p
3321
+ },
3322
+ p
3323
+ );
3324
+ })
3325
+ }
3326
+ )
3327
+ ] })
3328
+ ]
3329
+ }
3330
+ )
2655
3331
  }
2656
3332
  ) });
2657
3333
  }
@@ -2729,135 +3405,205 @@ var Combobox = React66.forwardRef(
2729
3405
  emptyText = "No items found.",
2730
3406
  disabled = false,
2731
3407
  clearable = true,
2732
- className
3408
+ className,
3409
+ name,
3410
+ id: customId,
3411
+ "aria-label": ariaLabel = "Select option"
2733
3412
  }, ref) => {
3413
+ const baseId = React66.useId();
3414
+ const comboId = customId || `combobox-${baseId}`;
3415
+ const listboxId = `listbox-${baseId}`;
2734
3416
  const [internalVal, setInternalVal] = React66.useState(defaultValue);
2735
- const selectedValue = controlledVal !== void 0 ? controlledVal : internalVal;
3417
+ const isControlled = controlledVal !== void 0;
3418
+ const selectedValue = isControlled ? controlledVal : internalVal;
2736
3419
  const [search, setSearch] = React66.useState("");
2737
3420
  const [isOpen, setIsOpen] = React66.useState(false);
2738
3421
  const [highlightIndex, setHighlightIndex] = React66.useState(0);
3422
+ const searchInputRef = React66.useRef(null);
3423
+ const triggerRef = React66.useRef(null);
2739
3424
  const filteredOptions = options.filter(
2740
3425
  (opt) => opt.label.toLowerCase().includes(search.toLowerCase()) || opt.value.toLowerCase().includes(search.toLowerCase()) || opt.description && opt.description.toLowerCase().includes(search.toLowerCase())
2741
3426
  );
2742
3427
  const selectedOption = options.find((opt) => opt.value === selectedValue);
3428
+ React66.useEffect(() => {
3429
+ if (filteredOptions.length > 0) {
3430
+ setHighlightIndex((prev) => Math.min(prev, filteredOptions.length - 1));
3431
+ }
3432
+ }, [filteredOptions.length]);
2743
3433
  const handleSelect = (val) => {
2744
- setInternalVal(val);
3434
+ if (!isControlled) {
3435
+ setInternalVal(val);
3436
+ }
2745
3437
  onChange?.(val);
2746
3438
  setIsOpen(false);
2747
3439
  setSearch("");
3440
+ triggerRef.current?.focus();
2748
3441
  };
2749
3442
  const handleClear = (e) => {
2750
3443
  e.stopPropagation();
2751
- setInternalVal("");
3444
+ if (!isControlled) {
3445
+ setInternalVal("");
3446
+ }
2752
3447
  onChange?.("");
2753
3448
  };
2754
3449
  const handleKeyDown = (e) => {
2755
3450
  if (!isOpen) {
2756
- if (e.key === "ArrowDown" || e.key === "Enter") {
3451
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
3452
+ e.preventDefault();
2757
3453
  setIsOpen(true);
2758
3454
  }
2759
3455
  return;
2760
3456
  }
2761
3457
  if (e.key === "ArrowDown") {
2762
3458
  e.preventDefault();
2763
- setHighlightIndex((prev) => (prev + 1) % Math.max(1, filteredOptions.length));
3459
+ let next = highlightIndex + 1;
3460
+ while (next < filteredOptions.length && filteredOptions[next]?.disabled) {
3461
+ next++;
3462
+ }
3463
+ if (next < filteredOptions.length) {
3464
+ setHighlightIndex(next);
3465
+ }
2764
3466
  } else if (e.key === "ArrowUp") {
2765
3467
  e.preventDefault();
2766
- setHighlightIndex((prev) => (prev - 1 + filteredOptions.length) % Math.max(1, filteredOptions.length));
3468
+ let prev = highlightIndex - 1;
3469
+ while (prev >= 0 && filteredOptions[prev]?.disabled) {
3470
+ prev--;
3471
+ }
3472
+ if (prev >= 0) {
3473
+ setHighlightIndex(prev);
3474
+ }
3475
+ } else if (e.key === "Home") {
3476
+ e.preventDefault();
3477
+ let first = 0;
3478
+ while (first < filteredOptions.length && filteredOptions[first]?.disabled) {
3479
+ first++;
3480
+ }
3481
+ setHighlightIndex(first);
3482
+ } else if (e.key === "End") {
3483
+ e.preventDefault();
3484
+ let last = filteredOptions.length - 1;
3485
+ while (last >= 0 && filteredOptions[last]?.disabled) {
3486
+ last--;
3487
+ }
3488
+ setHighlightIndex(last);
2767
3489
  } else if (e.key === "Enter") {
2768
3490
  e.preventDefault();
2769
- if (filteredOptions[highlightIndex] && !filteredOptions[highlightIndex].disabled) {
2770
- handleSelect(filteredOptions[highlightIndex].value);
3491
+ const highlighted = filteredOptions[highlightIndex];
3492
+ if (highlighted && !highlighted.disabled) {
3493
+ handleSelect(highlighted.value);
2771
3494
  }
2772
3495
  } else if (e.key === "Escape") {
3496
+ e.preventDefault();
2773
3497
  setIsOpen(false);
3498
+ triggerRef.current?.focus();
2774
3499
  }
2775
3500
  };
2776
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("relative w-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
2777
- Popover,
2778
- {
2779
- isOpen,
2780
- onClose: () => setIsOpen(false),
2781
- trigger: /* @__PURE__ */ jsxRuntime.jsxs(
2782
- "button",
2783
- {
2784
- type: "button",
2785
- disabled,
2786
- onClick: () => !disabled && setIsOpen(!isOpen),
2787
- onKeyDown: handleKeyDown,
2788
- className: cn(
2789
- "w-full h-10 px-3.5 rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] text-left flex items-center justify-between text-sm transition-all shadow-inner cursor-pointer",
2790
- disabled ? "opacity-50 cursor-not-allowed border-[var(--ui-border-subtle)]" : "border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)] focus:ring-2 focus:ring-[var(--ui-primary)]/50"
2791
- ),
2792
- children: [
2793
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 truncate", children: [
2794
- selectedOption?.icon,
2795
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: selectedOption ? "text-[var(--ui-text-primary)] font-medium" : "text-[var(--ui-text-muted)]", children: selectedOption ? selectedOption.label : placeholder })
2796
- ] }),
2797
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-[var(--ui-text-muted)]", children: [
2798
- clearable && selectedValue && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
2799
- "span",
2800
- {
2801
- onClick: handleClear,
2802
- className: "p-1 rounded-full hover:bg-[var(--ui-bg-tertiary)] hover:text-[var(--ui-text-primary)] transition-colors",
2803
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
2804
- }
2805
- ),
2806
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "w-4 h-4 opacity-50 shrink-0" })
2807
- ] })
2808
- ]
2809
- }
2810
- ),
2811
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2 w-72 rounded-2xl bg-[var(--ui-bg-surface)] backdrop-blur-2xl border border-[var(--ui-border-default)] shadow-2xl space-y-2", children: [
2812
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
2813
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-2.5 w-3.5 h-3.5 text-[var(--ui-text-muted)] pointer-events-none" }),
2814
- /* @__PURE__ */ jsxRuntime.jsx(
2815
- "input",
2816
- {
2817
- type: "text",
2818
- autoFocus: true,
2819
- placeholder: searchPlaceholder,
2820
- value: search,
2821
- onChange: (e) => {
2822
- setSearch(e.target.value);
2823
- setHighlightIndex(0);
3501
+ const activeDescendantId = filteredOptions[highlightIndex] ? `combobox-opt-${baseId}-${filteredOptions[highlightIndex].value}` : void 0;
3502
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn("relative w-full", className), children: [
3503
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selectedValue }),
3504
+ /* @__PURE__ */ jsxRuntime.jsx(
3505
+ Popover,
3506
+ {
3507
+ isOpen,
3508
+ onClose: () => setIsOpen(false),
3509
+ trigger: /* @__PURE__ */ jsxRuntime.jsxs(
3510
+ "button",
3511
+ {
3512
+ ref: triggerRef,
3513
+ id: comboId,
3514
+ type: "button",
3515
+ disabled,
3516
+ role: "combobox",
3517
+ "aria-expanded": isOpen,
3518
+ "aria-haspopup": "listbox",
3519
+ "aria-controls": isOpen ? listboxId : void 0,
3520
+ "aria-label": ariaLabel,
3521
+ onClick: () => !disabled && setIsOpen(!isOpen),
3522
+ onKeyDown: handleKeyDown,
3523
+ className: cn(
3524
+ "w-full h-10 px-3.5 rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] text-left flex items-center justify-between text-sm transition-all shadow-inner cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
3525
+ disabled ? "opacity-50 cursor-not-allowed border-[var(--ui-border-subtle)]" : "border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)]"
3526
+ ),
3527
+ children: [
3528
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 truncate", children: [
3529
+ selectedOption?.icon,
3530
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: selectedOption ? "text-[var(--ui-text-primary)] font-medium" : "text-[var(--ui-text-muted)]", children: selectedOption ? selectedOption.label : placeholder })
3531
+ ] }),
3532
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-[var(--ui-text-muted)]", children: [
3533
+ clearable && selectedValue && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
3534
+ "span",
3535
+ {
3536
+ onClick: handleClear,
3537
+ className: "p-1 rounded-full hover:bg-[var(--ui-bg-tertiary)] hover:text-[var(--ui-text-primary)] transition-colors cursor-pointer",
3538
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
3539
+ }
3540
+ ),
3541
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "w-4 h-4 opacity-50 shrink-0" })
3542
+ ] })
3543
+ ]
3544
+ }
3545
+ ),
3546
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2 w-72 rounded-2xl bg-[var(--ui-bg-surface)] backdrop-blur-2xl border border-[var(--ui-border-default)] shadow-2xl space-y-2", children: [
3547
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
3548
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-2.5 w-3.5 h-3.5 text-[var(--ui-text-muted)] pointer-events-none" }),
3549
+ /* @__PURE__ */ jsxRuntime.jsx(
3550
+ "input",
3551
+ {
3552
+ ref: searchInputRef,
3553
+ type: "text",
3554
+ autoFocus: true,
3555
+ placeholder: searchPlaceholder,
3556
+ value: search,
3557
+ onChange: (e) => {
3558
+ setSearch(e.target.value);
3559
+ setHighlightIndex(0);
3560
+ },
3561
+ onKeyDown: handleKeyDown,
3562
+ role: "combobox",
3563
+ "aria-autocomplete": "list",
3564
+ "aria-expanded": "true",
3565
+ "aria-controls": listboxId,
3566
+ "aria-activedescendant": activeDescendantId,
3567
+ className: "w-full h-8 pl-8 pr-3 text-xs rounded-xl bg-[var(--ui-bg-secondary)] border border-[var(--ui-border-subtle)] focus:outline-none focus:border-[var(--ui-primary)] text-[var(--ui-text-primary)] placeholder:text-[var(--ui-text-muted)]"
3568
+ }
3569
+ )
3570
+ ] }),
3571
+ /* @__PURE__ */ jsxRuntime.jsx("div", { id: listboxId, className: "max-h-56 overflow-y-auto space-y-0.5", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-4 text-center text-xs text-[var(--ui-text-muted)]", children: emptyText }) : filteredOptions.map((opt, idx) => {
3572
+ const isSelected = opt.value === selectedValue;
3573
+ const isHighlighted = idx === highlightIndex;
3574
+ const optId = `combobox-opt-${baseId}-${opt.value}`;
3575
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3576
+ "div",
3577
+ {
3578
+ id: optId,
3579
+ role: "option",
3580
+ "aria-selected": isSelected,
3581
+ "aria-disabled": opt.disabled,
3582
+ onClick: () => !opt.disabled && handleSelect(opt.value),
3583
+ onMouseEnter: () => !opt.disabled && setHighlightIndex(idx),
3584
+ className: cn(
3585
+ "flex items-center justify-between px-3 py-2 rounded-xl text-xs font-medium cursor-pointer transition-colors select-none",
3586
+ opt.disabled && "opacity-40 cursor-not-allowed pointer-events-none",
3587
+ isSelected ? "bg-[var(--ui-primary)] text-white font-semibold shadow-xs" : isHighlighted ? "bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]" : "text-[var(--ui-text-primary)]"
3588
+ ),
3589
+ children: [
3590
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 truncate", children: [
3591
+ opt.icon,
3592
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3593
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate", children: opt.label }),
3594
+ opt.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-[10px] block truncate", isSelected ? "text-white/80" : "text-[var(--ui-text-muted)]"), children: opt.description })
3595
+ ] })
3596
+ ] }),
3597
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-3.5 h-3.5 shrink-0" })
3598
+ ]
2824
3599
  },
2825
- className: "w-full h-8 pl-8 pr-3 text-xs rounded-xl bg-[var(--ui-bg-secondary)] border border-[var(--ui-border-subtle)] focus:outline-none focus:border-[var(--ui-primary)] text-[var(--ui-text-primary)] placeholder:text-[var(--ui-text-muted)]"
2826
- }
2827
- )
2828
- ] }),
2829
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 overflow-y-auto space-y-0.5", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-4 text-center text-xs text-[var(--ui-text-muted)]", children: emptyText }) : filteredOptions.map((opt, idx) => {
2830
- const isSelected = opt.value === selectedValue;
2831
- const isHighlighted = idx === highlightIndex;
2832
- return /* @__PURE__ */ jsxRuntime.jsxs(
2833
- "div",
2834
- {
2835
- role: "option",
2836
- "aria-selected": isSelected,
2837
- onClick: () => !opt.disabled && handleSelect(opt.value),
2838
- onMouseEnter: () => setHighlightIndex(idx),
2839
- className: cn(
2840
- "flex items-center justify-between px-3 py-2 rounded-xl text-xs font-medium cursor-pointer transition-colors",
2841
- opt.disabled && "opacity-40 cursor-not-allowed pointer-events-none",
2842
- isSelected ? "bg-[var(--ui-primary)] text-white font-semibold shadow-xs" : isHighlighted ? "bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]" : "text-[var(--ui-text-primary)]"
2843
- ),
2844
- children: [
2845
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 truncate", children: [
2846
- opt.icon,
2847
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2848
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate", children: opt.label }),
2849
- opt.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-[10px] block truncate", isSelected ? "text-white/80" : "text-[var(--ui-text-muted)]"), children: opt.description })
2850
- ] })
2851
- ] }),
2852
- isSelected && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-3.5 h-3.5 shrink-0" })
2853
- ]
2854
- },
2855
- opt.value
2856
- );
2857
- }) })
2858
- ] })
2859
- }
2860
- ) });
3600
+ opt.value
3601
+ );
3602
+ }) })
3603
+ ] })
3604
+ }
3605
+ )
3606
+ ] });
2861
3607
  }
2862
3608
  );
2863
3609
  Combobox.displayName = "Combobox";
@@ -2874,28 +3620,69 @@ var MultiSelect = React66.forwardRef(
2874
3620
  className
2875
3621
  }, ref) => {
2876
3622
  const [internalVal, setInternalVal] = React66.useState(defaultValue);
2877
- const selectedValues = controlledVal !== void 0 ? controlledVal : internalVal;
3623
+ const isControlled = controlledVal !== void 0;
3624
+ const selectedValues = isControlled ? controlledVal : internalVal;
2878
3625
  const [search, setSearch] = React66.useState("");
2879
3626
  const [isOpen, setIsOpen] = React66.useState(false);
3627
+ const [highlightedIndex, setHighlightedIndex] = React66.useState(0);
2880
3628
  const filteredOptions = options.filter(
2881
3629
  (opt) => opt.label.toLowerCase().includes(search.toLowerCase()) || opt.value.toLowerCase().includes(search.toLowerCase())
2882
3630
  );
2883
3631
  const toggleOption = (val) => {
3632
+ const opt = options.find((o) => o.value === val);
3633
+ if (opt?.disabled) return;
2884
3634
  const next = selectedValues.includes(val) ? selectedValues.filter((v) => v !== val) : [...selectedValues, val];
2885
- setInternalVal(next);
3635
+ if (!isControlled) {
3636
+ setInternalVal(next);
3637
+ }
2886
3638
  onChange?.(next);
2887
3639
  };
2888
3640
  const removeTag = (val, e) => {
2889
3641
  e?.stopPropagation?.();
2890
3642
  const next = selectedValues.filter((v) => v !== val);
2891
- setInternalVal(next);
3643
+ if (!isControlled) {
3644
+ setInternalVal(next);
3645
+ }
2892
3646
  onChange?.(next);
2893
3647
  };
2894
3648
  const clearAll = (e) => {
2895
3649
  e?.stopPropagation?.();
2896
- setInternalVal([]);
3650
+ if (!isControlled) {
3651
+ setInternalVal([]);
3652
+ }
2897
3653
  onChange?.([]);
2898
3654
  };
3655
+ const handleSearchKeyDown = (e) => {
3656
+ if (filteredOptions.length === 0) return;
3657
+ if (e.key === "ArrowDown") {
3658
+ e.preventDefault();
3659
+ setHighlightedIndex((prev) => {
3660
+ let next = (prev + 1) % filteredOptions.length;
3661
+ while (filteredOptions[next]?.disabled && next !== prev) {
3662
+ next = (next + 1) % filteredOptions.length;
3663
+ }
3664
+ return next;
3665
+ });
3666
+ } else if (e.key === "ArrowUp") {
3667
+ e.preventDefault();
3668
+ setHighlightedIndex((prev) => {
3669
+ let next = (prev - 1 + filteredOptions.length) % filteredOptions.length;
3670
+ while (filteredOptions[next]?.disabled && next !== prev) {
3671
+ next = (next - 1 + filteredOptions.length) % filteredOptions.length;
3672
+ }
3673
+ return next;
3674
+ });
3675
+ } else if (e.key === "Enter" || e.key === " ") {
3676
+ e.preventDefault();
3677
+ const current = filteredOptions[highlightedIndex];
3678
+ if (current && !current.disabled) {
3679
+ toggleOption(current.value);
3680
+ }
3681
+ } else if (e.key === "Escape") {
3682
+ e.preventDefault();
3683
+ setIsOpen(false);
3684
+ }
3685
+ };
2899
3686
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("relative w-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
2900
3687
  Popover,
2901
3688
  {
@@ -2904,9 +3691,19 @@ var MultiSelect = React66.forwardRef(
2904
3691
  trigger: /* @__PURE__ */ jsxRuntime.jsxs(
2905
3692
  "div",
2906
3693
  {
3694
+ tabIndex: disabled ? -1 : 0,
3695
+ role: "combobox",
3696
+ "aria-expanded": isOpen,
3697
+ "aria-haspopup": "listbox",
2907
3698
  onClick: () => !disabled && setIsOpen(!isOpen),
3699
+ onKeyDown: (e) => {
3700
+ if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !disabled) {
3701
+ e.preventDefault();
3702
+ setIsOpen(true);
3703
+ }
3704
+ },
2908
3705
  className: cn(
2909
- "min-h-10 py-1.5 px-3 rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] flex flex-wrap items-center justify-between gap-1.5 text-sm transition-all shadow-inner cursor-pointer",
3706
+ "min-h-10 py-1.5 px-3 rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] flex flex-wrap items-center justify-between gap-1.5 text-sm transition-all shadow-inner cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
2910
3707
  disabled ? "opacity-50 cursor-not-allowed border-[var(--ui-border-subtle)]" : "border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)]"
2911
3708
  ),
2912
3709
  children: [
@@ -2935,7 +3732,7 @@ var MultiSelect = React66.forwardRef(
2935
3732
  "span",
2936
3733
  {
2937
3734
  onClick: clearAll,
2938
- className: "p-1 rounded-full hover:bg-[var(--ui-bg-tertiary)] hover:text-[var(--ui-text-primary)] transition-colors",
3735
+ className: "p-1 rounded-full hover:bg-[var(--ui-bg-tertiary)] hover:text-[var(--ui-text-primary)] transition-colors cursor-pointer",
2939
3736
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
2940
3737
  }
2941
3738
  ),
@@ -2954,20 +3751,30 @@ var MultiSelect = React66.forwardRef(
2954
3751
  autoFocus: true,
2955
3752
  placeholder: searchPlaceholder,
2956
3753
  value: search,
2957
- onChange: (e) => setSearch(e.target.value),
3754
+ onChange: (e) => {
3755
+ setSearch(e.target.value);
3756
+ setHighlightedIndex(0);
3757
+ },
3758
+ onKeyDown: handleSearchKeyDown,
2958
3759
  className: "w-full h-8 pl-8 pr-3 text-xs rounded-xl bg-[var(--ui-bg-secondary)] border border-[var(--ui-border-subtle)] focus:outline-none focus:border-[var(--ui-primary)] text-[var(--ui-text-primary)]"
2959
3760
  }
2960
3761
  )
2961
3762
  ] }),
2962
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 overflow-y-auto space-y-0.5", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-4 text-center text-xs text-[var(--ui-text-muted)]", children: "No options found." }) : filteredOptions.map((opt) => {
3763
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 overflow-y-auto space-y-0.5", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-4 text-center text-xs text-[var(--ui-text-muted)]", children: "No options found." }) : filteredOptions.map((opt, index) => {
2963
3764
  const isChecked = selectedValues.includes(opt.value);
3765
+ const isHighlighted = highlightedIndex === index;
2964
3766
  return /* @__PURE__ */ jsxRuntime.jsxs(
2965
3767
  "div",
2966
3768
  {
2967
- onClick: () => toggleOption(opt.value),
3769
+ role: "option",
3770
+ "aria-selected": isChecked,
3771
+ "aria-disabled": opt.disabled,
3772
+ onClick: () => !opt.disabled && toggleOption(opt.value),
2968
3773
  className: cn(
2969
- "flex items-center justify-between px-3 py-2 rounded-xl text-xs font-medium cursor-pointer transition-colors",
2970
- isChecked ? "bg-[var(--ui-primary)]/15 text-[var(--ui-primary)] font-semibold" : "hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)]"
3774
+ "flex items-center justify-between px-3 py-2 rounded-xl text-xs font-medium transition-colors select-none",
3775
+ opt.disabled ? "opacity-40 cursor-not-allowed text-[var(--ui-text-muted)]" : "cursor-pointer",
3776
+ isChecked && "text-[var(--ui-primary)] font-semibold",
3777
+ isHighlighted && !opt.disabled && "bg-[var(--ui-primary)]/15"
2971
3778
  ),
2972
3779
  children: [
2973
3780
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 truncate", children: [
@@ -3009,26 +3816,58 @@ var FileUpload = React66.forwardRef(
3009
3816
  const [isDragOver, setIsDragOver] = React66.useState(false);
3010
3817
  const [fileList, setFileList] = React66.useState([]);
3011
3818
  const inputRef = React66.useRef(null);
3819
+ const fileListRef = React66.useRef(fileList);
3820
+ fileListRef.current = fileList;
3821
+ React66.useEffect(() => {
3822
+ return () => {
3823
+ fileListRef.current.forEach((item) => {
3824
+ if (item.previewUrl) {
3825
+ URL.revokeObjectURL(item.previewUrl);
3826
+ }
3827
+ });
3828
+ };
3829
+ }, []);
3830
+ const matchesAccept = (file, acceptStr) => {
3831
+ if (!acceptStr) return true;
3832
+ const patterns = acceptStr.split(",").map((p) => p.trim().toLowerCase());
3833
+ const fileName = file.name.toLowerCase();
3834
+ const fileType = file.type.toLowerCase();
3835
+ return patterns.some((p) => {
3836
+ if (p.startsWith(".")) {
3837
+ return fileName.endsWith(p);
3838
+ }
3839
+ if (p.endsWith("/*")) {
3840
+ const baseType = p.replace("/*", "");
3841
+ return fileType.startsWith(baseType);
3842
+ }
3843
+ return fileType === p;
3844
+ });
3845
+ };
3012
3846
  const processFiles = (incoming) => {
3013
3847
  if (!incoming) return;
3014
3848
  const validFiles = [];
3015
3849
  const newUploaded = [];
3016
3850
  Array.from(incoming).forEach((file) => {
3017
3851
  if (fileList.length + newUploaded.length >= maxFiles) return;
3852
+ const isAccepted = matchesAccept(file, accept);
3018
3853
  const isOversized = file.size > maxSizeMB * 1024 * 1024;
3854
+ const hasError = !isAccepted || isOversized;
3855
+ let errorMsg;
3856
+ if (!isAccepted) errorMsg = `File type not supported (${accept})`;
3857
+ else if (isOversized) errorMsg = `File exceeds ${maxSizeMB}MB limit`;
3019
3858
  const fileObj = {
3020
3859
  id: `${file.name}-${Date.now()}-${Math.random()}`,
3021
3860
  file,
3022
3861
  name: file.name,
3023
3862
  size: file.size,
3024
3863
  type: file.type,
3025
- progress: isOversized ? 0 : 100,
3026
- status: isOversized ? "error" : "completed",
3027
- errorMessage: isOversized ? `File exceeds ${maxSizeMB}MB limit` : void 0,
3028
- previewUrl: file.type.startsWith("image/") ? URL.createObjectURL(file) : void 0
3864
+ progress: hasError ? 0 : 100,
3865
+ status: hasError ? "error" : "completed",
3866
+ errorMessage: errorMsg,
3867
+ previewUrl: file.type.startsWith("image/") && !hasError ? URL.createObjectURL(file) : void 0
3029
3868
  };
3030
3869
  newUploaded.push(fileObj);
3031
- if (!isOversized) validFiles.push(file);
3870
+ if (!hasError) validFiles.push(file);
3032
3871
  });
3033
3872
  setFileList((prev) => [...prev, ...newUploaded]);
3034
3873
  if (validFiles.length > 0) {
@@ -3054,7 +3893,13 @@ var FileUpload = React66.forwardRef(
3054
3893
  if (inputRef.current) inputRef.current.value = "";
3055
3894
  };
3056
3895
  const removeFile = (id) => {
3057
- setFileList((prev) => prev.filter((f) => f.id !== id));
3896
+ setFileList((prev) => {
3897
+ const item = prev.find((f) => f.id === id);
3898
+ if (item?.previewUrl) {
3899
+ URL.revokeObjectURL(item.previewUrl);
3900
+ }
3901
+ return prev.filter((f) => f.id !== id);
3902
+ });
3058
3903
  onFileRemove?.(id);
3059
3904
  };
3060
3905
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn("w-full space-y-4", className), children: [
@@ -3162,8 +4007,12 @@ var NumberInput = React66.forwardRef(
3162
4007
  className,
3163
4008
  ...props
3164
4009
  }, ref) => {
3165
- const [internalVal, setInternalVal] = React66.useState(defaultValue);
3166
- const value = controlledVal !== void 0 ? controlledVal : internalVal;
4010
+ const [value, setValue] = useControllableState({
4011
+ value: controlledVal,
4012
+ defaultValue,
4013
+ onChange,
4014
+ name: "NumberInput"
4015
+ });
3167
4016
  const clamp = (num) => {
3168
4017
  let clamped = Math.min(max, Math.max(min, num));
3169
4018
  if (precision > 0) {
@@ -3174,14 +4023,12 @@ var NumberInput = React66.forwardRef(
3174
4023
  const handleIncrement = () => {
3175
4024
  if (disabled) return;
3176
4025
  const next = clamp(value + step);
3177
- setInternalVal(next);
3178
- onChange?.(next);
4026
+ setValue(next);
3179
4027
  };
3180
4028
  const handleDecrement = () => {
3181
4029
  if (disabled) return;
3182
4030
  const next = clamp(value - step);
3183
- setInternalVal(next);
3184
- onChange?.(next);
4031
+ setValue(next);
3185
4032
  };
3186
4033
  const handleInputChange = (e) => {
3187
4034
  const val = e.target.value;
@@ -3191,8 +4038,7 @@ var NumberInput = React66.forwardRef(
3191
4038
  const parsed = parseFloat(val);
3192
4039
  if (!isNaN(parsed)) {
3193
4040
  const next = clamp(parsed);
3194
- setInternalVal(next);
3195
- onChange?.(next);
4041
+ setValue(next);
3196
4042
  }
3197
4043
  };
3198
4044
  const handleKeyDown = (e) => {
@@ -3213,7 +4059,7 @@ var NumberInput = React66.forwardRef(
3213
4059
  type: "button",
3214
4060
  disabled: disabled || value <= min,
3215
4061
  onClick: handleDecrement,
3216
- className: "px-3 h-full flex items-center justify-center hover:bg-[var(--ui-bg-tertiary)] disabled:opacity-30 disabled:pointer-events-none text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors border-r border-[var(--ui-border-subtle)]",
4062
+ className: "px-3 h-full flex items-center justify-center hover:bg-[var(--ui-bg-tertiary)] disabled:opacity-30 disabled:pointer-events-none text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors border-r border-[var(--ui-border-subtle)] cursor-pointer",
3217
4063
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { className: "w-3.5 h-3.5" })
3218
4064
  }
3219
4065
  ),
@@ -3236,7 +4082,7 @@ var NumberInput = React66.forwardRef(
3236
4082
  type: "button",
3237
4083
  disabled: disabled || value >= max,
3238
4084
  onClick: handleIncrement,
3239
- className: "px-3 h-full flex items-center justify-center hover:bg-[var(--ui-bg-tertiary)] disabled:opacity-30 disabled:pointer-events-none text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors border-l border-[var(--ui-border-subtle)]",
4085
+ className: "px-3 h-full flex items-center justify-center hover:bg-[var(--ui-bg-tertiary)] disabled:opacity-30 disabled:pointer-events-none text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors border-l border-[var(--ui-border-subtle)] cursor-pointer",
3240
4086
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-3.5 h-3.5" })
3241
4087
  }
3242
4088
  )
@@ -3267,7 +4113,7 @@ var NumberInput = React66.forwardRef(
3267
4113
  type: "button",
3268
4114
  disabled: disabled || value >= max,
3269
4115
  onClick: handleIncrement,
3270
- className: "px-1.5 h-1/2 flex items-center justify-center hover:bg-[var(--ui-bg-tertiary)] rounded-t text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] disabled:opacity-30 transition-colors",
4116
+ className: "px-1.5 h-1/2 flex items-center justify-center hover:bg-[var(--ui-bg-tertiary)] rounded-t text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] disabled:opacity-30 transition-colors cursor-pointer",
3271
4117
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-3 h-3" })
3272
4118
  }
3273
4119
  ),
@@ -3277,7 +4123,7 @@ var NumberInput = React66.forwardRef(
3277
4123
  type: "button",
3278
4124
  disabled: disabled || value <= min,
3279
4125
  onClick: handleDecrement,
3280
- className: "px-1.5 h-1/2 flex items-center justify-center hover:bg-[var(--ui-bg-tertiary)] rounded-b text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] disabled:opacity-30 transition-colors",
4126
+ className: "px-1.5 h-1/2 flex items-center justify-center hover:bg-[var(--ui-bg-tertiary)] rounded-b text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] disabled:opacity-30 transition-colors cursor-pointer",
3281
4127
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { className: "w-3 h-3" })
3282
4128
  }
3283
4129
  )
@@ -3306,19 +4152,27 @@ var CurrencyInput = React66.forwardRef(
3306
4152
  }, ref) => {
3307
4153
  const symbol = currencySymbol || (currency === "VND" ? "\u20AB" : currency === "EUR" ? "\u20AC" : "$");
3308
4154
  const isSuffix = position === "suffix" || currency === "VND";
3309
- const [internalVal, setInternalVal] = React66.useState(defaultValue);
3310
- const value = controlledVal !== void 0 ? controlledVal : internalVal;
3311
- const [displayVal, setDisplayVal] = React66.useState(formatCurrencyString(value));
4155
+ const [value, setValue, isControlled] = useControllableState({
4156
+ value: controlledVal,
4157
+ defaultValue,
4158
+ onChange,
4159
+ name: "CurrencyInput"
4160
+ });
4161
+ const [internalInputString, setInternalInputString] = React66.useState(formatCurrencyString(value));
3312
4162
  React66.useEffect(() => {
3313
- setDisplayVal(formatCurrencyString(value));
3314
- }, [value]);
4163
+ if (!isControlled) {
4164
+ setInternalInputString(formatCurrencyString(value));
4165
+ }
4166
+ }, [value, isControlled]);
4167
+ const displayVal = isControlled ? formatCurrencyString(value) : internalInputString;
3315
4168
  const handleInputChange = (e) => {
3316
4169
  const raw = e.target.value.replace(/[^0-9.]/g, "");
3317
4170
  const parsed = parseFloat(raw);
3318
4171
  const num = isNaN(parsed) ? 0 : parsed;
3319
- setDisplayVal(raw ? formatCurrencyString(num) : "");
3320
- setInternalVal(num);
3321
- onChange?.(num);
4172
+ if (!isControlled) {
4173
+ setInternalInputString(raw ? formatCurrencyString(num) : "");
4174
+ }
4175
+ setValue(num);
3322
4176
  };
3323
4177
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative w-full flex items-center", className), children: [
3324
4178
  !isSuffix && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute left-3.5 text-sm font-bold text-[var(--ui-text-muted)] pointer-events-none font-mono", children: symbol }),
@@ -3440,6 +4294,7 @@ __export(overlays_exports, {
3440
4294
  ModalTitle: () => ModalTitle,
3441
4295
  Popover: () => Popover,
3442
4296
  Portal: () => Portal,
4297
+ Toast: () => Toast,
3443
4298
  ToasterProvider: () => ToasterProvider,
3444
4299
  Tooltip: () => Tooltip,
3445
4300
  drawerVariants: () => drawerVariants,
@@ -3450,37 +4305,112 @@ __export(overlays_exports, {
3450
4305
  useModal: () => useModal,
3451
4306
  useToast: () => useToast
3452
4307
  });
3453
- var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
3454
- function FocusTrap({ children, active = true }) {
4308
+ var FOCUSABLE_SELECTOR = [
4309
+ "a[href]",
4310
+ "button:not([disabled])",
4311
+ "textarea:not([disabled])",
4312
+ "input:not([disabled])",
4313
+ "select:not([disabled])",
4314
+ '[tabindex]:not([tabindex="-1"])',
4315
+ '[contenteditable="true"]'
4316
+ ].join(", ");
4317
+ var trapStack = [];
4318
+ function FocusTrap({
4319
+ children,
4320
+ active = true,
4321
+ autoFocus = true,
4322
+ restoreFocus = true
4323
+ }) {
3455
4324
  const rootRef = React66.useRef(null);
4325
+ const previousActiveElementRef = React66.useRef(null);
3456
4326
  React66.useEffect(() => {
3457
4327
  if (!active) return;
4328
+ if (typeof document !== "undefined") {
4329
+ previousActiveElementRef.current = document.activeElement;
4330
+ }
3458
4331
  const root = rootRef.current;
3459
4332
  if (!root) return;
3460
- const focusable = root.querySelectorAll(FOCUSABLE_SELECTOR);
3461
- const first = focusable[0];
3462
- const last = focusable[focusable.length - 1];
3463
- if (first) {
3464
- first.focus();
4333
+ trapStack.push(root);
4334
+ if (autoFocus) {
4335
+ const timer = setTimeout(() => {
4336
+ const focusable = root.querySelectorAll(FOCUSABLE_SELECTOR);
4337
+ if (focusable.length > 0) {
4338
+ focusable[0].focus();
4339
+ } else if (typeof root.focus === "function") {
4340
+ root.focus();
4341
+ }
4342
+ }, 0);
4343
+ return () => clearTimeout(timer);
3465
4344
  }
4345
+ return () => {
4346
+ const idx = trapStack.indexOf(root);
4347
+ if (idx !== -1) trapStack.splice(idx, 1);
4348
+ };
4349
+ }, [active, autoFocus]);
4350
+ React66.useEffect(() => {
4351
+ if (!active) return;
3466
4352
  const handleKeyDown = (e) => {
3467
4353
  if (e.key !== "Tab") return;
4354
+ const root = rootRef.current;
4355
+ if (!root) return;
4356
+ if (trapStack.length > 0 && trapStack[trapStack.length - 1] !== root) {
4357
+ return;
4358
+ }
4359
+ const focusable = Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
4360
+ (el) => el.offsetParent !== null || el.getClientRects().length > 0
4361
+ );
4362
+ if (focusable.length === 0) {
4363
+ e.preventDefault();
4364
+ root.focus();
4365
+ return;
4366
+ }
4367
+ const first = focusable[0];
4368
+ const last = focusable[focusable.length - 1];
3468
4369
  if (e.shiftKey) {
3469
- if (document.activeElement === first) {
4370
+ if (document.activeElement === first || !root.contains(document.activeElement)) {
3470
4371
  e.preventDefault();
3471
- last?.focus();
4372
+ last.focus();
3472
4373
  }
3473
4374
  } else {
3474
- if (document.activeElement === last) {
4375
+ if (document.activeElement === last || !root.contains(document.activeElement)) {
3475
4376
  e.preventDefault();
3476
- first?.focus();
4377
+ first.focus();
3477
4378
  }
3478
4379
  }
3479
4380
  };
3480
- window.addEventListener("keydown", handleKeyDown);
3481
- return () => window.removeEventListener("keydown", handleKeyDown);
3482
- }, [active]);
3483
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: rootRef, className: "contents", children });
4381
+ window.addEventListener("keydown", handleKeyDown, true);
4382
+ return () => {
4383
+ window.removeEventListener("keydown", handleKeyDown, true);
4384
+ const root = rootRef.current;
4385
+ if (root) {
4386
+ const idx = trapStack.indexOf(root);
4387
+ if (idx !== -1) trapStack.splice(idx, 1);
4388
+ }
4389
+ if (restoreFocus && previousActiveElementRef.current && typeof previousActiveElementRef.current.focus === "function") {
4390
+ previousActiveElementRef.current.focus();
4391
+ }
4392
+ };
4393
+ }, [active, restoreFocus]);
4394
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: rootRef, tabIndex: -1, className: "contents", style: { outline: "none" }, children });
4395
+ }
4396
+
4397
+ // src/utils/scroll-lock.ts
4398
+ var lockCount = 0;
4399
+ var previousOverflow = "";
4400
+ function lockBodyScroll() {
4401
+ if (typeof document === "undefined") return;
4402
+ if (lockCount === 0) {
4403
+ previousOverflow = document.body.style.overflow;
4404
+ document.body.style.overflow = "hidden";
4405
+ }
4406
+ lockCount++;
4407
+ }
4408
+ function unlockBodyScroll() {
4409
+ if (typeof document === "undefined") return;
4410
+ lockCount = Math.max(0, lockCount - 1);
4411
+ if (lockCount === 0) {
4412
+ document.body.style.overflow = previousOverflow;
4413
+ }
3484
4414
  }
3485
4415
  var ModalContext = React66.createContext(null);
3486
4416
  function useModal() {
@@ -3495,33 +4425,76 @@ function Modal({
3495
4425
  onClose,
3496
4426
  children,
3497
4427
  closeOnEsc = true,
3498
- closeOnOverlayClick = true
4428
+ closeOnOverlayClick = true,
4429
+ role = "dialog",
4430
+ "aria-label": ariaLabel,
4431
+ "aria-labelledby": customLabelledBy,
4432
+ "aria-describedby": customDescribedBy
3499
4433
  }) {
4434
+ const generatedId = React66.useId();
4435
+ const titleId = customLabelledBy || `modal-title-${generatedId}`;
4436
+ const descriptionId = customDescribedBy || `modal-desc-${generatedId}`;
4437
+ const [mountedTitleId, setMountedTitleId] = React66.useState(null);
4438
+ const [mountedDescId, setMountedDescId] = React66.useState(null);
4439
+ const registerTitle = React66.useCallback(
4440
+ (id) => {
4441
+ const activeId = id || titleId;
4442
+ setMountedTitleId(activeId);
4443
+ return () => setMountedTitleId(null);
4444
+ },
4445
+ [titleId]
4446
+ );
4447
+ const registerDescription = React66.useCallback(
4448
+ (id) => {
4449
+ const activeId = id || descriptionId;
4450
+ setMountedDescId(activeId);
4451
+ return () => setMountedDescId(null);
4452
+ },
4453
+ [descriptionId]
4454
+ );
3500
4455
  React66.useEffect(() => {
3501
4456
  if (!isOpen) return;
4457
+ lockBodyScroll();
3502
4458
  const handleKeyDown = (e) => {
3503
4459
  if (closeOnEsc && e.key === "Escape") {
3504
4460
  onClose();
3505
4461
  }
3506
4462
  };
3507
- const originalOverflow = document.body.style.overflow;
3508
- document.body.style.overflow = "hidden";
3509
4463
  window.addEventListener("keydown", handleKeyDown);
3510
4464
  return () => {
3511
4465
  window.removeEventListener("keydown", handleKeyDown);
3512
- document.body.style.overflow = originalOverflow;
4466
+ unlockBodyScroll();
3513
4467
  };
3514
4468
  }, [isOpen, closeOnEsc, onClose]);
3515
4469
  if (!isOpen) return null;
3516
- return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(ModalContext.Provider, { value: { isOpen, onClose, closeOnOverlayClick }, children: /* @__PURE__ */ jsxRuntime.jsx(FocusTrap, { active: isOpen, children: /* @__PURE__ */ jsxRuntime.jsx(
3517
- "div",
4470
+ const resolvedLabelledBy = customLabelledBy || (ariaLabel ? void 0 : mountedTitleId || void 0);
4471
+ const resolvedDescribedBy = customDescribedBy || (mountedDescId || void 0);
4472
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
4473
+ ModalContext.Provider,
3518
4474
  {
3519
- className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6",
3520
- role: "dialog",
3521
- "aria-modal": "true",
3522
- children
4475
+ value: {
4476
+ isOpen,
4477
+ onClose,
4478
+ closeOnOverlayClick,
4479
+ titleId,
4480
+ descriptionId,
4481
+ registerTitle,
4482
+ registerDescription
4483
+ },
4484
+ children: /* @__PURE__ */ jsxRuntime.jsx(FocusTrap, { active: isOpen, restoreFocus: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4485
+ "div",
4486
+ {
4487
+ className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6",
4488
+ role,
4489
+ "aria-modal": "true",
4490
+ "aria-label": ariaLabel,
4491
+ "aria-labelledby": resolvedLabelledBy,
4492
+ "aria-describedby": resolvedDescribedBy,
4493
+ children
4494
+ }
4495
+ ) })
3523
4496
  }
3524
- ) }) }) });
4497
+ ) });
3525
4498
  }
3526
4499
  var ModalOverlay = React66.forwardRef(
3527
4500
  ({ className, onClick, ...props }, ref) => {
@@ -3584,14 +4557,42 @@ var ModalHeader = React66.forwardRef(
3584
4557
  );
3585
4558
  ModalHeader.displayName = "ModalHeader";
3586
4559
  var ModalTitle = React66.forwardRef(
3587
- ({ className, children, ...props }, ref) => {
3588
- return /* @__PURE__ */ jsxRuntime.jsx("h2", { ref, className: cn("text-lg sm:text-xl font-bold tracking-tight text-[var(--ui-text-primary)]", className), ...props, children });
4560
+ ({ className, children, id, ...props }, ref) => {
4561
+ const { titleId, registerTitle } = useModal();
4562
+ const activeId = id || titleId;
4563
+ React66.useEffect(() => {
4564
+ return registerTitle(activeId);
4565
+ }, [registerTitle, activeId]);
4566
+ return /* @__PURE__ */ jsxRuntime.jsx(
4567
+ "h2",
4568
+ {
4569
+ ref,
4570
+ id: activeId,
4571
+ className: cn("text-lg sm:text-xl font-bold tracking-tight text-[var(--ui-text-primary)]", className),
4572
+ ...props,
4573
+ children
4574
+ }
4575
+ );
3589
4576
  }
3590
4577
  );
3591
4578
  ModalTitle.displayName = "ModalTitle";
3592
4579
  var ModalDescription = React66.forwardRef(
3593
- ({ className, children, ...props }, ref) => {
3594
- return /* @__PURE__ */ jsxRuntime.jsx("p", { ref, className: cn("text-sm text-[var(--ui-text-muted)]", className), ...props, children });
4580
+ ({ className, children, id, ...props }, ref) => {
4581
+ const { descriptionId, registerDescription } = useModal();
4582
+ const activeId = id || descriptionId;
4583
+ React66.useEffect(() => {
4584
+ return registerDescription(activeId);
4585
+ }, [registerDescription, activeId]);
4586
+ return /* @__PURE__ */ jsxRuntime.jsx(
4587
+ "p",
4588
+ {
4589
+ ref,
4590
+ id: activeId,
4591
+ className: cn("text-sm text-[var(--ui-text-muted)]", className),
4592
+ ...props,
4593
+ children
4594
+ }
4595
+ );
3595
4596
  }
3596
4597
  );
3597
4598
  ModalDescription.displayName = "ModalDescription";
@@ -3621,7 +4622,7 @@ var ModalCloseButton = React66.forwardRef(
3621
4622
  },
3622
4623
  "aria-label": "Close dialog",
3623
4624
  className: cn(
3624
- "absolute top-4 right-4 p-1.5 rounded-full text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)] transition-colors",
4625
+ "absolute top-4 right-4 p-1.5 rounded-full text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)] transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
3625
4626
  className
3626
4627
  ),
3627
4628
  ...props,
@@ -3640,32 +4641,43 @@ function AlertDialog({
3640
4641
  confirmText = "Confirm",
3641
4642
  cancelText = "Cancel",
3642
4643
  variant = "danger",
3643
- isLoading = false
4644
+ isLoading = false,
4645
+ closeOnOverlayClick = false
3644
4646
  }) {
3645
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { isOpen, onClose, children: [
3646
- /* @__PURE__ */ jsxRuntime.jsx(ModalOverlay, {}),
3647
- /* @__PURE__ */ jsxRuntime.jsxs(ModalContent, { size: "sm", children: [
3648
- /* @__PURE__ */ jsxRuntime.jsx(ModalCloseButton, {}),
3649
- /* @__PURE__ */ jsxRuntime.jsxs(ModalHeader, { children: [
3650
- /* @__PURE__ */ jsxRuntime.jsx(ModalTitle, { children: title }),
3651
- /* @__PURE__ */ jsxRuntime.jsx(ModalDescription, { children: description })
3652
- ] }),
3653
- /* @__PURE__ */ jsxRuntime.jsxs(ModalFooter, { children: [
3654
- /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: onClose, disabled: isLoading, children: cancelText }),
3655
- /* @__PURE__ */ jsxRuntime.jsx(
3656
- Button,
3657
- {
3658
- variant: variant === "danger" ? "danger" : "primary",
3659
- size: "sm",
3660
- loading: isLoading,
3661
- onClick: onConfirm,
3662
- children: confirmText
3663
- }
3664
- )
3665
- ] })
3666
- ] })
3667
- ] });
4647
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4648
+ Modal,
4649
+ {
4650
+ isOpen,
4651
+ onClose,
4652
+ role: "alertdialog",
4653
+ closeOnOverlayClick,
4654
+ children: [
4655
+ /* @__PURE__ */ jsxRuntime.jsx(ModalOverlay, {}),
4656
+ /* @__PURE__ */ jsxRuntime.jsxs(ModalContent, { size: "sm", children: [
4657
+ /* @__PURE__ */ jsxRuntime.jsx(ModalCloseButton, {}),
4658
+ /* @__PURE__ */ jsxRuntime.jsxs(ModalHeader, { children: [
4659
+ /* @__PURE__ */ jsxRuntime.jsx(ModalTitle, { children: title }),
4660
+ /* @__PURE__ */ jsxRuntime.jsx(ModalDescription, { children: description })
4661
+ ] }),
4662
+ /* @__PURE__ */ jsxRuntime.jsxs(ModalFooter, { children: [
4663
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: onClose, disabled: isLoading, autoFocus: true, children: cancelText }),
4664
+ /* @__PURE__ */ jsxRuntime.jsx(
4665
+ Button,
4666
+ {
4667
+ variant: variant === "danger" ? "danger" : "primary",
4668
+ size: "sm",
4669
+ loading: isLoading,
4670
+ onClick: onConfirm,
4671
+ children: confirmText
4672
+ }
4673
+ )
4674
+ ] })
4675
+ ] })
4676
+ ]
4677
+ }
4678
+ );
3668
4679
  }
4680
+ AlertDialog.displayName = "AlertDialog";
3669
4681
  var DrawerContext = React66.createContext(null);
3670
4682
  function useDrawer() {
3671
4683
  const context = React66.useContext(DrawerContext);
@@ -3695,54 +4707,68 @@ function Drawer({
3695
4707
  onClose,
3696
4708
  placement = "right",
3697
4709
  children,
3698
- closeOnEsc = true
4710
+ closeOnEsc = true,
4711
+ "aria-label": ariaLabel,
4712
+ "aria-labelledby": customLabelledBy
3699
4713
  }) {
4714
+ const generatedId = React66.useId();
4715
+ const titleId = customLabelledBy || `drawer-title-${generatedId}`;
3700
4716
  React66.useEffect(() => {
3701
4717
  if (!isOpen) return;
4718
+ lockBodyScroll();
3702
4719
  const handleKeyDown = (e) => {
3703
4720
  if (closeOnEsc && e.key === "Escape") {
3704
4721
  onClose();
3705
4722
  }
3706
4723
  };
3707
- const originalOverflow = document.body.style.overflow;
3708
- document.body.style.overflow = "hidden";
3709
4724
  window.addEventListener("keydown", handleKeyDown);
3710
4725
  return () => {
3711
4726
  window.removeEventListener("keydown", handleKeyDown);
3712
- document.body.style.overflow = originalOverflow;
4727
+ unlockBodyScroll();
3713
4728
  };
3714
4729
  }, [isOpen, closeOnEsc, onClose]);
3715
4730
  if (!isOpen) return null;
3716
- return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: { isOpen, onClose }, children: /* @__PURE__ */ jsxRuntime.jsx(FocusTrap, { active: isOpen, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 overflow-hidden", children: [
3717
- /* @__PURE__ */ jsxRuntime.jsx(
3718
- "div",
3719
- {
3720
- "aria-hidden": "true",
3721
- onClick: onClose,
3722
- className: "fixed inset-0 bg-[var(--ui-bg-overlay)] backdrop-blur-sm transition-opacity animate-in fade-in duration-200"
3723
- }
3724
- ),
3725
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(drawerVariants({ placement })), children })
3726
- ] }) }) }) });
4731
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: { isOpen, onClose, titleId }, children: /* @__PURE__ */ jsxRuntime.jsx(FocusTrap, { active: isOpen, restoreFocus: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
4732
+ "div",
4733
+ {
4734
+ className: "fixed inset-0 z-50 overflow-hidden",
4735
+ role: "dialog",
4736
+ "aria-modal": "true",
4737
+ "aria-label": ariaLabel,
4738
+ "aria-labelledby": ariaLabel ? void 0 : titleId,
4739
+ children: [
4740
+ /* @__PURE__ */ jsxRuntime.jsx(
4741
+ "div",
4742
+ {
4743
+ "aria-hidden": "true",
4744
+ onClick: onClose,
4745
+ className: "fixed inset-0 bg-[var(--ui-bg-overlay)] backdrop-blur-sm transition-opacity animate-in fade-in duration-200"
4746
+ }
4747
+ ),
4748
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(drawerVariants({ placement })), children })
4749
+ ]
4750
+ }
4751
+ ) }) }) });
3727
4752
  }
3728
4753
  var DrawerHeader = React66.forwardRef(
3729
- ({ className, children, ...props }, ref) => {
3730
- const { onClose } = useDrawer();
4754
+ ({ className, children, id, ...props }, ref) => {
4755
+ const { onClose, titleId } = useDrawer();
3731
4756
  return /* @__PURE__ */ jsxRuntime.jsxs(
3732
4757
  "div",
3733
4758
  {
3734
4759
  ref,
4760
+ id: id || titleId,
3735
4761
  className: cn("flex items-center justify-between p-5 border-b border-[var(--ui-border-subtle)]", className),
3736
4762
  ...props,
3737
4763
  children: [
3738
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children }),
4764
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 font-semibold text-base text-[var(--ui-text-primary)]", children }),
3739
4765
  /* @__PURE__ */ jsxRuntime.jsx(
3740
4766
  "button",
3741
4767
  {
3742
4768
  type: "button",
3743
4769
  onClick: onClose,
3744
4770
  "aria-label": "Close drawer",
3745
- className: "p-1.5 rounded-full text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)] transition-colors ml-4",
4771
+ className: "p-1.5 rounded-full text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)] transition-colors ml-4 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
3746
4772
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
3747
4773
  }
3748
4774
  )
@@ -3793,40 +4819,57 @@ function Tooltip({
3793
4819
  children,
3794
4820
  placement = "top",
3795
4821
  delayDuration = 200,
3796
- className
4822
+ className,
4823
+ id: customId
3797
4824
  }) {
4825
+ const generatedId = React66.useId();
4826
+ const tooltipId = customId || `tooltip-${generatedId}`;
3798
4827
  const [isVisible, setIsVisible] = React66.useState(false);
3799
4828
  const [coords, setCoords] = React66.useState({ top: 0, left: 0 });
3800
4829
  const timeoutRef = React66.useRef(null);
3801
4830
  const triggerRef = React66.useRef(null);
4831
+ const calculatePosition = () => {
4832
+ if (triggerRef.current) {
4833
+ const rect = triggerRef.current.getBoundingClientRect();
4834
+ let top = 0;
4835
+ let left = 0;
4836
+ if (placement === "top") {
4837
+ top = rect.top + window.scrollY;
4838
+ left = rect.left + rect.width / 2 + window.scrollX;
4839
+ } else if (placement === "bottom") {
4840
+ top = rect.bottom + window.scrollY;
4841
+ left = rect.left + rect.width / 2 + window.scrollX;
4842
+ } else if (placement === "left") {
4843
+ top = rect.top + rect.height / 2 + window.scrollY;
4844
+ left = rect.left + window.scrollX;
4845
+ } else if (placement === "right") {
4846
+ top = rect.top + rect.height / 2 + window.scrollY;
4847
+ left = rect.right + window.scrollX;
4848
+ }
4849
+ setCoords({ top, left });
4850
+ }
4851
+ };
3802
4852
  const handleMouseEnter = () => {
4853
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
3803
4854
  timeoutRef.current = setTimeout(() => {
3804
- if (triggerRef.current) {
3805
- const rect = triggerRef.current.getBoundingClientRect();
3806
- let top = 0;
3807
- let left = 0;
3808
- if (placement === "top") {
3809
- top = rect.top + window.scrollY;
3810
- left = rect.left + rect.width / 2 + window.scrollX;
3811
- } else if (placement === "bottom") {
3812
- top = rect.bottom + window.scrollY;
3813
- left = rect.left + rect.width / 2 + window.scrollX;
3814
- } else if (placement === "left") {
3815
- top = rect.top + rect.height / 2 + window.scrollY;
3816
- left = rect.left + window.scrollX;
3817
- } else if (placement === "right") {
3818
- top = rect.top + rect.height / 2 + window.scrollY;
3819
- left = rect.right + window.scrollX;
3820
- }
3821
- setCoords({ top, left });
3822
- setIsVisible(true);
3823
- }
4855
+ calculatePosition();
4856
+ setIsVisible(true);
3824
4857
  }, delayDuration);
3825
4858
  };
3826
4859
  const handleMouseLeave = () => {
3827
4860
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
3828
4861
  setIsVisible(false);
3829
4862
  };
4863
+ React66.useEffect(() => {
4864
+ if (!isVisible) return;
4865
+ const handleKeyDown = (e) => {
4866
+ if (e.key === "Escape") {
4867
+ setIsVisible(false);
4868
+ }
4869
+ };
4870
+ window.addEventListener("keydown", handleKeyDown);
4871
+ return () => window.removeEventListener("keydown", handleKeyDown);
4872
+ }, [isVisible]);
3830
4873
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3831
4874
  /* @__PURE__ */ jsxRuntime.jsx(
3832
4875
  "div",
@@ -3836,6 +4879,7 @@ function Tooltip({
3836
4879
  onMouseLeave: handleMouseLeave,
3837
4880
  onFocus: handleMouseEnter,
3838
4881
  onBlur: handleMouseLeave,
4882
+ "aria-describedby": isVisible ? tooltipId : void 0,
3839
4883
  className: "inline-flex cursor-pointer",
3840
4884
  children
3841
4885
  }
@@ -3843,8 +4887,9 @@ function Tooltip({
3843
4887
  isVisible && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
3844
4888
  "div",
3845
4889
  {
3846
- style: { top: `${coords.top}px`, left: `${coords.left}px`, position: "absolute" },
4890
+ id: tooltipId,
3847
4891
  role: "tooltip",
4892
+ style: { top: `${coords.top}px`, left: `${coords.left}px`, position: "absolute" },
3848
4893
  className: cn(tooltipVariants({ placement }), className),
3849
4894
  children: content
3850
4895
  }
@@ -3881,6 +4926,38 @@ var toastIcons = {
3881
4926
  warning: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "w-5 h-5 text-[var(--ui-warning)] flex-shrink-0" }),
3882
4927
  error: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-5 h-5 text-[var(--ui-error)] flex-shrink-0" })
3883
4928
  };
4929
+ var Toast = React66__default.default.forwardRef(
4930
+ ({ className, title, description, type = "info", onDismiss, ...props }, ref) => {
4931
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4932
+ "div",
4933
+ {
4934
+ ref,
4935
+ role: "status",
4936
+ "aria-live": "polite",
4937
+ className: cn(toastVariants({ type }), className),
4938
+ ...props,
4939
+ children: [
4940
+ toastIcons[type || "info"],
4941
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-1", children: [
4942
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold text-[var(--ui-text-primary)]", children: title }),
4943
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-[var(--ui-text-muted)]", children: description })
4944
+ ] }),
4945
+ onDismiss && /* @__PURE__ */ jsxRuntime.jsx(
4946
+ "button",
4947
+ {
4948
+ type: "button",
4949
+ onClick: onDismiss,
4950
+ "aria-label": "Dismiss toast",
4951
+ className: "p-1 rounded text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)] transition-colors cursor-pointer",
4952
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
4953
+ }
4954
+ )
4955
+ ]
4956
+ }
4957
+ );
4958
+ }
4959
+ );
4960
+ Toast.displayName = "Toast";
3884
4961
  function ToasterProvider({ children, position = "bottom-right" }) {
3885
4962
  const [toasts, setToasts] = React66.useState([]);
3886
4963
  const dismiss = React66.useCallback((id) => {
@@ -3953,66 +5030,86 @@ var BottomSheet = React66.forwardRef(
3953
5030
  description,
3954
5031
  className,
3955
5032
  showHandle = true,
3956
- maxHeight = "max-h-[85vh]"
5033
+ maxHeight = "max-h-[85vh]",
5034
+ "aria-label": ariaLabel
3957
5035
  }, ref) => {
3958
- React66.useRef(null);
5036
+ const generatedId = React66.useId();
5037
+ const titleId = `bottomsheet-title-${generatedId}`;
5038
+ const descId = `bottomsheet-desc-${generatedId}`;
3959
5039
  React66.useEffect(() => {
5040
+ if (!isOpen) return;
5041
+ lockBodyScroll();
3960
5042
  const handleKeyDown = (e) => {
3961
- if (e.key === "Escape" && isOpen) {
5043
+ if (e.key === "Escape") {
3962
5044
  onClose();
3963
5045
  }
3964
5046
  };
3965
5047
  window.addEventListener("keydown", handleKeyDown);
3966
- return () => window.removeEventListener("keydown", handleKeyDown);
5048
+ return () => {
5049
+ window.removeEventListener("keydown", handleKeyDown);
5050
+ unlockBodyScroll();
5051
+ };
3967
5052
  }, [isOpen, onClose]);
3968
5053
  if (!isOpen) return null;
3969
- return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(FocusTrap, { active: isOpen, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex flex-col justify-end", children: [
3970
- /* @__PURE__ */ jsxRuntime.jsx(
3971
- "div",
3972
- {
3973
- onClick: onClose,
3974
- className: "fixed inset-0 bg-black/60 backdrop-blur-md transition-opacity animate-in fade-in duration-300",
3975
- "aria-hidden": "true"
3976
- }
3977
- ),
3978
- /* @__PURE__ */ jsxRuntime.jsxs(
3979
- "div",
3980
- {
3981
- ref,
3982
- className: cn(
3983
- "relative z-50 w-full max-w-2xl mx-auto rounded-t-[2rem] border-t border-x border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] backdrop-blur-3xl shadow-2xl overflow-hidden flex flex-col transition-transform animate-in slide-in-from-bottom duration-300",
3984
- maxHeight,
3985
- className
5054
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(FocusTrap, { active: isOpen, restoreFocus: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5055
+ "div",
5056
+ {
5057
+ className: "fixed inset-0 z-50 flex flex-col justify-end",
5058
+ role: "dialog",
5059
+ "aria-modal": "true",
5060
+ "aria-label": ariaLabel,
5061
+ "aria-labelledby": title ? titleId : void 0,
5062
+ "aria-describedby": description ? descId : void 0,
5063
+ children: [
5064
+ /* @__PURE__ */ jsxRuntime.jsx(
5065
+ "div",
5066
+ {
5067
+ onClick: onClose,
5068
+ className: "fixed inset-0 bg-black/60 backdrop-blur-md transition-opacity animate-in fade-in duration-300",
5069
+ "aria-hidden": "true"
5070
+ }
3986
5071
  ),
3987
- children: [
3988
- showHandle && /* @__PURE__ */ jsxRuntime.jsx(
3989
- "div",
3990
- {
3991
- onClick: onClose,
3992
- className: "w-full py-3 flex items-center justify-center cursor-grab active:cursor-grabbing",
3993
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-1.5 rounded-full bg-[var(--ui-text-muted)]/40 hover:bg-[var(--ui-text-muted)] transition-colors" })
3994
- }
3995
- ),
3996
- (title || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 pb-3 flex items-center justify-between border-b border-[var(--ui-border-subtle)]", children: [
3997
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3998
- title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-bold tracking-tight text-[var(--ui-text-primary)]", children: title }),
3999
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-[var(--ui-text-muted)]", children: description })
4000
- ] }),
4001
- /* @__PURE__ */ jsxRuntime.jsx(
4002
- "button",
4003
- {
4004
- type: "button",
4005
- onClick: onClose,
4006
- className: "p-1.5 rounded-full hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors",
4007
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
4008
- }
4009
- )
4010
- ] }),
4011
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 overflow-y-auto flex-1 space-y-4", children })
4012
- ]
4013
- }
4014
- )
4015
- ] }) }) });
5072
+ /* @__PURE__ */ jsxRuntime.jsxs(
5073
+ "div",
5074
+ {
5075
+ ref,
5076
+ className: cn(
5077
+ "relative z-50 w-full max-w-2xl mx-auto rounded-t-[2rem] border-t border-x border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] backdrop-blur-3xl shadow-2xl overflow-hidden flex flex-col transition-transform animate-in slide-in-from-bottom duration-300",
5078
+ maxHeight,
5079
+ className
5080
+ ),
5081
+ children: [
5082
+ showHandle && /* @__PURE__ */ jsxRuntime.jsx(
5083
+ "div",
5084
+ {
5085
+ onClick: onClose,
5086
+ className: "w-full py-3 flex items-center justify-center cursor-grab active:cursor-grabbing",
5087
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-1.5 rounded-full bg-[var(--ui-text-muted)]/40 hover:bg-[var(--ui-text-muted)] transition-colors" })
5088
+ }
5089
+ ),
5090
+ (title || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 pb-3 flex items-center justify-between border-b border-[var(--ui-border-subtle)]", children: [
5091
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5092
+ title && /* @__PURE__ */ jsxRuntime.jsx("h3", { id: titleId, className: "text-base font-bold tracking-tight text-[var(--ui-text-primary)]", children: title }),
5093
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { id: descId, className: "text-xs text-[var(--ui-text-muted)]", children: description })
5094
+ ] }),
5095
+ /* @__PURE__ */ jsxRuntime.jsx(
5096
+ "button",
5097
+ {
5098
+ type: "button",
5099
+ onClick: onClose,
5100
+ "aria-label": "Close sheet",
5101
+ className: "p-1.5 rounded-full hover:bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
5102
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
5103
+ }
5104
+ )
5105
+ ] }),
5106
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 overflow-y-auto flex-1 space-y-4", children })
5107
+ ]
5108
+ }
5109
+ )
5110
+ ]
5111
+ }
5112
+ ) }) });
4016
5113
  }
4017
5114
  );
4018
5115
  BottomSheet.displayName = "BottomSheet";
@@ -4033,14 +5130,21 @@ var ContextMenuItem = ({
4033
5130
  "div",
4034
5131
  {
4035
5132
  role: "menuitem",
5133
+ tabIndex: disabled ? -1 : 0,
4036
5134
  "aria-disabled": disabled,
4037
5135
  onClick: () => {
4038
5136
  if (!disabled) onSelect?.();
4039
5137
  },
5138
+ onKeyDown: (e) => {
5139
+ if (!disabled && (e.key === "Enter" || e.key === " ")) {
5140
+ e.preventDefault();
5141
+ onSelect?.();
5142
+ }
5143
+ },
4040
5144
  className: cn(
4041
- "flex items-center justify-between px-3 py-1.5 rounded-xl text-xs font-medium cursor-pointer transition-colors select-none",
5145
+ "flex items-center justify-between px-3 py-1.5 rounded-xl text-xs font-medium cursor-pointer transition-colors select-none focus:outline-none focus:bg-[var(--ui-bg-tertiary)]",
4042
5146
  disabled && "opacity-40 cursor-not-allowed pointer-events-none",
4043
- destructive ? "text-rose-500 hover:bg-rose-500/10" : "text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)]",
5147
+ destructive ? "text-rose-500 hover:bg-rose-500/10 focus:bg-rose-500/10" : "text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)]",
4044
5148
  className
4045
5149
  ),
4046
5150
  ...props,
@@ -4068,20 +5172,47 @@ var ContextMenu = React66.forwardRef(
4068
5172
  setIsOpen(true);
4069
5173
  };
4070
5174
  React66.useEffect(() => {
5175
+ if (!isOpen) return;
5176
+ const timer = setTimeout(() => {
5177
+ const firstItem = menuRef.current?.querySelector('[role="menuitem"]:not([aria-disabled="true"])');
5178
+ firstItem?.focus();
5179
+ }, 10);
4071
5180
  const handleClickOutside = (e) => {
4072
5181
  if (menuRef.current && !menuRef.current.contains(e.target)) {
4073
5182
  setIsOpen(false);
4074
5183
  }
4075
5184
  };
4076
5185
  const handleKeyDown = (e) => {
4077
- if (e.key === "Escape") setIsOpen(false);
5186
+ if (!menuRef.current) return;
5187
+ const items = Array.from(
5188
+ menuRef.current.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
5189
+ );
5190
+ if (items.length === 0) return;
5191
+ const currentIndex = items.indexOf(document.activeElement);
5192
+ if (e.key === "ArrowDown") {
5193
+ e.preventDefault();
5194
+ const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % items.length;
5195
+ items[nextIndex]?.focus();
5196
+ } else if (e.key === "ArrowUp") {
5197
+ e.preventDefault();
5198
+ const prevIndex = currentIndex === -1 ? items.length - 1 : (currentIndex - 1 + items.length) % items.length;
5199
+ items[prevIndex]?.focus();
5200
+ } else if (e.key === "Home") {
5201
+ e.preventDefault();
5202
+ items[0]?.focus();
5203
+ } else if (e.key === "End") {
5204
+ e.preventDefault();
5205
+ items[items.length - 1]?.focus();
5206
+ } else if (e.key === "Escape") {
5207
+ e.preventDefault();
5208
+ setIsOpen(false);
5209
+ }
4078
5210
  };
4079
- if (isOpen) {
4080
- window.addEventListener("click", handleClickOutside);
4081
- window.addEventListener("contextmenu", handleClickOutside);
4082
- window.addEventListener("keydown", handleKeyDown);
4083
- }
5211
+ window.addEventListener("click", handleClickOutside);
5212
+ window.addEventListener("contextmenu", handleClickOutside);
5213
+ window.addEventListener("keydown", handleKeyDown);
4084
5214
  return () => {
5215
+ clearTimeout(timer);
4085
5216
  window.removeEventListener("click", handleClickOutside);
4086
5217
  window.removeEventListener("contextmenu", handleClickOutside);
4087
5218
  window.removeEventListener("keydown", handleKeyDown);
@@ -4093,9 +5224,11 @@ var ContextMenu = React66.forwardRef(
4093
5224
  "div",
4094
5225
  {
4095
5226
  ref: menuRef,
5227
+ role: "menu",
5228
+ tabIndex: -1,
4096
5229
  style: { top: `${position.y}px`, left: `${position.x}px` },
4097
5230
  onClick: () => setIsOpen(false),
4098
- className: "fixed z-50 min-w-48 p-1.5 rounded-2xl bg-[var(--ui-bg-surface)]/90 backdrop-blur-3xl border border-[var(--ui-border-default)] shadow-2xl space-y-0.5 animate-in fade-in zoom-in-95 duration-150",
5231
+ className: "fixed z-50 min-w-48 p-1.5 rounded-2xl bg-[var(--ui-bg-surface)]/90 backdrop-blur-3xl border border-[var(--ui-border-default)] shadow-2xl space-y-0.5 animate-in fade-in zoom-in-95 duration-150 focus:outline-none",
4099
5232
  children: menuContent
4100
5233
  }
4101
5234
  ) })
@@ -4189,29 +5322,114 @@ function useTabs() {
4189
5322
  return context;
4190
5323
  }
4191
5324
  var Tabs = React66.forwardRef(
4192
- ({ defaultValue = "", value: controlledValue, onValueChange, variant = "pills", className, children, ...props }, ref) => {
5325
+ ({
5326
+ defaultValue = "",
5327
+ value: controlledValue,
5328
+ onValueChange,
5329
+ variant = "pills",
5330
+ orientation = "horizontal",
5331
+ className,
5332
+ children,
5333
+ ...props
5334
+ }, ref) => {
5335
+ const baseId = React66.useId();
4193
5336
  const [selectedTab, setSelectedTab] = React66.useState(defaultValue);
4194
5337
  const activeTab = controlledValue !== void 0 ? controlledValue : selectedTab;
5338
+ const tabRegistryRef = React66.useRef(
5339
+ /* @__PURE__ */ new Map()
5340
+ );
5341
+ const registerTab = (value, disabled, element) => {
5342
+ tabRegistryRef.current.set(value, { disabled, element });
5343
+ };
5344
+ const unregisterTab = (value) => {
5345
+ tabRegistryRef.current.delete(value);
5346
+ };
4195
5347
  const handleTabChange = (val) => {
4196
- setSelectedTab(val);
5348
+ if (!controlledValue) {
5349
+ setSelectedTab(val);
5350
+ }
4197
5351
  onValueChange?.(val);
4198
5352
  };
4199
- return /* @__PURE__ */ jsxRuntime.jsx(TabsContext.Provider, { value: { activeTab, setActiveTab: handleTabChange, variant }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("w-full flex flex-col space-y-4", className), ...props, children }) });
5353
+ return /* @__PURE__ */ jsxRuntime.jsx(
5354
+ TabsContext.Provider,
5355
+ {
5356
+ value: {
5357
+ activeTab,
5358
+ setActiveTab: handleTabChange,
5359
+ variant,
5360
+ orientation,
5361
+ baseId,
5362
+ registerTab,
5363
+ unregisterTab
5364
+ },
5365
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5366
+ "div",
5367
+ {
5368
+ ref,
5369
+ className: cn(
5370
+ "w-full flex space-y-4",
5371
+ orientation === "vertical" ? "flex-row space-y-0 space-x-6" : "flex-col",
5372
+ className
5373
+ ),
5374
+ ...props,
5375
+ children
5376
+ }
5377
+ )
5378
+ }
5379
+ );
4200
5380
  }
4201
5381
  );
4202
5382
  Tabs.displayName = "Tabs";
4203
5383
  var TabsList = React66.forwardRef(
4204
- ({ className, children, ...props }, ref) => {
4205
- const { variant } = useTabs();
5384
+ ({ className, children, onKeyDown, ...props }, ref) => {
5385
+ const { variant, orientation, activeTab, setActiveTab } = useTabs();
5386
+ const listRef = React66.useRef(null);
5387
+ const handleKeyDown = (e) => {
5388
+ onKeyDown?.(e);
5389
+ if (e.defaultPrevented) return;
5390
+ const tabs = Array.from(
5391
+ listRef.current?.querySelectorAll('[role="tab"]:not([disabled])') || []
5392
+ );
5393
+ if (tabs.length === 0) return;
5394
+ const currentIndex = tabs.findIndex((tab) => tab.getAttribute("aria-selected") === "true");
5395
+ let nextIndex = -1;
5396
+ const isPrev = orientation === "horizontal" && e.key === "ArrowLeft" || orientation === "vertical" && e.key === "ArrowUp";
5397
+ const isNext = orientation === "horizontal" && e.key === "ArrowRight" || orientation === "vertical" && e.key === "ArrowDown";
5398
+ if (isNext) {
5399
+ e.preventDefault();
5400
+ nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % tabs.length;
5401
+ } else if (isPrev) {
5402
+ e.preventDefault();
5403
+ nextIndex = currentIndex === -1 ? tabs.length - 1 : (currentIndex - 1 + tabs.length) % tabs.length;
5404
+ } else if (e.key === "Home") {
5405
+ e.preventDefault();
5406
+ nextIndex = 0;
5407
+ } else if (e.key === "End") {
5408
+ e.preventDefault();
5409
+ nextIndex = tabs.length - 1;
5410
+ }
5411
+ if (nextIndex !== -1 && tabs[nextIndex]) {
5412
+ const nextTab = tabs[nextIndex];
5413
+ nextTab.focus();
5414
+ nextTab.click();
5415
+ }
5416
+ };
4206
5417
  return /* @__PURE__ */ jsxRuntime.jsx(
4207
5418
  "div",
4208
5419
  {
4209
- ref,
5420
+ ref: (node) => {
5421
+ listRef.current = node;
5422
+ if (typeof ref === "function") ref(node);
5423
+ else if (ref) ref.current = node;
5424
+ },
4210
5425
  role: "tablist",
5426
+ "aria-orientation": orientation,
5427
+ onKeyDown: handleKeyDown,
4211
5428
  className: cn(
4212
- "inline-flex items-center",
5429
+ "inline-flex",
5430
+ orientation === "vertical" ? "flex-col items-stretch gap-1" : "items-center",
4213
5431
  variant === "pills" && "p-1 rounded-[var(--ui-radius-lg)] bg-[var(--ui-bg-secondary)] border border-[var(--ui-border-subtle)] gap-1",
4214
- variant === "line" && "border-b border-[var(--ui-border-default)] gap-6",
5432
+ variant === "line" && (orientation === "vertical" ? "border-r border-[var(--ui-border-default)] pr-2" : "border-b border-[var(--ui-border-default)] gap-6"),
4215
5433
  variant === "enclosed" && "border-b border-[var(--ui-border-default)] gap-1",
4216
5434
  className
4217
5435
  ),
@@ -4223,17 +5441,35 @@ var TabsList = React66.forwardRef(
4223
5441
  );
4224
5442
  TabsList.displayName = "TabsList";
4225
5443
  var TabsTrigger = React66.forwardRef(
4226
- ({ className, value, children, ...props }, ref) => {
4227
- const { activeTab, setActiveTab, variant } = useTabs();
5444
+ ({ className, value, disabled = false, children, id, onClick, ...props }, ref) => {
5445
+ const { activeTab, setActiveTab, variant, baseId, registerTab, unregisterTab } = useTabs();
4228
5446
  const isActive = activeTab === value;
5447
+ const triggerId = id || `tab-${baseId}-${value}`;
5448
+ const panelId = `panel-${baseId}-${value}`;
5449
+ const innerRef = React66.useRef(null);
5450
+ React66.useEffect(() => {
5451
+ registerTab(value, Boolean(disabled), innerRef.current);
5452
+ return () => unregisterTab(value);
5453
+ }, [value, disabled, registerTab, unregisterTab]);
4229
5454
  return /* @__PURE__ */ jsxRuntime.jsx(
4230
5455
  "button",
4231
5456
  {
4232
- ref,
5457
+ ref: (node) => {
5458
+ innerRef.current = node;
5459
+ if (typeof ref === "function") ref(node);
5460
+ else if (ref) ref.current = node;
5461
+ },
5462
+ id: triggerId,
4233
5463
  role: "tab",
4234
5464
  type: "button",
5465
+ disabled,
4235
5466
  "aria-selected": isActive,
4236
- onClick: () => setActiveTab(value),
5467
+ "aria-controls": panelId,
5468
+ tabIndex: isActive ? 0 : -1,
5469
+ onClick: (e) => {
5470
+ onClick?.(e);
5471
+ if (!disabled) setActiveTab(value);
5472
+ },
4237
5473
  className: cn(
4238
5474
  "inline-flex items-center justify-center font-medium text-sm transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:pointer-events-none disabled:opacity-50 select-none cursor-pointer",
4239
5475
  variant === "pills" && "px-3.5 py-1.5 rounded-[var(--ui-radius-md)] text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)]",
@@ -4252,16 +5488,24 @@ var TabsTrigger = React66.forwardRef(
4252
5488
  );
4253
5489
  TabsTrigger.displayName = "TabsTrigger";
4254
5490
  var TabsContent = React66.forwardRef(
4255
- ({ className, value, children, ...props }, ref) => {
4256
- const { activeTab } = useTabs();
4257
- if (activeTab !== value) return null;
5491
+ ({ className, value, children, id, ...props }, ref) => {
5492
+ const { activeTab, baseId } = useTabs();
5493
+ const isActive = activeTab === value;
5494
+ if (!isActive) return null;
5495
+ const panelId = id || `panel-${baseId}-${value}`;
5496
+ const triggerId = `tab-${baseId}-${value}`;
4258
5497
  return /* @__PURE__ */ jsxRuntime.jsx(
4259
5498
  "div",
4260
5499
  {
4261
5500
  ref,
5501
+ id: panelId,
4262
5502
  role: "tabpanel",
5503
+ "aria-labelledby": triggerId,
4263
5504
  tabIndex: 0,
4264
- className: cn("focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] animate-in fade-in duration-150", className),
5505
+ className: cn(
5506
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] animate-in fade-in duration-150",
5507
+ className
5508
+ ),
4265
5509
  ...props,
4266
5510
  children
4267
5511
  }
@@ -4489,12 +5733,13 @@ function useDropdown() {
4489
5733
  function DropdownMenu({ children }) {
4490
5734
  const [isOpen, setIsOpen] = React66.useState(false);
4491
5735
  const [triggerRect, setTriggerRect] = React66.useState(null);
4492
- return /* @__PURE__ */ jsxRuntime.jsx(DropdownContext.Provider, { value: { isOpen, setIsOpen, triggerRect, setTriggerRect }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative inline-block text-left", children }) });
5736
+ const triggerRef = React66.useRef(null);
5737
+ const menuId = React66.useId();
5738
+ return /* @__PURE__ */ jsxRuntime.jsx(DropdownContext.Provider, { value: { isOpen, setIsOpen, triggerRect, setTriggerRect, triggerRef, menuId }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative inline-block text-left", children }) });
4493
5739
  }
4494
5740
  var DropdownMenuTrigger = React66.forwardRef(
4495
- ({ children, onClick, ...props }, ref) => {
4496
- const { isOpen, setIsOpen, setTriggerRect } = useDropdown();
4497
- const triggerRef = React66.useRef(null);
5741
+ ({ children, onClick, onKeyDown, ...props }, ref) => {
5742
+ const { isOpen, setIsOpen, setTriggerRect, triggerRef, menuId } = useDropdown();
4498
5743
  const handleClick = (e) => {
4499
5744
  if (triggerRef.current) {
4500
5745
  setTriggerRect(triggerRef.current.getBoundingClientRect());
@@ -4502,39 +5747,110 @@ var DropdownMenuTrigger = React66.forwardRef(
4502
5747
  setIsOpen(!isOpen);
4503
5748
  onClick?.(e);
4504
5749
  };
4505
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: triggerRef, onClick: handleClick, className: "inline-flex cursor-pointer", ...props, children });
5750
+ const handleKeyDown = (e) => {
5751
+ onKeyDown?.(e);
5752
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
5753
+ e.preventDefault();
5754
+ if (triggerRef.current) {
5755
+ setTriggerRect(triggerRef.current.getBoundingClientRect());
5756
+ }
5757
+ setIsOpen(true);
5758
+ }
5759
+ };
5760
+ return /* @__PURE__ */ jsxRuntime.jsx(
5761
+ "div",
5762
+ {
5763
+ ref: (node) => {
5764
+ triggerRef.current = node;
5765
+ if (typeof ref === "function") ref(node);
5766
+ else if (ref) ref.current = node;
5767
+ },
5768
+ onClick: handleClick,
5769
+ onKeyDown: handleKeyDown,
5770
+ role: "button",
5771
+ tabIndex: 0,
5772
+ "aria-haspopup": "menu",
5773
+ "aria-expanded": isOpen,
5774
+ "aria-controls": isOpen ? `menu-${menuId}` : void 0,
5775
+ className: "inline-flex cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] rounded-[var(--ui-radius-sm)]",
5776
+ ...props,
5777
+ children
5778
+ }
5779
+ );
4506
5780
  }
4507
5781
  );
4508
5782
  DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
4509
5783
  var DropdownMenuContent = React66.forwardRef(
4510
5784
  ({ className, align = "start", children, ...props }, ref) => {
4511
- const { isOpen, setIsOpen, triggerRect } = useDropdown();
5785
+ const { isOpen, setIsOpen, triggerRect, triggerRef, menuId } = useDropdown();
4512
5786
  const menuRef = React66.useRef(null);
4513
5787
  React66.useEffect(() => {
5788
+ if (!isOpen) return;
5789
+ const timer = setTimeout(() => {
5790
+ const firstItem = menuRef.current?.querySelector('[role="menuitem"]:not([disabled])');
5791
+ firstItem?.focus();
5792
+ }, 10);
4514
5793
  const handleOutside = (e) => {
4515
- if (menuRef.current && !menuRef.current.contains(e.target)) {
5794
+ if (menuRef.current && !menuRef.current.contains(e.target) && !triggerRef.current?.contains(e.target)) {
4516
5795
  setIsOpen(false);
4517
5796
  }
4518
5797
  };
4519
- if (isOpen) {
4520
- document.addEventListener("mousedown", handleOutside);
4521
- }
4522
- return () => document.removeEventListener("mousedown", handleOutside);
4523
- }, [isOpen, setIsOpen]);
5798
+ const handleKeyDown = (e) => {
5799
+ if (!menuRef.current) return;
5800
+ const items = Array.from(
5801
+ menuRef.current.querySelectorAll('[role="menuitem"]:not([disabled])')
5802
+ );
5803
+ if (items.length === 0) return;
5804
+ const currentIndex = items.indexOf(document.activeElement);
5805
+ if (e.key === "ArrowDown") {
5806
+ e.preventDefault();
5807
+ const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % items.length;
5808
+ items[nextIndex]?.focus();
5809
+ } else if (e.key === "ArrowUp") {
5810
+ e.preventDefault();
5811
+ const prevIndex = currentIndex === -1 ? items.length - 1 : (currentIndex - 1 + items.length) % items.length;
5812
+ items[prevIndex]?.focus();
5813
+ } else if (e.key === "Home") {
5814
+ e.preventDefault();
5815
+ items[0]?.focus();
5816
+ } else if (e.key === "End") {
5817
+ e.preventDefault();
5818
+ items[items.length - 1]?.focus();
5819
+ } else if (e.key === "Escape") {
5820
+ e.preventDefault();
5821
+ setIsOpen(false);
5822
+ triggerRef.current?.focus();
5823
+ }
5824
+ };
5825
+ document.addEventListener("mousedown", handleOutside);
5826
+ window.addEventListener("keydown", handleKeyDown);
5827
+ return () => {
5828
+ clearTimeout(timer);
5829
+ document.removeEventListener("mousedown", handleOutside);
5830
+ window.removeEventListener("keydown", handleKeyDown);
5831
+ };
5832
+ }, [isOpen, setIsOpen, triggerRef]);
4524
5833
  if (!isOpen || !triggerRect) return null;
4525
5834
  const top = triggerRect.bottom + window.scrollY + 6;
4526
5835
  const left = align === "end" ? triggerRect.right + window.scrollX : triggerRect.left + window.scrollX;
4527
5836
  return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
4528
5837
  "div",
4529
5838
  {
4530
- ref: menuRef,
5839
+ ref: (node) => {
5840
+ menuRef.current = node;
5841
+ if (typeof ref === "function") ref(node);
5842
+ else if (ref) ref.current = node;
5843
+ },
5844
+ id: `menu-${menuId}`,
5845
+ role: "menu",
5846
+ tabIndex: -1,
4531
5847
  style: {
4532
5848
  position: "absolute",
4533
5849
  top: `${top}px`,
4534
5850
  left: `${left}px`
4535
5851
  },
4536
5852
  className: cn(
4537
- "z-50 min-w-[12rem] overflow-hidden rounded-[var(--ui-radius-lg)] border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] p-1 text-[var(--ui-text-primary)] shadow-xl animate-in fade-in zoom-in-95",
5853
+ "z-50 min-w-[12rem] overflow-hidden rounded-[var(--ui-radius-lg)] border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] p-1 text-[var(--ui-text-primary)] shadow-xl animate-in fade-in zoom-in-95 focus:outline-none",
4538
5854
  align === "end" && "-translate-x-full",
4539
5855
  className
4540
5856
  ),
@@ -4552,14 +5868,15 @@ var DropdownMenuItem = React66.forwardRef(
4552
5868
  "button",
4553
5869
  {
4554
5870
  ref,
5871
+ role: "menuitem",
4555
5872
  type: "button",
4556
5873
  onClick: (e) => {
4557
5874
  onClick?.(e);
4558
5875
  setIsOpen(false);
4559
5876
  },
4560
5877
  className: cn(
4561
- "relative flex w-full cursor-pointer select-none items-center gap-2 rounded-[var(--ui-radius-sm)] px-2.5 py-1.5 text-xs font-medium outline-none transition-colors hover:bg-[var(--ui-bg-tertiary)] disabled:pointer-events-none disabled:opacity-50 text-left",
4562
- destructive && "text-[var(--ui-error)] hover:bg-[var(--ui-error-subtle)]",
5878
+ "relative flex w-full cursor-pointer select-none items-center gap-2 rounded-[var(--ui-radius-sm)] px-2.5 py-1.5 text-xs font-medium outline-none transition-colors hover:bg-[var(--ui-bg-tertiary)] focus:bg-[var(--ui-bg-tertiary)] disabled:pointer-events-none disabled:opacity-50 text-left",
5879
+ destructive && "text-[var(--ui-error)] hover:bg-[var(--ui-error-subtle)] focus:bg-[var(--ui-error-subtle)]",
4563
5880
  className
4564
5881
  ),
4565
5882
  ...props,
@@ -4575,7 +5892,7 @@ var DropdownMenuItem = React66.forwardRef(
4575
5892
  DropdownMenuItem.displayName = "DropdownMenuItem";
4576
5893
  var DropdownMenuSeparator = React66.forwardRef(
4577
5894
  ({ className, ...props }, ref) => {
4578
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("-mx-1 my-1 h-px bg-[var(--ui-border-subtle)]", className), ...props });
5895
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, role: "separator", className: cn("-mx-1 my-1 h-px bg-[var(--ui-border-subtle)]", className), ...props });
4579
5896
  }
4580
5897
  );
4581
5898
  DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
@@ -5110,79 +6427,183 @@ function DataTablePro({
5110
6427
  searchable = true,
5111
6428
  searchPlaceholder = "Search all records...",
5112
6429
  selectable = true,
6430
+ selectedKeys: controlledSelectedKeys,
6431
+ defaultSelectedKeys = [],
5113
6432
  onSelectionChange,
5114
6433
  pageSize: initialPageSize = 10,
5115
6434
  pageSizeOptions = [5, 10, 25, 50],
5116
6435
  title,
5117
6436
  description,
5118
6437
  actions,
5119
- className
6438
+ className,
6439
+ loading = false,
6440
+ // Multi-column sorting
6441
+ multiSort = true,
6442
+ sorting: controlledSorting,
6443
+ defaultSorting = [],
6444
+ onSortingChange,
6445
+ // Server-side / manual mode props
6446
+ manualPagination = false,
6447
+ manualSorting = false,
6448
+ manualFiltering = false,
6449
+ pageIndex: controlledPageIndex,
6450
+ pageCount: controlledPageCount,
6451
+ totalCount: controlledTotalCount,
6452
+ onPaginationChange,
6453
+ searchQuery: controlledSearchQuery,
6454
+ onSearchChange,
6455
+ onSelectAllDataset,
6456
+ emptyText = "No matching records found."
5120
6457
  }) {
5121
- const [search, setSearch] = React66.useState("");
5122
- const [sortKey, setSortKey] = React66.useState(null);
5123
- const [sortDirection, setSortDirection] = React66.useState("asc");
5124
- const [currentPage, setCurrentPage] = React66.useState(1);
5125
- const [pageSize, setPageSize] = React66.useState(initialPageSize);
5126
- const [selectedKeys, setSelectedKeys] = React66.useState([]);
6458
+ const [internalSearch, setInternalSearch] = React66.useState("");
6459
+ const search = controlledSearchQuery !== void 0 ? controlledSearchQuery : internalSearch;
6460
+ const [internalSorting, setInternalSorting] = React66.useState(defaultSorting);
6461
+ const currentSorting = controlledSorting !== void 0 ? controlledSorting ?? [] : internalSorting;
6462
+ const [internalCurrentPage, setInternalCurrentPage] = React66.useState(1);
6463
+ const currentPage = controlledPageIndex !== void 0 ? controlledPageIndex : internalCurrentPage;
6464
+ const [internalPageSize, setInternalPageSize] = React66.useState(initialPageSize);
6465
+ const pageSize = internalPageSize;
6466
+ const [internalSelectedKeys, setInternalSelectedKeys] = React66.useState(defaultSelectedKeys);
6467
+ const isSelectedControlled = controlledSelectedKeys !== void 0;
6468
+ const selectedKeys = isSelectedControlled ? controlledSelectedKeys : internalSelectedKeys;
6469
+ const handleSelectionUpdate = (nextKeys) => {
6470
+ if (!isSelectedControlled) {
6471
+ setInternalSelectedKeys(nextKeys);
6472
+ }
6473
+ onSelectionChange?.(nextKeys);
6474
+ };
5127
6475
  const filteredData = React66.useMemo(() => {
5128
- if (!search.trim()) return data;
6476
+ if (manualFiltering || !search.trim()) return data;
5129
6477
  const q = search.toLowerCase();
5130
6478
  return data.filter((row) => {
5131
- return Object.values(row).some((val) => {
6479
+ return columns.some((col) => {
6480
+ const val = col.accessor ? col.accessor(row) : row[col.key];
5132
6481
  if (val === null || val === void 0) return false;
5133
6482
  return String(val).toLowerCase().includes(q);
5134
6483
  });
5135
6484
  });
5136
- }, [data, search]);
6485
+ }, [data, search, columns, manualFiltering]);
5137
6486
  const sortedData = React66.useMemo(() => {
5138
- if (!sortKey) return filteredData;
5139
- const col = columns.find((c) => c.key === sortKey);
6487
+ if (manualSorting || currentSorting.length === 0) return filteredData;
5140
6488
  return [...filteredData].sort((a, b) => {
5141
- const valA = col?.accessor ? col.accessor(a) : a[sortKey];
5142
- const valB = col?.accessor ? col.accessor(b) : b[sortKey];
5143
- if (valA === valB) return 0;
5144
- if (valA === null || valA === void 0) return 1;
5145
- if (valB === null || valB === void 0) return -1;
5146
- const comp = valA < valB ? -1 : 1;
5147
- return sortDirection === "asc" ? comp : -comp;
6489
+ for (const { key, direction } of currentSorting) {
6490
+ const col = columns.find((c) => c.key === key);
6491
+ let comp = 0;
6492
+ if (col?.sortComparator) {
6493
+ comp = col.sortComparator(a, b);
6494
+ } else {
6495
+ const valA = col?.accessor ? col.accessor(a) : a[key];
6496
+ const valB = col?.accessor ? col.accessor(b) : b[key];
6497
+ if (valA === valB) continue;
6498
+ if (valA === null || valA === void 0) return 1;
6499
+ if (valB === null || valB === void 0) return -1;
6500
+ comp = valA < valB ? -1 : 1;
6501
+ }
6502
+ if (comp !== 0) {
6503
+ return direction === "asc" ? comp : -comp;
6504
+ }
6505
+ }
6506
+ return 0;
5148
6507
  });
5149
- }, [filteredData, sortKey, sortDirection, columns]);
5150
- const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
6508
+ }, [filteredData, currentSorting, columns, manualSorting]);
6509
+ const totalItems = controlledTotalCount !== void 0 ? controlledTotalCount : sortedData.length;
6510
+ const totalPages = controlledPageCount !== void 0 ? Math.max(1, controlledPageCount) : Math.max(1, Math.ceil(totalItems / pageSize));
6511
+ React66.useEffect(() => {
6512
+ if (!manualPagination && currentPage > totalPages && totalPages > 0) {
6513
+ setInternalCurrentPage(totalPages);
6514
+ onPaginationChange?.(totalPages, pageSize);
6515
+ }
6516
+ }, [totalPages, currentPage, manualPagination, pageSize, onPaginationChange]);
5151
6517
  const paginatedData = React66.useMemo(() => {
6518
+ if (manualPagination) return data;
5152
6519
  const start = (currentPage - 1) * pageSize;
5153
6520
  return sortedData.slice(start, start + pageSize);
5154
- }, [sortedData, currentPage, pageSize]);
5155
- const handleSort = (key) => {
5156
- if (sortKey === key) {
5157
- if (sortDirection === "asc") {
5158
- setSortDirection("desc");
6521
+ }, [data, sortedData, currentPage, pageSize, manualPagination]);
6522
+ const handleSort = (key, e) => {
6523
+ const isShift = e?.shiftKey || false;
6524
+ let nextSorting = [...currentSorting];
6525
+ const existingIdx = nextSorting.findIndex((s) => s.key === key);
6526
+ if (isShift && multiSort) {
6527
+ if (existingIdx >= 0) {
6528
+ if (nextSorting[existingIdx].direction === "asc") {
6529
+ nextSorting[existingIdx] = { key, direction: "desc" };
6530
+ } else {
6531
+ nextSorting.splice(existingIdx, 1);
6532
+ }
5159
6533
  } else {
5160
- setSortKey(null);
5161
- setSortDirection("asc");
6534
+ nextSorting.push({ key, direction: "asc" });
5162
6535
  }
5163
6536
  } else {
5164
- setSortKey(key);
5165
- setSortDirection("asc");
6537
+ if (existingIdx >= 0 && nextSorting.length === 1) {
6538
+ if (nextSorting[0].direction === "asc") {
6539
+ nextSorting = [{ key, direction: "desc" }];
6540
+ } else {
6541
+ nextSorting = [];
6542
+ }
6543
+ } else {
6544
+ nextSorting = [{ key, direction: "asc" }];
6545
+ }
6546
+ }
6547
+ if (controlledSorting === void 0) {
6548
+ setInternalSorting(nextSorting);
5166
6549
  }
6550
+ onSortingChange?.(nextSorting);
5167
6551
  };
5168
- const toggleSelectAll = () => {
5169
- if (selectedKeys.length === paginatedData.length) {
5170
- setSelectedKeys([]);
5171
- onSelectionChange?.([]);
6552
+ const handlePageChange = (newPage) => {
6553
+ const clamped = Math.max(1, Math.min(totalPages, newPage));
6554
+ if (controlledPageIndex === void 0) {
6555
+ setInternalCurrentPage(clamped);
6556
+ }
6557
+ onPaginationChange?.(clamped, pageSize);
6558
+ };
6559
+ const handlePageSizeChange = (newSize) => {
6560
+ setInternalPageSize(newSize);
6561
+ if (controlledPageIndex === void 0) {
6562
+ setInternalCurrentPage(1);
6563
+ }
6564
+ onPaginationChange?.(1, newSize);
6565
+ };
6566
+ const handleSearchChange = (query) => {
6567
+ if (controlledSearchQuery === void 0) {
6568
+ setInternalSearch(query);
6569
+ if (controlledPageIndex === void 0) {
6570
+ setInternalCurrentPage(1);
6571
+ }
6572
+ }
6573
+ onSearchChange?.(query);
6574
+ };
6575
+ const currentPageKeys = React66.useMemo(() => paginatedData.map(keyExtractor), [paginatedData, keyExtractor]);
6576
+ const isAllCurrentPageSelected = currentPageKeys.length > 0 && currentPageKeys.every((k) => selectedKeys.includes(k));
6577
+ const isSomeCurrentPageSelected = currentPageKeys.some((k) => selectedKeys.includes(k)) && !isAllCurrentPageSelected;
6578
+ const toggleSelectAllCurrentPage = () => {
6579
+ if (isAllCurrentPageSelected) {
6580
+ const next = selectedKeys.filter((k) => !currentPageKeys.includes(k));
6581
+ handleSelectionUpdate(next);
5172
6582
  } else {
5173
- const allKeys = paginatedData.map(keyExtractor);
5174
- setSelectedKeys(allKeys);
5175
- onSelectionChange?.(allKeys);
6583
+ const combined = Array.from(/* @__PURE__ */ new Set([...selectedKeys, ...currentPageKeys]));
6584
+ handleSelectionUpdate(combined);
5176
6585
  }
5177
6586
  };
5178
6587
  const toggleSelectRow = (key) => {
5179
6588
  const next = selectedKeys.includes(key) ? selectedKeys.filter((k) => k !== key) : [...selectedKeys, key];
5180
- setSelectedKeys(next);
5181
- onSelectionChange?.(next);
6589
+ handleSelectionUpdate(next);
6590
+ };
6591
+ const canSelectAllDataset = !manualPagination || typeof onSelectAllDataset === "function";
6592
+ const selectAllDataset = () => {
6593
+ if (manualPagination) {
6594
+ onSelectAllDataset?.();
6595
+ return;
6596
+ }
6597
+ const allKeys = sortedData.map(keyExtractor);
6598
+ handleSelectionUpdate(allKeys);
6599
+ };
6600
+ const clearSelection = () => {
6601
+ handleSelectionUpdate([]);
5182
6602
  };
5183
6603
  const exportCSV = () => {
6604
+ const exportRows = manualPagination ? data : sortedData;
5184
6605
  const headers = columns.map((c) => typeof c.header === "string" ? c.header : c.key);
5185
- const rows = sortedData.map(
6606
+ const rows = exportRows.map(
5186
6607
  (row) => columns.map((c) => {
5187
6608
  const val = c.accessor ? c.accessor(row) : row[c.key];
5188
6609
  return `"${String(val ?? "").replace(/"/g, '""')}"`;
@@ -5197,7 +6618,6 @@ function DataTablePro({
5197
6618
  link.click();
5198
6619
  document.body.removeChild(link);
5199
6620
  };
5200
- const isAllSelected = paginatedData.length > 0 && selectedKeys.length === paginatedData.length;
5201
6621
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("w-full space-y-4", className), children: [
5202
6622
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-4", children: [
5203
6623
  (title || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -5213,10 +6633,7 @@ function DataTablePro({
5213
6633
  type: "text",
5214
6634
  placeholder: searchPlaceholder,
5215
6635
  value: search,
5216
- onChange: (e) => {
5217
- setSearch(e.target.value);
5218
- setCurrentPage(1);
5219
- },
6636
+ onChange: (e) => handleSearchChange(e.target.value),
5220
6637
  className: "w-full h-8 pl-8 pr-3 text-xs rounded-xl bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)] focus:outline-none focus:ring-2 focus:ring-[var(--ui-primary)]/40 text-[var(--ui-text-primary)] placeholder:text-[var(--ui-text-muted)] transition-all shadow-inner"
5221
6638
  }
5222
6639
  )
@@ -5225,83 +6642,110 @@ function DataTablePro({
5225
6642
  actions
5226
6643
  ] })
5227
6644
  ] }),
5228
- selectedKeys.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-2 rounded-xl bg-[var(--ui-primary)]/15 border border-[var(--ui-primary)]/30 flex items-center justify-between animate-in fade-in duration-200", children: [
5229
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 text-xs font-semibold text-[var(--ui-primary)]", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5230
- "Selected ",
5231
- selectedKeys.length,
5232
- " of ",
5233
- sortedData.length,
5234
- " records"
5235
- ] }) }),
6645
+ selectedKeys.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-2 rounded-xl bg-[var(--ui-primary)]/15 border border-[var(--ui-primary)]/30 flex flex-wrap items-center justify-between gap-2 animate-in fade-in duration-200", children: [
6646
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs font-semibold text-[var(--ui-primary)]", children: [
6647
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
6648
+ "Selected ",
6649
+ selectedKeys.length,
6650
+ " records"
6651
+ ] }),
6652
+ canSelectAllDataset && selectedKeys.length < totalItems && /* @__PURE__ */ jsxRuntime.jsxs(
6653
+ "button",
6654
+ {
6655
+ type: "button",
6656
+ onClick: selectAllDataset,
6657
+ className: "underline hover:text-[var(--ui-primary)]/80 cursor-pointer font-bold ml-1",
6658
+ children: [
6659
+ "Select all ",
6660
+ totalItems,
6661
+ " records"
6662
+ ]
6663
+ }
6664
+ )
6665
+ ] }),
5236
6666
  /* @__PURE__ */ jsxRuntime.jsx(
5237
6667
  Button,
5238
6668
  {
5239
6669
  size: "xs",
5240
6670
  variant: "ghost",
5241
6671
  className: "text-xs text-rose-500 hover:text-rose-600 hover:bg-rose-500/10",
5242
- onClick: () => {
5243
- setSelectedKeys([]);
5244
- onSelectionChange?.([]);
5245
- },
6672
+ onClick: clearSelection,
5246
6673
  children: "Clear Selection"
5247
6674
  }
5248
6675
  )
5249
6676
  ] }),
5250
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full overflow-x-auto rounded-2xl border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs(Table, { children: [
5251
- /* @__PURE__ */ jsxRuntime.jsx(TableHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(TableRow, { children: [
5252
- selectable && /* @__PURE__ */ jsxRuntime.jsx(TableHead, { className: "w-10 px-4", children: /* @__PURE__ */ jsxRuntime.jsx(
5253
- "input",
6677
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full overflow-x-auto rounded-2xl border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] shadow-sm", children: [
6678
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-20 bg-[var(--ui-bg-surface)]/60 backdrop-blur-xs flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "w-6 h-6 animate-spin text-[var(--ui-primary)]" }) }),
6679
+ /* @__PURE__ */ jsxRuntime.jsxs(Table, { children: [
6680
+ /* @__PURE__ */ jsxRuntime.jsx(TableHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(TableRow, { children: [
6681
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(TableHead, { className: "w-10 px-4", children: /* @__PURE__ */ jsxRuntime.jsx(
6682
+ Checkbox,
6683
+ {
6684
+ checked: isAllCurrentPageSelected,
6685
+ indeterminate: isSomeCurrentPageSelected,
6686
+ onCheckedChange: toggleSelectAllCurrentPage,
6687
+ "aria-label": "Select all on page"
6688
+ }
6689
+ ) }),
6690
+ columns.map((col) => {
6691
+ const sortIndex = currentSorting.findIndex((s) => s.key === col.key);
6692
+ const sortRule = sortIndex >= 0 ? currentSorting[sortIndex] : null;
6693
+ return /* @__PURE__ */ jsxRuntime.jsx(
6694
+ TableHead,
6695
+ {
6696
+ style: { width: col.width },
6697
+ onClick: (e) => col.sortable !== false && handleSort(col.key, e),
6698
+ className: cn(
6699
+ col.sortable !== false && "cursor-pointer select-none hover:text-[var(--ui-text-primary)] transition-colors"
6700
+ ),
6701
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
6702
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: col.header }),
6703
+ col.sortable !== false && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 text-[var(--ui-text-muted)]", children: [
6704
+ sortRule ? sortRule.direction === "asc" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "w-3.5 h-3.5 text-[var(--ui-primary)] font-bold" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "w-3.5 h-3.5 text-[var(--ui-primary)] font-bold" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "w-3 h-3 opacity-40" }),
6705
+ currentSorting.length > 1 && sortIndex >= 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-mono font-bold text-[var(--ui-primary)]", children: sortIndex + 1 })
6706
+ ] })
6707
+ ] })
6708
+ },
6709
+ col.key
6710
+ );
6711
+ })
6712
+ ] }) }),
6713
+ /* @__PURE__ */ jsxRuntime.jsx(TableBody, { children: paginatedData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(TableRow, { children: /* @__PURE__ */ jsxRuntime.jsx(
6714
+ TableCell,
5254
6715
  {
5255
- type: "checkbox",
5256
- checked: isAllSelected,
5257
- onChange: toggleSelectAll,
5258
- className: "rounded border-[var(--ui-border-default)] text-[var(--ui-primary)] focus:ring-[var(--ui-primary)] cursor-pointer"
6716
+ colSpan: columns.length + (selectable ? 1 : 0),
6717
+ className: "h-32 text-center text-xs text-[var(--ui-text-muted)]",
6718
+ children: emptyText
5259
6719
  }
5260
- ) }),
5261
- columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
5262
- TableHead,
5263
- {
5264
- style: { width: col.width },
5265
- onClick: () => col.sortable !== false && handleSort(col.key),
5266
- className: cn(
5267
- col.sortable !== false && "cursor-pointer select-none hover:text-[var(--ui-text-primary)] transition-colors"
5268
- ),
5269
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
5270
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: col.header }),
5271
- col.sortable !== false && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[var(--ui-text-muted)]", children: sortKey === col.key ? sortDirection === "asc" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "w-3.5 h-3.5 text-[var(--ui-primary)] font-bold" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "w-3.5 h-3.5 text-[var(--ui-primary)] font-bold" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "w-3 h-3 opacity-40" }) })
5272
- ] })
5273
- },
5274
- col.key
5275
- ))
5276
- ] }) }),
5277
- /* @__PURE__ */ jsxRuntime.jsx(TableBody, { children: paginatedData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(TableRow, { children: /* @__PURE__ */ jsxRuntime.jsx(TableCell, { colSpan: columns.length + (selectable ? 1 : 0), className: "h-32 text-center text-xs text-[var(--ui-text-muted)]", children: "No matching records found." }) }) : paginatedData.map((row, rowIdx) => {
5278
- const key = keyExtractor(row);
5279
- const isSelected = selectedKeys.includes(key);
5280
- return /* @__PURE__ */ jsxRuntime.jsxs(
5281
- TableRow,
5282
- {
5283
- "data-state": isSelected ? "selected" : void 0,
5284
- className: cn(isSelected && "bg-[var(--ui-primary)]/10"),
5285
- children: [
5286
- selectable && /* @__PURE__ */ jsxRuntime.jsx(TableCell, { className: "w-10 px-4", children: /* @__PURE__ */ jsxRuntime.jsx(
5287
- "input",
5288
- {
5289
- type: "checkbox",
5290
- checked: isSelected,
5291
- onChange: () => toggleSelectRow(key),
5292
- className: "rounded border-[var(--ui-border-default)] text-[var(--ui-primary)] focus:ring-[var(--ui-primary)] cursor-pointer"
5293
- }
5294
- ) }),
5295
- columns.map((col) => {
5296
- const cellContent = col.render ? col.render(row, rowIdx) : col.accessor ? col.accessor(row) : row[col.key];
5297
- return /* @__PURE__ */ jsxRuntime.jsx(TableCell, { children: cellContent }, col.key);
5298
- })
5299
- ]
5300
- },
5301
- key
5302
- );
5303
- }) })
5304
- ] }) }),
6720
+ ) }) : paginatedData.map((row, rowIdx) => {
6721
+ const key = keyExtractor(row);
6722
+ const isSelected = selectedKeys.includes(key);
6723
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6724
+ TableRow,
6725
+ {
6726
+ "data-state": isSelected ? "selected" : void 0,
6727
+ className: cn(isSelected && "bg-[var(--ui-primary)]/10"),
6728
+ children: [
6729
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(TableCell, { className: "w-10 px-4", children: /* @__PURE__ */ jsxRuntime.jsx(
6730
+ Checkbox,
6731
+ {
6732
+ checked: isSelected,
6733
+ onCheckedChange: () => toggleSelectRow(key),
6734
+ "aria-label": `Select row ${key}`
6735
+ }
6736
+ ) }),
6737
+ columns.map((col) => {
6738
+ const val = col.accessor ? col.accessor(row) : row[col.key];
6739
+ const cellContent = col.render ? col.render(row, rowIdx, val) : val;
6740
+ return /* @__PURE__ */ jsxRuntime.jsx(TableCell, { children: cellContent }, col.key);
6741
+ })
6742
+ ]
6743
+ },
6744
+ key
6745
+ );
6746
+ }) })
6747
+ ] })
6748
+ ] }),
5305
6749
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-3 px-1 text-xs text-[var(--ui-text-muted)] font-mono", children: [
5306
6750
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
5307
6751
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Rows per page:" }),
@@ -5309,21 +6753,18 @@ function DataTablePro({
5309
6753
  "select",
5310
6754
  {
5311
6755
  value: pageSize,
5312
- onChange: (e) => {
5313
- setPageSize(Number(e.target.value));
5314
- setCurrentPage(1);
5315
- },
6756
+ onChange: (e) => handlePageSizeChange(Number(e.target.value)),
5316
6757
  className: "px-2 py-1 rounded-lg bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] text-[var(--ui-text-primary)] font-mono text-xs focus:outline-none",
5317
6758
  children: pageSizeOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt, children: opt }, opt))
5318
6759
  }
5319
6760
  ),
5320
6761
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[var(--ui-text-muted)]", children: [
5321
6762
  "Showing ",
5322
- Math.min((currentPage - 1) * pageSize + 1, sortedData.length),
6763
+ totalItems === 0 ? 0 : Math.min((currentPage - 1) * pageSize + 1, totalItems),
5323
6764
  "\u2013",
5324
- Math.min(currentPage * pageSize, sortedData.length),
6765
+ Math.min(currentPage * pageSize, totalItems),
5325
6766
  " of ",
5326
- sortedData.length
6767
+ totalItems
5327
6768
  ] })
5328
6769
  ] }),
5329
6770
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
@@ -5332,8 +6773,9 @@ function DataTablePro({
5332
6773
  {
5333
6774
  size: "xs",
5334
6775
  variant: "outline",
5335
- disabled: currentPage === 1,
5336
- onClick: () => setCurrentPage((p) => Math.max(1, p - 1)),
6776
+ disabled: currentPage <= 1 || loading,
6777
+ onClick: () => handlePageChange(currentPage - 1),
6778
+ "aria-label": "Previous page",
5337
6779
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "w-3.5 h-3.5" })
5338
6780
  }
5339
6781
  ),
@@ -5347,8 +6789,9 @@ function DataTablePro({
5347
6789
  {
5348
6790
  size: "xs",
5349
6791
  variant: "outline",
5350
- disabled: currentPage === totalPages,
5351
- onClick: () => setCurrentPage((p) => Math.min(totalPages, p + 1)),
6792
+ disabled: currentPage >= totalPages || loading,
6793
+ onClick: () => handlePageChange(currentPage + 1),
6794
+ "aria-label": "Next page",
5352
6795
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-3.5 h-3.5" })
5353
6796
  }
5354
6797
  )
@@ -5356,100 +6799,218 @@ function DataTablePro({
5356
6799
  ] })
5357
6800
  ] });
5358
6801
  }
5359
- function TreeNode({ item, level, selectedId, onSelect }) {
5360
- const [isExpanded, setIsExpanded] = React66.useState(Boolean(item.defaultExpanded));
5361
- const hasChildren = Boolean(item.children && item.children.length > 0);
5362
- const isSelected = selectedId === item.id;
5363
- const handleClick = (e) => {
5364
- e.stopPropagation();
5365
- if (hasChildren) {
5366
- setIsExpanded(!isExpanded);
5367
- }
5368
- onSelect?.(item);
5369
- };
5370
- const getIcon = () => {
5371
- if (item.icon) return item.icon;
5372
- if (hasChildren) {
5373
- return isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: "w-4 h-4 text-amber-500 shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Folder, { className: "w-4 h-4 text-amber-500 shrink-0" });
5374
- }
5375
- if (item.label.endsWith(".tsx") || item.label.endsWith(".ts") || item.label.endsWith(".js")) {
5376
- return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileCode, { className: "w-4 h-4 text-blue-500 shrink-0" });
5377
- }
5378
- if (item.label.endsWith(".png") || item.label.endsWith(".jpg") || item.label.endsWith(".svg")) {
5379
- return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Image, { className: "w-4 h-4 text-purple-500 shrink-0" });
5380
- }
5381
- return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "w-4 h-4 text-[var(--ui-text-muted)] shrink-0" });
5382
- };
5383
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "select-none text-xs", children: [
5384
- /* @__PURE__ */ jsxRuntime.jsxs(
5385
- "div",
5386
- {
5387
- onClick: handleClick,
5388
- style: { paddingLeft: `${level * 16 + 8}px` },
5389
- className: cn(
5390
- "flex items-center justify-between py-1.5 pr-2.5 rounded-xl cursor-pointer transition-colors group",
5391
- isSelected ? "bg-[var(--ui-primary)] text-white font-semibold shadow-xs" : "text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)]"
5392
- ),
5393
- children: [
5394
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 truncate", children: [
5395
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("p-0.5 rounded transition-transform duration-200", isExpanded && "rotate-90"), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: cn("w-3 h-3", isSelected ? "text-white" : "text-[var(--ui-text-muted)]") }) }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-4" }),
5396
- getIcon(),
5397
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: item.label })
5398
- ] }),
5399
- item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
5400
- "span",
6802
+ var TreeView = React66.forwardRef(
6803
+ ({
6804
+ data,
6805
+ selectedId,
6806
+ defaultExpandedIds,
6807
+ onSelect,
6808
+ onExpandedChange,
6809
+ className,
6810
+ "aria-label": ariaLabel = "Tree View"
6811
+ }, ref) => {
6812
+ const initialExpanded = React66.useMemo(() => {
6813
+ if (defaultExpandedIds) return new Set(defaultExpandedIds);
6814
+ const set = /* @__PURE__ */ new Set();
6815
+ const collect = (items) => {
6816
+ for (const it of items) {
6817
+ if (it.defaultExpanded) set.add(it.id);
6818
+ if (it.children) collect(it.children);
6819
+ }
6820
+ };
6821
+ collect(data);
6822
+ return set;
6823
+ }, [data, defaultExpandedIds]);
6824
+ const [expandedSet, setExpandedSet] = React66.useState(initialExpanded);
6825
+ const [focusedId, setFocusedId] = React66.useState(null);
6826
+ const itemRefs = React66.useRef(/* @__PURE__ */ new Map());
6827
+ const focusNode = (id) => {
6828
+ setFocusedId(id);
6829
+ const el = itemRefs.current.get(id);
6830
+ if (el) {
6831
+ el.focus();
6832
+ }
6833
+ };
6834
+ const toggleExpand = (id) => {
6835
+ setExpandedSet((prev) => {
6836
+ const next = new Set(prev);
6837
+ if (next.has(id)) next.delete(id);
6838
+ else next.add(id);
6839
+ onExpandedChange?.(Array.from(next));
6840
+ return next;
6841
+ });
6842
+ };
6843
+ const expandNode = (id) => {
6844
+ if (!expandedSet.has(id)) {
6845
+ setExpandedSet((prev) => {
6846
+ const next = new Set(prev).add(id);
6847
+ onExpandedChange?.(Array.from(next));
6848
+ return next;
6849
+ });
6850
+ }
6851
+ };
6852
+ const collapseNode = (id) => {
6853
+ if (expandedSet.has(id)) {
6854
+ setExpandedSet((prev) => {
6855
+ const next = new Set(prev);
6856
+ next.delete(id);
6857
+ onExpandedChange?.(Array.from(next));
6858
+ return next;
6859
+ });
6860
+ }
6861
+ };
6862
+ const visibleList = React66.useMemo(() => {
6863
+ const list = [];
6864
+ const traverse = (items, level, parentId) => {
6865
+ for (const item of items) {
6866
+ const hasChildren = Boolean(item.children && item.children.length > 0);
6867
+ list.push({ item, level, parentId, hasChildren });
6868
+ if (hasChildren && expandedSet.has(item.id)) {
6869
+ traverse(item.children, level + 1, item.id);
6870
+ }
6871
+ }
6872
+ };
6873
+ traverse(data, 0, null);
6874
+ return list;
6875
+ }, [data, expandedSet]);
6876
+ const handleKeyDown = (e) => {
6877
+ if (visibleList.length === 0) return;
6878
+ const currentIdx = focusedId ? visibleList.findIndex((f) => f.item.id === focusedId) : 0;
6879
+ const currentFlat = visibleList[currentIdx] || visibleList[0];
6880
+ if (e.key === "ArrowDown") {
6881
+ e.preventDefault();
6882
+ const nextIdx = Math.min(visibleList.length - 1, currentIdx + 1);
6883
+ focusNode(visibleList[nextIdx].item.id);
6884
+ } else if (e.key === "ArrowUp") {
6885
+ e.preventDefault();
6886
+ const prevIdx = Math.max(0, currentIdx - 1);
6887
+ focusNode(visibleList[prevIdx].item.id);
6888
+ } else if (e.key === "ArrowRight") {
6889
+ e.preventDefault();
6890
+ if (currentFlat.hasChildren) {
6891
+ if (!expandedSet.has(currentFlat.item.id)) {
6892
+ expandNode(currentFlat.item.id);
6893
+ } else {
6894
+ const nextIdx = currentIdx + 1;
6895
+ if (nextIdx < visibleList.length) {
6896
+ focusNode(visibleList[nextIdx].item.id);
6897
+ }
6898
+ }
6899
+ }
6900
+ } else if (e.key === "ArrowLeft") {
6901
+ e.preventDefault();
6902
+ if (currentFlat.hasChildren && expandedSet.has(currentFlat.item.id)) {
6903
+ collapseNode(currentFlat.item.id);
6904
+ } else if (currentFlat.parentId) {
6905
+ focusNode(currentFlat.parentId);
6906
+ }
6907
+ } else if (e.key === "Home") {
6908
+ e.preventDefault();
6909
+ focusNode(visibleList[0].item.id);
6910
+ } else if (e.key === "End") {
6911
+ e.preventDefault();
6912
+ focusNode(visibleList[visibleList.length - 1].item.id);
6913
+ } else if (e.key === "Enter" || e.key === " ") {
6914
+ e.preventDefault();
6915
+ if (currentFlat) {
6916
+ if (currentFlat.hasChildren) {
6917
+ toggleExpand(currentFlat.item.id);
6918
+ }
6919
+ onSelect?.(currentFlat.item);
6920
+ }
6921
+ }
6922
+ };
6923
+ const getIcon = (item, hasChildren, isExpanded) => {
6924
+ if (item.icon) return item.icon;
6925
+ if (hasChildren) {
6926
+ return isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: "w-4 h-4 text-amber-500 shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Folder, { className: "w-4 h-4 text-amber-500 shrink-0" });
6927
+ }
6928
+ if (item.label.endsWith(".tsx") || item.label.endsWith(".ts") || item.label.endsWith(".js")) {
6929
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileCode, { className: "w-4 h-4 text-blue-500 shrink-0" });
6930
+ }
6931
+ if (item.label.endsWith(".png") || item.label.endsWith(".jpg") || item.label.endsWith(".svg")) {
6932
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Image, { className: "w-4 h-4 text-purple-500 shrink-0" });
6933
+ }
6934
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "w-4 h-4 text-[var(--ui-text-muted)] shrink-0" });
6935
+ };
6936
+ const renderNodes = (items, level) => {
6937
+ return items.map((item, index) => {
6938
+ const hasChildren = Boolean(item.children && item.children.length > 0);
6939
+ const isExpanded = expandedSet.has(item.id);
6940
+ const isSelected = selectedId === item.id;
6941
+ const isFocused = focusedId === item.id || !focusedId && level === 0 && index === 0;
6942
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "none", className: "select-none text-xs", children: [
6943
+ /* @__PURE__ */ jsxRuntime.jsxs(
6944
+ "div",
5401
6945
  {
6946
+ ref: (el) => {
6947
+ if (el) itemRefs.current.set(item.id, el);
6948
+ else itemRefs.current.delete(item.id);
6949
+ },
6950
+ role: "treeitem",
6951
+ tabIndex: isFocused ? 0 : -1,
6952
+ "aria-expanded": hasChildren ? isExpanded : void 0,
6953
+ "aria-selected": isSelected,
6954
+ "aria-level": level + 1,
6955
+ "aria-posinset": index + 1,
6956
+ "aria-setsize": items.length,
6957
+ onClick: (e) => {
6958
+ e.stopPropagation();
6959
+ focusNode(item.id);
6960
+ if (hasChildren) {
6961
+ toggleExpand(item.id);
6962
+ }
6963
+ onSelect?.(item);
6964
+ },
6965
+ style: { paddingLeft: `${level * 16 + 8}px` },
5402
6966
  className: cn(
5403
- "px-1.5 py-0.5 rounded-md text-[10px] font-mono",
5404
- isSelected ? "bg-white/20 text-white" : "bg-[var(--ui-bg-secondary)] text-[var(--ui-text-muted)]"
6967
+ "flex items-center justify-between py-1.5 pr-2.5 rounded-xl cursor-pointer transition-colors group outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
6968
+ isSelected ? "bg-[var(--ui-primary)] text-white font-semibold shadow-xs" : "text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)]"
5405
6969
  ),
5406
- children: item.badge
6970
+ children: [
6971
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 truncate", children: [
6972
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("p-0.5 rounded transition-transform duration-200", isExpanded && "rotate-90"), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: cn("w-3 h-3", isSelected ? "text-white" : "text-[var(--ui-text-muted)]") }) }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-4" }),
6973
+ getIcon(item, hasChildren, isExpanded),
6974
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: item.label })
6975
+ ] }),
6976
+ item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
6977
+ "span",
6978
+ {
6979
+ className: cn(
6980
+ "px-1.5 py-0.5 rounded-md text-[10px] font-mono",
6981
+ isSelected ? "bg-white/20 text-white" : "bg-[var(--ui-bg-secondary)] text-[var(--ui-text-muted)]"
6982
+ ),
6983
+ children: item.badge
6984
+ }
6985
+ )
6986
+ ]
5407
6987
  }
5408
- )
5409
- ]
5410
- }
5411
- ),
5412
- hasChildren && isExpanded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative space-y-0.5 animate-in fade-in duration-150", children: [
5413
- /* @__PURE__ */ jsxRuntime.jsx(
5414
- "div",
5415
- {
5416
- className: "absolute left-[15px] top-0 bottom-0 w-[1px] bg-[var(--ui-border-subtle)]",
5417
- style: { left: `${level * 16 + 15}px` }
5418
- }
5419
- ),
5420
- item.children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
5421
- TreeNode,
5422
- {
5423
- item: child,
5424
- level: level + 1,
5425
- selectedId,
5426
- onSelect
5427
- },
5428
- child.id
5429
- ))
5430
- ] })
5431
- ] });
5432
- }
5433
- var TreeView = React66.forwardRef(
5434
- ({ data, selectedId, onSelect, className }, ref) => {
6988
+ ),
6989
+ hasChildren && isExpanded && /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", className: "relative space-y-0.5 animate-in fade-in duration-150", children: [
6990
+ /* @__PURE__ */ jsxRuntime.jsx(
6991
+ "div",
6992
+ {
6993
+ className: "absolute left-[15px] top-0 bottom-0 w-[1px] bg-[var(--ui-border-subtle)]",
6994
+ style: { left: `${level * 16 + 15}px` }
6995
+ }
6996
+ ),
6997
+ renderNodes(item.children, level + 1)
6998
+ ] })
6999
+ ] }, item.id);
7000
+ });
7001
+ };
5435
7002
  return /* @__PURE__ */ jsxRuntime.jsx(
5436
7003
  "div",
5437
7004
  {
5438
7005
  ref,
7006
+ role: "tree",
7007
+ "aria-label": ariaLabel,
7008
+ onKeyDown: handleKeyDown,
5439
7009
  className: cn(
5440
- "w-full p-2 rounded-2xl bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] space-y-0.5 shadow-sm overflow-hidden",
7010
+ "w-full p-2 rounded-2xl bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] space-y-0.5 shadow-sm overflow-hidden outline-none",
5441
7011
  className
5442
7012
  ),
5443
- children: data.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
5444
- TreeNode,
5445
- {
5446
- item,
5447
- level: 0,
5448
- selectedId,
5449
- onSelect
5450
- },
5451
- item.id
5452
- ))
7013
+ children: renderNodes(data, 0)
5453
7014
  }
5454
7015
  );
5455
7016
  }
@@ -6232,6 +7793,7 @@ exports.CardTitle = CardTitle;
6232
7793
  exports.Carousel = Carousel;
6233
7794
  exports.Checkbox = Checkbox;
6234
7795
  exports.CheckboxGroup = CheckboxGroup;
7796
+ exports.CheckboxGroupContext = CheckboxGroupContext;
6235
7797
  exports.Chip = Chip;
6236
7798
  exports.CircularProgress = CircularProgress;
6237
7799
  exports.Code = Code;
@@ -6342,6 +7904,7 @@ exports.Textarea = Textarea;
6342
7904
  exports.ThemeProvider = ThemeProvider;
6343
7905
  exports.TimePicker = TimePicker;
6344
7906
  exports.Timeline = Timeline;
7907
+ exports.Toast = Toast;
6345
7908
  exports.ToasterProvider = ToasterProvider;
6346
7909
  exports.ToggleButton = ToggleButton;
6347
7910
  exports.ToggleGroup = ToggleGroup;
@@ -6389,6 +7952,7 @@ exports.toastVariants = toastVariants;
6389
7952
  exports.toggleVariants = toggleVariants;
6390
7953
  exports.tooltipVariants = tooltipVariants;
6391
7954
  exports.useAccordion = useAccordion;
7955
+ exports.useCheckboxGroup = useCheckboxGroup;
6392
7956
  exports.useDrawer = useDrawer;
6393
7957
  exports.useDropdown = useDropdown;
6394
7958
  exports.useFormField = useFormField;