@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
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ var React8 = require('react');
4
4
  var reactDom = require('react-dom');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var classVarianceAuthority = require('class-variance-authority');
@@ -8,10 +8,14 @@ var lucideReact = require('lucide-react');
8
8
  var clsx = require('clsx');
9
9
  var tailwindMerge = require('tailwind-merge');
10
10
 
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var React8__default = /*#__PURE__*/_interopDefault(React8);
14
+
11
15
  // src/overlays/Portal.tsx
12
16
  function Portal({ children, container }) {
13
- const [mounted, setMounted] = react.useState(false);
14
- react.useEffect(() => {
17
+ const [mounted, setMounted] = React8.useState(false);
18
+ React8.useEffect(() => {
15
19
  setMounted(true);
16
20
  return () => setMounted(false);
17
21
  }, []);
@@ -20,44 +24,119 @@ function Portal({ children, container }) {
20
24
  if (!target) return null;
21
25
  return reactDom.createPortal(children, target);
22
26
  }
23
- var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
24
- function FocusTrap({ children, active = true }) {
25
- const rootRef = react.useRef(null);
26
- react.useEffect(() => {
27
+ var FOCUSABLE_SELECTOR = [
28
+ "a[href]",
29
+ "button:not([disabled])",
30
+ "textarea:not([disabled])",
31
+ "input:not([disabled])",
32
+ "select:not([disabled])",
33
+ '[tabindex]:not([tabindex="-1"])',
34
+ '[contenteditable="true"]'
35
+ ].join(", ");
36
+ var trapStack = [];
37
+ function FocusTrap({
38
+ children,
39
+ active = true,
40
+ autoFocus = true,
41
+ restoreFocus = true
42
+ }) {
43
+ const rootRef = React8.useRef(null);
44
+ const previousActiveElementRef = React8.useRef(null);
45
+ React8.useEffect(() => {
27
46
  if (!active) return;
47
+ if (typeof document !== "undefined") {
48
+ previousActiveElementRef.current = document.activeElement;
49
+ }
28
50
  const root = rootRef.current;
29
51
  if (!root) return;
30
- const focusable = root.querySelectorAll(FOCUSABLE_SELECTOR);
31
- const first = focusable[0];
32
- const last = focusable[focusable.length - 1];
33
- if (first) {
34
- first.focus();
52
+ trapStack.push(root);
53
+ if (autoFocus) {
54
+ const timer = setTimeout(() => {
55
+ const focusable = root.querySelectorAll(FOCUSABLE_SELECTOR);
56
+ if (focusable.length > 0) {
57
+ focusable[0].focus();
58
+ } else if (typeof root.focus === "function") {
59
+ root.focus();
60
+ }
61
+ }, 0);
62
+ return () => clearTimeout(timer);
35
63
  }
64
+ return () => {
65
+ const idx = trapStack.indexOf(root);
66
+ if (idx !== -1) trapStack.splice(idx, 1);
67
+ };
68
+ }, [active, autoFocus]);
69
+ React8.useEffect(() => {
70
+ if (!active) return;
36
71
  const handleKeyDown = (e) => {
37
72
  if (e.key !== "Tab") return;
73
+ const root = rootRef.current;
74
+ if (!root) return;
75
+ if (trapStack.length > 0 && trapStack[trapStack.length - 1] !== root) {
76
+ return;
77
+ }
78
+ const focusable = Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
79
+ (el) => el.offsetParent !== null || el.getClientRects().length > 0
80
+ );
81
+ if (focusable.length === 0) {
82
+ e.preventDefault();
83
+ root.focus();
84
+ return;
85
+ }
86
+ const first = focusable[0];
87
+ const last = focusable[focusable.length - 1];
38
88
  if (e.shiftKey) {
39
- if (document.activeElement === first) {
89
+ if (document.activeElement === first || !root.contains(document.activeElement)) {
40
90
  e.preventDefault();
41
- last?.focus();
91
+ last.focus();
42
92
  }
43
93
  } else {
44
- if (document.activeElement === last) {
94
+ if (document.activeElement === last || !root.contains(document.activeElement)) {
45
95
  e.preventDefault();
46
- first?.focus();
96
+ first.focus();
47
97
  }
48
98
  }
49
99
  };
50
- window.addEventListener("keydown", handleKeyDown);
51
- return () => window.removeEventListener("keydown", handleKeyDown);
52
- }, [active]);
53
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: rootRef, className: "contents", children });
100
+ window.addEventListener("keydown", handleKeyDown, true);
101
+ return () => {
102
+ window.removeEventListener("keydown", handleKeyDown, true);
103
+ const root = rootRef.current;
104
+ if (root) {
105
+ const idx = trapStack.indexOf(root);
106
+ if (idx !== -1) trapStack.splice(idx, 1);
107
+ }
108
+ if (restoreFocus && previousActiveElementRef.current && typeof previousActiveElementRef.current.focus === "function") {
109
+ previousActiveElementRef.current.focus();
110
+ }
111
+ };
112
+ }, [active, restoreFocus]);
113
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: rootRef, tabIndex: -1, className: "contents", style: { outline: "none" }, children });
114
+ }
115
+
116
+ // src/utils/scroll-lock.ts
117
+ var lockCount = 0;
118
+ var previousOverflow = "";
119
+ function lockBodyScroll() {
120
+ if (typeof document === "undefined") return;
121
+ if (lockCount === 0) {
122
+ previousOverflow = document.body.style.overflow;
123
+ document.body.style.overflow = "hidden";
124
+ }
125
+ lockCount++;
126
+ }
127
+ function unlockBodyScroll() {
128
+ if (typeof document === "undefined") return;
129
+ lockCount = Math.max(0, lockCount - 1);
130
+ if (lockCount === 0) {
131
+ document.body.style.overflow = previousOverflow;
132
+ }
54
133
  }
55
134
  function cn(...inputs) {
56
135
  return tailwindMerge.twMerge(clsx.clsx(inputs));
57
136
  }
58
- var ModalContext = react.createContext(null);
137
+ var ModalContext = React8.createContext(null);
59
138
  function useModal() {
60
- const context = react.useContext(ModalContext);
139
+ const context = React8.useContext(ModalContext);
61
140
  if (!context) {
62
141
  throw new Error("Modal compound components must be used within a <Modal />");
63
142
  }
@@ -68,35 +147,78 @@ function Modal({
68
147
  onClose,
69
148
  children,
70
149
  closeOnEsc = true,
71
- closeOnOverlayClick = true
150
+ closeOnOverlayClick = true,
151
+ role = "dialog",
152
+ "aria-label": ariaLabel,
153
+ "aria-labelledby": customLabelledBy,
154
+ "aria-describedby": customDescribedBy
72
155
  }) {
73
- react.useEffect(() => {
156
+ const generatedId = React8.useId();
157
+ const titleId = customLabelledBy || `modal-title-${generatedId}`;
158
+ const descriptionId = customDescribedBy || `modal-desc-${generatedId}`;
159
+ const [mountedTitleId, setMountedTitleId] = React8.useState(null);
160
+ const [mountedDescId, setMountedDescId] = React8.useState(null);
161
+ const registerTitle = React8.useCallback(
162
+ (id) => {
163
+ const activeId = id || titleId;
164
+ setMountedTitleId(activeId);
165
+ return () => setMountedTitleId(null);
166
+ },
167
+ [titleId]
168
+ );
169
+ const registerDescription = React8.useCallback(
170
+ (id) => {
171
+ const activeId = id || descriptionId;
172
+ setMountedDescId(activeId);
173
+ return () => setMountedDescId(null);
174
+ },
175
+ [descriptionId]
176
+ );
177
+ React8.useEffect(() => {
74
178
  if (!isOpen) return;
179
+ lockBodyScroll();
75
180
  const handleKeyDown = (e) => {
76
181
  if (closeOnEsc && e.key === "Escape") {
77
182
  onClose();
78
183
  }
79
184
  };
80
- const originalOverflow = document.body.style.overflow;
81
- document.body.style.overflow = "hidden";
82
185
  window.addEventListener("keydown", handleKeyDown);
83
186
  return () => {
84
187
  window.removeEventListener("keydown", handleKeyDown);
85
- document.body.style.overflow = originalOverflow;
188
+ unlockBodyScroll();
86
189
  };
87
190
  }, [isOpen, closeOnEsc, onClose]);
88
191
  if (!isOpen) return null;
89
- 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(
90
- "div",
192
+ const resolvedLabelledBy = customLabelledBy || (ariaLabel ? void 0 : mountedTitleId || void 0);
193
+ const resolvedDescribedBy = customDescribedBy || (mountedDescId || void 0);
194
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
195
+ ModalContext.Provider,
91
196
  {
92
- className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6",
93
- role: "dialog",
94
- "aria-modal": "true",
95
- children
197
+ value: {
198
+ isOpen,
199
+ onClose,
200
+ closeOnOverlayClick,
201
+ titleId,
202
+ descriptionId,
203
+ registerTitle,
204
+ registerDescription
205
+ },
206
+ children: /* @__PURE__ */ jsxRuntime.jsx(FocusTrap, { active: isOpen, restoreFocus: true, children: /* @__PURE__ */ jsxRuntime.jsx(
207
+ "div",
208
+ {
209
+ className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6",
210
+ role,
211
+ "aria-modal": "true",
212
+ "aria-label": ariaLabel,
213
+ "aria-labelledby": resolvedLabelledBy,
214
+ "aria-describedby": resolvedDescribedBy,
215
+ children
216
+ }
217
+ ) })
96
218
  }
97
- ) }) }) });
219
+ ) });
98
220
  }
99
- var ModalOverlay = react.forwardRef(
221
+ var ModalOverlay = React8.forwardRef(
100
222
  ({ className, onClick, ...props }, ref) => {
101
223
  const { onClose, closeOnOverlayClick } = useModal();
102
224
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -136,7 +258,7 @@ var modalContentVariants = classVarianceAuthority.cva(
136
258
  }
137
259
  }
138
260
  );
139
- var ModalContent = react.forwardRef(
261
+ var ModalContent = React8.forwardRef(
140
262
  ({ className, size, children, ...props }, ref) => {
141
263
  return /* @__PURE__ */ jsxRuntime.jsx(
142
264
  "div",
@@ -150,37 +272,65 @@ var ModalContent = react.forwardRef(
150
272
  }
151
273
  );
152
274
  ModalContent.displayName = "ModalContent";
153
- var ModalHeader = react.forwardRef(
275
+ var ModalHeader = React8.forwardRef(
154
276
  ({ className, children, ...props }, ref) => {
155
277
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("flex flex-col space-y-1.5 mb-4", className), ...props, children });
156
278
  }
157
279
  );
158
280
  ModalHeader.displayName = "ModalHeader";
159
- var ModalTitle = react.forwardRef(
160
- ({ className, children, ...props }, ref) => {
161
- 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 });
281
+ var ModalTitle = React8.forwardRef(
282
+ ({ className, children, id, ...props }, ref) => {
283
+ const { titleId, registerTitle } = useModal();
284
+ const activeId = id || titleId;
285
+ React8.useEffect(() => {
286
+ return registerTitle(activeId);
287
+ }, [registerTitle, activeId]);
288
+ return /* @__PURE__ */ jsxRuntime.jsx(
289
+ "h2",
290
+ {
291
+ ref,
292
+ id: activeId,
293
+ className: cn("text-lg sm:text-xl font-bold tracking-tight text-[var(--ui-text-primary)]", className),
294
+ ...props,
295
+ children
296
+ }
297
+ );
162
298
  }
163
299
  );
164
300
  ModalTitle.displayName = "ModalTitle";
165
- var ModalDescription = react.forwardRef(
166
- ({ className, children, ...props }, ref) => {
167
- return /* @__PURE__ */ jsxRuntime.jsx("p", { ref, className: cn("text-sm text-[var(--ui-text-muted)]", className), ...props, children });
301
+ var ModalDescription = React8.forwardRef(
302
+ ({ className, children, id, ...props }, ref) => {
303
+ const { descriptionId, registerDescription } = useModal();
304
+ const activeId = id || descriptionId;
305
+ React8.useEffect(() => {
306
+ return registerDescription(activeId);
307
+ }, [registerDescription, activeId]);
308
+ return /* @__PURE__ */ jsxRuntime.jsx(
309
+ "p",
310
+ {
311
+ ref,
312
+ id: activeId,
313
+ className: cn("text-sm text-[var(--ui-text-muted)]", className),
314
+ ...props,
315
+ children
316
+ }
317
+ );
168
318
  }
169
319
  );
170
320
  ModalDescription.displayName = "ModalDescription";
171
- var ModalBody = react.forwardRef(
321
+ var ModalBody = React8.forwardRef(
172
322
  ({ className, children, ...props }, ref) => {
173
323
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("py-2", className), ...props, children });
174
324
  }
175
325
  );
176
326
  ModalBody.displayName = "ModalBody";
177
- var ModalFooter = react.forwardRef(
327
+ var ModalFooter = React8.forwardRef(
178
328
  ({ className, children, ...props }, ref) => {
179
329
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("flex items-center justify-end gap-3 mt-6 pt-4 border-t border-[var(--ui-border-subtle)]", className), ...props, children });
180
330
  }
181
331
  );
182
332
  ModalFooter.displayName = "ModalFooter";
183
- var ModalCloseButton = react.forwardRef(
333
+ var ModalCloseButton = React8.forwardRef(
184
334
  ({ className, onClick, ...props }, ref) => {
185
335
  const { onClose } = useModal();
186
336
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -194,7 +344,7 @@ var ModalCloseButton = react.forwardRef(
194
344
  },
195
345
  "aria-label": "Close dialog",
196
346
  className: cn(
197
- "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",
347
+ "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)]",
198
348
  className
199
349
  ),
200
350
  ...props,
@@ -236,7 +386,7 @@ var buttonVariants = classVarianceAuthority.cva(
236
386
  }
237
387
  }
238
388
  );
239
- var Button = react.forwardRef(
389
+ var Button = React8.forwardRef(
240
390
  ({
241
391
  className,
242
392
  variant,
@@ -289,35 +439,46 @@ function AlertDialog({
289
439
  confirmText = "Confirm",
290
440
  cancelText = "Cancel",
291
441
  variant = "danger",
292
- isLoading = false
442
+ isLoading = false,
443
+ closeOnOverlayClick = false
293
444
  }) {
294
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { isOpen, onClose, children: [
295
- /* @__PURE__ */ jsxRuntime.jsx(ModalOverlay, {}),
296
- /* @__PURE__ */ jsxRuntime.jsxs(ModalContent, { size: "sm", children: [
297
- /* @__PURE__ */ jsxRuntime.jsx(ModalCloseButton, {}),
298
- /* @__PURE__ */ jsxRuntime.jsxs(ModalHeader, { children: [
299
- /* @__PURE__ */ jsxRuntime.jsx(ModalTitle, { children: title }),
300
- /* @__PURE__ */ jsxRuntime.jsx(ModalDescription, { children: description })
301
- ] }),
302
- /* @__PURE__ */ jsxRuntime.jsxs(ModalFooter, { children: [
303
- /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: onClose, disabled: isLoading, children: cancelText }),
304
- /* @__PURE__ */ jsxRuntime.jsx(
305
- Button,
306
- {
307
- variant: variant === "danger" ? "danger" : "primary",
308
- size: "sm",
309
- loading: isLoading,
310
- onClick: onConfirm,
311
- children: confirmText
312
- }
313
- )
314
- ] })
315
- ] })
316
- ] });
445
+ return /* @__PURE__ */ jsxRuntime.jsxs(
446
+ Modal,
447
+ {
448
+ isOpen,
449
+ onClose,
450
+ role: "alertdialog",
451
+ closeOnOverlayClick,
452
+ children: [
453
+ /* @__PURE__ */ jsxRuntime.jsx(ModalOverlay, {}),
454
+ /* @__PURE__ */ jsxRuntime.jsxs(ModalContent, { size: "sm", children: [
455
+ /* @__PURE__ */ jsxRuntime.jsx(ModalCloseButton, {}),
456
+ /* @__PURE__ */ jsxRuntime.jsxs(ModalHeader, { children: [
457
+ /* @__PURE__ */ jsxRuntime.jsx(ModalTitle, { children: title }),
458
+ /* @__PURE__ */ jsxRuntime.jsx(ModalDescription, { children: description })
459
+ ] }),
460
+ /* @__PURE__ */ jsxRuntime.jsxs(ModalFooter, { children: [
461
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: onClose, disabled: isLoading, autoFocus: true, children: cancelText }),
462
+ /* @__PURE__ */ jsxRuntime.jsx(
463
+ Button,
464
+ {
465
+ variant: variant === "danger" ? "danger" : "primary",
466
+ size: "sm",
467
+ loading: isLoading,
468
+ onClick: onConfirm,
469
+ children: confirmText
470
+ }
471
+ )
472
+ ] })
473
+ ] })
474
+ ]
475
+ }
476
+ );
317
477
  }
318
- var DrawerContext = react.createContext(null);
478
+ AlertDialog.displayName = "AlertDialog";
479
+ var DrawerContext = React8.createContext(null);
319
480
  function useDrawer() {
320
- const context = react.useContext(DrawerContext);
481
+ const context = React8.useContext(DrawerContext);
321
482
  if (!context) {
322
483
  throw new Error("Drawer compound components must be used within a <Drawer />");
323
484
  }
@@ -344,54 +505,68 @@ function Drawer({
344
505
  onClose,
345
506
  placement = "right",
346
507
  children,
347
- closeOnEsc = true
508
+ closeOnEsc = true,
509
+ "aria-label": ariaLabel,
510
+ "aria-labelledby": customLabelledBy
348
511
  }) {
349
- react.useEffect(() => {
512
+ const generatedId = React8.useId();
513
+ const titleId = customLabelledBy || `drawer-title-${generatedId}`;
514
+ React8.useEffect(() => {
350
515
  if (!isOpen) return;
516
+ lockBodyScroll();
351
517
  const handleKeyDown = (e) => {
352
518
  if (closeOnEsc && e.key === "Escape") {
353
519
  onClose();
354
520
  }
355
521
  };
356
- const originalOverflow = document.body.style.overflow;
357
- document.body.style.overflow = "hidden";
358
522
  window.addEventListener("keydown", handleKeyDown);
359
523
  return () => {
360
524
  window.removeEventListener("keydown", handleKeyDown);
361
- document.body.style.overflow = originalOverflow;
525
+ unlockBodyScroll();
362
526
  };
363
527
  }, [isOpen, closeOnEsc, onClose]);
364
528
  if (!isOpen) return null;
365
- 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: [
366
- /* @__PURE__ */ jsxRuntime.jsx(
367
- "div",
368
- {
369
- "aria-hidden": "true",
370
- onClick: onClose,
371
- className: "fixed inset-0 bg-[var(--ui-bg-overlay)] backdrop-blur-sm transition-opacity animate-in fade-in duration-200"
372
- }
373
- ),
374
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(drawerVariants({ placement })), children })
375
- ] }) }) }) });
529
+ 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(
530
+ "div",
531
+ {
532
+ className: "fixed inset-0 z-50 overflow-hidden",
533
+ role: "dialog",
534
+ "aria-modal": "true",
535
+ "aria-label": ariaLabel,
536
+ "aria-labelledby": ariaLabel ? void 0 : titleId,
537
+ children: [
538
+ /* @__PURE__ */ jsxRuntime.jsx(
539
+ "div",
540
+ {
541
+ "aria-hidden": "true",
542
+ onClick: onClose,
543
+ className: "fixed inset-0 bg-[var(--ui-bg-overlay)] backdrop-blur-sm transition-opacity animate-in fade-in duration-200"
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(drawerVariants({ placement })), children })
547
+ ]
548
+ }
549
+ ) }) }) });
376
550
  }
377
- var DrawerHeader = react.forwardRef(
378
- ({ className, children, ...props }, ref) => {
379
- const { onClose } = useDrawer();
551
+ var DrawerHeader = React8.forwardRef(
552
+ ({ className, children, id, ...props }, ref) => {
553
+ const { onClose, titleId } = useDrawer();
380
554
  return /* @__PURE__ */ jsxRuntime.jsxs(
381
555
  "div",
382
556
  {
383
557
  ref,
558
+ id: id || titleId,
384
559
  className: cn("flex items-center justify-between p-5 border-b border-[var(--ui-border-subtle)]", className),
385
560
  ...props,
386
561
  children: [
387
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children }),
562
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 font-semibold text-base text-[var(--ui-text-primary)]", children }),
388
563
  /* @__PURE__ */ jsxRuntime.jsx(
389
564
  "button",
390
565
  {
391
566
  type: "button",
392
567
  onClick: onClose,
393
568
  "aria-label": "Close drawer",
394
- 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",
569
+ 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)]",
395
570
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
396
571
  }
397
572
  )
@@ -401,13 +576,13 @@ var DrawerHeader = react.forwardRef(
401
576
  }
402
577
  );
403
578
  DrawerHeader.displayName = "DrawerHeader";
404
- var DrawerBody = react.forwardRef(
579
+ var DrawerBody = React8.forwardRef(
405
580
  ({ className, children, ...props }, ref) => {
406
581
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("flex-1 p-5 overflow-y-auto", className), ...props, children });
407
582
  }
408
583
  );
409
584
  DrawerBody.displayName = "DrawerBody";
410
- var DrawerFooter = react.forwardRef(
585
+ var DrawerFooter = React8.forwardRef(
411
586
  ({ className, children, ...props }, ref) => {
412
587
  return /* @__PURE__ */ jsxRuntime.jsx(
413
588
  "div",
@@ -442,40 +617,57 @@ function Tooltip({
442
617
  children,
443
618
  placement = "top",
444
619
  delayDuration = 200,
445
- className
620
+ className,
621
+ id: customId
446
622
  }) {
447
- const [isVisible, setIsVisible] = react.useState(false);
448
- const [coords, setCoords] = react.useState({ top: 0, left: 0 });
449
- const timeoutRef = react.useRef(null);
450
- const triggerRef = react.useRef(null);
623
+ const generatedId = React8.useId();
624
+ const tooltipId = customId || `tooltip-${generatedId}`;
625
+ const [isVisible, setIsVisible] = React8.useState(false);
626
+ const [coords, setCoords] = React8.useState({ top: 0, left: 0 });
627
+ const timeoutRef = React8.useRef(null);
628
+ const triggerRef = React8.useRef(null);
629
+ const calculatePosition = () => {
630
+ if (triggerRef.current) {
631
+ const rect = triggerRef.current.getBoundingClientRect();
632
+ let top = 0;
633
+ let left = 0;
634
+ if (placement === "top") {
635
+ top = rect.top + window.scrollY;
636
+ left = rect.left + rect.width / 2 + window.scrollX;
637
+ } else if (placement === "bottom") {
638
+ top = rect.bottom + window.scrollY;
639
+ left = rect.left + rect.width / 2 + window.scrollX;
640
+ } else if (placement === "left") {
641
+ top = rect.top + rect.height / 2 + window.scrollY;
642
+ left = rect.left + window.scrollX;
643
+ } else if (placement === "right") {
644
+ top = rect.top + rect.height / 2 + window.scrollY;
645
+ left = rect.right + window.scrollX;
646
+ }
647
+ setCoords({ top, left });
648
+ }
649
+ };
451
650
  const handleMouseEnter = () => {
651
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
452
652
  timeoutRef.current = setTimeout(() => {
453
- if (triggerRef.current) {
454
- const rect = triggerRef.current.getBoundingClientRect();
455
- let top = 0;
456
- let left = 0;
457
- if (placement === "top") {
458
- top = rect.top + window.scrollY;
459
- left = rect.left + rect.width / 2 + window.scrollX;
460
- } else if (placement === "bottom") {
461
- top = rect.bottom + window.scrollY;
462
- left = rect.left + rect.width / 2 + window.scrollX;
463
- } else if (placement === "left") {
464
- top = rect.top + rect.height / 2 + window.scrollY;
465
- left = rect.left + window.scrollX;
466
- } else if (placement === "right") {
467
- top = rect.top + rect.height / 2 + window.scrollY;
468
- left = rect.right + window.scrollX;
469
- }
470
- setCoords({ top, left });
471
- setIsVisible(true);
472
- }
653
+ calculatePosition();
654
+ setIsVisible(true);
473
655
  }, delayDuration);
474
656
  };
475
657
  const handleMouseLeave = () => {
476
658
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
477
659
  setIsVisible(false);
478
660
  };
661
+ React8.useEffect(() => {
662
+ if (!isVisible) return;
663
+ const handleKeyDown = (e) => {
664
+ if (e.key === "Escape") {
665
+ setIsVisible(false);
666
+ }
667
+ };
668
+ window.addEventListener("keydown", handleKeyDown);
669
+ return () => window.removeEventListener("keydown", handleKeyDown);
670
+ }, [isVisible]);
479
671
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
480
672
  /* @__PURE__ */ jsxRuntime.jsx(
481
673
  "div",
@@ -485,6 +677,7 @@ function Tooltip({
485
677
  onMouseLeave: handleMouseLeave,
486
678
  onFocus: handleMouseEnter,
487
679
  onBlur: handleMouseLeave,
680
+ "aria-describedby": isVisible ? tooltipId : void 0,
488
681
  className: "inline-flex cursor-pointer",
489
682
  children
490
683
  }
@@ -492,28 +685,76 @@ function Tooltip({
492
685
  isVisible && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
493
686
  "div",
494
687
  {
495
- style: { top: `${coords.top}px`, left: `${coords.left}px`, position: "absolute" },
688
+ id: tooltipId,
496
689
  role: "tooltip",
690
+ style: { top: `${coords.top}px`, left: `${coords.left}px`, position: "absolute" },
497
691
  className: cn(tooltipVariants({ placement }), className),
498
692
  children: content
499
693
  }
500
694
  ) })
501
695
  ] });
502
696
  }
697
+ function useControllableState({
698
+ value: controlledValue,
699
+ defaultValue,
700
+ onChange,
701
+ name
702
+ }) {
703
+ const isControlled = controlledValue !== void 0;
704
+ const isControlledRef = React8.useRef(isControlled);
705
+ React8.useEffect(() => {
706
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && isControlledRef.current !== isControlled) {
707
+ console.warn(
708
+ `[${name}] is changing from ${isControlledRef.current ? "controlled" : "uncontrolled"} to ${isControlled ? "controlled" : "uncontrolled"}. Check the value/defaultValue props.`
709
+ );
710
+ }
711
+ isControlledRef.current = isControlled;
712
+ }, [isControlled, name]);
713
+ const [internalState, setInternalState] = React8.useState(defaultValue);
714
+ const value = isControlled ? controlledValue : internalState;
715
+ const setValue = React8.useCallback(
716
+ (next) => {
717
+ const nextValue = typeof next === "function" ? next(value) : next;
718
+ if (!isControlled) {
719
+ setInternalState(nextValue);
720
+ }
721
+ onChange?.(nextValue);
722
+ },
723
+ [isControlled, value, onChange]
724
+ );
725
+ return [value, setValue, isControlled];
726
+ }
503
727
  var Popover = ({
504
728
  trigger,
505
729
  children,
506
- isOpen: controlledIsOpen,
730
+ open: controlledOpen,
731
+ isOpen: legacyIsOpen,
732
+ defaultOpen = false,
733
+ onOpenChange,
507
734
  onClose,
508
735
  placement = "bottom",
509
736
  className
510
737
  }) => {
511
- const [internalIsOpen, setInternalIsOpen] = react.useState(false);
512
- const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
513
- const [coords, setCoords] = react.useState({ top: 0, left: 0 });
514
- const triggerRef = react.useRef(null);
515
- const popoverRef = react.useRef(null);
516
- const calculatePosition = () => {
738
+ const activeControlled = controlledOpen !== void 0 ? controlledOpen : legacyIsOpen;
739
+ const handleStateChange = React8.useCallback(
740
+ (nextOpen) => {
741
+ onOpenChange?.(nextOpen);
742
+ if (!nextOpen) {
743
+ onClose?.();
744
+ }
745
+ },
746
+ [onOpenChange, onClose]
747
+ );
748
+ const [isOpen, setIsOpen] = useControllableState({
749
+ value: activeControlled,
750
+ defaultValue: defaultOpen,
751
+ onChange: handleStateChange,
752
+ name: "Popover"
753
+ });
754
+ const [coords, setCoords] = React8.useState({ top: 0, left: 0 });
755
+ const triggerRef = React8.useRef(null);
756
+ const popoverRef = React8.useRef(null);
757
+ const calculatePosition = React8.useCallback(() => {
517
758
  if (triggerRef.current) {
518
759
  const rect = triggerRef.current.getBoundingClientRect();
519
760
  let top = 0;
@@ -533,45 +774,52 @@ var Popover = ({
533
774
  }
534
775
  setCoords({ top, left });
535
776
  }
536
- };
777
+ }, [placement]);
537
778
  const handleToggle = () => {
538
779
  if (!isOpen) {
539
780
  calculatePosition();
540
781
  }
541
- if (controlledIsOpen !== void 0) {
542
- if (isOpen) onClose?.();
543
- } else {
544
- setInternalIsOpen(!internalIsOpen);
545
- }
782
+ setIsOpen(!isOpen);
546
783
  };
547
- react.useEffect(() => {
784
+ React8.useEffect(() => {
548
785
  if (isOpen) {
549
786
  calculatePosition();
550
787
  }
551
- }, [isOpen]);
552
- react.useEffect(() => {
788
+ }, [isOpen, calculatePosition]);
789
+ React8.useEffect(() => {
790
+ if (!isOpen) return;
553
791
  const handleOutside = (e) => {
554
792
  if (popoverRef.current && !popoverRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) {
555
- if (controlledIsOpen !== void 0) {
556
- onClose?.();
557
- } else {
558
- setInternalIsOpen(false);
559
- }
793
+ setIsOpen(false);
560
794
  }
561
795
  };
562
- if (isOpen) {
563
- document.addEventListener("mousedown", handleOutside);
564
- window.addEventListener("resize", calculatePosition);
565
- window.addEventListener("scroll", calculatePosition);
566
- }
796
+ const handleKeyDown = (e) => {
797
+ if (e.key === "Escape") {
798
+ setIsOpen(false);
799
+ }
800
+ };
801
+ document.addEventListener("mousedown", handleOutside);
802
+ document.addEventListener("keydown", handleKeyDown);
803
+ window.addEventListener("resize", calculatePosition);
804
+ window.addEventListener("scroll", calculatePosition);
567
805
  return () => {
568
806
  document.removeEventListener("mousedown", handleOutside);
807
+ document.removeEventListener("keydown", handleKeyDown);
569
808
  window.removeEventListener("resize", calculatePosition);
570
809
  window.removeEventListener("scroll", calculatePosition);
571
810
  };
572
- }, [isOpen, controlledIsOpen, onClose]);
811
+ }, [isOpen, setIsOpen, calculatePosition]);
573
812
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
574
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: triggerRef, onClick: handleToggle, className: "w-full inline-block cursor-pointer", children: trigger }),
813
+ /* @__PURE__ */ jsxRuntime.jsx(
814
+ "div",
815
+ {
816
+ ref: triggerRef,
817
+ onClick: handleToggle,
818
+ className: "w-full inline-block cursor-pointer",
819
+ "aria-expanded": isOpen,
820
+ children: trigger
821
+ }
822
+ ),
575
823
  isOpen && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
576
824
  "div",
577
825
  {
@@ -592,9 +840,9 @@ var Popover = ({
592
840
  ) })
593
841
  ] });
594
842
  };
595
- var ToastContext = react.createContext(null);
843
+ var ToastContext = React8.createContext(null);
596
844
  function useToast() {
597
- const context = react.useContext(ToastContext);
845
+ const context = React8.useContext(ToastContext);
598
846
  if (!context) {
599
847
  throw new Error("useToast must be used within a ToasterProvider");
600
848
  }
@@ -622,12 +870,44 @@ var toastIcons = {
622
870
  warning: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "w-5 h-5 text-[var(--ui-warning)] flex-shrink-0" }),
623
871
  error: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-5 h-5 text-[var(--ui-error)] flex-shrink-0" })
624
872
  };
873
+ var Toast = React8__default.default.forwardRef(
874
+ ({ className, title, description, type = "info", onDismiss, ...props }, ref) => {
875
+ return /* @__PURE__ */ jsxRuntime.jsxs(
876
+ "div",
877
+ {
878
+ ref,
879
+ role: "status",
880
+ "aria-live": "polite",
881
+ className: cn(toastVariants({ type }), className),
882
+ ...props,
883
+ children: [
884
+ toastIcons[type || "info"],
885
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-1", children: [
886
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold text-[var(--ui-text-primary)]", children: title }),
887
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-[var(--ui-text-muted)]", children: description })
888
+ ] }),
889
+ onDismiss && /* @__PURE__ */ jsxRuntime.jsx(
890
+ "button",
891
+ {
892
+ type: "button",
893
+ onClick: onDismiss,
894
+ "aria-label": "Dismiss toast",
895
+ 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",
896
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
897
+ }
898
+ )
899
+ ]
900
+ }
901
+ );
902
+ }
903
+ );
904
+ Toast.displayName = "Toast";
625
905
  function ToasterProvider({ children, position = "bottom-right" }) {
626
- const [toasts, setToasts] = react.useState([]);
627
- const dismiss = react.useCallback((id) => {
906
+ const [toasts, setToasts] = React8.useState([]);
907
+ const dismiss = React8.useCallback((id) => {
628
908
  setToasts((prev) => prev.filter((t) => t.id !== id));
629
909
  }, []);
630
- const toast = react.useCallback(
910
+ const toast = React8.useCallback(
631
911
  ({ id = Math.random().toString(36).slice(2, 9), title, description, type = "info", duration = 4e3 }) => {
632
912
  const item = { id, title, description, type, duration };
633
913
  setToasts((prev) => [...prev, item]);
@@ -685,7 +965,7 @@ function ToasterProvider({ children, position = "bottom-right" }) {
685
965
  ) })
686
966
  ] });
687
967
  }
688
- var BottomSheet = react.forwardRef(
968
+ var BottomSheet = React8.forwardRef(
689
969
  ({
690
970
  isOpen,
691
971
  onClose,
@@ -694,73 +974,93 @@ var BottomSheet = react.forwardRef(
694
974
  description,
695
975
  className,
696
976
  showHandle = true,
697
- maxHeight = "max-h-[85vh]"
977
+ maxHeight = "max-h-[85vh]",
978
+ "aria-label": ariaLabel
698
979
  }, ref) => {
699
- react.useRef(null);
700
- react.useEffect(() => {
980
+ const generatedId = React8.useId();
981
+ const titleId = `bottomsheet-title-${generatedId}`;
982
+ const descId = `bottomsheet-desc-${generatedId}`;
983
+ React8.useEffect(() => {
984
+ if (!isOpen) return;
985
+ lockBodyScroll();
701
986
  const handleKeyDown = (e) => {
702
- if (e.key === "Escape" && isOpen) {
987
+ if (e.key === "Escape") {
703
988
  onClose();
704
989
  }
705
990
  };
706
991
  window.addEventListener("keydown", handleKeyDown);
707
- return () => window.removeEventListener("keydown", handleKeyDown);
992
+ return () => {
993
+ window.removeEventListener("keydown", handleKeyDown);
994
+ unlockBodyScroll();
995
+ };
708
996
  }, [isOpen, onClose]);
709
997
  if (!isOpen) return null;
710
- 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: [
711
- /* @__PURE__ */ jsxRuntime.jsx(
712
- "div",
713
- {
714
- onClick: onClose,
715
- className: "fixed inset-0 bg-black/60 backdrop-blur-md transition-opacity animate-in fade-in duration-300",
716
- "aria-hidden": "true"
717
- }
718
- ),
719
- /* @__PURE__ */ jsxRuntime.jsxs(
720
- "div",
721
- {
722
- ref,
723
- className: cn(
724
- "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",
725
- maxHeight,
726
- className
998
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(FocusTrap, { active: isOpen, restoreFocus: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
999
+ "div",
1000
+ {
1001
+ className: "fixed inset-0 z-50 flex flex-col justify-end",
1002
+ role: "dialog",
1003
+ "aria-modal": "true",
1004
+ "aria-label": ariaLabel,
1005
+ "aria-labelledby": title ? titleId : void 0,
1006
+ "aria-describedby": description ? descId : void 0,
1007
+ children: [
1008
+ /* @__PURE__ */ jsxRuntime.jsx(
1009
+ "div",
1010
+ {
1011
+ onClick: onClose,
1012
+ className: "fixed inset-0 bg-black/60 backdrop-blur-md transition-opacity animate-in fade-in duration-300",
1013
+ "aria-hidden": "true"
1014
+ }
727
1015
  ),
728
- children: [
729
- showHandle && /* @__PURE__ */ jsxRuntime.jsx(
730
- "div",
731
- {
732
- onClick: onClose,
733
- className: "w-full py-3 flex items-center justify-center cursor-grab active:cursor-grabbing",
734
- 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" })
735
- }
736
- ),
737
- (title || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 pb-3 flex items-center justify-between border-b border-[var(--ui-border-subtle)]", children: [
738
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
739
- title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-bold tracking-tight text-[var(--ui-text-primary)]", children: title }),
740
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-[var(--ui-text-muted)]", children: description })
741
- ] }),
742
- /* @__PURE__ */ jsxRuntime.jsx(
743
- "button",
744
- {
745
- type: "button",
746
- onClick: onClose,
747
- 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",
748
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
749
- }
750
- )
751
- ] }),
752
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 overflow-y-auto flex-1 space-y-4", children })
753
- ]
754
- }
755
- )
756
- ] }) }) });
1016
+ /* @__PURE__ */ jsxRuntime.jsxs(
1017
+ "div",
1018
+ {
1019
+ ref,
1020
+ className: cn(
1021
+ "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",
1022
+ maxHeight,
1023
+ className
1024
+ ),
1025
+ children: [
1026
+ showHandle && /* @__PURE__ */ jsxRuntime.jsx(
1027
+ "div",
1028
+ {
1029
+ onClick: onClose,
1030
+ className: "w-full py-3 flex items-center justify-center cursor-grab active:cursor-grabbing",
1031
+ 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" })
1032
+ }
1033
+ ),
1034
+ (title || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 pb-3 flex items-center justify-between border-b border-[var(--ui-border-subtle)]", children: [
1035
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1036
+ title && /* @__PURE__ */ jsxRuntime.jsx("h3", { id: titleId, className: "text-base font-bold tracking-tight text-[var(--ui-text-primary)]", children: title }),
1037
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { id: descId, className: "text-xs text-[var(--ui-text-muted)]", children: description })
1038
+ ] }),
1039
+ /* @__PURE__ */ jsxRuntime.jsx(
1040
+ "button",
1041
+ {
1042
+ type: "button",
1043
+ onClick: onClose,
1044
+ "aria-label": "Close sheet",
1045
+ 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)]",
1046
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
1047
+ }
1048
+ )
1049
+ ] }),
1050
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 overflow-y-auto flex-1 space-y-4", children })
1051
+ ]
1052
+ }
1053
+ )
1054
+ ]
1055
+ }
1056
+ ) }) });
757
1057
  }
758
1058
  );
759
1059
  BottomSheet.displayName = "BottomSheet";
760
1060
  var BottomSheetHeader = ({ className, children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("px-6 pb-2", className), ...props, children });
761
1061
  var BottomSheetBody = ({ className, children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("px-6 py-2 overflow-y-auto", className), ...props, children });
762
1062
  var BottomSheetFooter = ({ className, children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("p-6 pt-3 border-t border-[var(--ui-border-subtle)] flex items-center justify-end gap-3", className), ...props, children });
763
- var Kbd = react.forwardRef(
1063
+ var Kbd = React8.forwardRef(
764
1064
  ({ className, children, ...props }, ref) => {
765
1065
  return /* @__PURE__ */ jsxRuntime.jsx(
766
1066
  "kbd",
@@ -791,14 +1091,21 @@ var ContextMenuItem = ({
791
1091
  "div",
792
1092
  {
793
1093
  role: "menuitem",
1094
+ tabIndex: disabled ? -1 : 0,
794
1095
  "aria-disabled": disabled,
795
1096
  onClick: () => {
796
1097
  if (!disabled) onSelect?.();
797
1098
  },
1099
+ onKeyDown: (e) => {
1100
+ if (!disabled && (e.key === "Enter" || e.key === " ")) {
1101
+ e.preventDefault();
1102
+ onSelect?.();
1103
+ }
1104
+ },
798
1105
  className: cn(
799
- "flex items-center justify-between px-3 py-1.5 rounded-xl text-xs font-medium cursor-pointer transition-colors select-none",
1106
+ "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)]",
800
1107
  disabled && "opacity-40 cursor-not-allowed pointer-events-none",
801
- destructive ? "text-rose-500 hover:bg-rose-500/10" : "text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)]",
1108
+ 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)]",
802
1109
  className
803
1110
  ),
804
1111
  ...props,
@@ -813,11 +1120,11 @@ var ContextMenuItem = ({
813
1120
  );
814
1121
  };
815
1122
  var ContextMenuSeparator = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px my-1 bg-[var(--ui-border-subtle)]", role: "separator" });
816
- var ContextMenu = react.forwardRef(
1123
+ var ContextMenu = React8.forwardRef(
817
1124
  ({ children, menuContent, className }, ref) => {
818
- const [isOpen, setIsOpen] = react.useState(false);
819
- const [position, setPosition] = react.useState({ x: 0, y: 0 });
820
- const menuRef = react.useRef(null);
1125
+ const [isOpen, setIsOpen] = React8.useState(false);
1126
+ const [position, setPosition] = React8.useState({ x: 0, y: 0 });
1127
+ const menuRef = React8.useRef(null);
821
1128
  const handleContextMenu = (e) => {
822
1129
  e.preventDefault();
823
1130
  const x = Math.min(window.innerWidth - 220, e.clientX);
@@ -825,21 +1132,48 @@ var ContextMenu = react.forwardRef(
825
1132
  setPosition({ x, y });
826
1133
  setIsOpen(true);
827
1134
  };
828
- react.useEffect(() => {
1135
+ React8.useEffect(() => {
1136
+ if (!isOpen) return;
1137
+ const timer = setTimeout(() => {
1138
+ const firstItem = menuRef.current?.querySelector('[role="menuitem"]:not([aria-disabled="true"])');
1139
+ firstItem?.focus();
1140
+ }, 10);
829
1141
  const handleClickOutside = (e) => {
830
1142
  if (menuRef.current && !menuRef.current.contains(e.target)) {
831
1143
  setIsOpen(false);
832
1144
  }
833
1145
  };
834
1146
  const handleKeyDown = (e) => {
835
- if (e.key === "Escape") setIsOpen(false);
1147
+ if (!menuRef.current) return;
1148
+ const items = Array.from(
1149
+ menuRef.current.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
1150
+ );
1151
+ if (items.length === 0) return;
1152
+ const currentIndex = items.indexOf(document.activeElement);
1153
+ if (e.key === "ArrowDown") {
1154
+ e.preventDefault();
1155
+ const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % items.length;
1156
+ items[nextIndex]?.focus();
1157
+ } else if (e.key === "ArrowUp") {
1158
+ e.preventDefault();
1159
+ const prevIndex = currentIndex === -1 ? items.length - 1 : (currentIndex - 1 + items.length) % items.length;
1160
+ items[prevIndex]?.focus();
1161
+ } else if (e.key === "Home") {
1162
+ e.preventDefault();
1163
+ items[0]?.focus();
1164
+ } else if (e.key === "End") {
1165
+ e.preventDefault();
1166
+ items[items.length - 1]?.focus();
1167
+ } else if (e.key === "Escape") {
1168
+ e.preventDefault();
1169
+ setIsOpen(false);
1170
+ }
836
1171
  };
837
- if (isOpen) {
838
- window.addEventListener("click", handleClickOutside);
839
- window.addEventListener("contextmenu", handleClickOutside);
840
- window.addEventListener("keydown", handleKeyDown);
841
- }
1172
+ window.addEventListener("click", handleClickOutside);
1173
+ window.addEventListener("contextmenu", handleClickOutside);
1174
+ window.addEventListener("keydown", handleKeyDown);
842
1175
  return () => {
1176
+ clearTimeout(timer);
843
1177
  window.removeEventListener("click", handleClickOutside);
844
1178
  window.removeEventListener("contextmenu", handleClickOutside);
845
1179
  window.removeEventListener("keydown", handleKeyDown);
@@ -851,9 +1185,11 @@ var ContextMenu = react.forwardRef(
851
1185
  "div",
852
1186
  {
853
1187
  ref: menuRef,
1188
+ role: "menu",
1189
+ tabIndex: -1,
854
1190
  style: { top: `${position.y}px`, left: `${position.x}px` },
855
1191
  onClick: () => setIsOpen(false),
856
- 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",
1192
+ 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",
857
1193
  children: menuContent
858
1194
  }
859
1195
  ) })
@@ -861,13 +1197,13 @@ var ContextMenu = react.forwardRef(
861
1197
  }
862
1198
  );
863
1199
  ContextMenu.displayName = "ContextMenu";
864
- var HoverCard = react.forwardRef(
1200
+ var HoverCard = React8.forwardRef(
865
1201
  ({ trigger, content, openDelay = 200, closeDelay = 250, className }, ref) => {
866
- const [isOpen, setIsOpen] = react.useState(false);
867
- const [coords, setCoords] = react.useState({ top: 0, left: 0 });
868
- const triggerRef = react.useRef(null);
869
- const openTimerRef = react.useRef(null);
870
- const closeTimerRef = react.useRef(null);
1202
+ const [isOpen, setIsOpen] = React8.useState(false);
1203
+ const [coords, setCoords] = React8.useState({ top: 0, left: 0 });
1204
+ const triggerRef = React8.useRef(null);
1205
+ const openTimerRef = React8.useRef(null);
1206
+ const closeTimerRef = React8.useRef(null);
871
1207
  const handleMouseEnter = () => {
872
1208
  clearTimeout(closeTimerRef.current);
873
1209
  openTimerRef.current = setTimeout(() => {
@@ -941,6 +1277,7 @@ exports.ModalOverlay = ModalOverlay;
941
1277
  exports.ModalTitle = ModalTitle;
942
1278
  exports.Popover = Popover;
943
1279
  exports.Portal = Portal;
1280
+ exports.Toast = Toast;
944
1281
  exports.ToasterProvider = ToasterProvider;
945
1282
  exports.Tooltip = Tooltip;
946
1283
  exports.drawerVariants = drawerVariants;