react-18-ui-library 0.1.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 (156) hide show
  1. package/README.md +202 -0
  2. package/dist/components/actions/CopyButton/CopyButton.d.ts +14 -0
  3. package/dist/components/actions/CopyButton/index.d.ts +2 -0
  4. package/dist/components/display/Avatar/Avatar.d.ts +21 -0
  5. package/dist/components/display/Avatar/index.d.ts +2 -0
  6. package/dist/components/display/Badge/Badge.d.ts +20 -0
  7. package/dist/components/display/Badge/index.d.ts +2 -0
  8. package/dist/components/display/Box/Box.d.ts +18 -0
  9. package/dist/components/display/Box/index.d.ts +2 -0
  10. package/dist/components/display/Card/Card.d.ts +11 -0
  11. package/dist/components/display/Card/index.d.ts +2 -0
  12. package/dist/components/display/Collapsible/Collapsible.d.ts +15 -0
  13. package/dist/components/display/Collapsible/index.d.ts +2 -0
  14. package/dist/components/display/DataTable/DataTable.d.ts +46 -0
  15. package/dist/components/display/EmptyState/EmptyState.d.ts +10 -0
  16. package/dist/components/display/EmptyState/index.d.ts +2 -0
  17. package/dist/components/display/Icon/Icon.d.ts +9 -0
  18. package/dist/components/display/Icon/index.d.ts +2 -0
  19. package/dist/components/display/Image/Image.d.ts +15 -0
  20. package/dist/components/display/Image/index.d.ts +2 -0
  21. package/dist/components/display/List/List.d.ts +24 -0
  22. package/dist/components/display/List/index.d.ts +2 -0
  23. package/dist/components/display/MarkdownReader/MarkdownReader.d.ts +7 -0
  24. package/dist/components/display/SVG/SVG.d.ts +18 -0
  25. package/dist/components/display/SVG/index.d.ts +2 -0
  26. package/dist/components/display/Stat/Stat.d.ts +13 -0
  27. package/dist/components/display/Stat/index.d.ts +2 -0
  28. package/dist/components/display/Table/Table.d.ts +31 -0
  29. package/dist/components/display/Table/index.d.ts +2 -0
  30. package/dist/components/display/Tag/Tag.d.ts +16 -0
  31. package/dist/components/display/Tag/index.d.ts +2 -0
  32. package/dist/components/display/Timeline/Timeline.d.ts +15 -0
  33. package/dist/components/display/Timeline/index.d.ts +2 -0
  34. package/dist/components/display/TreeView/TreeView.d.ts +23 -0
  35. package/dist/components/display/TreeView/index.d.ts +2 -0
  36. package/dist/components/feedback/Alert/Alert.d.ts +15 -0
  37. package/dist/components/feedback/Alert/index.d.ts +2 -0
  38. package/dist/components/feedback/ErrorBoundary/ErrorBoundary.d.ts +18 -0
  39. package/dist/components/feedback/ErrorBoundary/index.d.ts +2 -0
  40. package/dist/components/feedback/FullScreenLoader/FullScreenLoader.d.ts +27 -0
  41. package/dist/components/feedback/ProgressBar/ProgressBar.d.ts +15 -0
  42. package/dist/components/feedback/ProgressBar/index.d.ts +2 -0
  43. package/dist/components/feedback/Skeleton/Skeleton.d.ts +16 -0
  44. package/dist/components/feedback/Skeleton/index.d.ts +2 -0
  45. package/dist/components/feedback/Spinner/Spinner.d.ts +9 -0
  46. package/dist/components/feedback/Spinner/index.d.ts +2 -0
  47. package/dist/components/feedback/Toast/Toast.d.ts +7 -0
  48. package/dist/components/feedback/Toast/index.d.ts +2 -0
  49. package/dist/components/forms/FileUpload/FileUpload.d.ts +24 -0
  50. package/dist/components/forms/FileUpload/index.d.ts +2 -0
  51. package/dist/components/forms/FormField/FormField.d.ts +12 -0
  52. package/dist/components/forms/FormField/index.d.ts +2 -0
  53. package/dist/components/forms/JSONForm/FormValidator.d.ts +157 -0
  54. package/dist/components/forms/JSONForm/JSONForm.d.ts +53 -0
  55. package/dist/components/forms/JSONForm/index.d.ts +4 -0
  56. package/dist/components/inputs/Button/Button.d.ts +15 -0
  57. package/dist/components/inputs/Button/index.d.ts +2 -0
  58. package/dist/components/inputs/Checkbox/Checkbox.d.ts +14 -0
  59. package/dist/components/inputs/Checkbox/index.d.ts +2 -0
  60. package/dist/components/inputs/ChipSelect/ChipSelect.d.ts +24 -0
  61. package/dist/components/inputs/DatePicker/DatePicker.d.ts +39 -0
  62. package/dist/components/inputs/DatePicker/index.d.ts +2 -0
  63. package/dist/components/inputs/IconButton/IconButton.d.ts +14 -0
  64. package/dist/components/inputs/IconButton/index.d.ts +2 -0
  65. package/dist/components/inputs/MultiSelect/MultiSelect.d.ts +35 -0
  66. package/dist/components/inputs/MultiSelect/index.d.ts +2 -0
  67. package/dist/components/inputs/NumberInput/NumberInput.d.ts +23 -0
  68. package/dist/components/inputs/NumberInput/index.d.ts +2 -0
  69. package/dist/components/inputs/OTPInput/OTPInput.d.ts +18 -0
  70. package/dist/components/inputs/OTPInput/index.d.ts +2 -0
  71. package/dist/components/inputs/PhoneInput/PhoneInput.d.ts +35 -0
  72. package/dist/components/inputs/PhoneInput/index.d.ts +2 -0
  73. package/dist/components/inputs/Radio/Radio.d.ts +20 -0
  74. package/dist/components/inputs/Radio/index.d.ts +2 -0
  75. package/dist/components/inputs/Rating/Rating.d.ts +19 -0
  76. package/dist/components/inputs/Rating/index.d.ts +2 -0
  77. package/dist/components/inputs/SearchSelect/SearchSelect.d.ts +39 -0
  78. package/dist/components/inputs/Select/Select.d.ts +25 -0
  79. package/dist/components/inputs/Select/index.d.ts +2 -0
  80. package/dist/components/inputs/Slider/Slider.d.ts +20 -0
  81. package/dist/components/inputs/Slider/index.d.ts +2 -0
  82. package/dist/components/inputs/Switch/Switch.d.ts +14 -0
  83. package/dist/components/inputs/Switch/index.d.ts +2 -0
  84. package/dist/components/inputs/TagInput/TagInput.d.ts +17 -0
  85. package/dist/components/inputs/TextArea/TextArea.d.ts +18 -0
  86. package/dist/components/inputs/TextArea/index.d.ts +2 -0
  87. package/dist/components/inputs/TextField/TextField.d.ts +21 -0
  88. package/dist/components/inputs/TextField/index.d.ts +2 -0
  89. package/dist/components/layout/AppShell/AppShell.d.ts +12 -0
  90. package/dist/components/layout/AppShell/index.d.ts +2 -0
  91. package/dist/components/layout/Container/Container.d.ts +8 -0
  92. package/dist/components/layout/Container/index.d.ts +2 -0
  93. package/dist/components/layout/Divider/Divider.d.ts +10 -0
  94. package/dist/components/layout/Divider/index.d.ts +2 -0
  95. package/dist/components/layout/Grid/Grid.d.ts +13 -0
  96. package/dist/components/layout/Grid/index.d.ts +2 -0
  97. package/dist/components/layout/Navbar/Navbar.d.ts +19 -0
  98. package/dist/components/layout/Navbar/index.d.ts +2 -0
  99. package/dist/components/layout/Sidebar/Sidebar.d.ts +24 -0
  100. package/dist/components/layout/Sidebar/index.d.ts +2 -0
  101. package/dist/components/layout/Spacer/Spacer.d.ts +6 -0
  102. package/dist/components/layout/Spacer/index.d.ts +2 -0
  103. package/dist/components/layout/Stack/Stack.d.ts +14 -0
  104. package/dist/components/layout/Stack/index.d.ts +2 -0
  105. package/dist/components/navigation/Breadcrumb/Breadcrumb.d.ts +13 -0
  106. package/dist/components/navigation/Breadcrumb/index.d.ts +2 -0
  107. package/dist/components/navigation/Pagination/Pagination.d.ts +10 -0
  108. package/dist/components/navigation/Pagination/index.d.ts +2 -0
  109. package/dist/components/navigation/StepIndicator/StepIndicator.d.ts +15 -0
  110. package/dist/components/navigation/StepIndicator/index.d.ts +2 -0
  111. package/dist/components/navigation/Tabs/Tabs.d.ts +25 -0
  112. package/dist/components/navigation/Tabs/index.d.ts +2 -0
  113. package/dist/components/overlay/CommandPalette/CommandPalette.d.ts +35 -0
  114. package/dist/components/overlay/CommandPalette/index.d.ts +2 -0
  115. package/dist/components/overlay/ConfirmDialog/ConfirmDialog.d.ts +20 -0
  116. package/dist/components/overlay/ContextMenu/ContextMenu.d.ts +19 -0
  117. package/dist/components/overlay/ContextMenu/index.d.ts +2 -0
  118. package/dist/components/overlay/Drawer/Drawer.d.ts +18 -0
  119. package/dist/components/overlay/Drawer/index.d.ts +2 -0
  120. package/dist/components/overlay/Modal/Modal.d.ts +17 -0
  121. package/dist/components/overlay/Modal/index.d.ts +2 -0
  122. package/dist/components/overlay/Popover/Popover.d.ts +15 -0
  123. package/dist/components/overlay/Popover/index.d.ts +2 -0
  124. package/dist/components/overlay/Tooltip/Tooltip.d.ts +13 -0
  125. package/dist/components/overlay/Tooltip/index.d.ts +2 -0
  126. package/dist/components/typography/Code/Code.d.ts +11 -0
  127. package/dist/components/typography/Code/index.d.ts +2 -0
  128. package/dist/components/typography/Heading/Heading.d.ts +12 -0
  129. package/dist/components/typography/Heading/index.d.ts +2 -0
  130. package/dist/components/typography/Kbd/Kbd.d.ts +8 -0
  131. package/dist/components/typography/Kbd/index.d.ts +2 -0
  132. package/dist/components/typography/Label/Label.d.ts +8 -0
  133. package/dist/components/typography/Label/index.d.ts +2 -0
  134. package/dist/components/typography/Link/Link.d.ts +9 -0
  135. package/dist/components/typography/Link/index.d.ts +2 -0
  136. package/dist/components/typography/Text/Text.d.ts +16 -0
  137. package/dist/components/typography/Text/index.d.ts +2 -0
  138. package/dist/hooks/useClickOutside.d.ts +2 -0
  139. package/dist/hooks/useClipboard.d.ts +11 -0
  140. package/dist/hooks/useDebounce.d.ts +1 -0
  141. package/dist/hooks/useIntersectionObserver.d.ts +5 -0
  142. package/dist/hooks/useKeyboard.d.ts +9 -0
  143. package/dist/hooks/useLocalStorage.d.ts +1 -0
  144. package/dist/hooks/useMediaQuery.d.ts +1 -0
  145. package/dist/hooks/useTheme.d.ts +4 -0
  146. package/dist/hooks/useToast.d.ts +16 -0
  147. package/dist/hooks/useWindowSize.d.ts +8 -0
  148. package/dist/index.cjs.js +3639 -0
  149. package/dist/index.cjs.js.map +1 -0
  150. package/dist/index.d.ts +1587 -0
  151. package/dist/index.esm.js +3512 -0
  152. package/dist/index.esm.js.map +1 -0
  153. package/dist/styles.css +1 -0
  154. package/dist/theme/ThemeProvider.d.ts +59 -0
  155. package/dist/utils/cn.d.ts +2 -0
  156. package/package.json +112 -0
@@ -0,0 +1,3512 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import React, { createContext, useEffect, useCallback, useState, useRef, useId, useMemo, Component, useContext } from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { X, Menu, ChevronRight, ChevronLeft, ChevronDown, ChevronsLeft, ChevronsRight, Check, Loader2, EyeOff, Eye, ChevronUp, Minus, Search, Star, Calendar, AlertCircle, Upload, Image as Image$1, FileText, File as File$1, ImageOff, User, ChevronsUpDown, TrendingUp, TrendingDown, Inbox, AlertTriangle, CheckCircle, Info, RefreshCw, Trash2, Copy, ExternalLink } from 'lucide-react';
6
+ import * as RadixTabs from '@radix-ui/react-tabs';
7
+ import { cva } from 'class-variance-authority';
8
+ import * as RadixSelect from '@radix-ui/react-select';
9
+ import * as RadixCheckbox from '@radix-ui/react-checkbox';
10
+ import * as RadixRadio from '@radix-ui/react-radio-group';
11
+ import * as RadixSwitch from '@radix-ui/react-switch';
12
+ import { createPortal } from 'react-dom';
13
+ import * as RadixSlider from '@radix-ui/react-slider';
14
+ import { DayPicker } from 'react-day-picker';
15
+ import { format, isValid, getYear, getMonth, setMonth, setYear } from 'date-fns';
16
+ import { useForm, Controller } from 'react-hook-form';
17
+ import { useDropzone } from 'react-dropzone';
18
+ import ReactMarkdown from 'react-markdown';
19
+ import remarkGfm from 'remark-gfm';
20
+ import { themes, Highlight } from 'prism-react-renderer';
21
+ import * as RadixToast from '@radix-ui/react-toast';
22
+ import * as RadixDialog from '@radix-ui/react-dialog';
23
+ import * as RadixTooltip from '@radix-ui/react-tooltip';
24
+ import * as RadixPopover from '@radix-ui/react-popover';
25
+ import * as RadixContextMenu from '@radix-ui/react-context-menu';
26
+ import * as RadixLabel from '@radix-ui/react-label';
27
+
28
+ const TOKEN_MAP = {
29
+ colorPrimary: '--color-primary',
30
+ colorPrimaryHover: '--color-primary-hover',
31
+ colorPrimaryForeground: '--color-primary-foreground',
32
+ colorSecondary: '--color-secondary',
33
+ colorSecondaryHover: '--color-secondary-hover',
34
+ colorSecondaryForeground: '--color-secondary-foreground',
35
+ colorBackground: '--color-background',
36
+ colorSurface: '--color-surface',
37
+ colorSurfaceHover: '--color-surface-hover',
38
+ colorBorder: '--color-border',
39
+ colorBorderFocus: '--color-border-focus',
40
+ colorText: '--color-text',
41
+ colorTextMuted: '--color-text-muted',
42
+ colorTextDisabled: '--color-text-disabled',
43
+ colorError: '--color-error',
44
+ colorErrorBg: '--color-error-bg',
45
+ colorSuccess: '--color-success',
46
+ colorSuccessBg: '--color-success-bg',
47
+ colorWarning: '--color-warning',
48
+ colorWarningBg: '--color-warning-bg',
49
+ colorInfo: '--color-info',
50
+ colorInfoBg: '--color-info-bg',
51
+ colorOverlay: '--color-overlay',
52
+ colorNavbar: '--color-navbar',
53
+ colorSidebar: '--color-sidebar',
54
+ colorSidebarText: '--color-sidebar-text',
55
+ colorSidebarActive: '--color-sidebar-active',
56
+ colorSidebarActiveBg: '--color-sidebar-active-bg',
57
+ radiusSm: '--radius-sm',
58
+ radiusMd: '--radius-md',
59
+ radiusLg: '--radius-lg',
60
+ radiusXl: '--radius-xl',
61
+ fontFamilyBase: '--font-family-base',
62
+ fontFamilyMono: '--font-family-mono',
63
+ shadowSm: '--shadow-sm',
64
+ shadowMd: '--shadow-md',
65
+ shadowLg: '--shadow-lg',
66
+ shadowXl: '--shadow-xl',
67
+ transitionFast: '--transition-fast',
68
+ transitionSpeed: '--transition-speed',
69
+ transitionSlow: '--transition-slow',
70
+ navbarHeight: '--navbar-height',
71
+ sidebarWidth: '--sidebar-width',
72
+ sidebarCollapsedWidth: '--sidebar-collapsed-width',
73
+ };
74
+ const ThemeContext = createContext({
75
+ tokens: {},
76
+ setToken: () => undefined,
77
+ });
78
+ function ThemeProvider({ tokens = {}, children }) {
79
+ useEffect(() => {
80
+ const root = document.documentElement;
81
+ Object.entries(tokens).forEach(([key, value]) => {
82
+ if (value !== undefined) {
83
+ const cssVar = TOKEN_MAP[key];
84
+ if (cssVar)
85
+ root.style.setProperty(cssVar, value);
86
+ }
87
+ });
88
+ }, [tokens]);
89
+ const setToken = (key, value) => {
90
+ const cssVar = TOKEN_MAP[key];
91
+ if (cssVar)
92
+ document.documentElement.style.setProperty(cssVar, value);
93
+ };
94
+ return (jsx(ThemeContext.Provider, { value: { tokens, setToken }, children: children }));
95
+ }
96
+
97
+ function useTheme() {
98
+ const getToken = useCallback((token) => {
99
+ if (typeof window === 'undefined')
100
+ return '';
101
+ return getComputedStyle(document.documentElement).getPropertyValue(token).trim();
102
+ }, []);
103
+ const setToken = useCallback((token, value) => {
104
+ if (typeof window === 'undefined')
105
+ return;
106
+ document.documentElement.style.setProperty(token, value);
107
+ }, []);
108
+ return { getToken, setToken };
109
+ }
110
+
111
+ function useMediaQuery(query) {
112
+ const [matches, setMatches] = useState(() => {
113
+ if (typeof window === 'undefined')
114
+ return false;
115
+ return window.matchMedia(query).matches;
116
+ });
117
+ useEffect(() => {
118
+ if (typeof window === 'undefined')
119
+ return;
120
+ const mediaQuery = window.matchMedia(query);
121
+ const handler = (e) => setMatches(e.matches);
122
+ mediaQuery.addEventListener('change', handler);
123
+ setMatches(mediaQuery.matches);
124
+ return () => mediaQuery.removeEventListener('change', handler);
125
+ }, [query]);
126
+ return matches;
127
+ }
128
+
129
+ function useClickOutside(ref, handler, enabled = true) {
130
+ useEffect(() => {
131
+ if (!enabled)
132
+ return;
133
+ const listener = (event) => {
134
+ if (!ref.current || ref.current.contains(event.target))
135
+ return;
136
+ handler(event);
137
+ };
138
+ document.addEventListener('mousedown', listener);
139
+ document.addEventListener('touchstart', listener);
140
+ return () => {
141
+ document.removeEventListener('mousedown', listener);
142
+ document.removeEventListener('touchstart', listener);
143
+ };
144
+ }, [ref, handler, enabled]);
145
+ }
146
+
147
+ function useDebounce(value, delay = 300) {
148
+ const [debouncedValue, setDebouncedValue] = useState(value);
149
+ useEffect(() => {
150
+ const timer = setTimeout(() => setDebouncedValue(value), delay);
151
+ return () => clearTimeout(timer);
152
+ }, [value, delay]);
153
+ return debouncedValue;
154
+ }
155
+
156
+ let toastCounter = 0;
157
+ function useToast() {
158
+ const [toasts, setToasts] = useState([]);
159
+ const toast = useCallback((options) => {
160
+ const id = `toast-${++toastCounter}`;
161
+ const newToast = { id, duration: 5000, variant: 'info', ...options };
162
+ setToasts((prev) => [...prev, newToast]);
163
+ if (newToast.duration && newToast.duration > 0) {
164
+ setTimeout(() => {
165
+ setToasts((prev) => prev.filter((t) => t.id !== id));
166
+ }, newToast.duration);
167
+ }
168
+ return id;
169
+ }, []);
170
+ const dismiss = useCallback((id) => {
171
+ setToasts((prev) => prev.filter((t) => t.id !== id));
172
+ }, []);
173
+ const dismissAll = useCallback(() => {
174
+ setToasts([]);
175
+ }, []);
176
+ return { toasts, toast, dismiss, dismissAll };
177
+ }
178
+
179
+ function useLocalStorage(key, initialValue) {
180
+ const [storedValue, setStoredValue] = useState(() => {
181
+ try {
182
+ const item = window.localStorage.getItem(key);
183
+ return item ? JSON.parse(item) : initialValue;
184
+ }
185
+ catch {
186
+ return initialValue;
187
+ }
188
+ });
189
+ const setValue = useCallback((value) => {
190
+ try {
191
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
192
+ setStoredValue(valueToStore);
193
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
194
+ }
195
+ catch {
196
+ // ignore write errors
197
+ }
198
+ }, [key, storedValue]);
199
+ const removeValue = useCallback(() => {
200
+ try {
201
+ window.localStorage.removeItem(key);
202
+ setStoredValue(initialValue);
203
+ }
204
+ catch {
205
+ // ignore
206
+ }
207
+ }, [key, initialValue]);
208
+ useEffect(() => {
209
+ const handler = (e) => {
210
+ if (e.key === key && e.newValue !== null) {
211
+ try {
212
+ setStoredValue(JSON.parse(e.newValue));
213
+ }
214
+ catch {
215
+ // ignore
216
+ }
217
+ }
218
+ };
219
+ window.addEventListener('storage', handler);
220
+ return () => window.removeEventListener('storage', handler);
221
+ }, [key]);
222
+ return [storedValue, setValue, removeValue];
223
+ }
224
+
225
+ function useClipboard(options = {}) {
226
+ const { timeout = 2000, onSuccess, onError } = options;
227
+ const [copied, setCopied] = useState(false);
228
+ const [timeoutId, setTimeoutId] = useState(null);
229
+ const reset = useCallback(() => {
230
+ setCopied(false);
231
+ if (timeoutId)
232
+ clearTimeout(timeoutId);
233
+ }, [timeoutId]);
234
+ const copy = useCallback(async (value) => {
235
+ try {
236
+ if (navigator.clipboard) {
237
+ await navigator.clipboard.writeText(value);
238
+ }
239
+ else {
240
+ const el = document.createElement('textarea');
241
+ el.value = value;
242
+ el.style.position = 'fixed';
243
+ el.style.opacity = '0';
244
+ document.body.appendChild(el);
245
+ el.select();
246
+ document.execCommand('copy');
247
+ document.body.removeChild(el);
248
+ }
249
+ setCopied(true);
250
+ onSuccess?.(value);
251
+ const id = setTimeout(() => setCopied(false), timeout);
252
+ setTimeoutId(id);
253
+ }
254
+ catch (err) {
255
+ const error = err instanceof Error ? err : new Error('Failed to copy');
256
+ onError?.(error);
257
+ }
258
+ }, [timeout, onSuccess, onError]);
259
+ return { copied, copy, reset };
260
+ }
261
+
262
+ function useWindowSize() {
263
+ const [size, setSize] = useState(() => {
264
+ if (typeof window === 'undefined') {
265
+ return { width: 0, height: 0, isMobile: false, isTablet: false, isDesktop: true };
266
+ }
267
+ return {
268
+ width: window.innerWidth,
269
+ height: window.innerHeight,
270
+ isMobile: window.innerWidth < 768,
271
+ isTablet: window.innerWidth >= 768 && window.innerWidth < 1024,
272
+ isDesktop: window.innerWidth >= 1024,
273
+ };
274
+ });
275
+ useEffect(() => {
276
+ const handler = () => {
277
+ setSize({
278
+ width: window.innerWidth,
279
+ height: window.innerHeight,
280
+ isMobile: window.innerWidth < 768,
281
+ isTablet: window.innerWidth >= 768 && window.innerWidth < 1024,
282
+ isDesktop: window.innerWidth >= 1024,
283
+ });
284
+ };
285
+ window.addEventListener('resize', handler);
286
+ return () => window.removeEventListener('resize', handler);
287
+ }, []);
288
+ return size;
289
+ }
290
+
291
+ function useIntersectionObserver(options = {}) {
292
+ const { freezeOnceVisible = false, ...observerOptions } = options;
293
+ const ref = useRef(null);
294
+ const [entry, setEntry] = useState(null);
295
+ const isVisible = entry?.isIntersecting ?? false;
296
+ const frozen = isVisible && freezeOnceVisible;
297
+ useEffect(() => {
298
+ const el = ref.current;
299
+ if (!el || frozen)
300
+ return;
301
+ const observer = new IntersectionObserver(([e]) => setEntry(e), observerOptions);
302
+ observer.observe(el);
303
+ return () => observer.disconnect();
304
+ }, [frozen, observerOptions.root, observerOptions.rootMargin, observerOptions.threshold]);
305
+ return [ref, isVisible, entry];
306
+ }
307
+
308
+ function useKeyboard(shortcuts) {
309
+ const shortcutList = Array.isArray(shortcuts) ? shortcuts : [shortcuts];
310
+ const handleKeyDown = useCallback((e) => {
311
+ for (const shortcut of shortcutList) {
312
+ if (shortcut.enabled === false)
313
+ continue;
314
+ const keyMatch = e.key.toLowerCase() === shortcut.key.toLowerCase();
315
+ const modifiers = shortcut.modifiers ?? [];
316
+ modifiers.includes('ctrl') ? e.ctrlKey : !e.ctrlKey || modifiers.length === 0;
317
+ modifiers.includes('meta') ? e.metaKey : !e.metaKey || modifiers.length === 0;
318
+ modifiers.includes('shift') ? e.shiftKey : !e.shiftKey;
319
+ modifiers.includes('alt') ? e.altKey : !e.altKey;
320
+ const modMatch = modifiers.every((mod) => {
321
+ if (mod === 'ctrl')
322
+ return e.ctrlKey;
323
+ if (mod === 'meta')
324
+ return e.metaKey;
325
+ if (mod === 'shift')
326
+ return e.shiftKey;
327
+ if (mod === 'alt')
328
+ return e.altKey;
329
+ return false;
330
+ });
331
+ const noExtraModifiers = (e.ctrlKey === modifiers.includes('ctrl')) &&
332
+ (e.metaKey === modifiers.includes('meta')) &&
333
+ (e.shiftKey === modifiers.includes('shift')) &&
334
+ (e.altKey === modifiers.includes('alt'));
335
+ if (keyMatch && modMatch && noExtraModifiers) {
336
+ if (shortcut.preventDefault !== false)
337
+ e.preventDefault();
338
+ shortcut.handler(e);
339
+ }
340
+ }
341
+ }, [shortcutList]);
342
+ useEffect(() => {
343
+ window.addEventListener('keydown', handleKeyDown);
344
+ return () => window.removeEventListener('keydown', handleKeyDown);
345
+ }, [handleKeyDown]);
346
+ }
347
+
348
+ function cn(...inputs) {
349
+ return twMerge(clsx(inputs));
350
+ }
351
+
352
+ function Navbar({ logo, links = [], actions, sticky = false, fixed = false, bordered = true, className, onMenuToggle, }) {
353
+ const [mobileOpen, setMobileOpen] = useState(false);
354
+ const handleMenuToggle = () => {
355
+ const next = !mobileOpen;
356
+ setMobileOpen(next);
357
+ onMenuToggle?.(next);
358
+ };
359
+ return (jsxs("header", { className: cn('w-full bg-navbar font-base', sticky && 'sticky top-0', fixed && 'fixed top-0 left-0 right-0', bordered && 'border-b border-border', 'z-navbar', className), style: { height: 'var(--navbar-height)' }, children: [jsxs("div", { className: "flex items-center justify-between h-full px-4 md:px-6", children: [logo && jsx("div", { className: "flex-shrink-0", children: logo }), links.length > 0 && (jsx("nav", { className: "hidden md:flex items-center gap-1 ml-6", children: links.map((link, i) => (jsxs("a", { href: link.href, onClick: link.onClick, className: cn('flex items-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors cursor-pointer', link.active
360
+ ? 'bg-primary text-primary-foreground'
361
+ : 'text-text-muted hover:text-text hover:bg-surface-hover'), children: [link.icon && jsx("span", { className: "flex-shrink-0", children: link.icon }), link.label] }, i))) })), jsx("div", { className: "flex-1" }), actions && (jsx("div", { className: "hidden md:flex items-center gap-2", children: actions })), jsx("button", { type: "button", className: "md:hidden p-2 rounded-md text-text-muted hover:text-text hover:bg-surface-hover transition-colors", onClick: handleMenuToggle, "aria-label": "Toggle menu", children: mobileOpen ? jsx(X, { size: 20 }) : jsx(Menu, { size: 20 }) })] }), mobileOpen && (jsxs("div", { className: "md:hidden border-t border-border bg-navbar px-4 pb-4", children: [jsx("nav", { className: "flex flex-col gap-1 mt-2", children: links.map((link, i) => (jsxs("a", { href: link.href, onClick: () => {
362
+ link.onClick?.();
363
+ setMobileOpen(false);
364
+ }, className: cn('flex items-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors cursor-pointer', link.active
365
+ ? 'bg-primary text-primary-foreground'
366
+ : 'text-text-muted hover:text-text hover:bg-surface-hover'), children: [link.icon && jsx("span", { className: "flex-shrink-0", children: link.icon }), link.label] }, i))) }), actions && jsx("div", { className: "flex items-center gap-2 mt-3", children: actions })] }))] }));
367
+ }
368
+
369
+ function SidebarItemComponent({ item, collapsed, depth = 0, }) {
370
+ const [expanded, setExpanded] = useState(false);
371
+ const hasChildren = item.children && item.children.length > 0;
372
+ const handleClick = () => {
373
+ if (hasChildren)
374
+ setExpanded((v) => !v);
375
+ item.onClick?.();
376
+ };
377
+ return (jsxs("li", { children: [jsxs("a", { href: item.href, onClick: handleClick, title: collapsed ? item.label : undefined, className: cn('flex items-center gap-3 px-3 py-2 rounded-md cursor-pointer transition-colors text-sm', 'text-sidebar-text hover:bg-sidebar-active-bg hover:text-white', item.active && 'bg-sidebar-active-bg text-white', depth > 0 && 'ml-4', collapsed && 'justify-center px-2'), children: [item.icon && (jsx("span", { className: "flex-shrink-0 w-5 h-5 flex items-center justify-center", children: item.icon })), !collapsed && (jsxs(Fragment, { children: [jsx("span", { className: "flex-1 truncate", children: item.label }), item.badge !== undefined && (jsx("span", { className: "ml-auto text-xs bg-primary text-primary-foreground rounded-full px-1.5 py-0.5 min-w-[1.25rem] text-center", children: item.badge })), hasChildren && (jsx("span", { className: "ml-auto", children: expanded ? jsx(ChevronDown, { size: 14 }) : jsx(ChevronRight, { size: 14 }) }))] }))] }), hasChildren && expanded && !collapsed && (jsx("ul", { className: "mt-1 space-y-1", children: item.children.map((child) => (jsx(SidebarItemComponent, { item: child, collapsed: collapsed, depth: depth + 1 }, child.id))) }))] }));
378
+ }
379
+ function Sidebar({ items = [], collapsed: controlledCollapsed, defaultCollapsed = false, onCollapsedChange, header, footer, overlay = false, open = true, onClose, className, }) {
380
+ const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed);
381
+ const collapsed = controlledCollapsed ?? internalCollapsed;
382
+ const toggleCollapsed = () => {
383
+ const next = !collapsed;
384
+ setInternalCollapsed(next);
385
+ onCollapsedChange?.(next);
386
+ };
387
+ return (jsxs(Fragment, { children: [overlay && open && (jsx("div", { className: "fixed inset-0 bg-overlay md:hidden", style: { zIndex: 'calc(var(--z-sidebar) - 1)' }, onClick: onClose })), jsxs("aside", { className: cn('flex flex-col h-full bg-sidebar transition-all duration-300', collapsed
388
+ ? 'w-[var(--sidebar-collapsed-width)]'
389
+ : 'w-[var(--sidebar-width)]', overlay && 'fixed top-0 left-0 bottom-0', overlay && !open && '-translate-x-full', 'z-sidebar', className), children: [header && (jsx("div", { className: cn('flex-shrink-0 p-4', collapsed && 'px-2'), children: header })), jsx("nav", { className: "flex-1 overflow-y-auto py-2 px-2", children: jsx("ul", { className: "space-y-1", children: items.map((item) => (jsx(SidebarItemComponent, { item: item, collapsed: collapsed }, item.id))) }) }), footer && (jsx("div", { className: cn('flex-shrink-0 p-4 border-t border-border/20', collapsed && 'px-2'), children: footer })), jsx("button", { type: "button", onClick: toggleCollapsed, className: "flex-shrink-0 flex items-center justify-center h-10 border-t border-border/20 text-sidebar-text hover:text-white hover:bg-sidebar-active-bg transition-colors", "aria-label": collapsed ? 'Expand sidebar' : 'Collapse sidebar', children: collapsed ? jsx(ChevronRight, { size: 16 }) : jsx(ChevronLeft, { size: 16 }) })] })] }));
390
+ }
391
+
392
+ function AppShell({ navbar, sidebar, children, defaultSidebarCollapsed = false, className, contentClassName, }) {
393
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(defaultSidebarCollapsed);
394
+ const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
395
+ return (jsxs("div", { className: cn('flex flex-col h-screen bg-background', className), children: [navbar && (jsx(Navbar, { ...navbar, sticky: true, onMenuToggle: (open) => setMobileSidebarOpen(open) })), jsxs("div", { className: "flex flex-1 overflow-hidden", children: [sidebar && (jsxs(Fragment, { children: [jsx("div", { className: "hidden md:flex flex-shrink-0", children: jsx(Sidebar, { ...sidebar, collapsed: sidebarCollapsed, onCollapsedChange: setSidebarCollapsed }) }), jsx("div", { className: "md:hidden", children: jsx(Sidebar, { ...sidebar, overlay: true, open: mobileSidebarOpen, onClose: () => setMobileSidebarOpen(false) }) })] })), jsx("main", { className: cn('flex-1 overflow-auto bg-background', contentClassName), children: children })] })] }));
396
+ }
397
+
398
+ const sizeClasses$g = {
399
+ sm: 'max-w-screen-sm',
400
+ md: 'max-w-screen-md',
401
+ lg: 'max-w-screen-lg',
402
+ xl: 'max-w-screen-xl',
403
+ '2xl': 'max-w-screen-2xl',
404
+ full: 'max-w-full',
405
+ };
406
+ function Container({ size = 'xl', centered = true, padded = true, className, children, ...props }) {
407
+ return (jsx("div", { className: cn('w-full', sizeClasses$g[size], centered && 'mx-auto', padded && 'px-4 md:px-6 lg:px-8', className), ...props, children: children }));
408
+ }
409
+
410
+ const gapClasses$1 = {
411
+ none: 'gap-0',
412
+ xs: 'gap-1',
413
+ sm: 'gap-2',
414
+ md: 'gap-4',
415
+ lg: 'gap-6',
416
+ xl: 'gap-8',
417
+ };
418
+ const alignClasses = {
419
+ start: 'items-start',
420
+ center: 'items-center',
421
+ end: 'items-end',
422
+ stretch: 'items-stretch',
423
+ baseline: 'items-baseline',
424
+ };
425
+ const justifyClasses = {
426
+ start: 'justify-start',
427
+ center: 'justify-center',
428
+ end: 'justify-end',
429
+ between: 'justify-between',
430
+ around: 'justify-around',
431
+ evenly: 'justify-evenly',
432
+ };
433
+ function Stack({ direction = 'col', align = 'stretch', justify = 'start', gap = 'md', wrap = false, inline = false, className, children, ...props }) {
434
+ return (jsx("div", { className: cn(inline ? 'inline-flex' : 'flex', direction === 'row' ? 'flex-row' : 'flex-col', alignClasses[align], justifyClasses[justify], gapClasses$1[gap], wrap && 'flex-wrap', className), ...props, children: children }));
435
+ }
436
+
437
+ const colClasses = {
438
+ 1: 'grid-cols-1',
439
+ 2: 'grid-cols-2',
440
+ 3: 'grid-cols-3',
441
+ 4: 'grid-cols-4',
442
+ 5: 'grid-cols-5',
443
+ 6: 'grid-cols-6',
444
+ 7: 'grid-cols-7',
445
+ 8: 'grid-cols-8',
446
+ 9: 'grid-cols-9',
447
+ 10: 'grid-cols-10',
448
+ 11: 'grid-cols-11',
449
+ 12: 'grid-cols-12',
450
+ };
451
+ const smColClasses = {
452
+ 1: 'sm:grid-cols-1', 2: 'sm:grid-cols-2', 3: 'sm:grid-cols-3',
453
+ 4: 'sm:grid-cols-4', 5: 'sm:grid-cols-5', 6: 'sm:grid-cols-6',
454
+ 7: 'sm:grid-cols-7', 8: 'sm:grid-cols-8', 9: 'sm:grid-cols-9',
455
+ 10: 'sm:grid-cols-10', 11: 'sm:grid-cols-11', 12: 'sm:grid-cols-12',
456
+ };
457
+ const mdColClasses = {
458
+ 1: 'md:grid-cols-1', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3',
459
+ 4: 'md:grid-cols-4', 5: 'md:grid-cols-5', 6: 'md:grid-cols-6',
460
+ 7: 'md:grid-cols-7', 8: 'md:grid-cols-8', 9: 'md:grid-cols-9',
461
+ 10: 'md:grid-cols-10', 11: 'md:grid-cols-11', 12: 'md:grid-cols-12',
462
+ };
463
+ const lgColClasses = {
464
+ 1: 'lg:grid-cols-1', 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3',
465
+ 4: 'lg:grid-cols-4', 5: 'lg:grid-cols-5', 6: 'lg:grid-cols-6',
466
+ 7: 'lg:grid-cols-7', 8: 'lg:grid-cols-8', 9: 'lg:grid-cols-9',
467
+ 10: 'lg:grid-cols-10', 11: 'lg:grid-cols-11', 12: 'lg:grid-cols-12',
468
+ };
469
+ const gapClasses = {
470
+ none: 'gap-0', xs: 'gap-1', sm: 'gap-2', md: 'gap-4', lg: 'gap-6', xl: 'gap-8',
471
+ };
472
+ function Grid({ cols = 1, smCols, mdCols, lgCols, gap = 'md', autoFill = false, minColWidth = '200px', className, style, children, ...props }) {
473
+ return (jsx("div", { className: cn('grid', !autoFill && colClasses[cols], !autoFill && smCols && smColClasses[smCols], !autoFill && mdCols && mdColClasses[mdCols], !autoFill && lgCols && lgColClasses[lgCols], gapClasses[gap], className), style: autoFill ? { gridTemplateColumns: `repeat(auto-fill, minmax(${minColWidth}, 1fr))`, ...style } : style, ...props, children: children }));
474
+ }
475
+
476
+ function Divider({ orientation = 'horizontal', label, labelPosition = 'center', className, style, id, }) {
477
+ if (orientation === 'vertical') {
478
+ return (jsx("div", { className: cn('inline-block self-stretch w-px bg-border mx-2', className), style: style, id: id, role: "separator", "aria-orientation": "vertical" }));
479
+ }
480
+ if (label) {
481
+ return (jsxs("div", { className: cn('flex items-center gap-3 w-full my-2', labelPosition === 'left' && 'flex-row', labelPosition === 'right' && 'flex-row-reverse', className), style: style, id: id, role: "separator", children: [labelPosition !== 'left' && jsx("div", { className: "flex-1 h-px bg-border" }), jsx("span", { className: "text-xs text-text-muted whitespace-nowrap px-1", children: label }), labelPosition !== 'right' && jsx("div", { className: "flex-1 h-px bg-border" })] }));
482
+ }
483
+ return (jsx("hr", { className: cn('border-0 border-t border-border my-2 w-full', className), style: style, id: id, role: "separator" }));
484
+ }
485
+
486
+ function Spacer({ size = 4, axis = 'vertical', className }) {
487
+ const sizeValue = typeof size === 'number' ? `${size * 0.25}rem` : size;
488
+ return (jsx("span", { className: cn('block', className), style: {
489
+ width: axis === 'horizontal' || axis === 'both' ? sizeValue : undefined,
490
+ height: axis === 'vertical' || axis === 'both' ? sizeValue : undefined,
491
+ minWidth: axis === 'horizontal' || axis === 'both' ? sizeValue : undefined,
492
+ minHeight: axis === 'vertical' || axis === 'both' ? sizeValue : undefined,
493
+ }, "aria-hidden": "true" }));
494
+ }
495
+
496
+ function Breadcrumb({ items, separator = jsx(ChevronRight, { size: 14 }), maxItems, className, }) {
497
+ let displayItems = items;
498
+ let truncated = false;
499
+ if (maxItems && items.length > maxItems) {
500
+ truncated = true;
501
+ displayItems = [
502
+ items[0],
503
+ { label: '...', href: undefined },
504
+ ...items.slice(-(maxItems - 2)),
505
+ ];
506
+ }
507
+ return (jsx("nav", { "aria-label": "Breadcrumb", className: cn('flex items-center', className), children: jsx("ol", { className: "flex items-center gap-1 flex-wrap", children: displayItems.map((item, i) => {
508
+ const isLast = i === displayItems.length - 1;
509
+ const isEllipsis = truncated && item.label === '...';
510
+ return (jsxs("li", { className: "flex items-center gap-1", children: [i > 0 && (jsx("span", { className: "text-text-muted flex items-center", "aria-hidden": "true", children: separator })), isLast || isEllipsis ? (jsx("span", { className: cn('text-sm', isLast ? 'text-text font-medium' : 'text-text-muted'), "aria-current": isLast ? 'page' : undefined, children: item.label })) : (jsx("a", { href: item.href, onClick: item.onClick, className: "text-sm text-text-muted hover:text-text transition-colors cursor-pointer", children: item.label }))] }, i));
511
+ }) }) }));
512
+ }
513
+
514
+ function Tabs({ items, defaultValue, value, onValueChange, variant = 'underline', children, className, listClassName, }) {
515
+ return (jsxs(RadixTabs.Root, { defaultValue: defaultValue ?? items[0]?.value, value: value, onValueChange: onValueChange, className: cn('w-full', className), children: [jsx(RadixTabs.List, { className: cn('flex', variant === 'underline' && 'border-b border-border gap-0', variant === 'pill' && 'bg-surface rounded-lg p-1 gap-1 w-fit', variant === 'card' && 'border-b border-border gap-0', listClassName), children: items.map((item) => (jsxs(RadixTabs.Trigger, { value: item.value, disabled: item.disabled, className: cn('flex items-center gap-2 text-sm font-medium transition-colors outline-none cursor-pointer', 'disabled:opacity-50 disabled:cursor-not-allowed', variant === 'underline' && [
516
+ 'px-4 py-2.5 border-b-2 border-transparent -mb-px',
517
+ 'text-text-muted hover:text-text hover:border-border',
518
+ 'data-[state=active]:text-primary data-[state=active]:border-primary',
519
+ ], variant === 'pill' && [
520
+ 'px-3 py-1.5 rounded-md',
521
+ 'text-text-muted hover:text-text hover:bg-surface-hover',
522
+ 'data-[state=active]:bg-primary data-[state=active]:text-primary-foreground',
523
+ ], variant === 'card' && [
524
+ 'px-4 py-2.5 border border-transparent border-b-0 rounded-t-md -mb-px',
525
+ 'text-text-muted hover:text-text hover:bg-surface-hover',
526
+ 'data-[state=active]:bg-surface data-[state=active]:text-text data-[state=active]:border-border',
527
+ ]), children: [item.icon && jsx("span", { className: "w-4 h-4 flex items-center", children: item.icon }), item.label, item.badge !== undefined && (jsx("span", { className: "ml-1 text-xs bg-primary/10 text-primary rounded-full px-1.5 py-0.5 min-w-[1.25rem] text-center", children: item.badge }))] }, item.value))) }), children] }));
528
+ }
529
+ function TabsContent({ value, children, className, }) {
530
+ return (jsx(RadixTabs.Content, { value: value, className: cn('outline-none mt-4', className), children: children }));
531
+ }
532
+
533
+ function range(start, end) {
534
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
535
+ }
536
+ function Pagination({ page, pageSize = 10, total, siblingCount = 1, showFirstLast = true, onPageChange, className, }) {
537
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
538
+ const pages = React.useMemo(() => {
539
+ const totalPageNumbers = siblingCount * 2 + 5;
540
+ if (totalPages <= totalPageNumbers)
541
+ return range(1, totalPages);
542
+ const leftSibling = Math.max(page - siblingCount, 1);
543
+ const rightSibling = Math.min(page + siblingCount, totalPages);
544
+ const showLeftDots = leftSibling > 2;
545
+ const showRightDots = rightSibling < totalPages - 1;
546
+ if (!showLeftDots && showRightDots) {
547
+ const leftRange = range(1, 3 + siblingCount * 2);
548
+ return [...leftRange, '...', totalPages];
549
+ }
550
+ if (showLeftDots && !showRightDots) {
551
+ const rightRange = range(totalPages - (2 + siblingCount * 2), totalPages);
552
+ return [1, '...', ...rightRange];
553
+ }
554
+ return [1, '...', ...range(leftSibling, rightSibling), '...', totalPages];
555
+ }, [page, siblingCount, totalPages]);
556
+ const btnBase = 'flex items-center justify-center w-8 h-8 rounded-md text-sm font-medium transition-colors';
557
+ const btnActive = 'bg-primary text-primary-foreground';
558
+ const btnInactive = 'text-text-muted hover:bg-surface-hover hover:text-text';
559
+ const btnDisabled = 'opacity-40 cursor-not-allowed pointer-events-none';
560
+ return (jsxs("nav", { "aria-label": "Pagination", className: cn('flex items-center gap-1', className), children: [showFirstLast && (jsx("button", { type: "button", onClick: () => onPageChange(1), disabled: page === 1, className: cn(btnBase, page === 1 ? btnDisabled : btnInactive), "aria-label": "First page", children: jsx(ChevronsLeft, { size: 14 }) })), jsx("button", { type: "button", onClick: () => onPageChange(page - 1), disabled: page === 1, className: cn(btnBase, page === 1 ? btnDisabled : btnInactive), "aria-label": "Previous page", children: jsx(ChevronLeft, { size: 14 }) }), pages.map((p, i) => p === '...' ? (jsx("span", { className: "flex items-center justify-center w-8 h-8 text-text-muted text-sm", children: "\u2026" }, `dots-${i}`)) : (jsx("button", { type: "button", onClick: () => onPageChange(p), className: cn(btnBase, page === p ? btnActive : btnInactive), "aria-label": `Page ${p}`, "aria-current": page === p ? 'page' : undefined, children: p }, p))), jsx("button", { type: "button", onClick: () => onPageChange(page + 1), disabled: page === totalPages, className: cn(btnBase, page === totalPages ? btnDisabled : btnInactive), "aria-label": "Next page", children: jsx(ChevronRight, { size: 14 }) }), showFirstLast && (jsx("button", { type: "button", onClick: () => onPageChange(totalPages), disabled: page === totalPages, className: cn(btnBase, page === totalPages ? btnDisabled : btnInactive), "aria-label": "Last page", children: jsx(ChevronsRight, { size: 14 }) }))] }));
561
+ }
562
+
563
+ function StepIndicator({ steps, currentStep, orientation = 'horizontal', className, }) {
564
+ const getStatus = (index) => {
565
+ if (index < currentStep)
566
+ return 'completed';
567
+ if (index === currentStep)
568
+ return 'current';
569
+ return 'upcoming';
570
+ };
571
+ return (jsx("ol", { className: cn('flex', orientation === 'horizontal' ? 'flex-row items-center' : 'flex-col', className), children: steps.map((step, index) => {
572
+ const status = getStatus(index);
573
+ const isLast = index === steps.length - 1;
574
+ return (jsxs("li", { className: cn('flex', orientation === 'horizontal' ? 'flex-col items-center flex-1' : 'flex-row items-start'), children: [jsxs("div", { className: cn('flex items-center', orientation === 'horizontal' ? 'w-full' : 'flex-col'), children: [jsx("div", { className: cn('flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold border-2 transition-colors', status === 'completed' && 'bg-primary border-primary text-primary-foreground', status === 'current' && 'bg-surface border-primary text-primary', status === 'upcoming' && 'bg-surface border-border text-text-muted'), children: status === 'completed' ? (jsx(Check, { size: 14 })) : step.icon ? (step.icon) : (jsx("span", { children: index + 1 })) }), !isLast && (jsx("div", { className: cn('transition-colors', orientation === 'horizontal' ? 'flex-1 h-0.5 mx-2' : 'w-0.5 h-8 my-1 ml-3.5', index < currentStep ? 'bg-primary' : 'bg-border') }))] }), jsxs("div", { className: cn(orientation === 'horizontal' ? 'text-center mt-2' : 'ml-3 pb-6', isLast && orientation === 'vertical' && 'pb-0'), children: [jsx("p", { className: cn('text-sm font-medium', status === 'current' && 'text-primary', status === 'completed' && 'text-text', status === 'upcoming' && 'text-text-muted'), children: step.label }), step.description && (jsx("p", { className: "text-xs text-text-muted mt-0.5", children: step.description }))] })] }, step.id));
575
+ }) }));
576
+ }
577
+
578
+ const buttonVariants = cva([
579
+ 'inline-flex items-center justify-center gap-2 font-medium rounded-md',
580
+ 'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2',
581
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
582
+ 'select-none cursor-pointer',
583
+ ], {
584
+ variants: {
585
+ variant: {
586
+ primary: 'bg-primary text-primary-foreground hover:bg-primary-hover',
587
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary-hover',
588
+ outline: 'border border-border bg-transparent text-text hover:bg-surface-hover',
589
+ ghost: 'bg-transparent text-text hover:bg-surface-hover',
590
+ danger: 'bg-error text-white hover:opacity-90',
591
+ success: 'bg-success text-white hover:opacity-90',
592
+ link: 'bg-transparent text-primary underline-offset-4 hover:underline p-0 h-auto',
593
+ },
594
+ size: {
595
+ xs: 'h-7 px-2.5 text-xs',
596
+ sm: 'h-8 px-3 text-sm',
597
+ md: 'h-9 px-4 text-sm',
598
+ lg: 'h-10 px-5 text-base',
599
+ xl: 'h-12 px-6 text-base',
600
+ },
601
+ fullWidth: {
602
+ true: 'w-full',
603
+ false: '',
604
+ },
605
+ },
606
+ defaultVariants: {
607
+ variant: 'primary',
608
+ size: 'md',
609
+ fullWidth: false,
610
+ },
611
+ });
612
+ const Button = React.forwardRef(({ variant, size, fullWidth, loading = false, leftIcon, rightIcon, disabled, className, children, ...props }, ref) => {
613
+ return (jsxs("button", { ref: ref, disabled: disabled || loading, className: cn(buttonVariants({ variant, size, fullWidth }), className), ...props, children: [loading ? (jsx(Loader2, { size: 16, className: "animate-spin" })) : (leftIcon && jsx("span", { className: "flex-shrink-0", children: leftIcon })), children, !loading && rightIcon && jsx("span", { className: "flex-shrink-0", children: rightIcon })] }));
614
+ });
615
+ Button.displayName = 'Button';
616
+
617
+ const iconButtonVariants = cva([
618
+ 'inline-flex items-center justify-center rounded-md',
619
+ 'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2',
620
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
621
+ 'select-none cursor-pointer flex-shrink-0',
622
+ ], {
623
+ variants: {
624
+ variant: {
625
+ primary: 'bg-primary text-primary-foreground hover:bg-primary-hover',
626
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary-hover',
627
+ outline: 'border border-border bg-transparent text-text hover:bg-surface-hover',
628
+ ghost: 'bg-transparent text-text-muted hover:bg-surface-hover hover:text-text',
629
+ danger: 'bg-error text-white hover:opacity-90',
630
+ },
631
+ size: {
632
+ xs: 'w-6 h-6',
633
+ sm: 'w-7 h-7',
634
+ md: 'w-8 h-8',
635
+ lg: 'w-10 h-10',
636
+ xl: 'w-12 h-12',
637
+ },
638
+ rounded: {
639
+ true: 'rounded-full',
640
+ false: '',
641
+ },
642
+ },
643
+ defaultVariants: {
644
+ variant: 'ghost',
645
+ size: 'md',
646
+ rounded: false,
647
+ },
648
+ });
649
+ const IconButton = React.forwardRef(({ variant, size, rounded, loading = false, icon, disabled, className, ...props }, ref) => {
650
+ return (jsx("button", { ref: ref, disabled: disabled || loading, className: cn(iconButtonVariants({ variant, size, rounded }), className), ...props, children: loading ? jsx(Loader2, { size: 14, className: "animate-spin" }) : icon }));
651
+ });
652
+ IconButton.displayName = 'IconButton';
653
+
654
+ const sizeClasses$f = {
655
+ sm: { input: 'h-8 text-sm px-2.5', icon: 'w-8', adornment: 'text-xs' },
656
+ md: { input: 'h-9 text-sm px-3', icon: 'w-9', adornment: 'text-sm' },
657
+ lg: { input: 'h-11 text-base px-4', icon: 'w-11', adornment: 'text-base' },
658
+ };
659
+ const TextField = React.forwardRef(({ label, helperText, error, size = 'md', prefixIcon, prefixImage, prefixText, suffixIcon, suffixImage, suffixText, clearable = false, onClear, fullWidth = false, showMaxLength = false, containerClassName, inputClassName, type = 'text', value, onChange, disabled, required, id: externalId, ...props }, ref) => {
660
+ const generatedId = useId();
661
+ const id = externalId ?? generatedId;
662
+ const [showPassword, setShowPassword] = useState(false);
663
+ const isPassword = type === 'password';
664
+ const inputType = isPassword ? (showPassword ? 'text' : 'password') : type;
665
+ const hasPrefix = !!(prefixIcon || prefixImage || prefixText);
666
+ const hasSuffix = !!(suffixIcon || suffixImage || suffixText || clearable || isPassword);
667
+ const sz = sizeClasses$f[size];
668
+ const hasValue = value !== undefined ? String(value).length > 0 : false;
669
+ const charCount = value !== undefined ? String(value).length : 0;
670
+ const maxLen = props.maxLength;
671
+ return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { className: cn('relative flex items-center', fullWidth && 'w-full'), children: [hasPrefix && (jsx("div", { className: cn('absolute left-0 flex items-center justify-center h-full border-r border-border bg-surface-hover rounded-l-md', sz.icon, 'text-text-muted', sz.adornment), children: prefixImage ? (jsx("img", { src: prefixImage, alt: "", className: "w-4 h-4 object-contain" })) : prefixIcon ? (jsx("span", { className: "flex items-center justify-center w-4 h-4", children: prefixIcon })) : (jsx("span", { className: "px-2 whitespace-nowrap", children: prefixText })) })), jsx("input", { ref: ref, id: id, type: inputType, value: value, onChange: onChange, disabled: disabled, required: required, className: cn('w-full rounded-md border bg-surface text-text placeholder:text-text-muted', 'transition-colors outline-none', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', 'disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-surface-hover', error ? 'border-error focus:border-error focus:ring-error/20' : 'border-border', sz.input, hasPrefix && 'pl-[calc(var(--prefix-width,2.25rem)+0.5rem)]', hasSuffix && 'pr-[calc(var(--suffix-width,2.25rem)+0.5rem)]', inputClassName), style: {
672
+ paddingLeft: hasPrefix ? `calc(${sz.icon.replace('w-', '')}*0.25rem + 0.75rem)` : undefined,
673
+ paddingRight: hasSuffix ? `calc(${sz.icon.replace('w-', '')}*0.25rem + 0.75rem)` : undefined,
674
+ }, "aria-invalid": !!error, "aria-describedby": error ? `${id}-error` : helperText ? `${id}-helper` : undefined, ...props }), hasSuffix && (jsxs("div", { className: cn('absolute right-0 flex items-center justify-center h-full gap-1 pr-2', 'text-text-muted', sz.adornment), children: [suffixImage ? (jsx("img", { src: suffixImage, alt: "", className: "w-4 h-4 object-contain" })) : suffixIcon ? (jsx("span", { className: "flex items-center justify-center w-4 h-4", children: suffixIcon })) : suffixText ? (jsx("span", { className: "whitespace-nowrap border-l border-border pl-2", children: suffixText })) : null, clearable && hasValue && !disabled && (jsx("button", { type: "button", onClick: onClear, className: "flex items-center justify-center w-4 h-4 rounded-full hover:bg-surface-hover transition-colors", "aria-label": "Clear input", children: jsx(X, { size: 12 }) })), isPassword && (jsx("button", { type: "button", onClick: () => setShowPassword((v) => !v), className: "flex items-center justify-center w-4 h-4 hover:text-text transition-colors", "aria-label": showPassword ? 'Hide password' : 'Show password', children: showPassword ? jsx(EyeOff, { size: 14 }) : jsx(Eye, { size: 14 }) }))] }))] }), jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { children: [error && (jsx("p", { id: `${id}-error`, className: "text-xs text-error", role: "alert", children: error })), !error && helperText && (jsx("p", { id: `${id}-helper`, className: "text-xs text-text-muted", children: helperText }))] }), showMaxLength && maxLen !== undefined && (jsxs("p", { className: cn('text-xs text-text-muted ml-auto', charCount >= maxLen && 'text-error'), children: [charCount, "/", maxLen] }))] })] }));
675
+ });
676
+ TextField.displayName = 'TextField';
677
+
678
+ const TextArea = React.forwardRef(({ label, helperText, error, prefixIcon, prefixImage, suffixIcon, suffixImage, autoResize = false, showCharCount = false, showMaxLength = false, maxLength, fullWidth = false, containerClassName, textareaClassName, value, onChange, disabled, required, rows = 3, id: externalId, ...props }, ref) => {
679
+ const generatedId = useId();
680
+ const id = externalId ?? generatedId;
681
+ const internalRef = useRef(null);
682
+ const resolvedRef = ref ?? internalRef;
683
+ const charCount = value !== undefined ? String(value).length : 0;
684
+ const hasPrefix = !!(prefixIcon || prefixImage);
685
+ const hasSuffix = !!(suffixIcon || suffixImage);
686
+ useEffect(() => {
687
+ if (!autoResize || !resolvedRef.current)
688
+ return;
689
+ const el = resolvedRef.current;
690
+ el.style.height = 'auto';
691
+ el.style.height = `${el.scrollHeight}px`;
692
+ }, [value, autoResize, resolvedRef]);
693
+ return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { className: cn('relative', fullWidth && 'w-full'), children: [hasPrefix && (jsx("div", { className: "absolute top-2.5 left-0 flex items-center justify-center w-9 text-text-muted", children: prefixImage ? (jsx("img", { src: prefixImage, alt: "", className: "w-4 h-4 object-contain" })) : (jsx("span", { className: "flex items-center justify-center w-4 h-4", children: prefixIcon })) })), jsx("textarea", { ref: resolvedRef, id: id, value: value, onChange: onChange, disabled: disabled, required: required, rows: rows, maxLength: maxLength, className: cn('w-full rounded-md border bg-surface text-text placeholder:text-text-muted text-sm', 'transition-colors outline-none resize-y py-2 px-3', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', 'disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-surface-hover', error ? 'border-error focus:border-error focus:ring-error/20' : 'border-border', hasPrefix && 'pl-9', hasSuffix && 'pr-9', autoResize && 'resize-none overflow-hidden', textareaClassName), "aria-invalid": !!error, "aria-describedby": error ? `${id}-error` : helperText ? `${id}-helper` : undefined, ...props }), hasSuffix && (jsx("div", { className: "absolute top-2.5 right-2 flex items-center justify-center text-text-muted", children: suffixImage ? (jsx("img", { src: suffixImage, alt: "", className: "w-4 h-4 object-contain" })) : (jsx("span", { className: "flex items-center justify-center w-4 h-4", children: suffixIcon })) }))] }), jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { children: [error && (jsx("p", { id: `${id}-error`, className: "text-xs text-error", role: "alert", children: error })), !error && helperText && (jsx("p", { id: `${id}-helper`, className: "text-xs text-text-muted", children: helperText }))] }), (showCharCount || showMaxLength) && (jsxs("p", { className: cn('text-xs text-text-muted ml-auto', maxLength && charCount >= maxLength && 'text-error'), children: [charCount, maxLength ? `/${maxLength}` : ''] }))] })] }));
694
+ });
695
+ TextArea.displayName = 'TextArea';
696
+
697
+ function Select({ options = [], groups = [], value, defaultValue, onValueChange, placeholder = 'Select an option', label, helperText, error, disabled, required, fullWidth = false, className, }) {
698
+ const id = useId();
699
+ const renderOption = (opt) => (jsxs(RadixSelect.Item, { value: opt.value, disabled: opt.disabled, className: cn('flex items-center gap-2 px-3 py-2 text-sm rounded-md cursor-pointer outline-none', 'text-text hover:bg-surface-hover focus:bg-surface-hover', 'data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed', 'data-[highlighted]:bg-primary/10 data-[highlighted]:text-primary'), children: [jsx(RadixSelect.ItemText, { children: opt.label }), jsx(RadixSelect.ItemIndicator, { className: "ml-auto", children: jsx(Check, { size: 14 }) })] }, opt.value));
700
+ return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', className), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs(RadixSelect.Root, { value: value, defaultValue: defaultValue, onValueChange: onValueChange, disabled: disabled, children: [jsxs(RadixSelect.Trigger, { id: id, className: cn('flex items-center justify-between gap-2 h-9 px-3 w-full rounded-md border bg-surface text-sm', 'transition-colors outline-none cursor-pointer', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', 'disabled:opacity-50 disabled:cursor-not-allowed', 'data-[placeholder]:text-text-muted', error ? 'border-error' : 'border-border', fullWidth && 'w-full'), "aria-invalid": !!error, children: [jsx(RadixSelect.Value, { placeholder: placeholder }), jsx(RadixSelect.Icon, { children: jsx(ChevronDown, { size: 14, className: "text-text-muted" }) })] }), jsx(RadixSelect.Portal, { children: jsxs(RadixSelect.Content, { className: cn('z-dropdown bg-surface border border-border rounded-md shadow-lg overflow-hidden', 'animate-in fade-in-0 zoom-in-95'), position: "popper", sideOffset: 4, children: [jsx(RadixSelect.ScrollUpButton, { className: "flex items-center justify-center py-1 text-text-muted", children: jsx(ChevronUp, { size: 14 }) }), jsx(RadixSelect.Viewport, { className: "p-1 max-h-60", children: groups.length > 0
701
+ ? groups.map((group) => (jsxs(RadixSelect.Group, { children: [jsx(RadixSelect.Label, { className: "px-3 py-1.5 text-xs font-semibold text-text-muted uppercase tracking-wide", children: group.label }), group.options.map(renderOption)] }, group.label)))
702
+ : options.map(renderOption) }), jsx(RadixSelect.ScrollDownButton, { className: "flex items-center justify-center py-1 text-text-muted", children: jsx(ChevronDown, { size: 14 }) })] }) })] }), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
703
+ }
704
+
705
+ const sizeMap$5 = {
706
+ sm: { box: 'w-3.5 h-3.5', icon: 10, label: 'text-sm' },
707
+ md: { box: 'w-4 h-4', icon: 12, label: 'text-sm' },
708
+ lg: { box: 'w-5 h-5', icon: 14, label: 'text-base' },
709
+ };
710
+ function Checkbox({ label, description, checked, defaultChecked, onCheckedChange, disabled, required, error, size = 'md', className, id: externalId, }) {
711
+ const generatedId = useId();
712
+ const id = externalId ?? generatedId;
713
+ const sz = sizeMap$5[size];
714
+ return (jsxs("div", { className: cn('flex flex-col gap-1', className), children: [jsxs("div", { className: "flex items-start gap-2", children: [jsx(RadixCheckbox.Root, { id: id, checked: checked, defaultChecked: defaultChecked, onCheckedChange: onCheckedChange, disabled: disabled, required: required, className: cn('flex-shrink-0 flex items-center justify-center rounded border-2 transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2', 'disabled:opacity-50 disabled:cursor-not-allowed', sz.box, checked === true || checked === 'indeterminate'
715
+ ? 'bg-primary border-primary'
716
+ : 'bg-surface border-border hover:border-primary'), children: jsx(RadixCheckbox.Indicator, { children: checked === 'indeterminate' ? (jsx(Minus, { size: sz.icon, className: "text-primary-foreground" })) : (jsx(Check, { size: sz.icon, className: "text-primary-foreground" })) }) }), (label || description) && (jsxs("div", { className: "flex flex-col", children: [label && (jsxs("label", { htmlFor: id, className: cn('font-medium text-text cursor-pointer leading-tight', sz.label, disabled && 'opacity-50 cursor-not-allowed'), children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), description && (jsx("p", { className: "text-xs text-text-muted mt-0.5", children: description }))] }))] }), error && jsx("p", { className: "text-xs text-error ml-6", role: "alert", children: error })] }));
717
+ }
718
+
719
+ const sizeMap$4 = {
720
+ sm: { radio: 'w-3.5 h-3.5', dot: 'w-1.5 h-1.5', label: 'text-sm' },
721
+ md: { radio: 'w-4 h-4', dot: 'w-2 h-2', label: 'text-sm' },
722
+ lg: { radio: 'w-5 h-5', dot: 'w-2.5 h-2.5', label: 'text-base' },
723
+ };
724
+ function RadioGroup({ options, value, defaultValue, onValueChange, orientation = 'vertical', label, error, disabled, required, size = 'md', className, }) {
725
+ const groupId = useId();
726
+ const sz = sizeMap$4[size];
727
+ return (jsxs("div", { className: cn('flex flex-col gap-1.5', className), children: [label && (jsxs("p", { className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsx(RadixRadio.Root, { value: value, defaultValue: defaultValue, onValueChange: onValueChange, disabled: disabled, orientation: orientation, className: cn('flex gap-3', orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap'), "aria-label": label, children: options.map((opt) => {
728
+ const id = `${groupId}-${opt.value}`;
729
+ return (jsxs("div", { className: "flex items-start gap-2", children: [jsx(RadixRadio.Item, { id: id, value: opt.value, disabled: opt.disabled, className: cn('flex-shrink-0 flex items-center justify-center rounded-full border-2 transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2', 'disabled:opacity-50 disabled:cursor-not-allowed', sz.radio, 'border-border bg-surface hover:border-primary', 'data-[state=checked]:border-primary data-[state=checked]:bg-surface'), children: jsx(RadixRadio.Indicator, { className: cn('rounded-full bg-primary', sz.dot) }) }), jsxs("div", { className: "flex flex-col", children: [jsx("label", { htmlFor: id, className: cn('font-medium text-text cursor-pointer leading-tight', sz.label, opt.disabled && 'opacity-50 cursor-not-allowed'), children: opt.label }), opt.description && (jsx("p", { className: "text-xs text-text-muted mt-0.5", children: opt.description }))] })] }, opt.value));
730
+ }) }), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error })] }));
731
+ }
732
+
733
+ const sizeMap$3 = {
734
+ sm: { root: 'w-8 h-4', thumb: 'w-3 h-3 data-[state=checked]:translate-x-4', label: 'text-sm' },
735
+ md: { root: 'w-10 h-5', thumb: 'w-4 h-4 data-[state=checked]:translate-x-5', label: 'text-sm' },
736
+ lg: { root: 'w-12 h-6', thumb: 'w-5 h-5 data-[state=checked]:translate-x-6', label: 'text-base' },
737
+ };
738
+ function Switch({ label, description, checked, defaultChecked, onCheckedChange, disabled, required, size = 'md', labelPosition = 'right', className, id: externalId, }) {
739
+ const generatedId = useId();
740
+ const id = externalId ?? generatedId;
741
+ const sz = sizeMap$3[size];
742
+ const switchEl = (jsx(RadixSwitch.Root, { id: id, checked: checked, defaultChecked: defaultChecked, onCheckedChange: onCheckedChange, disabled: disabled, required: required, className: cn('relative inline-flex flex-shrink-0 items-center rounded-full border-2 border-transparent', 'transition-colors cursor-pointer', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2', 'disabled:opacity-50 disabled:cursor-not-allowed', 'bg-border data-[state=checked]:bg-primary', sz.root), children: jsx(RadixSwitch.Thumb, { className: cn('block rounded-full bg-white shadow-sm transition-transform translate-x-0', sz.thumb) }) }));
743
+ if (!label && !description)
744
+ return switchEl;
745
+ return (jsxs("div", { className: cn('flex items-start gap-3', className), children: [labelPosition === 'left' && (jsxs("div", { className: "flex flex-col flex-1", children: [label && (jsxs("label", { htmlFor: id, className: cn('font-medium text-text cursor-pointer', sz.label, disabled && 'opacity-50 cursor-not-allowed'), children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), description && jsx("p", { className: "text-xs text-text-muted", children: description })] })), switchEl, labelPosition === 'right' && (jsxs("div", { className: "flex flex-col", children: [label && (jsxs("label", { htmlFor: id, className: cn('font-medium text-text cursor-pointer', sz.label, disabled && 'opacity-50 cursor-not-allowed'), children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), description && jsx("p", { className: "text-xs text-text-muted", children: description })] }))] }));
746
+ }
747
+
748
+ function flattenOptions(options = [], groups = []) {
749
+ return [...options, ...groups.flatMap((g) => g.options)];
750
+ }
751
+ function SearchSelect(props) {
752
+ const { options = [], groups = [], placeholder = 'Select…', searchPlaceholder = 'Search…', label, helperText, error, disabled = false, required = false, clearable = true, fullWidth = false, className, maxSelected, } = props;
753
+ const id = useId();
754
+ const [open, setOpen] = useState(false);
755
+ const [query, setQuery] = useState('');
756
+ const containerRef = useRef(null);
757
+ const triggerRef = useRef(null);
758
+ useRef(null);
759
+ const searchRef = useRef(null);
760
+ const [dropdownStyle, setDropdownStyle] = useState({});
761
+ const [visibleCount, setVisibleCount] = useState(Infinity);
762
+ const measureRef = useRef(null);
763
+ const rafRef = useRef(0);
764
+ const allOptions = flattenOptions(options, groups);
765
+ const selectedValues = props.multiple
766
+ ? (props.value ?? [])
767
+ : props.value
768
+ ? [props.value]
769
+ : [];
770
+ const filtered = allOptions.filter((o) => o.label.toLowerCase().includes(query.toLowerCase()));
771
+ const filteredGroups = groups.length > 0
772
+ ? groups
773
+ .map((g) => ({
774
+ ...g,
775
+ options: g.options.filter((o) => o.label.toLowerCase().includes(query.toLowerCase())),
776
+ }))
777
+ .filter((g) => g.options.length > 0)
778
+ : [];
779
+ const isSelected = (val) => selectedValues.includes(val);
780
+ const toggle = useCallback((val) => {
781
+ if (props.multiple) {
782
+ const current = props.value ?? [];
783
+ const next = current.includes(val)
784
+ ? current.filter((v) => v !== val)
785
+ : maxSelected && current.length >= maxSelected
786
+ ? current
787
+ : [...current, val];
788
+ props.onChange?.(next);
789
+ }
790
+ else {
791
+ props.onChange?.(val);
792
+ setOpen(false);
793
+ setQuery('');
794
+ }
795
+ },
796
+ // eslint-disable-next-line react-hooks/exhaustive-deps
797
+ [props.multiple, props.value, maxSelected]);
798
+ const removeChip = (val, e) => {
799
+ e.stopPropagation();
800
+ if (props.multiple) {
801
+ props.onChange?.((props.value ?? []).filter((v) => v !== val));
802
+ }
803
+ };
804
+ const clearAll = (e) => {
805
+ e.stopPropagation();
806
+ if (props.multiple) {
807
+ props.onChange?.([]);
808
+ }
809
+ else {
810
+ props.onChange?.('');
811
+ }
812
+ };
813
+ const updateDropdownPosition = useCallback(() => {
814
+ if (!triggerRef.current)
815
+ return;
816
+ const rect = triggerRef.current.getBoundingClientRect();
817
+ const spaceBelow = window.innerHeight - rect.bottom;
818
+ const spaceAbove = rect.top;
819
+ const openUpward = spaceBelow < 260 && spaceAbove > spaceBelow;
820
+ setDropdownStyle({
821
+ position: 'fixed',
822
+ top: openUpward ? undefined : rect.bottom + 4,
823
+ bottom: openUpward ? window.innerHeight - rect.top + 4 : undefined,
824
+ left: rect.left,
825
+ width: rect.width,
826
+ zIndex: 9999,
827
+ });
828
+ }, []);
829
+ useEffect(() => {
830
+ if (open) {
831
+ updateDropdownPosition();
832
+ setTimeout(() => searchRef.current?.focus(), 10);
833
+ }
834
+ else {
835
+ setQuery('');
836
+ }
837
+ }, [open, updateDropdownPosition]);
838
+ useEffect(() => {
839
+ if (!open)
840
+ return;
841
+ const onScroll = () => updateDropdownPosition();
842
+ const onResize = () => updateDropdownPosition();
843
+ window.addEventListener('scroll', onScroll, true);
844
+ window.addEventListener('resize', onResize);
845
+ return () => {
846
+ window.removeEventListener('scroll', onScroll, true);
847
+ window.removeEventListener('resize', onResize);
848
+ };
849
+ }, [open, updateDropdownPosition]);
850
+ useEffect(() => {
851
+ const handler = (e) => {
852
+ if (containerRef.current && !containerRef.current.contains(e.target) &&
853
+ !e.target.closest('[data-searchselect-dropdown]')) {
854
+ setOpen(false);
855
+ }
856
+ };
857
+ document.addEventListener('mousedown', handler);
858
+ return () => document.removeEventListener('mousedown', handler);
859
+ }, []);
860
+ const selectedLabels = selectedValues
861
+ .map((v) => allOptions.find((o) => o.value === v))
862
+ .filter(Boolean);
863
+ const hasValue = selectedValues.length > 0;
864
+ // Measure from the off-screen clone (measureRef) — never mutates the visible DOM
865
+ const recalcVisibleCount = useCallback(() => {
866
+ if (!measureRef.current || !triggerRef.current || !props.multiple)
867
+ return;
868
+ const chips = Array.from(measureRef.current.querySelectorAll('[data-chip-measure]'));
869
+ if (chips.length === 0) {
870
+ setVisibleCount(Infinity);
871
+ return;
872
+ }
873
+ // Available width = trigger width minus actions area (clear btn + chevron ~52px) minus padding (~24px)
874
+ const triggerW = triggerRef.current.offsetWidth;
875
+ const ACTIONS_W = 56;
876
+ const PADDING = 24;
877
+ const MORE_BADGE_W = 60;
878
+ const GAP = 4;
879
+ const available = triggerW - ACTIONS_W - PADDING;
880
+ let used = 0;
881
+ let count = 0;
882
+ for (let i = 0; i < chips.length; i++) {
883
+ const chipW = chips[i].offsetWidth + GAP;
884
+ const isLast = i === chips.length - 1;
885
+ if (used + chipW <= available - (isLast ? 0 : MORE_BADGE_W)) {
886
+ used += chipW;
887
+ count++;
888
+ }
889
+ else {
890
+ break;
891
+ }
892
+ }
893
+ setVisibleCount(Math.max(1, count));
894
+ }, [props.multiple]);
895
+ // Re-measure whenever selection changes or trigger resizes
896
+ useEffect(() => {
897
+ if (!props.multiple)
898
+ return;
899
+ cancelAnimationFrame(rafRef.current);
900
+ rafRef.current = requestAnimationFrame(recalcVisibleCount);
901
+ return () => cancelAnimationFrame(rafRef.current);
902
+ // eslint-disable-next-line react-hooks/exhaustive-deps
903
+ }, [selectedValues.join(','), recalcVisibleCount]);
904
+ useEffect(() => {
905
+ if (!props.multiple || !triggerRef.current)
906
+ return;
907
+ const ro = new ResizeObserver(() => {
908
+ cancelAnimationFrame(rafRef.current);
909
+ rafRef.current = requestAnimationFrame(recalcVisibleCount);
910
+ });
911
+ ro.observe(triggerRef.current);
912
+ return () => ro.disconnect();
913
+ }, [props.multiple, recalcVisibleCount]);
914
+ const renderOption = (opt) => (jsxs("button", { type: "button", disabled: opt.disabled || (!isSelected(opt.value) && !!maxSelected && selectedValues.length >= maxSelected), onClick: () => toggle(opt.value), className: cn('flex items-center gap-2 w-full px-3 py-2 text-sm rounded-md text-left outline-none transition-colors', 'hover:bg-surface-hover focus:bg-surface-hover', 'disabled:opacity-50 disabled:cursor-not-allowed', isSelected(opt.value) && 'bg-primary/10 text-primary font-medium'), children: [opt.icon && jsx("span", { className: "flex-shrink-0", children: opt.icon }), jsx("span", { className: "flex-1 truncate", children: opt.label }), isSelected(opt.value) && jsx(Check, { size: 14, className: "flex-shrink-0" })] }, opt.value));
915
+ return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', className), ref: containerRef, children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", "aria-hidden": "true", children: "*" })] })), jsxs("div", { ref: triggerRef, id: id, role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-invalid": !!error, tabIndex: disabled ? -1 : 0, onClick: () => !disabled && setOpen((o) => !o), onKeyDown: (e) => {
916
+ if (e.key === 'Enter' || e.key === ' ') {
917
+ e.preventDefault();
918
+ !disabled && setOpen((o) => !o);
919
+ }
920
+ if (e.key === 'Escape')
921
+ setOpen(false);
922
+ }, className: cn('flex items-center gap-2 min-h-9 px-3 py-1.5 w-full rounded-md border bg-surface text-sm', 'transition-colors outline-none cursor-pointer select-none', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', disabled && 'opacity-50 cursor-not-allowed', error ? 'border-error' : open ? 'border-border-focus ring-2 ring-border-focus/20' : 'border-border'), children: [jsx("div", { className: "flex-1 flex items-center gap-1 min-w-0 overflow-hidden", children: props.multiple && selectedLabels.length > 0 ? (jsxs(Fragment, { children: [selectedLabels.slice(0, visibleCount).map((opt) => (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary flex-shrink-0", children: [jsx("span", { className: "max-w-[120px] truncate", children: opt.label }), jsx("button", { type: "button", onClick: (e) => removeChip(opt.value, e), className: "hover:text-primary/70 focus:outline-none", "aria-label": `Remove ${opt.label}`, children: jsx(X, { size: 10 }) })] }, opt.value))), selectedLabels.length > visibleCount && (jsxs("span", { className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-surface-hover text-text-muted flex-shrink-0 whitespace-nowrap", children: ["+", selectedLabels.length - visibleCount, " more"] }))] })) : !props.multiple && selectedLabels.length > 0 ? (jsx("span", { className: "text-text truncate", children: selectedLabels[0].label })) : (jsx("span", { className: "text-text-muted truncate", children: placeholder })) }), props.multiple && (jsx("div", { ref: measureRef, "aria-hidden": "true", style: {
923
+ position: 'fixed',
924
+ top: -9999,
925
+ left: -9999,
926
+ visibility: 'hidden',
927
+ pointerEvents: 'none',
928
+ display: 'flex',
929
+ gap: 4,
930
+ flexWrap: 'nowrap',
931
+ }, children: selectedLabels.map((opt) => (jsxs("span", { "data-chip-measure": "", className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary flex-shrink-0", children: [jsx("span", { className: "max-w-[120px] truncate", children: opt.label }), jsx("span", { style: { width: 10, height: 10, display: 'inline-block' } })] }, opt.value))) })), jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [clearable && hasValue && !disabled && (jsx("button", { type: "button", onClick: clearAll, className: "text-text-muted hover:text-text focus:outline-none", "aria-label": "Clear selection", children: jsx(X, { size: 14 }) })), jsx(ChevronDown, { size: 14, className: cn('text-text-muted transition-transform', open && 'rotate-180') })] })] }), open && createPortal(jsxs("div", { "data-searchselect-dropdown": "", className: cn('bg-surface border border-border rounded-md shadow-xl overflow-hidden', 'animate-in fade-in-0 zoom-in-95'), style: dropdownStyle, children: [jsx("div", { className: "p-2 border-b border-border", children: jsxs("div", { className: "flex items-center gap-2 px-2 py-1.5 rounded-md border border-border bg-background", children: [jsx(Search, { size: 14, className: "text-text-muted flex-shrink-0" }), jsx("input", { ref: searchRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: searchPlaceholder, className: "flex-1 text-sm bg-transparent outline-none text-text placeholder:text-text-muted", onClick: (e) => e.stopPropagation() }), query && (jsx("button", { type: "button", onClick: () => setQuery(''), className: "text-text-muted hover:text-text", children: jsx(X, { size: 12 }) }))] }) }), jsx("div", { className: "p-1 max-h-60 overflow-y-auto", role: "listbox", children: filteredGroups.length > 0
932
+ ? filteredGroups.map((group) => (jsxs("div", { children: [jsx("p", { className: "px-3 py-1.5 text-xs font-semibold text-text-muted uppercase tracking-wide", children: group.label }), group.options.map(renderOption)] }, group.label)))
933
+ : groups.length === 0
934
+ ? filtered.length > 0
935
+ ? filtered.map(renderOption)
936
+ : (jsx("p", { className: "px-3 py-4 text-sm text-text-muted text-center", children: "No options found" }))
937
+ : (jsx("p", { className: "px-3 py-4 text-sm text-text-muted text-center", children: "No options found" })) }), props.multiple && maxSelected && (jsxs("div", { className: "px-3 py-2 border-t border-border text-xs text-text-muted", children: [selectedValues.length, " / ", maxSelected, " selected"] }))] }), document.body), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
938
+ }
939
+
940
+ const sizeClasses$e = {
941
+ sm: 'px-2.5 py-1 text-xs gap-1',
942
+ md: 'px-3 py-1.5 text-sm gap-1.5',
943
+ lg: 'px-4 py-2 text-sm gap-2',
944
+ };
945
+ function ChipSelect({ options, value = [], onChange, multiple = true, maxSelect, label, helperText, error, disabled = false, required = false, size = 'md', className, fullWidth = true, }) {
946
+ const id = useId();
947
+ const isSelected = (val) => value.includes(val);
948
+ const toggle = (val) => {
949
+ if (disabled)
950
+ return;
951
+ const opt = options.find((o) => o.value === val);
952
+ if (opt?.disabled)
953
+ return;
954
+ if (multiple) {
955
+ if (isSelected(val)) {
956
+ onChange?.(value.filter((v) => v !== val));
957
+ }
958
+ else {
959
+ if (maxSelect && value.length >= maxSelect)
960
+ return;
961
+ onChange?.([...value, val]);
962
+ }
963
+ }
964
+ else {
965
+ onChange?.(isSelected(val) ? [] : [val]);
966
+ }
967
+ };
968
+ return (jsxs("div", { className: cn('flex flex-col gap-1.5', fullWidth && 'w-full', className), children: [label && (jsxs("label", { id: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", "aria-hidden": "true", children: "*" })] })), jsx("div", { role: "group", "aria-labelledby": label ? id : undefined, className: cn('flex flex-wrap gap-2', disabled && 'opacity-60 pointer-events-none'), children: options.map((opt) => {
969
+ const selected = isSelected(opt.value);
970
+ const atMax = !selected && !!maxSelect && value.length >= maxSelect;
971
+ const isDisabled = opt.disabled || atMax;
972
+ return (jsxs("button", { type: "button", role: "checkbox", "aria-checked": selected, disabled: isDisabled, onClick: () => toggle(opt.value), className: cn('inline-flex items-center rounded-full border font-medium transition-all', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-1', sizeClasses$e[size], selected
973
+ ? 'bg-primary text-primary-foreground border-primary shadow-sm'
974
+ : 'bg-surface text-text border-border hover:border-primary/50 hover:bg-primary/5', isDisabled && 'opacity-50 cursor-not-allowed pointer-events-none'), children: [opt.icon && (jsx("span", { className: cn('flex-shrink-0', selected ? 'text-primary-foreground' : 'text-text-muted'), children: opt.icon })), jsx("span", { children: opt.label }), selected && jsx(Check, { size: 12, className: "flex-shrink-0" })] }, opt.value));
975
+ }) }), maxSelect && (jsxs("p", { className: "text-xs text-text-muted", children: [value.length, " of ", maxSelect, " selected"] })), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
976
+ }
977
+
978
+ const SEPARATOR_KEYS = {
979
+ space: [' '],
980
+ comma: [','],
981
+ enter: ['Enter'],
982
+ };
983
+ function TagInput({ value = [], onChange, separator = ['space', 'comma', 'enter'], placeholder = 'Type and press Enter…', label, helperText, error, disabled = false, required = false, maxTags, allowDuplicates = false, fullWidth = false, className, }) {
984
+ const id = useId();
985
+ const [inputValue, setInputValue] = useState('');
986
+ const inputRef = useRef(null);
987
+ const separatorKeys = separator.flatMap((s) => SEPARATOR_KEYS[s]);
988
+ const addTag = (raw) => {
989
+ const tag = raw.trim();
990
+ if (!tag)
991
+ return;
992
+ if (!allowDuplicates && value.includes(tag)) {
993
+ setInputValue('');
994
+ return;
995
+ }
996
+ if (maxTags && value.length >= maxTags)
997
+ return;
998
+ onChange?.([...value, tag]);
999
+ setInputValue('');
1000
+ };
1001
+ const removeTag = (index) => {
1002
+ onChange?.(value.filter((_, i) => i !== index));
1003
+ };
1004
+ const handleKeyDown = (e) => {
1005
+ if (separatorKeys.includes(e.key)) {
1006
+ e.preventDefault();
1007
+ addTag(inputValue);
1008
+ return;
1009
+ }
1010
+ if (e.key === 'Backspace' && inputValue === '' && value.length > 0) {
1011
+ removeTag(value.length - 1);
1012
+ }
1013
+ };
1014
+ const handleChange = (e) => {
1015
+ const raw = e.target.value;
1016
+ if (separator.includes('comma') && raw.includes(',')) {
1017
+ const parts = raw.split(',');
1018
+ const last = parts.pop() ?? '';
1019
+ parts.forEach((p) => addTag(p));
1020
+ setInputValue(last);
1021
+ }
1022
+ else if (separator.includes('space') && raw.endsWith(' ')) {
1023
+ addTag(raw);
1024
+ }
1025
+ else {
1026
+ setInputValue(raw);
1027
+ }
1028
+ };
1029
+ const atMax = !!maxTags && value.length >= maxTags;
1030
+ return (jsxs("div", { className: cn('flex flex-col gap-1.5', fullWidth && 'w-full', className), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", "aria-hidden": "true", children: "*" })] })), jsxs("div", { onClick: () => inputRef.current?.focus(), className: cn('flex flex-col gap-2 min-h-[80px] w-full rounded-md border bg-surface px-3 py-2', 'transition-colors cursor-text', 'focus-within:border-border-focus focus-within:ring-2 focus-within:ring-border-focus/20', disabled && 'opacity-50 cursor-not-allowed', error ? 'border-error' : 'border-border'), children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("input", { ref: inputRef, id: id, type: "text", value: inputValue, onChange: handleChange, onKeyDown: handleKeyDown, placeholder: atMax ? `Max ${maxTags} tags reached` : placeholder, disabled: disabled || atMax, "aria-invalid": !!error, className: cn('flex-1 text-sm bg-transparent outline-none text-text placeholder:text-text-muted', 'disabled:cursor-not-allowed') }), inputValue && (jsx("button", { type: "button", onClick: () => setInputValue(''), className: "text-text-muted hover:text-text focus:outline-none", tabIndex: -1, "aria-label": "Clear input", children: jsx(X, { size: 12 }) }))] }), value.length > 0 && (jsx("div", { className: "border-t border-border" })), value.length > 0 && (jsx("div", { className: "flex flex-wrap gap-1.5", children: value.map((tag, i) => (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20", children: [jsx("span", { children: tag }), !disabled && (jsx("button", { type: "button", onClick: () => removeTag(i), className: "hover:text-primary/70 focus:outline-none flex-shrink-0", "aria-label": `Remove ${tag}`, children: jsx(X, { size: 10 }) }))] }, `${tag}-${i}`))) }))] }), maxTags && (jsxs("p", { className: "text-xs text-text-muted", children: [value.length, " / ", maxTags, " tags"] })), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
1031
+ }
1032
+
1033
+ const trackSize = {
1034
+ sm: 'h-1',
1035
+ md: 'h-1.5',
1036
+ lg: 'h-2',
1037
+ };
1038
+ const thumbSize = {
1039
+ sm: 'w-3.5 h-3.5',
1040
+ md: 'w-4 h-4',
1041
+ lg: 'w-5 h-5',
1042
+ };
1043
+ function Slider({ value, defaultValue = [0], onValueChange, onValueCommit, min = 0, max = 100, step = 1, disabled = false, orientation = 'horizontal', size = 'md', showTooltip = false, showMarks = false, formatValue = (v) => String(v), label, helperText, className, }) {
1044
+ const [internalValue, setInternalValue] = React.useState(value ?? defaultValue);
1045
+ const [showTip, setShowTip] = React.useState(false);
1046
+ const currentValue = value ?? internalValue;
1047
+ const handleValueChange = (v) => {
1048
+ setInternalValue(v);
1049
+ onValueChange?.(v);
1050
+ };
1051
+ const marks = React.useMemo(() => {
1052
+ if (!showMarks)
1053
+ return [];
1054
+ const count = Math.floor((max - min) / step);
1055
+ return Array.from({ length: count + 1 }, (_, i) => min + i * step);
1056
+ }, [showMarks, min, max, step]);
1057
+ return (jsxs("div", { className: cn('flex flex-col gap-1.5', className), children: [label && (jsxs("div", { className: "flex items-center justify-between", children: [jsx("span", { className: "text-sm font-medium text-text", children: label }), jsx("span", { className: "text-sm text-text-muted", children: currentValue.map(formatValue).join(' – ') })] })), jsxs(RadixSlider.Root, { className: cn('relative flex items-center select-none touch-none', orientation === 'horizontal' ? 'w-full' : 'flex-col h-40 w-5', disabled && 'opacity-50 cursor-not-allowed'), value: currentValue, defaultValue: defaultValue, onValueChange: handleValueChange, onValueCommit: onValueCommit, min: min, max: max, step: step, disabled: disabled, orientation: orientation, children: [jsx(RadixSlider.Track, { className: cn('relative grow rounded-full bg-surface-hover overflow-hidden', orientation === 'horizontal' ? `w-full ${trackSize[size]}` : `h-full w-${size === 'sm' ? '1' : size === 'md' ? '1.5' : '2'}`), children: jsx(RadixSlider.Range, { className: "absolute bg-primary rounded-full h-full" }) }), currentValue.map((_, i) => (jsx(RadixSlider.Thumb, { className: cn('block rounded-full bg-white border-2 border-primary shadow-md', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40', 'transition-transform hover:scale-110', 'cursor-grab active:cursor-grabbing', thumbSize[size], disabled && 'cursor-not-allowed hover:scale-100'), onMouseEnter: () => showTooltip && setShowTip(true), onMouseLeave: () => showTooltip && setShowTip(false), "aria-label": `Slider thumb ${i + 1}`, children: showTooltip && showTip && (jsx("div", { className: "absolute -top-8 left-1/2 -translate-x-1/2 bg-text text-background text-xs px-2 py-0.5 rounded whitespace-nowrap pointer-events-none", children: formatValue(currentValue[i]) })) }, i)))] }), showMarks && (jsx("div", { className: "relative flex justify-between mt-1", children: marks.map((mark) => (jsx("span", { className: "text-xs text-text-muted", children: formatValue(mark) }, mark))) })), helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
1058
+ }
1059
+
1060
+ const sizeClasses$d = {
1061
+ sm: { input: 'h-8 text-sm px-2.5', btn: 'h-4 w-6' },
1062
+ md: { input: 'h-9 text-sm px-3', btn: 'h-[18px] w-7' },
1063
+ lg: { input: 'h-11 text-base px-4', btn: 'h-5 w-8' },
1064
+ };
1065
+ const NumberInput = React.forwardRef(({ value: controlledValue, defaultValue, onChange, min, max, step = 1, precision, prefix, suffix, label, helperText, error, size = 'md', fullWidth = false, hideControls = false, showMaxLength = false, formatValue, disabled, required, id: externalId, containerClassName, className, ...props }, ref) => {
1066
+ const generatedId = useId();
1067
+ const id = externalId ?? generatedId;
1068
+ const [internalValue, setInternalValue] = useState(defaultValue);
1069
+ const [inputStr, setInputStr] = useState(defaultValue !== undefined ? String(defaultValue) : '');
1070
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
1071
+ const sz = sizeClasses$d[size];
1072
+ const clamp = (v) => {
1073
+ let result = v;
1074
+ if (min !== undefined)
1075
+ result = Math.max(min, result);
1076
+ if (max !== undefined)
1077
+ result = Math.min(max, result);
1078
+ if (precision !== undefined)
1079
+ result = parseFloat(result.toFixed(precision));
1080
+ return result;
1081
+ };
1082
+ const commit = (v) => {
1083
+ setInternalValue(v);
1084
+ onChange?.(v);
1085
+ setInputStr(v !== undefined ? (formatValue ? formatValue(v) : String(v)) : '');
1086
+ };
1087
+ const increment = () => {
1088
+ const base = value ?? (min ?? 0);
1089
+ commit(clamp(base + step));
1090
+ };
1091
+ const decrement = () => {
1092
+ const base = value ?? (min ?? 0);
1093
+ commit(clamp(base - step));
1094
+ };
1095
+ const handleChange = (e) => {
1096
+ const raw = e.target.value;
1097
+ setInputStr(raw);
1098
+ const parsed = parseFloat(raw);
1099
+ if (raw === '' || raw === '-') {
1100
+ setInternalValue(undefined);
1101
+ onChange?.(undefined);
1102
+ }
1103
+ else if (!isNaN(parsed)) {
1104
+ setInternalValue(parsed);
1105
+ onChange?.(parsed);
1106
+ }
1107
+ };
1108
+ const handleBlur = () => {
1109
+ if (value !== undefined)
1110
+ commit(clamp(value));
1111
+ else
1112
+ setInputStr('');
1113
+ };
1114
+ const handleKeyDown = (e) => {
1115
+ if (!hideControls) {
1116
+ if (e.key === 'ArrowUp') {
1117
+ e.preventDefault();
1118
+ increment();
1119
+ return;
1120
+ }
1121
+ if (e.key === 'ArrowDown') {
1122
+ e.preventDefault();
1123
+ decrement();
1124
+ return;
1125
+ }
1126
+ }
1127
+ const allowed = [
1128
+ 'Backspace', 'Delete', 'Tab', 'Escape', 'Enter',
1129
+ 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown',
1130
+ 'Home', 'End',
1131
+ ];
1132
+ if (allowed.includes(e.key))
1133
+ return;
1134
+ if (e.ctrlKey || e.metaKey)
1135
+ return;
1136
+ const isDigit = /^[0-9]$/.test(e.key);
1137
+ const isMinus = e.key === '-' && (e.currentTarget.selectionStart === 0) && !e.currentTarget.value.includes('-');
1138
+ const isDot = (e.key === '.' || e.key === ',') && !e.currentTarget.value.includes('.');
1139
+ if (!isDigit && !isMinus && !isDot)
1140
+ e.preventDefault();
1141
+ };
1142
+ const displayValue = document.activeElement?.id === id
1143
+ ? inputStr
1144
+ : value !== undefined
1145
+ ? (formatValue ? formatValue(value) : String(value))
1146
+ : '';
1147
+ return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { className: cn('relative flex items-center', fullWidth && 'w-full'), children: [prefix && (jsx("span", { className: "absolute left-0 flex items-center justify-center h-full px-2.5 border-r border-border bg-surface-hover rounded-l-md text-sm text-text-muted select-none", children: prefix })), jsx("input", { ref: ref, id: id, type: "text", inputMode: "decimal", value: displayValue, onChange: handleChange, onBlur: handleBlur, onKeyDown: handleKeyDown, disabled: disabled, required: required, "aria-invalid": !!error, className: cn('w-full rounded-md border bg-surface text-text placeholder:text-text-muted', 'transition-colors outline-none', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', 'disabled:opacity-50 disabled:cursor-not-allowed', error ? 'border-error focus:border-error focus:ring-error/20' : 'border-border', sz.input, prefix && 'pl-10', (suffix || !hideControls) && 'pr-10', className), ...props }), suffix && hideControls && (jsx("span", { className: "absolute right-0 flex items-center justify-center h-full px-2.5 border-l border-border bg-surface-hover rounded-r-md text-sm text-text-muted select-none", children: suffix })), !hideControls && (jsxs("div", { className: "absolute right-0 flex flex-col h-full border-l border-border rounded-r-md overflow-hidden", children: [jsx("button", { type: "button", tabIndex: -1, onClick: increment, disabled: disabled || (max !== undefined && (value ?? -Infinity) >= max), className: cn('flex items-center justify-center flex-1 bg-surface-hover hover:bg-surface-active transition-colors disabled:opacity-40 disabled:cursor-not-allowed', sz.btn), "aria-label": "Increment", children: jsx(ChevronUp, { size: 10 }) }), jsx("button", { type: "button", tabIndex: -1, onClick: decrement, disabled: disabled || (min !== undefined && (value ?? Infinity) <= min), className: cn('flex items-center justify-center flex-1 bg-surface-hover hover:bg-surface-active transition-colors border-t border-border disabled:opacity-40 disabled:cursor-not-allowed', sz.btn), "aria-label": "Decrement", children: jsx(ChevronDown, { size: 10 }) })] }))] }), jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { children: [error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }), showMaxLength && props.maxLength !== undefined && (jsxs("p", { className: cn('text-xs text-text-muted ml-auto', displayValue.length >= (props.maxLength ?? Infinity) && 'text-error'), children: [displayValue.length, "/", props.maxLength] }))] })] }));
1148
+ });
1149
+ NumberInput.displayName = 'NumberInput';
1150
+
1151
+ const DEFAULT_COUNTRY_CODES = [
1152
+ { code: 'US', dialCode: '+1', flag: '🇺🇸', name: 'United States' },
1153
+ { code: 'GB', dialCode: '+44', flag: '🇬🇧', name: 'United Kingdom' },
1154
+ { code: 'IN', dialCode: '+91', flag: '🇮🇳', name: 'India' },
1155
+ { code: 'CA', dialCode: '+1', flag: '🇨🇦', name: 'Canada' },
1156
+ { code: 'AU', dialCode: '+61', flag: '🇦🇺', name: 'Australia' },
1157
+ { code: 'DE', dialCode: '+49', flag: '🇩🇪', name: 'Germany' },
1158
+ { code: 'FR', dialCode: '+33', flag: '🇫🇷', name: 'France' },
1159
+ { code: 'JP', dialCode: '+81', flag: '🇯🇵', name: 'Japan' },
1160
+ { code: 'CN', dialCode: '+86', flag: '🇨🇳', name: 'China' },
1161
+ { code: 'BR', dialCode: '+55', flag: '🇧🇷', name: 'Brazil' },
1162
+ { code: 'MX', dialCode: '+52', flag: '🇲🇽', name: 'Mexico' },
1163
+ { code: 'ZA', dialCode: '+27', flag: '🇿🇦', name: 'South Africa' },
1164
+ { code: 'NG', dialCode: '+234', flag: '🇳🇬', name: 'Nigeria' },
1165
+ { code: 'AE', dialCode: '+971', flag: '🇦🇪', name: 'UAE' },
1166
+ { code: 'SG', dialCode: '+65', flag: '🇸🇬', name: 'Singapore' },
1167
+ { code: 'KR', dialCode: '+82', flag: '🇰🇷', name: 'South Korea' },
1168
+ { code: 'IT', dialCode: '+39', flag: '🇮🇹', name: 'Italy' },
1169
+ { code: 'ES', dialCode: '+34', flag: '🇪🇸', name: 'Spain' },
1170
+ { code: 'RU', dialCode: '+7', flag: '🇷🇺', name: 'Russia' },
1171
+ { code: 'PK', dialCode: '+92', flag: '🇵🇰', name: 'Pakistan' },
1172
+ { code: 'BD', dialCode: '+880', flag: '🇧🇩', name: 'Bangladesh' },
1173
+ { code: 'ID', dialCode: '+62', flag: '🇮🇩', name: 'Indonesia' },
1174
+ { code: 'TR', dialCode: '+90', flag: '🇹🇷', name: 'Turkey' },
1175
+ { code: 'SA', dialCode: '+966', flag: '🇸🇦', name: 'Saudi Arabia' },
1176
+ { code: 'NL', dialCode: '+31', flag: '🇳🇱', name: 'Netherlands' },
1177
+ { code: 'CH', dialCode: '+41', flag: '🇨🇭', name: 'Switzerland' },
1178
+ { code: 'SE', dialCode: '+46', flag: '🇸🇪', name: 'Sweden' },
1179
+ { code: 'NO', dialCode: '+47', flag: '🇳🇴', name: 'Norway' },
1180
+ { code: 'PL', dialCode: '+48', flag: '🇵🇱', name: 'Poland' },
1181
+ { code: 'AR', dialCode: '+54', flag: '🇦🇷', name: 'Argentina' },
1182
+ ];
1183
+ const sizeClasses$c = {
1184
+ sm: 'h-8 text-sm',
1185
+ md: 'h-9 text-sm',
1186
+ lg: 'h-11 text-base',
1187
+ };
1188
+ const dialSelectSizes = {
1189
+ sm: 'text-sm px-1.5',
1190
+ md: 'text-sm px-2',
1191
+ lg: 'text-base px-2.5',
1192
+ };
1193
+ const PhoneInput = React.forwardRef(({ value: controlledValue, defaultCountry = 'US', onChange, label, helperText, error, placeholder = 'Phone number', disabled = false, required = false, size = 'md', fullWidth = false, maxLength, showMaxLength = false, countryCodes = DEFAULT_COUNTRY_CODES, className, containerClassName, id: externalId, }, ref) => {
1194
+ const generatedId = useId();
1195
+ const id = externalId ?? generatedId;
1196
+ const defaultCC = countryCodes.find((c) => c.code === defaultCountry) ?? countryCodes[0];
1197
+ const [selectedCountry, setSelectedCountry] = useState(controlledValue?.countryCode ?? defaultCC);
1198
+ const [phoneNumber, setPhoneNumber] = useState(controlledValue?.number ?? '');
1199
+ const activeCountry = controlledValue?.countryCode ?? selectedCountry;
1200
+ const activeNumber = controlledValue?.number ?? phoneNumber;
1201
+ const buildValue = (country, number) => ({
1202
+ countryCode: country,
1203
+ number,
1204
+ full: `${country.dialCode} ${number}`.trim(),
1205
+ });
1206
+ const handleCountryChange = (e) => {
1207
+ const country = countryCodes.find((c) => c.code === e.target.value) ?? defaultCC;
1208
+ setSelectedCountry(country);
1209
+ onChange?.(buildValue(country, activeNumber));
1210
+ };
1211
+ const handleNumberChange = (e) => {
1212
+ const raw = e.target.value.replace(/[^\d\s\-().+]/g, '');
1213
+ if (maxLength !== undefined && raw.replace(/\D/g, '').length > maxLength)
1214
+ return;
1215
+ setPhoneNumber(raw);
1216
+ onChange?.(buildValue(activeCountry, raw));
1217
+ };
1218
+ const handleKeyDown = (e) => {
1219
+ const allowed = [
1220
+ 'Backspace', 'Delete', 'Tab', 'Escape', 'Enter',
1221
+ 'ArrowLeft', 'ArrowRight', 'Home', 'End',
1222
+ ' ', '-', '(', ')', '.', '+',
1223
+ ];
1224
+ if (allowed.includes(e.key))
1225
+ return;
1226
+ if (e.ctrlKey || e.metaKey)
1227
+ return;
1228
+ if (!/^\d$/.test(e.key))
1229
+ e.preventDefault();
1230
+ };
1231
+ const height = sizeClasses$c[size];
1232
+ const dialSize = dialSelectSizes[size];
1233
+ return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { className: cn('flex items-stretch rounded-md border overflow-hidden bg-surface transition-colors', 'focus-within:border-border-focus focus-within:ring-2 focus-within:ring-border-focus/20', error ? 'border-error focus-within:border-error focus-within:ring-error/20' : 'border-border', disabled && 'opacity-50 cursor-not-allowed', fullWidth && 'w-full'), children: [jsx("select", { value: activeCountry.code, onChange: handleCountryChange, disabled: disabled, "aria-label": "Country code", className: cn('border-r border-border bg-surface-hover text-text cursor-pointer outline-none', 'appearance-none focus:outline-none', height, dialSize), style: { minWidth: size === 'sm' ? 72 : size === 'lg' ? 96 : 84 }, children: countryCodes.map((c) => (jsxs("option", { value: c.code, children: [c.flag, " ", c.dialCode] }, c.code))) }), jsx("input", { ref: ref, id: id, type: "tel", inputMode: "tel", value: activeNumber, onChange: handleNumberChange, onKeyDown: handleKeyDown, disabled: disabled, required: required, placeholder: placeholder, "aria-invalid": !!error, className: cn('flex-1 bg-transparent text-text placeholder:text-text-muted outline-none px-3', 'disabled:cursor-not-allowed', height, className) })] }), jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { children: [error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }), showMaxLength && maxLength !== undefined && (jsxs("p", { className: cn('text-xs text-text-muted ml-auto', activeNumber.replace(/\D/g, '').length >= maxLength && 'text-error'), children: [activeNumber.replace(/\D/g, '').length, "/", maxLength] }))] })] }));
1234
+ });
1235
+ PhoneInput.displayName = 'PhoneInput';
1236
+
1237
+ const sizeClasses$b = {
1238
+ sm: 'w-8 h-9 text-sm',
1239
+ md: 'w-10 h-11 text-base',
1240
+ lg: 'w-12 h-13 text-lg',
1241
+ };
1242
+ function OTPInput({ length = 6, value: controlledValue, onChange, onComplete, size = 'md', mask = false, disabled = false, error, label, helperText, separator = false, separatorAt = [], autoFocus = false, className, }) {
1243
+ const id = useId();
1244
+ const [internalValue, setInternalValue] = useState('');
1245
+ const inputsRef = useRef([]);
1246
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
1247
+ const digits = value.split('').slice(0, length);
1248
+ const update = (newVal) => {
1249
+ setInternalValue(newVal);
1250
+ onChange?.(newVal);
1251
+ if (newVal.length === length)
1252
+ onComplete?.(newVal);
1253
+ };
1254
+ const handleChange = (index, e) => {
1255
+ const raw = e.target.value.replace(/\D/g, '');
1256
+ if (!raw)
1257
+ return;
1258
+ const char = raw[raw.length - 1];
1259
+ const newDigits = [...digits];
1260
+ newDigits[index] = char;
1261
+ const newVal = newDigits.join('').slice(0, length);
1262
+ update(newVal);
1263
+ if (index < length - 1)
1264
+ inputsRef.current[index + 1]?.focus();
1265
+ };
1266
+ const handleKeyDown = (index, e) => {
1267
+ if (e.key === 'Backspace') {
1268
+ e.preventDefault();
1269
+ const newDigits = [...digits];
1270
+ if (newDigits[index]) {
1271
+ newDigits[index] = '';
1272
+ update(newDigits.join(''));
1273
+ }
1274
+ else if (index > 0) {
1275
+ inputsRef.current[index - 1]?.focus();
1276
+ const prev = [...digits];
1277
+ prev[index - 1] = '';
1278
+ update(prev.join(''));
1279
+ }
1280
+ }
1281
+ else if (e.key === 'ArrowLeft' && index > 0) {
1282
+ inputsRef.current[index - 1]?.focus();
1283
+ }
1284
+ else if (e.key === 'ArrowRight' && index < length - 1) {
1285
+ inputsRef.current[index + 1]?.focus();
1286
+ }
1287
+ };
1288
+ const handlePaste = (e) => {
1289
+ e.preventDefault();
1290
+ const pasted = e.clipboardData.getData('text').replace(/\D/g, '').slice(0, length);
1291
+ update(pasted);
1292
+ const focusIdx = Math.min(pasted.length, length - 1);
1293
+ inputsRef.current[focusIdx]?.focus();
1294
+ };
1295
+ const defaultSeparatorAt = separator ? [Math.floor(length / 2) - 1] : separatorAt;
1296
+ return (jsxs("div", { className: cn('flex flex-col gap-1.5', className), children: [label && (jsx("label", { htmlFor: `${id}-0`, className: "text-sm font-medium text-text", children: label })), jsx("div", { className: "flex items-center gap-2", children: Array.from({ length }).map((_, i) => (jsxs(React.Fragment, { children: [jsx("input", { ref: (el) => { inputsRef.current[i] = el; }, id: `${id}-${i}`, type: mask ? 'password' : 'text', inputMode: "numeric", maxLength: 1, value: digits[i] ?? '', onChange: (e) => handleChange(i, e), onKeyDown: (e) => handleKeyDown(i, e), onPaste: handlePaste, onFocus: (e) => e.target.select(), disabled: disabled, autoFocus: autoFocus && i === 0, className: cn('rounded-md border text-center font-mono font-semibold bg-surface text-text', 'transition-colors outline-none', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', 'disabled:opacity-50 disabled:cursor-not-allowed', error ? 'border-error focus:border-error focus:ring-error/20' : 'border-border', sizeClasses$b[size]), "aria-label": `Digit ${i + 1} of ${length}` }), defaultSeparatorAt.includes(i) && (jsx("span", { className: "text-text-muted font-bold text-lg select-none", children: "\u2013" }))] }, i))) }), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
1297
+ }
1298
+
1299
+ const sizeMap$2 = { sm: 16, md: 20, lg: 28 };
1300
+ function Rating({ value: controlledValue, defaultValue = 0, onChange, max = 5, size = 'md', disabled = false, readOnly = false, allowHalf = false, allowClear = true, label, helperText, className, icon, emptyIcon, }) {
1301
+ const [internalValue, setInternalValue] = useState(defaultValue);
1302
+ const [hoverValue, setHoverValue] = useState(null);
1303
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
1304
+ const iconSize = sizeMap$2[size];
1305
+ const interactive = !disabled && !readOnly;
1306
+ const handleClick = (star) => {
1307
+ if (!interactive)
1308
+ return;
1309
+ const newVal = allowClear && star === value ? 0 : star;
1310
+ setInternalValue(newVal);
1311
+ onChange?.(newVal);
1312
+ };
1313
+ const getStarFill = (star) => {
1314
+ const display = hoverValue ?? value;
1315
+ if (display >= star)
1316
+ return 'full';
1317
+ if (allowHalf && display >= star - 0.5)
1318
+ return 'half';
1319
+ return 'empty';
1320
+ };
1321
+ return (jsxs("div", { className: cn('flex flex-col gap-1', className), children: [label && jsx("span", { className: "text-sm font-medium text-text", children: label }), jsx("div", { className: cn('flex items-center gap-0.5', interactive && 'cursor-pointer'), onMouseLeave: () => interactive && setHoverValue(null), role: interactive ? 'slider' : 'img', "aria-label": `Rating: ${value} out of ${max}`, "aria-valuenow": value, "aria-valuemin": 0, "aria-valuemax": max, children: Array.from({ length: max }, (_, i) => {
1322
+ const star = i + 1;
1323
+ const fill = getStarFill(star);
1324
+ return (jsx("button", { type: "button", onClick: () => handleClick(star), onMouseEnter: () => interactive && setHoverValue(star), disabled: disabled, className: cn('relative transition-transform focus:outline-none', interactive && 'hover:scale-110', disabled && 'cursor-not-allowed opacity-50'), "aria-label": `Rate ${star} out of ${max}`, tabIndex: interactive ? 0 : -1, children: fill === 'empty' ? (emptyIcon ?? (jsx(Star, { size: iconSize, className: "text-border fill-transparent transition-colors" }))) : fill === 'full' ? (icon ?? (jsx(Star, { size: iconSize, className: cn('transition-colors', hoverValue !== null ? 'text-warning fill-warning' : 'text-warning fill-warning') }))) : (jsxs("span", { className: "relative inline-block", children: [jsx(Star, { size: iconSize, className: "text-border fill-transparent" }), jsx("span", { className: "absolute inset-0 overflow-hidden w-1/2", children: jsx(Star, { size: iconSize, className: "text-warning fill-warning" }) })] })) }, star));
1325
+ }) }), helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
1326
+ }
1327
+
1328
+ const sizeClasses$a = {
1329
+ sm: 'min-h-8 text-sm px-2.5',
1330
+ md: 'min-h-9 text-sm px-3',
1331
+ lg: 'min-h-11 text-base px-4',
1332
+ };
1333
+ function MultiSelect({ options = [], groups = [], value: controlledValue, defaultValue = [], onChange, placeholder = 'Select options…', searchPlaceholder = 'Search…', label, helperText, error, disabled = false, required = false, searchable = true, clearable = true, maxSelected, maxDisplayed = 3, size = 'md', fullWidth = false, className, containerClassName, }) {
1334
+ const id = useId();
1335
+ const [internalValue, setInternalValue] = useState(defaultValue);
1336
+ const [open, setOpen] = useState(false);
1337
+ const [search, setSearch] = useState('');
1338
+ const containerRef = useRef(null);
1339
+ const searchRef = useRef(null);
1340
+ const value = controlledValue ?? internalValue;
1341
+ const allOptions = [
1342
+ ...options,
1343
+ ...groups.flatMap((g) => g.options),
1344
+ ];
1345
+ const update = (next) => {
1346
+ setInternalValue(next);
1347
+ onChange?.(next);
1348
+ };
1349
+ const toggle = (optValue) => {
1350
+ if (value.includes(optValue)) {
1351
+ update(value.filter((v) => v !== optValue));
1352
+ }
1353
+ else {
1354
+ if (maxSelected && value.length >= maxSelected)
1355
+ return;
1356
+ update([...value, optValue]);
1357
+ }
1358
+ };
1359
+ const removeTag = (e, optValue) => {
1360
+ e.stopPropagation();
1361
+ update(value.filter((v) => v !== optValue));
1362
+ };
1363
+ const clearAll = (e) => {
1364
+ e.stopPropagation();
1365
+ update([]);
1366
+ };
1367
+ useEffect(() => {
1368
+ if (open)
1369
+ setTimeout(() => searchRef.current?.focus(), 10);
1370
+ else
1371
+ setSearch('');
1372
+ }, [open]);
1373
+ useEffect(() => {
1374
+ const handler = (e) => {
1375
+ if (!containerRef.current?.contains(e.target))
1376
+ setOpen(false);
1377
+ };
1378
+ document.addEventListener('mousedown', handler);
1379
+ return () => document.removeEventListener('mousedown', handler);
1380
+ }, []);
1381
+ const filterOption = (opt) => opt.label.toLowerCase().includes(search.toLowerCase()) ||
1382
+ opt.description?.toLowerCase().includes(search.toLowerCase());
1383
+ const filteredOptions = options.filter(filterOption);
1384
+ const filteredGroups = groups
1385
+ .map((g) => ({ ...g, options: g.options.filter(filterOption) }))
1386
+ .filter((g) => g.options.length > 0);
1387
+ const hasResults = filteredOptions.length > 0 || filteredGroups.length > 0;
1388
+ const selectedLabels = value.map((v) => allOptions.find((o) => o.value === v)?.label ?? v);
1389
+ const displayedTags = selectedLabels.slice(0, maxDisplayed);
1390
+ const overflow = selectedLabels.length - maxDisplayed;
1391
+ const renderOption = (opt) => {
1392
+ const selected = value.includes(opt.value);
1393
+ const atMax = !selected && maxSelected !== undefined && value.length >= maxSelected;
1394
+ return (jsxs("div", { role: "option", "aria-selected": selected, "aria-disabled": opt.disabled || atMax, onClick: () => !opt.disabled && !atMax && toggle(opt.value), className: cn('flex items-center gap-2.5 px-3 py-2 rounded-md cursor-pointer transition-colors text-sm', selected ? 'bg-primary/10 text-primary' : 'text-text hover:bg-surface-hover', (opt.disabled || atMax) && 'opacity-40 cursor-not-allowed pointer-events-none'), children: [jsx("div", { className: cn('flex-shrink-0 w-4 h-4 rounded border flex items-center justify-center transition-colors', selected ? 'bg-primary border-primary' : 'border-border bg-surface'), children: selected && jsx(Check, { size: 10, className: "text-primary-foreground" }) }), opt.icon && jsx("span", { className: "flex-shrink-0 text-text-muted", children: opt.icon }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("div", { className: "truncate", children: opt.label }), opt.description && jsx("div", { className: "text-xs text-text-muted truncate", children: opt.description })] })] }, opt.value));
1395
+ };
1396
+ return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: `${id}-trigger`, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { ref: containerRef, className: cn('relative', fullWidth && 'w-full', className), children: [jsxs("div", { id: `${id}-trigger`, role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-disabled": disabled, onClick: () => !disabled && setOpen((v) => !v), className: cn('flex items-center flex-wrap gap-1.5 rounded-md border bg-surface cursor-pointer', 'transition-colors outline-none py-1.5', open ? 'border-border-focus ring-2 ring-border-focus/20' : 'border-border', error && 'border-error ring-0', disabled && 'opacity-50 cursor-not-allowed', sizeClasses$a[size]), children: [value.length === 0 ? (jsx("span", { className: "text-text-muted flex-1", children: placeholder })) : (jsxs(Fragment, { children: [displayedTags.map((tag, i) => (jsxs("span", { className: "inline-flex items-center gap-1 bg-primary/10 text-primary text-xs px-2 py-0.5 rounded-full", children: [tag, jsx("button", { type: "button", onClick: (e) => removeTag(e, value[i]), className: "hover:text-primary/60 transition-colors", "aria-label": `Remove ${tag}`, children: jsx(X, { size: 10 }) })] }, value[i]))), overflow > 0 && (jsxs("span", { className: "text-xs text-text-muted bg-surface-hover px-2 py-0.5 rounded-full", children: ["+", overflow, " more"] })), jsx("span", { className: "flex-1" })] })), jsxs("div", { className: "flex items-center gap-1 ml-auto pr-1", children: [clearable && value.length > 0 && !disabled && (jsx("button", { type: "button", onClick: clearAll, className: "text-text-muted hover:text-text transition-colors", "aria-label": "Clear all", children: jsx(X, { size: 14 }) })), jsx(ChevronDown, { size: 16, className: cn('text-text-muted transition-transform', open && 'rotate-180') })] })] }), open && (jsxs("div", { className: "absolute z-50 w-full mt-1 bg-surface border border-border rounded-lg shadow-lg overflow-hidden", children: [searchable && (jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border", children: [jsx(Search, { size: 14, className: "text-text-muted flex-shrink-0" }), jsx("input", { ref: searchRef, type: "text", value: search, onChange: (e) => setSearch(e.target.value), placeholder: searchPlaceholder, className: "flex-1 bg-transparent text-sm text-text placeholder:text-text-muted outline-none" })] })), jsx("div", { role: "listbox", "aria-multiselectable": "true", className: "max-h-60 overflow-y-auto p-1.5", children: !hasResults ? (jsx("div", { className: "py-6 text-center text-sm text-text-muted", children: "No options found." })) : (jsxs(Fragment, { children: [filteredOptions.map(renderOption), filteredGroups.map((group) => (jsxs("div", { children: [jsx("div", { className: "px-3 py-1.5 text-xs font-semibold text-text-muted uppercase tracking-wider", children: group.label }), group.options.map(renderOption)] }, group.label)))] })) }), maxSelected && (jsxs("div", { className: "px-3 py-1.5 border-t border-border text-xs text-text-muted", children: [value.length, "/", maxSelected, " selected"] }))] }))] }), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
1397
+ }
1398
+
1399
+ const MONTHS = [
1400
+ 'January', 'February', 'March', 'April', 'May', 'June',
1401
+ 'July', 'August', 'September', 'October', 'November', 'December',
1402
+ ];
1403
+ const selectStyle = {
1404
+ appearance: 'none',
1405
+ WebkitAppearance: 'none',
1406
+ background: 'var(--color-surface-hover)',
1407
+ border: '1px solid var(--color-border)',
1408
+ borderRadius: 'var(--radius-sm, 4px)',
1409
+ color: 'var(--color-text)',
1410
+ cursor: 'pointer',
1411
+ fontSize: '0.8125rem',
1412
+ fontWeight: 600,
1413
+ padding: '2px 20px 2px 6px',
1414
+ outline: 'none',
1415
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23888' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E")`,
1416
+ backgroundRepeat: 'no-repeat',
1417
+ backgroundPosition: 'right 4px center',
1418
+ };
1419
+ function MonthCaption({ displayMonth, onMonthChange, minDate, maxDate, yearRangeBefore = 10, yearRangeAfter = 5 }) {
1420
+ const today = new Date();
1421
+ const currentYear = getYear(displayMonth);
1422
+ const currentMonth = getMonth(displayMonth);
1423
+ const minYear = minDate ? getYear(minDate) : getYear(today) - yearRangeBefore;
1424
+ const maxYear = maxDate ? getYear(maxDate) : getYear(today) + yearRangeAfter;
1425
+ const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) => minYear + i);
1426
+ return (jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '0.375rem' }, children: [jsx("select", { value: currentMonth, onChange: (e) => onMonthChange(setMonth(displayMonth, Number(e.target.value))), style: selectStyle, "aria-label": "Select month", children: MONTHS.map((m, i) => (jsx("option", { value: i, children: m }, m))) }), jsx("select", { value: currentYear, onChange: (e) => onMonthChange(setYear(displayMonth, Number(e.target.value))), style: selectStyle, "aria-label": "Select year", children: years.map((y) => (jsx("option", { value: y, children: y }, y))) })] }));
1427
+ }
1428
+ const sizeClasses$9 = {
1429
+ sm: 'h-8 text-sm px-2.5',
1430
+ md: 'h-9 text-sm px-3',
1431
+ lg: 'h-11 text-base px-4',
1432
+ };
1433
+ const dayPickerStyles = `
1434
+ /* react-day-picker v9 class names */
1435
+ .rdp-root { --rdp-accent-color: var(--color-primary); margin: 0; }
1436
+ .rdp-months { display: flex; gap: 1rem; }
1437
+ .rdp-month { width: auto; }
1438
+ .rdp-month_caption { display: flex; align-items: center; justify-content: space-between; padding: 0 0.25rem 0.75rem; min-width: 252px; }
1439
+ .rdp-month_grid { border-collapse: collapse; table-layout: fixed; }
1440
+ .rdp-caption_label { font-size: 0.875rem; font-weight: 600; color: var(--color-text); }
1441
+ .rdp-nav { display: flex; gap: 0.25rem; }
1442
+ .rdp-button_previous, .rdp-button_next { display: flex; align-items: center; justify-content: center; width: 1.75rem; height: 1.75rem; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-text-muted); cursor: pointer; transition: background 0.15s; }
1443
+ .rdp-button_previous:hover, .rdp-button_next:hover { background: var(--color-surface-hover); color: var(--color-text); }
1444
+ .rdp-weekdays { display: table-row; }
1445
+ .rdp-weekday { font-size: 0.75rem; font-weight: 500; color: var(--color-text-muted); text-align: center !important; padding: 0.25rem 0; width: 2.25rem; min-width: 2.25rem; }
1446
+ .rdp-weekday abbr { text-decoration: none; }
1447
+ .rdp-week { display: table-row; }
1448
+ .rdp-day { padding: 0.125rem; text-align: center; width: 2.25rem; min-width: 2.25rem; }
1449
+ .rdp-day_button { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; border-radius: var(--radius-md); font-size: 0.8125rem; color: var(--color-text); cursor: pointer; transition: background 0.15s, color 0.15s; border: none; background: transparent; }
1450
+ .rdp-day_button:hover:not(:disabled) { background: var(--color-surface-hover); }
1451
+ .rdp-selected .rdp-day_button { background: var(--color-primary) !important; color: var(--color-primary-foreground) !important; border-radius: var(--radius-md); }
1452
+ .rdp-today:not(.rdp-selected) .rdp-day_button { font-weight: 700; color: var(--color-primary); }
1453
+ .rdp-disabled .rdp-day_button { opacity: 0.35; cursor: not-allowed; }
1454
+ .rdp-outside .rdp-day_button { opacity: 0.4; }
1455
+ .rdp-range_start .rdp-day_button, .rdp-range_end .rdp-day_button { background: var(--color-primary) !important; color: var(--color-primary-foreground) !important; }
1456
+ .rdp-range_middle .rdp-day_button { background: color-mix(in srgb, var(--color-primary) 15%, transparent) !important; color: var(--color-primary) !important; border-radius: 0; }
1457
+ .rdp-range_start .rdp-day_button { border-radius: var(--radius-md) 0 0 var(--radius-md) !important; }
1458
+ .rdp-range_end .rdp-day_button { border-radius: 0 var(--radius-md) var(--radius-md) 0 !important; }
1459
+ `;
1460
+ function formatDateDisplay(date, fmt) {
1461
+ if (!date || !isValid(date))
1462
+ return '';
1463
+ return format(date, fmt);
1464
+ }
1465
+ function formatRangeDisplay(range, fmt) {
1466
+ if (!range)
1467
+ return '';
1468
+ const from = range.from ? format(range.from, fmt) : '';
1469
+ const to = range.to ? format(range.to, fmt) : '';
1470
+ if (from && to)
1471
+ return `${from} – ${to}`;
1472
+ if (from)
1473
+ return `${from} – …`;
1474
+ return '';
1475
+ }
1476
+ function DatePicker(props) {
1477
+ const { label, helperText, error, placeholder, dateFormat = 'MMM d, yyyy', disabled = false, required = false, clearable = true, size = 'md', fullWidth = false, minDate, maxDate, disabledDates, yearRangeBefore, yearRangeAfter, className, containerClassName, } = props;
1478
+ const id = useId();
1479
+ const [open, setOpen] = useState(false);
1480
+ const [popoverStyle, setPopoverStyle] = useState({});
1481
+ const [displayMonth, setDisplayMonth] = useState(new Date());
1482
+ const containerRef = useRef(null);
1483
+ const triggerRef = useRef(null);
1484
+ const isRange = props.mode === 'range';
1485
+ // Single mode state
1486
+ const [singleInternal, setSingleInternal] = useState(!isRange ? props.defaultValue : undefined);
1487
+ const singleValue = !isRange
1488
+ ? (props.value ?? singleInternal)
1489
+ : undefined;
1490
+ // Range mode state
1491
+ const [rangeInternal, setRangeInternal] = useState(isRange ? props.defaultValue : undefined);
1492
+ const rangeValue = isRange
1493
+ ? (props.value ?? rangeInternal)
1494
+ : undefined;
1495
+ const displayValue = isRange
1496
+ ? formatRangeDisplay(rangeValue, dateFormat)
1497
+ : formatDateDisplay(singleValue, dateFormat);
1498
+ const hasValue = isRange ? !!(rangeValue?.from) : !!singleValue;
1499
+ const updatePopoverPosition = useCallback(() => {
1500
+ if (!triggerRef.current)
1501
+ return;
1502
+ const rect = triggerRef.current.getBoundingClientRect();
1503
+ const spaceBelow = window.innerHeight - rect.bottom;
1504
+ const popoverHeight = 360;
1505
+ const openUpward = spaceBelow < popoverHeight && rect.top > popoverHeight;
1506
+ setPopoverStyle({
1507
+ position: 'fixed',
1508
+ left: rect.left,
1509
+ zIndex: 9999,
1510
+ ...(openUpward
1511
+ ? { bottom: window.innerHeight - rect.top + 4 }
1512
+ : { top: rect.bottom + 4 }),
1513
+ });
1514
+ }, []);
1515
+ useEffect(() => {
1516
+ if (open)
1517
+ updatePopoverPosition();
1518
+ }, [open, updatePopoverPosition]);
1519
+ useEffect(() => {
1520
+ const handler = (e) => {
1521
+ const target = e.target;
1522
+ if (!containerRef.current?.contains(target) &&
1523
+ !(document.getElementById(`${id}-popover`)?.contains(target))) {
1524
+ setOpen(false);
1525
+ }
1526
+ };
1527
+ document.addEventListener('mousedown', handler);
1528
+ return () => document.removeEventListener('mousedown', handler);
1529
+ }, [id]);
1530
+ const handleClear = (e) => {
1531
+ e.stopPropagation();
1532
+ if (isRange) {
1533
+ setRangeInternal(undefined);
1534
+ props.onChange?.(undefined);
1535
+ }
1536
+ else {
1537
+ setSingleInternal(undefined);
1538
+ props.onChange?.(undefined);
1539
+ }
1540
+ };
1541
+ const disabledMatcher = disabledDates
1542
+ ? (date) => disabledDates.some((d) => format(d, 'yyyy-MM-dd') === format(date, 'yyyy-MM-dd'))
1543
+ : undefined;
1544
+ return (jsxs(Fragment, { children: [jsx("style", { children: dayPickerStyles }), jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: `${id}-trigger`, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { ref: containerRef, className: cn(fullWidth && 'w-full'), children: [jsxs("div", { ref: triggerRef, id: `${id}-trigger`, role: "combobox", "aria-expanded": open, "aria-haspopup": "dialog", "aria-disabled": disabled, onClick: () => !disabled && setOpen((v) => !v), className: cn('flex items-center gap-2 rounded-md border bg-surface cursor-pointer', 'transition-colors outline-none', open ? 'border-border-focus ring-2 ring-border-focus/20' : 'border-border', error && 'border-error ring-0', disabled && 'opacity-50 cursor-not-allowed', sizeClasses$9[size], fullWidth && 'w-full', className), children: [jsx(Calendar, { size: 15, className: "text-text-muted flex-shrink-0" }), jsx("span", { className: cn('flex-1 text-sm', displayValue ? 'text-text' : 'text-text-muted'), children: displayValue || (placeholder ?? (isRange ? 'Select date range' : 'Select date')) }), clearable && hasValue && !disabled && (jsx("button", { type: "button", onClick: handleClear, className: "text-text-muted hover:text-text transition-colors flex-shrink-0", "aria-label": "Clear date", children: jsx(X, { size: 14 }) }))] }), open && typeof document !== 'undefined' && createPortal(jsxs("div", { id: `${id}-popover`, style: popoverStyle, className: "bg-surface border border-border rounded-xl shadow-2xl p-3", role: "dialog", "aria-label": "Date picker", children: [jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.25rem' }, children: [jsxs("div", { style: { display: 'flex', gap: '0.25rem' }, children: [jsx("button", { type: "button", onClick: () => setDisplayMonth((m) => new Date(m.getFullYear(), m.getMonth() - 1, 1)), style: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '1.75rem', height: '1.75rem', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border)', background: 'var(--color-surface)', color: 'var(--color-text-muted)', cursor: 'pointer' }, "aria-label": "Previous month", children: jsx(ChevronLeft, { size: 14 }) }), jsx("button", { type: "button", onClick: () => setDisplayMonth((m) => new Date(m.getFullYear(), m.getMonth() + 1, 1)), style: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '1.75rem', height: '1.75rem', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border)', background: 'var(--color-surface)', color: 'var(--color-text-muted)', cursor: 'pointer' }, "aria-label": "Next month", children: jsx(ChevronRight, { size: 14 }) })] }), jsx(MonthCaption, { displayMonth: displayMonth, onMonthChange: setDisplayMonth, minDate: minDate, maxDate: maxDate, yearRangeBefore: yearRangeBefore, yearRangeAfter: yearRangeAfter })] }), isRange ? (jsx(DayPicker, { mode: "range", month: displayMonth, onMonthChange: setDisplayMonth, selected: rangeValue, onSelect: (range) => {
1545
+ setRangeInternal(range);
1546
+ props.onChange?.(range);
1547
+ if (range?.from && range?.to)
1548
+ setOpen(false);
1549
+ }, disabled: [
1550
+ ...(minDate ? [{ before: minDate }] : []),
1551
+ ...(maxDate ? [{ after: maxDate }] : []),
1552
+ ...(disabledMatcher ? [disabledMatcher] : []),
1553
+ ], hideNavigation: true, components: {} })) : (jsx(DayPicker, { mode: "single", month: displayMonth, onMonthChange: setDisplayMonth, selected: singleValue, onSelect: (date) => {
1554
+ setSingleInternal(date);
1555
+ props.onChange?.(date);
1556
+ setOpen(false);
1557
+ }, disabled: [
1558
+ ...(minDate ? [{ before: minDate }] : []),
1559
+ ...(maxDate ? [{ after: maxDate }] : []),
1560
+ ...(disabledMatcher ? [disabledMatcher] : []),
1561
+ ], hideNavigation: true, components: {} }))] }), document.body)] }), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] })] }));
1562
+ }
1563
+
1564
+ function FormField({ label, helperText, error, required, disabled, htmlFor, children, className, }) {
1565
+ const generatedId = useId();
1566
+ const id = htmlFor ?? generatedId;
1567
+ return (jsxs("div", { className: cn('flex flex-col gap-1', disabled && 'opacity-60', className), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", "aria-hidden": "true", children: "*" })] })), children, error && (jsx("p", { className: "text-xs text-error", role: "alert", children: error })), !error && helperText && (jsx("p", { className: "text-xs text-text-muted", children: helperText }))] }));
1568
+ }
1569
+
1570
+ function JSONForm({ schema, defaultValues, onSubmit, onCancel, submitLabel = 'Submit', cancelLabel = 'Cancel', loading = false, columns = 1, className, actionsClassName, }) {
1571
+ const { control, handleSubmit, formState: { errors }, } = useForm({
1572
+ defaultValues: defaultValues ?? Object.fromEntries(schema.map((f) => [f.name, f.defaultValue ?? ''])),
1573
+ });
1574
+ const gridCols = {
1575
+ 1: 'grid-cols-1',
1576
+ 2: 'grid-cols-1 md:grid-cols-2',
1577
+ 3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
1578
+ };
1579
+ const colSpanClasses = {
1580
+ 1: 'col-span-1',
1581
+ 2: 'col-span-1 md:col-span-2',
1582
+ 3: 'col-span-1 md:col-span-2 lg:col-span-3',
1583
+ };
1584
+ return (jsxs("form", { onSubmit: handleSubmit(onSubmit), noValidate: true, className: cn('w-full', className), children: [jsx("div", { className: cn('grid gap-4', gridCols[columns]), children: schema.map((field) => {
1585
+ if (field.type === 'hidden') {
1586
+ return (jsx(Controller, { name: field.name, control: control, render: ({ field: f }) => jsx("input", { type: "hidden", ...f, value: String(f.value ?? '') }) }, field.name));
1587
+ }
1588
+ const errorMsg = errors[field.name]?.message;
1589
+ const colSpan = field.colSpan ?? 1;
1590
+ return (jsx("div", { className: cn(colSpanClasses[colSpan]), children: jsx(Controller, { name: field.name, control: control, rules: (() => {
1591
+ if (field.validator) {
1592
+ const compiled = field.validator.compile();
1593
+ return {
1594
+ required: field.required
1595
+ ? `${field.label ?? field.name} is required`
1596
+ : compiled.required,
1597
+ minLength: compiled.minLength,
1598
+ maxLength: compiled.maxLength,
1599
+ min: compiled.min,
1600
+ max: compiled.max,
1601
+ pattern: compiled.pattern,
1602
+ validate: compiled.validate,
1603
+ };
1604
+ }
1605
+ return {
1606
+ required: field.required ? `${field.label ?? field.name} is required` : false,
1607
+ minLength: field.validation?.minLength
1608
+ ? { value: field.validation.minLength, message: `Minimum ${field.validation.minLength} characters` }
1609
+ : undefined,
1610
+ maxLength: field.validation?.maxLength
1611
+ ? { value: field.validation.maxLength, message: `Maximum ${field.validation.maxLength} characters` }
1612
+ : undefined,
1613
+ min: field.validation?.min
1614
+ ? { value: field.validation.min, message: `Minimum value is ${field.validation.min}` }
1615
+ : undefined,
1616
+ max: field.validation?.max
1617
+ ? { value: field.validation.max, message: `Maximum value is ${field.validation.max}` }
1618
+ : undefined,
1619
+ pattern: field.validation?.pattern
1620
+ ? { value: field.validation.pattern, message: 'Invalid format' }
1621
+ : undefined,
1622
+ validate: field.validation?.validate,
1623
+ };
1624
+ })(), render: ({ field: f }) => {
1625
+ const commonProps = {
1626
+ label: field.label,
1627
+ helperText: field.helperText,
1628
+ error: errorMsg,
1629
+ disabled: field.disabled,
1630
+ required: field.required,
1631
+ fullWidth: true,
1632
+ };
1633
+ if (field.type === 'textarea') {
1634
+ return (jsx(TextArea, { ...commonProps, value: String(f.value ?? ''), onChange: f.onChange, onBlur: f.onBlur, placeholder: field.placeholder, rows: field.rows, autoResize: field.autoResize, showCharCount: field.showCharCount, maxLength: field.validation?.maxLength, prefixIcon: field.prefixIcon, prefixImage: field.prefixImage, suffixIcon: field.suffixIcon, suffixImage: field.suffixImage }));
1635
+ }
1636
+ if (field.type === 'select') {
1637
+ return (jsx(Select, { ...commonProps, options: field.options ?? [], value: String(f.value ?? ''), onValueChange: f.onChange, placeholder: field.placeholder }));
1638
+ }
1639
+ if (field.type === 'searchselect') {
1640
+ return (jsx(SearchSelect, { ...commonProps, options: field.options ?? [], value: String(f.value ?? ''), onChange: (v) => f.onChange(v), placeholder: field.placeholder, fullWidth: true }));
1641
+ }
1642
+ if (field.type === 'multiselect' || field.type === 'multisearchselect') {
1643
+ return (jsx(SearchSelect, { ...commonProps, multiple: true, options: field.options ?? [], value: Array.isArray(f.value) ? f.value : [], onChange: (v) => f.onChange(v), placeholder: field.placeholder, fullWidth: true }));
1644
+ }
1645
+ if (field.type === 'chipselect') {
1646
+ return (jsx(ChipSelect, { label: field.label, helperText: field.helperText, error: errorMsg, disabled: field.disabled, required: field.required, options: field.options ?? [], value: Array.isArray(f.value) ? f.value : [], onChange: (v) => f.onChange(v), fullWidth: true }));
1647
+ }
1648
+ if (field.type === 'taginput') {
1649
+ return (jsx(TagInput, { label: field.label, helperText: field.helperText, error: errorMsg, disabled: field.disabled, required: field.required, value: Array.isArray(f.value) ? f.value : [], onChange: (v) => f.onChange(v), placeholder: field.placeholder, fullWidth: true }));
1650
+ }
1651
+ if (field.type === 'checkbox') {
1652
+ return (jsx(Checkbox, { label: field.label, checked: Boolean(f.value), onCheckedChange: f.onChange, disabled: field.disabled, required: field.required, error: errorMsg }));
1653
+ }
1654
+ if (field.type === 'switch') {
1655
+ return (jsx(Switch, { label: field.label, checked: Boolean(f.value), onCheckedChange: f.onChange, disabled: field.disabled, required: field.required }));
1656
+ }
1657
+ if (field.type === 'radio') {
1658
+ return (jsx(RadioGroup, { label: field.label, options: field.options ?? [], value: String(f.value ?? ''), onValueChange: f.onChange, disabled: field.disabled, required: field.required, error: errorMsg }));
1659
+ }
1660
+ return (jsx(TextField, { ...commonProps, type: field.type, value: String(f.value ?? ''), onChange: f.onChange, onBlur: f.onBlur, placeholder: field.placeholder, prefixIcon: field.prefixIcon, prefixImage: field.prefixImage, prefixText: field.prefixText, suffixIcon: field.suffixIcon, suffixImage: field.suffixImage, suffixText: field.suffixText }));
1661
+ } }) }, field.name));
1662
+ }) }), jsxs("div", { className: cn('flex items-center justify-end gap-3 mt-6', actionsClassName), children: [onCancel && (jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: loading, children: cancelLabel })), jsx(Button, { type: "submit", variant: "primary", loading: loading, children: submitLabel })] })] }));
1663
+ }
1664
+
1665
+ // ─── Base builder ────────────────────────────────────────────────────────────
1666
+ class BaseValidator {
1667
+ constructor() {
1668
+ Object.defineProperty(this, "_required", {
1669
+ enumerable: true,
1670
+ configurable: true,
1671
+ writable: true,
1672
+ value: false
1673
+ });
1674
+ Object.defineProperty(this, "_validates", {
1675
+ enumerable: true,
1676
+ configurable: true,
1677
+ writable: true,
1678
+ value: {}
1679
+ });
1680
+ }
1681
+ required(message) {
1682
+ this._required = message ?? 'This field is required';
1683
+ return this;
1684
+ }
1685
+ custom(name, fn, message) {
1686
+ this._validates[name] = (v) => {
1687
+ const result = fn(v);
1688
+ if (result === false)
1689
+ return message ?? 'Invalid value';
1690
+ return result;
1691
+ };
1692
+ return this;
1693
+ }
1694
+ }
1695
+ // ─── String validator ─────────────────────────────────────────────────────────
1696
+ class StringValidator extends BaseValidator {
1697
+ constructor() {
1698
+ super(...arguments);
1699
+ Object.defineProperty(this, "_minLength", {
1700
+ enumerable: true,
1701
+ configurable: true,
1702
+ writable: true,
1703
+ value: void 0
1704
+ });
1705
+ Object.defineProperty(this, "_maxLength", {
1706
+ enumerable: true,
1707
+ configurable: true,
1708
+ writable: true,
1709
+ value: void 0
1710
+ });
1711
+ Object.defineProperty(this, "_pattern", {
1712
+ enumerable: true,
1713
+ configurable: true,
1714
+ writable: true,
1715
+ value: void 0
1716
+ });
1717
+ Object.defineProperty(this, "_mustBeString", {
1718
+ enumerable: true,
1719
+ configurable: true,
1720
+ writable: true,
1721
+ value: false
1722
+ });
1723
+ }
1724
+ mustBe(_type) {
1725
+ this._mustBeString = true;
1726
+ return this;
1727
+ }
1728
+ minLength(len, message) {
1729
+ this._minLength = { value: len, message: message ?? `Minimum ${len} characters required` };
1730
+ return this;
1731
+ }
1732
+ maxLength(len, message) {
1733
+ this._maxLength = { value: len, message: message ?? `Maximum ${len} characters allowed` };
1734
+ return this;
1735
+ }
1736
+ lengthBetween(min, max, message) {
1737
+ this._minLength = { value: min, message: message ?? `Must be between ${min} and ${max} characters` };
1738
+ this._maxLength = { value: max, message: message ?? `Must be between ${min} and ${max} characters` };
1739
+ return this;
1740
+ }
1741
+ length(exact, message) {
1742
+ this._minLength = { value: exact, message: message ?? `Must be exactly ${exact} characters` };
1743
+ this._maxLength = { value: exact, message: message ?? `Must be exactly ${exact} characters` };
1744
+ return this;
1745
+ }
1746
+ shouldMatch(pattern, message) {
1747
+ this._pattern = { value: pattern, message: message ?? 'Invalid format' };
1748
+ return this;
1749
+ }
1750
+ shouldBeIn(list, message) {
1751
+ this._validates['shouldBeIn'] = (v) => list.includes(String(v ?? '')) || (message ?? `Must be one of: ${list.join(', ')}`);
1752
+ return this;
1753
+ }
1754
+ notEmpty(message) {
1755
+ this._validates['notEmpty'] = (v) => (String(v ?? '').trim().length > 0) || (message ?? 'Cannot be empty or whitespace');
1756
+ return this;
1757
+ }
1758
+ noSpaces(message) {
1759
+ this._validates['noSpaces'] = (v) => !/\s/.test(String(v ?? '')) || (message ?? 'No spaces allowed');
1760
+ return this;
1761
+ }
1762
+ alphanumeric(message) {
1763
+ this._validates['alphanumeric'] = (v) => /^[a-zA-Z0-9]*$/.test(String(v ?? '')) || (message ?? 'Only letters and numbers allowed');
1764
+ return this;
1765
+ }
1766
+ startsWith(prefix, message) {
1767
+ this._validates['startsWith'] = (v) => String(v ?? '').startsWith(prefix) || (message ?? `Must start with "${prefix}"`);
1768
+ return this;
1769
+ }
1770
+ endsWith(suffix, message) {
1771
+ this._validates['endsWith'] = (v) => String(v ?? '').endsWith(suffix) || (message ?? `Must end with "${suffix}"`);
1772
+ return this;
1773
+ }
1774
+ compile() {
1775
+ const validates = { ...this._validates };
1776
+ if (this._mustBeString) {
1777
+ validates['mustBeString'] = (v) => typeof v === 'string' || 'Must be a string';
1778
+ }
1779
+ return {
1780
+ required: this._required || undefined,
1781
+ minLength: this._minLength,
1782
+ maxLength: this._maxLength,
1783
+ pattern: this._pattern,
1784
+ validate: Object.keys(validates).length ? validates : undefined,
1785
+ };
1786
+ }
1787
+ }
1788
+ // ─── Number validator ─────────────────────────────────────────────────────────
1789
+ class NumberValidator extends BaseValidator {
1790
+ constructor() {
1791
+ super(...arguments);
1792
+ Object.defineProperty(this, "_min", {
1793
+ enumerable: true,
1794
+ configurable: true,
1795
+ writable: true,
1796
+ value: void 0
1797
+ });
1798
+ Object.defineProperty(this, "_max", {
1799
+ enumerable: true,
1800
+ configurable: true,
1801
+ writable: true,
1802
+ value: void 0
1803
+ });
1804
+ Object.defineProperty(this, "_maxLength", {
1805
+ enumerable: true,
1806
+ configurable: true,
1807
+ writable: true,
1808
+ value: void 0
1809
+ });
1810
+ Object.defineProperty(this, "_mustBeNumber", {
1811
+ enumerable: true,
1812
+ configurable: true,
1813
+ writable: true,
1814
+ value: false
1815
+ });
1816
+ }
1817
+ mustBe(_type) {
1818
+ this._mustBeNumber = true;
1819
+ return this;
1820
+ }
1821
+ min(value, message) {
1822
+ this._min = { value, message: message ?? `Minimum value is ${value}` };
1823
+ return this;
1824
+ }
1825
+ max(value, message) {
1826
+ this._max = { value, message: message ?? `Maximum value is ${value}` };
1827
+ return this;
1828
+ }
1829
+ between(min, max, message) {
1830
+ this._min = { value: min, message: message ?? `Must be between ${min} and ${max}` };
1831
+ this._max = { value: max, message: message ?? `Must be between ${min} and ${max}` };
1832
+ return this;
1833
+ }
1834
+ length(digits, message) {
1835
+ this._maxLength = { value: digits, message: message ?? `Must be ${digits} digits` };
1836
+ this._validates['exactLength'] = (v) => {
1837
+ const s = String(v ?? '').replace(/\D/g, '');
1838
+ return s.length === digits || (message ?? `Must be exactly ${digits} digits`);
1839
+ };
1840
+ return this;
1841
+ }
1842
+ positive(message) {
1843
+ this._min = { value: 0, message: message ?? 'Must be a positive number' };
1844
+ return this;
1845
+ }
1846
+ integer(message) {
1847
+ this._validates['integer'] = (v) => Number.isInteger(Number(v)) || (message ?? 'Must be a whole number');
1848
+ return this;
1849
+ }
1850
+ nonZero(message) {
1851
+ this._validates['nonZero'] = (v) => Number(v) !== 0 || (message ?? 'Cannot be zero');
1852
+ return this;
1853
+ }
1854
+ negative(message) {
1855
+ this._max = { value: 0, message: message ?? 'Must be a negative number' };
1856
+ return this;
1857
+ }
1858
+ multipleOf(factor, message) {
1859
+ this._validates['multipleOf'] = (v) => Number(v) % factor === 0 || (message ?? `Must be a multiple of ${factor}`);
1860
+ return this;
1861
+ }
1862
+ compile() {
1863
+ const validates = { ...this._validates };
1864
+ if (this._mustBeNumber) {
1865
+ validates['mustBeNumber'] = (v) => !isNaN(Number(v)) || 'Must be a number';
1866
+ }
1867
+ return {
1868
+ required: this._required || undefined,
1869
+ min: this._min,
1870
+ max: this._max,
1871
+ maxLength: this._maxLength,
1872
+ validate: Object.keys(validates).length ? validates : undefined,
1873
+ };
1874
+ }
1875
+ }
1876
+ // ─── Email validator ──────────────────────────────────────────────────────────
1877
+ class EmailValidator extends BaseValidator {
1878
+ mustBe(_type) { return this; }
1879
+ shouldMatch(pattern, message) {
1880
+ this._validates['pattern'] = (v) => pattern.test(String(v ?? '')) || (message ?? 'Invalid email format');
1881
+ return this;
1882
+ }
1883
+ domain(allowedDomains, message) {
1884
+ this._validates['domain'] = (v) => {
1885
+ const domain = String(v ?? '').split('@')[1] ?? '';
1886
+ return allowedDomains.includes(domain) || (message ?? `Email must be from: ${allowedDomains.join(', ')}`);
1887
+ };
1888
+ return this;
1889
+ }
1890
+ blockedDomains(blocked, message) {
1891
+ this._validates['blockedDomains'] = (v) => {
1892
+ const domain = String(v ?? '').split('@')[1] ?? '';
1893
+ return !blocked.includes(domain) || (message ?? `Email from ${domain} is not allowed`);
1894
+ };
1895
+ return this;
1896
+ }
1897
+ compile() {
1898
+ return {
1899
+ required: this._required || undefined,
1900
+ pattern: { value: EmailValidator.EMAIL_RE, message: 'Enter a valid email address' },
1901
+ validate: Object.keys(this._validates).length ? { ...this._validates } : undefined,
1902
+ };
1903
+ }
1904
+ }
1905
+ Object.defineProperty(EmailValidator, "EMAIL_RE", {
1906
+ enumerable: true,
1907
+ configurable: true,
1908
+ writable: true,
1909
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
1910
+ });
1911
+ // ─── URL validator ────────────────────────────────────────────────────────────
1912
+ class UrlValidator extends BaseValidator {
1913
+ mustBe(_type) { return this; }
1914
+ httpsOnly(message) {
1915
+ this._validates['httpsOnly'] = (v) => String(v ?? '').startsWith('https://') || (message ?? 'URL must use HTTPS');
1916
+ return this;
1917
+ }
1918
+ allowedDomains(domains, message) {
1919
+ this._validates['allowedDomains'] = (v) => {
1920
+ try {
1921
+ const hostname = new URL(String(v ?? '')).hostname;
1922
+ return domains.some((d) => hostname === d || hostname.endsWith(`.${d}`)) ||
1923
+ (message ?? `URL must be from: ${domains.join(', ')}`);
1924
+ }
1925
+ catch {
1926
+ return message ?? 'Invalid URL';
1927
+ }
1928
+ };
1929
+ return this;
1930
+ }
1931
+ noTrailingSlash(message) {
1932
+ this._validates['noTrailingSlash'] = (v) => !String(v ?? '').endsWith('/') || (message ?? 'URL must not end with a slash');
1933
+ return this;
1934
+ }
1935
+ compile() {
1936
+ return {
1937
+ required: this._required || undefined,
1938
+ pattern: { value: UrlValidator.URL_RE, message: 'Enter a valid URL' },
1939
+ validate: Object.keys(this._validates).length ? { ...this._validates } : undefined,
1940
+ };
1941
+ }
1942
+ }
1943
+ Object.defineProperty(UrlValidator, "URL_RE", {
1944
+ enumerable: true,
1945
+ configurable: true,
1946
+ writable: true,
1947
+ value: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)$/
1948
+ });
1949
+ // ─── Tel / Phone validator ────────────────────────────────────────────────────
1950
+ class TelValidator extends BaseValidator {
1951
+ length(digits, message) {
1952
+ this._validates['length'] = (v) => {
1953
+ const s = String(v ?? '').replace(/\D/g, '');
1954
+ return s.length === digits || (message ?? `Must be exactly ${digits} digits`);
1955
+ };
1956
+ return this;
1957
+ }
1958
+ minLength(digits, message) {
1959
+ this._validates['minLength'] = (v) => {
1960
+ const s = String(v ?? '').replace(/\D/g, '');
1961
+ return s.length >= digits || (message ?? `Must be at least ${digits} digits`);
1962
+ };
1963
+ return this;
1964
+ }
1965
+ maxLength(digits, message) {
1966
+ this._validates['maxLength'] = (v) => {
1967
+ const s = String(v ?? '').replace(/\D/g, '');
1968
+ return s.length <= digits || (message ?? `Must be at most ${digits} digits`);
1969
+ };
1970
+ return this;
1971
+ }
1972
+ shouldMatch(pattern, message) {
1973
+ this._validates['pattern'] = (v) => pattern.test(String(v ?? '')) || (message ?? 'Invalid phone number format');
1974
+ return this;
1975
+ }
1976
+ compile() {
1977
+ return {
1978
+ required: this._required || undefined,
1979
+ validate: Object.keys(this._validates).length ? { ...this._validates } : undefined,
1980
+ };
1981
+ }
1982
+ }
1983
+ // ─── Boolean validator ────────────────────────────────────────────────────────
1984
+ class BooleanValidator extends BaseValidator {
1985
+ mustBeTrue(message) {
1986
+ this._validates['mustBeTrue'] = (v) => v === true || (message ?? 'Must be checked');
1987
+ return this;
1988
+ }
1989
+ compile() {
1990
+ return {
1991
+ required: this._required || undefined,
1992
+ validate: Object.keys(this._validates).length ? this._validates : undefined,
1993
+ };
1994
+ }
1995
+ }
1996
+ // ─── Array validator ──────────────────────────────────────────────────────────
1997
+ class ArrayValidator extends BaseValidator {
1998
+ constructor() {
1999
+ super(...arguments);
2000
+ Object.defineProperty(this, "_min", {
2001
+ enumerable: true,
2002
+ configurable: true,
2003
+ writable: true,
2004
+ value: void 0
2005
+ });
2006
+ Object.defineProperty(this, "_max", {
2007
+ enumerable: true,
2008
+ configurable: true,
2009
+ writable: true,
2010
+ value: void 0
2011
+ });
2012
+ }
2013
+ minItems(count, message) {
2014
+ this._min = count;
2015
+ this._validates['minItems'] = (v) => {
2016
+ const arr = Array.isArray(v) ? v : [];
2017
+ return arr.length >= count || (message ?? `Select at least ${count} item${count > 1 ? 's' : ''}`);
2018
+ };
2019
+ return this;
2020
+ }
2021
+ maxItems(count, message) {
2022
+ this._max = count;
2023
+ this._validates['maxItems'] = (v) => {
2024
+ const arr = Array.isArray(v) ? v : [];
2025
+ return arr.length <= count || (message ?? `Select at most ${count} item${count > 1 ? 's' : ''}`);
2026
+ };
2027
+ return this;
2028
+ }
2029
+ exactItems(count, message) {
2030
+ this._validates['exactItems'] = (v) => {
2031
+ const arr = Array.isArray(v) ? v : [];
2032
+ return arr.length === count || (message ?? `Must select exactly ${count} item${count > 1 ? 's' : ''}`);
2033
+ };
2034
+ return this;
2035
+ }
2036
+ noEmpty(message) {
2037
+ this._validates['noEmpty'] = (v) => {
2038
+ const arr = Array.isArray(v) ? v : [];
2039
+ return arr.every((item) => String(item).trim().length > 0) || (message ?? 'Items cannot be empty');
2040
+ };
2041
+ return this;
2042
+ }
2043
+ shouldBeIn(list, message) {
2044
+ this._validates['shouldBeIn'] = (v) => {
2045
+ const arr = Array.isArray(v) ? v : [];
2046
+ const invalid = arr.filter((item) => !list.includes(item));
2047
+ return invalid.length === 0 || (message ?? `Invalid selection: ${invalid.join(', ')}`);
2048
+ };
2049
+ return this;
2050
+ }
2051
+ compile() {
2052
+ return {
2053
+ required: this._required || undefined,
2054
+ validate: Object.keys(this._validates).length ? { ...this._validates } : undefined,
2055
+ };
2056
+ }
2057
+ }
2058
+ // ─── Password validator ───────────────────────────────────────────────────────
2059
+ class PasswordValidator extends BaseValidator {
2060
+ constructor() {
2061
+ super(...arguments);
2062
+ Object.defineProperty(this, "_minLength", {
2063
+ enumerable: true,
2064
+ configurable: true,
2065
+ writable: true,
2066
+ value: void 0
2067
+ });
2068
+ Object.defineProperty(this, "_maxLength", {
2069
+ enumerable: true,
2070
+ configurable: true,
2071
+ writable: true,
2072
+ value: void 0
2073
+ });
2074
+ }
2075
+ minLength(len, message) {
2076
+ this._minLength = { value: len, message: message ?? `Minimum ${len} characters required` };
2077
+ return this;
2078
+ }
2079
+ maxLength(len, message) {
2080
+ this._maxLength = { value: len, message: message ?? `Maximum ${len} characters allowed` };
2081
+ return this;
2082
+ }
2083
+ hasUppercase(message) {
2084
+ this._validates['hasUppercase'] = (v) => /[A-Z]/.test(String(v ?? '')) || (message ?? 'Must contain at least one uppercase letter');
2085
+ return this;
2086
+ }
2087
+ hasLowercase(message) {
2088
+ this._validates['hasLowercase'] = (v) => /[a-z]/.test(String(v ?? '')) || (message ?? 'Must contain at least one lowercase letter');
2089
+ return this;
2090
+ }
2091
+ hasDigit(message) {
2092
+ this._validates['hasDigit'] = (v) => /\d/.test(String(v ?? '')) || (message ?? 'Must contain at least one number');
2093
+ return this;
2094
+ }
2095
+ hasSpecialChar(message) {
2096
+ this._validates['hasSpecialChar'] = (v) => /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]/.test(String(v ?? '')) ||
2097
+ (message ?? 'Must contain at least one special character');
2098
+ return this;
2099
+ }
2100
+ noSpaces(message) {
2101
+ this._validates['noSpaces'] = (v) => !/\s/.test(String(v ?? '')) || (message ?? 'Password cannot contain spaces');
2102
+ return this;
2103
+ }
2104
+ /** Confirm this field matches another field value — pass a getter for the other field */
2105
+ confirmMatch(getOtherValue, message) {
2106
+ this._validates['confirmMatch'] = (v) => v === getOtherValue() || (message ?? 'Passwords do not match');
2107
+ return this;
2108
+ }
2109
+ /** Shorthand: minLength(8) + uppercase + lowercase + digit + special char */
2110
+ strong(message) {
2111
+ return this.minLength(8, message).hasUppercase(message).hasLowercase(message).hasDigit(message).hasSpecialChar(message);
2112
+ }
2113
+ compile() {
2114
+ return {
2115
+ required: this._required || undefined,
2116
+ minLength: this._minLength,
2117
+ maxLength: this._maxLength,
2118
+ validate: Object.keys(this._validates).length ? { ...this._validates } : undefined,
2119
+ };
2120
+ }
2121
+ }
2122
+ // ─── Date validator ───────────────────────────────────────────────────────────
2123
+ class DateValidator extends BaseValidator {
2124
+ _toDate(v) {
2125
+ if (v instanceof Date)
2126
+ return isNaN(v.getTime()) ? null : v;
2127
+ if (typeof v === 'string' || typeof v === 'number') {
2128
+ const d = new Date(v);
2129
+ return isNaN(d.getTime()) ? null : d;
2130
+ }
2131
+ return null;
2132
+ }
2133
+ validDate(message) {
2134
+ this._validates['validDate'] = (v) => this._toDate(v) !== null || (message ?? 'Must be a valid date');
2135
+ return this;
2136
+ }
2137
+ minDate(date, message) {
2138
+ this._validates['minDate'] = (v) => {
2139
+ const d = this._toDate(v);
2140
+ if (!d)
2141
+ return 'Invalid date';
2142
+ return d >= date || (message ?? `Date must be on or after ${date.toLocaleDateString()}`);
2143
+ };
2144
+ return this;
2145
+ }
2146
+ maxDate(date, message) {
2147
+ this._validates['maxDate'] = (v) => {
2148
+ const d = this._toDate(v);
2149
+ if (!d)
2150
+ return 'Invalid date';
2151
+ return d <= date || (message ?? `Date must be on or before ${date.toLocaleDateString()}`);
2152
+ };
2153
+ return this;
2154
+ }
2155
+ notInPast(message) {
2156
+ this._validates['notInPast'] = (v) => {
2157
+ const d = this._toDate(v);
2158
+ if (!d)
2159
+ return 'Invalid date';
2160
+ const today = new Date();
2161
+ today.setHours(0, 0, 0, 0);
2162
+ return d >= today || (message ?? 'Date cannot be in the past');
2163
+ };
2164
+ return this;
2165
+ }
2166
+ notInFuture(message) {
2167
+ this._validates['notInFuture'] = (v) => {
2168
+ const d = this._toDate(v);
2169
+ if (!d)
2170
+ return 'Invalid date';
2171
+ const today = new Date();
2172
+ today.setHours(23, 59, 59, 999);
2173
+ return d <= today || (message ?? 'Date cannot be in the future');
2174
+ };
2175
+ return this;
2176
+ }
2177
+ between(minDate, maxDate, message) {
2178
+ return this.minDate(minDate, message).maxDate(maxDate, message);
2179
+ }
2180
+ compile() {
2181
+ return {
2182
+ required: this._required || undefined,
2183
+ validate: Object.keys(this._validates).length ? { ...this._validates } : undefined,
2184
+ };
2185
+ }
2186
+ }
2187
+ // ─── Select validator (select / radio) ───────────────────────────────────────
2188
+ class SelectValidator extends BaseValidator {
2189
+ shouldBeIn(list, message) {
2190
+ this._validates['shouldBeIn'] = (v) => list.includes(String(v ?? '')) || (message ?? `Must be one of: ${list.join(', ')}`);
2191
+ return this;
2192
+ }
2193
+ notEmpty(message) {
2194
+ this._validates['notEmpty'] = (v) => (String(v ?? '').trim().length > 0) || (message ?? 'Please make a selection');
2195
+ return this;
2196
+ }
2197
+ compile() {
2198
+ return {
2199
+ required: this._required || undefined,
2200
+ validate: Object.keys(this._validates).length ? { ...this._validates } : undefined,
2201
+ };
2202
+ }
2203
+ }
2204
+ // ─── File validator ───────────────────────────────────────────────────────────
2205
+ class FileValidator extends BaseValidator {
2206
+ /** Max file size in bytes */
2207
+ maxSize(bytes, message) {
2208
+ this._validates['maxSize'] = (v) => {
2209
+ const files = Array.isArray(v) ? v : v instanceof File ? [v] : [];
2210
+ const tooBig = files.filter((f) => f.size > bytes);
2211
+ if (tooBig.length === 0)
2212
+ return true;
2213
+ const mb = (bytes / 1024 / 1024).toFixed(1);
2214
+ return message ?? `File size must not exceed ${mb} MB`;
2215
+ };
2216
+ return this;
2217
+ }
2218
+ allowedTypes(mimeTypes, message) {
2219
+ this._validates['allowedTypes'] = (v) => {
2220
+ const files = Array.isArray(v) ? v : v instanceof File ? [v] : [];
2221
+ const invalid = files.filter((f) => !mimeTypes.some((t) => t.endsWith('/*') ? f.type.startsWith(t.replace('/*', '/')) : f.type === t));
2222
+ return invalid.length === 0 || (message ?? `Allowed types: ${mimeTypes.join(', ')}`);
2223
+ };
2224
+ return this;
2225
+ }
2226
+ allowedExtensions(exts, message) {
2227
+ this._validates['allowedExtensions'] = (v) => {
2228
+ const files = Array.isArray(v) ? v : v instanceof File ? [v] : [];
2229
+ const invalid = files.filter((f) => {
2230
+ const ext = f.name.split('.').pop()?.toLowerCase() ?? '';
2231
+ return !exts.map((e) => e.toLowerCase().replace('.', '')).includes(ext);
2232
+ });
2233
+ return invalid.length === 0 || (message ?? `Allowed extensions: ${exts.join(', ')}`);
2234
+ };
2235
+ return this;
2236
+ }
2237
+ maxFiles(count, message) {
2238
+ this._validates['maxFiles'] = (v) => {
2239
+ const files = Array.isArray(v) ? v : v instanceof File ? [v] : [];
2240
+ return files.length <= count || (message ?? `Maximum ${count} file${count > 1 ? 's' : ''} allowed`);
2241
+ };
2242
+ return this;
2243
+ }
2244
+ minFiles(count, message) {
2245
+ this._validates['minFiles'] = (v) => {
2246
+ const files = Array.isArray(v) ? v : v instanceof File ? [v] : [];
2247
+ return files.length >= count || (message ?? `At least ${count} file${count > 1 ? 's' : ''} required`);
2248
+ };
2249
+ return this;
2250
+ }
2251
+ compile() {
2252
+ return {
2253
+ required: this._required || undefined,
2254
+ validate: Object.keys(this._validates).length ? { ...this._validates } : undefined,
2255
+ };
2256
+ }
2257
+ }
2258
+ // ─── Entry point ──────────────────────────────────────────────────────────────
2259
+ const v = {
2260
+ /** text, textarea fields — lengthBetween, shouldMatch, shouldBeIn, noSpaces, alphanumeric */
2261
+ string: () => new StringValidator(),
2262
+ /** number fields — between, integer, multipleOf, nonZero, length */
2263
+ number: () => new NumberValidator(),
2264
+ /** password fields — strong(), hasUppercase, hasDigit, hasSpecialChar, confirmMatch */
2265
+ password: () => new PasswordValidator(),
2266
+ /** email fields — auto email regex, domain whitelist/blacklist */
2267
+ email: () => new EmailValidator(),
2268
+ /** url fields — httpsOnly, allowedDomains, noTrailingSlash */
2269
+ url: () => new UrlValidator(),
2270
+ /** tel/phone fields — length, minLength, maxLength, shouldMatch */
2271
+ tel: () => new TelValidator(),
2272
+ /** DatePicker fields — minDate, maxDate, notInPast, notInFuture */
2273
+ date: () => new DateValidator(),
2274
+ /** select/radio fields — shouldBeIn, notEmpty */
2275
+ select: () => new SelectValidator(),
2276
+ /** checkbox/switch fields — mustBeTrue */
2277
+ boolean: () => new BooleanValidator(),
2278
+ /** multiselect/chipselect/taginput — minItems, maxItems, exactItems, shouldBeIn, noEmpty */
2279
+ array: () => new ArrayValidator(),
2280
+ /** FileUpload fields — maxSize, allowedTypes, allowedExtensions, maxFiles, minFiles */
2281
+ file: () => new FileValidator(),
2282
+ };
2283
+
2284
+ function getFileIcon(file) {
2285
+ if (file.type.startsWith('image/'))
2286
+ return jsx(Image$1, { size: 20 });
2287
+ if (file.type.includes('pdf') || file.type.includes('text'))
2288
+ return jsx(FileText, { size: 20 });
2289
+ return jsx(File$1, { size: 20 });
2290
+ }
2291
+ function formatBytes(bytes) {
2292
+ if (bytes === 0)
2293
+ return '0 B';
2294
+ const k = 1024;
2295
+ const sizes = ['B', 'KB', 'MB', 'GB'];
2296
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
2297
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
2298
+ }
2299
+ let fileIdCounter = 0;
2300
+ function FileUpload({ accept, maxFiles, maxSize, multiple = true, disabled = false, label, helperText, error, files = [], onFilesChange, onFileRemove, showPreview = true, className, }) {
2301
+ const onDrop = useCallback((acceptedFiles, rejectedFiles) => {
2302
+ const newFiles = acceptedFiles.map((file) => ({
2303
+ file,
2304
+ id: `file-${++fileIdCounter}`,
2305
+ preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,
2306
+ progress: 0,
2307
+ }));
2308
+ const rejectedMapped = rejectedFiles.map(({ file, errors }) => ({
2309
+ file,
2310
+ id: `file-${++fileIdCounter}`,
2311
+ error: errors[0]?.message ?? 'File rejected',
2312
+ }));
2313
+ onFilesChange?.([...files, ...newFiles, ...rejectedMapped]);
2314
+ }, [files, onFilesChange]);
2315
+ const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({
2316
+ onDrop,
2317
+ accept,
2318
+ maxFiles,
2319
+ maxSize,
2320
+ multiple,
2321
+ disabled,
2322
+ });
2323
+ return (jsxs("div", { className: cn('flex flex-col gap-3', className), children: [label && jsx("p", { className: "text-sm font-medium text-text", children: label }), jsxs("div", { ...getRootProps(), className: cn('relative flex flex-col items-center justify-center gap-3 p-8 rounded-lg border-2 border-dashed', 'transition-colors cursor-pointer outline-none', isDragActive && !isDragReject && 'border-primary bg-primary/5', isDragReject && 'border-error bg-error-bg', !isDragActive && !isDragReject && 'border-border hover:border-primary hover:bg-surface-hover', disabled && 'opacity-50 cursor-not-allowed pointer-events-none', error && 'border-error'), children: [jsx("input", { ...getInputProps() }), jsx("div", { className: cn('flex items-center justify-center w-12 h-12 rounded-full', isDragReject ? 'bg-error-bg text-error' : 'bg-surface-hover text-text-muted'), children: isDragReject ? jsx(AlertCircle, { size: 24 }) : jsx(Upload, { size: 24 }) }), jsxs("div", { className: "text-center", children: [jsx("p", { className: "text-sm font-medium text-text", children: isDragActive
2324
+ ? isDragReject
2325
+ ? 'File not accepted'
2326
+ : 'Drop files here'
2327
+ : 'Drag & drop files here' }), jsxs("p", { className: "text-xs text-text-muted mt-1", children: ["or", ' ', jsx("span", { className: "text-primary font-medium cursor-pointer hover:underline", children: "browse files" })] }), (maxSize || accept) && (jsxs("p", { className: "text-xs text-text-muted mt-2", children: [accept && `Accepted: ${Object.values(accept).flat().join(', ')} · `, maxSize && `Max size: ${formatBytes(maxSize)}`, maxFiles && ` · Max files: ${maxFiles}`] }))] })] }), error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText }), showPreview && files.length > 0 && (jsx("ul", { className: "flex flex-col gap-2", children: files.map((uploadedFile) => (jsxs("li", { className: cn('flex items-center gap-3 p-3 rounded-lg border bg-surface', uploadedFile.error ? 'border-error bg-error-bg' : 'border-border'), children: [jsx("div", { className: "flex-shrink-0 w-10 h-10 rounded-md overflow-hidden bg-surface-hover flex items-center justify-center text-text-muted", children: uploadedFile.preview ? (jsx("img", { src: uploadedFile.preview, alt: uploadedFile.file.name, className: "w-full h-full object-cover" })) : (getFileIcon(uploadedFile.file)) }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("p", { className: "text-sm font-medium text-text truncate", children: uploadedFile.file.name }), uploadedFile.error ? (jsx("p", { className: "text-xs text-error", children: uploadedFile.error })) : (jsx("p", { className: "text-xs text-text-muted", children: formatBytes(uploadedFile.file.size) })), uploadedFile.progress !== undefined && uploadedFile.progress < 100 && !uploadedFile.error && (jsx("div", { className: "mt-1.5 h-1 w-full bg-surface-hover rounded-full overflow-hidden", children: jsx("div", { className: "h-full bg-primary rounded-full transition-all", style: { width: `${uploadedFile.progress}%` } }) }))] }), jsx("button", { type: "button", onClick: () => onFileRemove?.(uploadedFile.id), className: "flex-shrink-0 flex items-center justify-center w-6 h-6 rounded-full text-text-muted hover:text-error hover:bg-error-bg transition-colors", "aria-label": `Remove ${uploadedFile.file.name}`, children: jsx(X, { size: 14 }) })] }, uploadedFile.id))) }))] }));
2328
+ }
2329
+
2330
+ const shadowClasses$1 = {
2331
+ none: '',
2332
+ sm: 'shadow-sm',
2333
+ md: 'shadow-md',
2334
+ lg: 'shadow-lg',
2335
+ };
2336
+ const paddingClasses$1 = {
2337
+ none: '',
2338
+ sm: 'p-3',
2339
+ md: 'p-4',
2340
+ lg: 'p-6',
2341
+ };
2342
+ function Card({ header, footer, hoverable = false, clickable = false, bordered = true, shadow = 'sm', padding = 'md', className, children, onClick, ...props }) {
2343
+ return (jsxs("div", { className: cn('rounded-lg bg-surface', bordered && 'border border-border', shadowClasses$1[shadow], hoverable && 'transition-shadow hover:shadow-md', (clickable || onClick) && 'cursor-pointer hover:shadow-md transition-shadow', className), onClick: onClick, role: clickable || onClick ? 'button' : undefined, tabIndex: clickable || onClick ? 0 : undefined, onKeyDown: (clickable || onClick)
2344
+ ? (e) => { if (e.key === 'Enter' || e.key === ' ')
2345
+ onClick?.(e); }
2346
+ : undefined, ...props, children: [header && (jsx("div", { className: cn('border-b border-border', paddingClasses$1[padding]), children: header })), jsx("div", { className: cn(paddingClasses$1[padding]), children: children }), footer && (jsx("div", { className: cn('border-t border-border', paddingClasses$1[padding]), children: footer }))] }));
2347
+ }
2348
+
2349
+ const bgClasses = {
2350
+ surface: 'bg-surface',
2351
+ background: 'bg-background',
2352
+ primary: 'bg-primary text-primary-foreground',
2353
+ secondary: 'bg-secondary text-secondary-foreground',
2354
+ error: 'bg-error-bg text-error',
2355
+ success: 'bg-success-bg text-success',
2356
+ warning: 'bg-warning-bg text-warning',
2357
+ info: 'bg-info-bg text-info',
2358
+ };
2359
+ const roundedClasses$1 = {
2360
+ none: 'rounded-none',
2361
+ sm: 'rounded-sm',
2362
+ md: 'rounded-md',
2363
+ lg: 'rounded-lg',
2364
+ xl: 'rounded-xl',
2365
+ full: 'rounded-full',
2366
+ };
2367
+ const shadowClasses = {
2368
+ none: '',
2369
+ sm: 'shadow-sm',
2370
+ md: 'shadow-md',
2371
+ lg: 'shadow-lg',
2372
+ };
2373
+ const paddingClasses = {
2374
+ none: '',
2375
+ xs: 'p-1',
2376
+ sm: 'p-2',
2377
+ md: 'p-4',
2378
+ lg: 'p-6',
2379
+ xl: 'p-8',
2380
+ };
2381
+ function Box({ as: Component = 'div', bg, rounded, shadow, bordered = false, padding, className, children, ...props }) {
2382
+ return (jsx(Component, { className: cn(bg && bgClasses[bg], rounded && roundedClasses$1[rounded], shadow && shadowClasses[shadow], bordered && 'border border-border', padding && paddingClasses[padding], className), ...props, children: children }));
2383
+ }
2384
+
2385
+ const ratioClasses = {
2386
+ '1/1': 'aspect-square',
2387
+ '4/3': 'aspect-[4/3]',
2388
+ '16/9': 'aspect-video',
2389
+ '3/2': 'aspect-[3/2]',
2390
+ '2/3': 'aspect-[2/3]',
2391
+ auto: '',
2392
+ };
2393
+ const fitClasses = {
2394
+ cover: 'object-cover',
2395
+ contain: 'object-contain',
2396
+ fill: 'object-fill',
2397
+ none: 'object-none',
2398
+ 'scale-down': 'object-scale-down',
2399
+ };
2400
+ const roundedClasses = {
2401
+ none: 'rounded-none',
2402
+ sm: 'rounded-sm',
2403
+ md: 'rounded-md',
2404
+ lg: 'rounded-lg',
2405
+ xl: 'rounded-xl',
2406
+ full: 'rounded-full',
2407
+ };
2408
+ function Image({ src, fallbackSrc, fallbackIcon, fit = 'cover', ratio = 'auto', lazy = true, rounded = 'none', blur = false, containerClassName, className, alt = '', ...props }) {
2409
+ const [error, setError] = useState(false);
2410
+ const [usedFallback, setUsedFallback] = useState(false);
2411
+ const [loaded, setLoaded] = useState(false);
2412
+ const handleError = () => {
2413
+ if (!usedFallback && fallbackSrc) {
2414
+ setUsedFallback(true);
2415
+ }
2416
+ else {
2417
+ setError(true);
2418
+ }
2419
+ };
2420
+ const currentSrc = usedFallback && fallbackSrc ? fallbackSrc : src;
2421
+ if (error) {
2422
+ return (jsx("div", { className: cn('flex items-center justify-center bg-surface-hover text-text-muted', ratioClasses[ratio], roundedClasses[rounded], containerClassName), children: fallbackIcon ?? jsx(ImageOff, { size: 24 }) }));
2423
+ }
2424
+ return (jsx("div", { className: cn('overflow-hidden', ratioClasses[ratio], roundedClasses[rounded], ratio !== 'auto' && 'relative', containerClassName), children: jsx("img", { src: currentSrc, alt: alt, loading: lazy ? 'lazy' : 'eager', onError: handleError, onLoad: () => setLoaded(true), className: cn(ratio !== 'auto' ? 'absolute inset-0 w-full h-full' : 'w-full h-full', fitClasses[fit], roundedClasses[rounded], blur && !loaded && 'blur-sm', loaded && blur && 'blur-0', 'transition-[filter] duration-300', className), ...props }) }));
2425
+ }
2426
+
2427
+ const sizeClasses$8 = {
2428
+ xs: { container: 'w-6 h-6', text: 'text-xs', icon: 12 },
2429
+ sm: { container: 'w-8 h-8', text: 'text-sm', icon: 14 },
2430
+ md: { container: 'w-10 h-10', text: 'text-sm', icon: 16 },
2431
+ lg: { container: 'w-12 h-12', text: 'text-base', icon: 20 },
2432
+ xl: { container: 'w-16 h-16', text: 'text-lg', icon: 24 },
2433
+ '2xl': { container: 'w-20 h-20', text: 'text-xl', icon: 28 },
2434
+ };
2435
+ const statusColors = {
2436
+ online: 'bg-success',
2437
+ offline: 'bg-text-muted',
2438
+ away: 'bg-warning',
2439
+ busy: 'bg-error',
2440
+ };
2441
+ function getInitials(name) {
2442
+ return name
2443
+ .split(' ')
2444
+ .slice(0, 2)
2445
+ .map((n) => n[0])
2446
+ .join('')
2447
+ .toUpperCase();
2448
+ }
2449
+ function stringToColor(str) {
2450
+ let hash = 0;
2451
+ for (let i = 0; i < str.length; i++) {
2452
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
2453
+ }
2454
+ const colors = [
2455
+ '#6366f1', '#8b5cf6', '#ec4899', '#ef4444',
2456
+ '#f59e0b', '#10b981', '#3b82f6', '#06b6d4',
2457
+ ];
2458
+ return colors[Math.abs(hash) % colors.length];
2459
+ }
2460
+ function Avatar({ src, alt, name, icon, size = 'md', status, rounded = true, className, }) {
2461
+ const [imgError, setImgError] = useState(false);
2462
+ const sz = sizeClasses$8[size];
2463
+ const initials = name ? getInitials(name) : null;
2464
+ const bgColor = name ? stringToColor(name) : undefined;
2465
+ return (jsxs("div", { className: cn('relative inline-flex flex-shrink-0', className), children: [jsx("div", { className: cn('flex items-center justify-center overflow-hidden', sz.container, rounded ? 'rounded-full' : 'rounded-md', !src || imgError ? 'text-white' : ''), style: { backgroundColor: !src || imgError ? bgColor ?? 'var(--color-secondary)' : undefined }, "aria-label": alt ?? name, children: src && !imgError ? (jsx("img", { src: src, alt: alt ?? name ?? '', className: "w-full h-full object-cover", onError: () => setImgError(true) })) : initials ? (jsx("span", { className: cn('font-semibold select-none', sz.text), children: initials })) : icon ? (jsx("span", { children: icon })) : (jsx(User, { size: sz.icon })) }), status && (jsx("span", { className: cn('absolute bottom-0 right-0 block rounded-full ring-2 ring-surface', statusColors[status], size === 'xs' || size === 'sm' ? 'w-2 h-2' : 'w-2.5 h-2.5'), "aria-label": status }))] }));
2466
+ }
2467
+ function AvatarGroup({ avatars, max = 4, size = 'md', className }) {
2468
+ const visible = avatars.slice(0, max);
2469
+ const overflow = avatars.length - max;
2470
+ const sz = sizeClasses$8[size];
2471
+ return (jsxs("div", { className: cn('flex items-center', className), children: [visible.map((avatar, i) => (jsx("div", { className: "-ml-2 first:ml-0 ring-2 ring-surface rounded-full", children: jsx(Avatar, { ...avatar, size: size }) }, i))), overflow > 0 && (jsxs("div", { className: cn('-ml-2 flex items-center justify-center rounded-full bg-surface-hover border-2 border-surface text-text-muted font-medium', sz.container, sz.text), children: ["+", overflow] }))] }));
2472
+ }
2473
+
2474
+ const badgeVariants = cva('inline-flex items-center justify-center font-medium rounded-full select-none', {
2475
+ variants: {
2476
+ variant: {
2477
+ primary: 'bg-primary text-primary-foreground',
2478
+ secondary: 'bg-secondary text-secondary-foreground',
2479
+ outline: 'border border-border text-text bg-transparent',
2480
+ success: 'bg-success-bg text-success',
2481
+ error: 'bg-error-bg text-error',
2482
+ warning: 'bg-warning-bg text-warning',
2483
+ info: 'bg-info-bg text-info',
2484
+ ghost: 'bg-surface-hover text-text-muted',
2485
+ },
2486
+ size: {
2487
+ sm: 'text-xs px-1.5 py-0.5 min-w-[1.25rem] h-4',
2488
+ md: 'text-xs px-2 py-0.5 min-w-[1.5rem] h-5',
2489
+ lg: 'text-sm px-2.5 py-1 min-w-[1.75rem] h-6',
2490
+ },
2491
+ dot: {
2492
+ true: 'w-2 h-2 p-0 min-w-0',
2493
+ false: '',
2494
+ },
2495
+ },
2496
+ defaultVariants: {
2497
+ variant: 'primary',
2498
+ size: 'md',
2499
+ dot: false,
2500
+ },
2501
+ });
2502
+ function Badge({ variant, size, dot, children, className }) {
2503
+ return (jsx("span", { className: cn(badgeVariants({ variant, size, dot }), className), children: !dot && children }));
2504
+ }
2505
+ const positionClasses$1 = {
2506
+ 'top-right': '-top-1 -right-1',
2507
+ 'top-left': '-top-1 -left-1',
2508
+ 'bottom-right': '-bottom-1 -right-1',
2509
+ 'bottom-left': '-bottom-1 -left-1',
2510
+ };
2511
+ function BadgeAnchor({ children, badge, position = 'top-right', className }) {
2512
+ return (jsxs("div", { className: cn('relative inline-flex', className), children: [children, badge && (jsx("span", { className: cn('absolute', positionClasses$1[position]), children: badge }))] }));
2513
+ }
2514
+
2515
+ const tagVariants = cva('inline-flex items-center gap-1 font-medium rounded-md select-none', {
2516
+ variants: {
2517
+ variant: {
2518
+ default: 'bg-surface-hover text-text border border-border',
2519
+ primary: 'bg-primary/10 text-primary border border-primary/20',
2520
+ success: 'bg-success-bg text-success border border-success/20',
2521
+ error: 'bg-error-bg text-error border border-error/20',
2522
+ warning: 'bg-warning-bg text-warning border border-warning/20',
2523
+ info: 'bg-info-bg text-info border border-info/20',
2524
+ },
2525
+ size: {
2526
+ sm: 'text-xs px-1.5 py-0.5',
2527
+ md: 'text-sm px-2 py-1',
2528
+ lg: 'text-sm px-3 py-1.5',
2529
+ },
2530
+ rounded: {
2531
+ true: 'rounded-full',
2532
+ false: '',
2533
+ },
2534
+ },
2535
+ defaultVariants: {
2536
+ variant: 'default',
2537
+ size: 'md',
2538
+ rounded: false,
2539
+ },
2540
+ });
2541
+ function Tag({ variant, size, rounded, children, icon, removable, onRemove, className }) {
2542
+ return (jsxs("span", { className: cn(tagVariants({ variant, size, rounded }), className), children: [icon && jsx("span", { className: "flex items-center w-3.5 h-3.5", children: icon }), children, removable && (jsx("button", { type: "button", onClick: onRemove, className: "flex items-center justify-center w-3.5 h-3.5 rounded-full hover:bg-black/10 transition-colors ml-0.5", "aria-label": "Remove tag", children: jsx(X, { size: 10 }) }))] }));
2543
+ }
2544
+
2545
+ const sizeMap$1 = {
2546
+ xs: 12,
2547
+ sm: 14,
2548
+ md: 16,
2549
+ lg: 20,
2550
+ xl: 24,
2551
+ };
2552
+ function Icon({ icon: LucideIconComponent, size = 'md', color, className, ...props }) {
2553
+ const resolvedSize = typeof size === 'number' ? size : sizeMap$1[size];
2554
+ return (jsx(LucideIconComponent, { size: resolvedSize, color: color, className: cn('flex-shrink-0', className), "aria-hidden": "true", ...props }));
2555
+ }
2556
+
2557
+ function SVG({ src, children, width, height, size, color, className, title, role = 'img', viewBox = '0 0 24 24', fill = 'none', stroke = 'currentColor', ...props }) {
2558
+ const resolvedWidth = size ?? width ?? 24;
2559
+ const resolvedHeight = size ?? height ?? 24;
2560
+ if (src) {
2561
+ return (jsx("img", { src: src, width: resolvedWidth, height: resolvedHeight, className: cn('flex-shrink-0', className), alt: props['aria-label'] ?? title ?? '', "aria-hidden": props['aria-hidden'], style: { color } }));
2562
+ }
2563
+ return (jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: resolvedWidth, height: resolvedHeight, viewBox: viewBox, fill: fill, stroke: stroke, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", role: role, className: cn('flex-shrink-0', className), style: { color }, "aria-label": props['aria-label'], "aria-hidden": props['aria-hidden'], children: [title && jsx("title", { children: title }), children] }));
2564
+ }
2565
+
2566
+ function Table({ columns, data, keyExtractor, selectable = false, selectedKeys = [], onSelectionChange, sortKey, sortDirection, onSortChange, loading = false, emptyMessage = 'No data available', stickyHeader = false, striped = false, hoverable = true, bordered = false, compact = false, className, }) {
2567
+ const [internalSort, setInternalSort] = useState({
2568
+ key: '',
2569
+ dir: null,
2570
+ });
2571
+ const effectiveSortKey = sortKey ?? internalSort.key;
2572
+ const effectiveSortDir = sortDirection ?? internalSort.dir;
2573
+ const handleSort = (key) => {
2574
+ let next = 'asc';
2575
+ if (effectiveSortKey === key) {
2576
+ next = effectiveSortDir === 'asc' ? 'desc' : effectiveSortDir === 'desc' ? null : 'asc';
2577
+ }
2578
+ if (onSortChange) {
2579
+ onSortChange(key, next);
2580
+ }
2581
+ else {
2582
+ setInternalSort({ key, dir: next });
2583
+ }
2584
+ };
2585
+ const sortedData = useMemo(() => {
2586
+ if (!effectiveSortKey || !effectiveSortDir)
2587
+ return data;
2588
+ const col = columns.find((c) => c.key === effectiveSortKey);
2589
+ if (!col)
2590
+ return data;
2591
+ return [...data].sort((a, b) => {
2592
+ const aVal = col.accessor ? String(col.accessor(a) ?? '') : String(a[effectiveSortKey] ?? '');
2593
+ const bVal = col.accessor ? String(col.accessor(b) ?? '') : String(b[effectiveSortKey] ?? '');
2594
+ const cmp = aVal.localeCompare(bVal, undefined, { numeric: true });
2595
+ return effectiveSortDir === 'asc' ? cmp : -cmp;
2596
+ });
2597
+ }, [data, effectiveSortKey, effectiveSortDir, columns]);
2598
+ const allKeys = sortedData.map(keyExtractor);
2599
+ const allSelected = allKeys.length > 0 && allKeys.every((k) => selectedKeys.includes(k));
2600
+ const someSelected = allKeys.some((k) => selectedKeys.includes(k)) && !allSelected;
2601
+ const toggleAll = () => {
2602
+ if (allSelected) {
2603
+ onSelectionChange?.(selectedKeys.filter((k) => !allKeys.includes(k)));
2604
+ }
2605
+ else {
2606
+ onSelectionChange?.([...new Set([...selectedKeys, ...allKeys])]);
2607
+ }
2608
+ };
2609
+ const toggleRow = (key) => {
2610
+ if (selectedKeys.includes(key)) {
2611
+ onSelectionChange?.(selectedKeys.filter((k) => k !== key));
2612
+ }
2613
+ else {
2614
+ onSelectionChange?.([...selectedKeys, key]);
2615
+ }
2616
+ };
2617
+ const cellPad = compact ? 'px-3 py-2' : 'px-4 py-3';
2618
+ return (jsx("div", { className: cn('w-full overflow-auto rounded-lg', bordered && 'border border-border', className), children: jsxs("table", { className: "w-full text-sm border-collapse", children: [jsx("thead", { className: cn(stickyHeader && 'sticky top-0 z-10'), children: jsxs("tr", { className: "bg-surface-hover border-b border-border", children: [selectable && (jsx("th", { className: cn('w-10', cellPad), children: jsx(Checkbox, { checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: toggleAll, "aria-label": "Select all rows" }) })), columns.map((col) => (jsx("th", { className: cn('text-left font-semibold text-text-muted whitespace-nowrap', cellPad, col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.sortable && 'cursor-pointer select-none hover:text-text', col.className), style: { width: col.width }, onClick: col.sortable ? () => handleSort(col.key) : undefined, children: jsxs("span", { className: "inline-flex items-center gap-1", children: [col.header, col.sortable && (jsx("span", { className: "flex-shrink-0", children: effectiveSortKey === col.key ? (effectiveSortDir === 'asc' ? (jsx(ChevronUp, { size: 14 })) : effectiveSortDir === 'desc' ? (jsx(ChevronDown, { size: 14 })) : (jsx(ChevronsUpDown, { size: 14, className: "opacity-40" }))) : (jsx(ChevronsUpDown, { size: 14, className: "opacity-40" })) }))] }) }, col.key)))] }) }), jsx("tbody", { children: loading ? (Array.from({ length: 5 }).map((_, i) => (jsxs("tr", { className: "border-b border-border", children: [selectable && jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 w-4 rounded bg-surface-hover animate-pulse" }) }), columns.map((col) => (jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 rounded bg-surface-hover animate-pulse", style: { width: `${60 + Math.random() * 40}%` } }) }, col.key)))] }, i)))) : sortedData.length === 0 ? (jsx("tr", { children: jsx("td", { colSpan: columns.length + (selectable ? 1 : 0), className: "text-center py-12 text-text-muted", children: emptyMessage }) })) : (sortedData.map((row, i) => {
2619
+ const key = keyExtractor(row, i);
2620
+ const isSelected = selectedKeys.includes(key);
2621
+ return (jsxs("tr", { className: cn('border-b border-border transition-colors', striped && i % 2 === 1 && 'bg-surface-hover/50', hoverable && 'hover:bg-surface-hover', isSelected && 'bg-primary/5'), children: [selectable && (jsx("td", { className: cellPad, children: jsx(Checkbox, { checked: isSelected, onCheckedChange: () => toggleRow(key), "aria-label": `Select row ${i + 1}` }) })), columns.map((col) => (jsx("td", { className: cn('text-text', cellPad, col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.className), children: col.accessor
2622
+ ? col.accessor(row)
2623
+ : String(row[col.key] ?? '') }, col.key)))] }, key));
2624
+ })) })] }) }));
2625
+ }
2626
+
2627
+ const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
2628
+ function DataTable({ columns, data, keyExtractor, selectable = false, selectedKeys = [], onSelectionChange, sortKey, sortDirection, onSortChange, loading = false, emptyMessage = 'No data available', stickyHeader = false, striped = false, hoverable = true, bordered = false, compact = false, className, pagination, toolbar, rowActions, caption, }) {
2629
+ const [internalSort, setInternalSort] = useState({ key: '', dir: null });
2630
+ const [internalPage, setInternalPage] = useState(1);
2631
+ const [internalPageSize, setInternalPageSize] = useState(pagination?.pageSize ?? 10);
2632
+ const effectiveSortKey = sortKey ?? internalSort.key;
2633
+ const effectiveSortDir = sortDirection ?? internalSort.dir;
2634
+ const handleSort = (key) => {
2635
+ let next = 'asc';
2636
+ if (effectiveSortKey === key) {
2637
+ next = effectiveSortDir === 'asc' ? 'desc' : effectiveSortDir === 'desc' ? null : 'asc';
2638
+ }
2639
+ if (onSortChange) {
2640
+ onSortChange(key, next);
2641
+ }
2642
+ else {
2643
+ setInternalSort({ key, dir: next });
2644
+ }
2645
+ };
2646
+ const sortedData = useMemo(() => {
2647
+ if (!effectiveSortKey || !effectiveSortDir)
2648
+ return data;
2649
+ const col = columns.find((c) => c.key === effectiveSortKey);
2650
+ if (!col)
2651
+ return data;
2652
+ return [...data].sort((a, b) => {
2653
+ // Priority: explicit sortValue fn > raw row field (never stringify React elements from accessor)
2654
+ const getRaw = (row) => {
2655
+ if (col.sortValue)
2656
+ return col.sortValue(row);
2657
+ const raw = row[effectiveSortKey];
2658
+ return typeof raw === 'number' ? raw : String(raw ?? '');
2659
+ };
2660
+ const aVal = getRaw(a);
2661
+ const bVal = getRaw(b);
2662
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
2663
+ return effectiveSortDir === 'asc' ? aVal - bVal : bVal - aVal;
2664
+ }
2665
+ const cmp = String(aVal).localeCompare(String(bVal), undefined, { numeric: true });
2666
+ return effectiveSortDir === 'asc' ? cmp : -cmp;
2667
+ });
2668
+ }, [data, effectiveSortKey, effectiveSortDir, columns]);
2669
+ // Pagination
2670
+ pagination?.mode === 'client';
2671
+ const isServerPagination = pagination?.mode === 'server';
2672
+ const hasPagination = !!pagination;
2673
+ const currentPage = isServerPagination && pagination.page !== undefined ? pagination.page : internalPage;
2674
+ const pageSize = internalPageSize;
2675
+ const pageSizeOptions = pagination?.pageSizeOptions ?? PAGE_SIZE_OPTIONS;
2676
+ const totalRows = isServerPagination ? (pagination.totalRows ?? data.length) : sortedData.length;
2677
+ const totalPages = Math.max(1, Math.ceil(totalRows / pageSize));
2678
+ const pagedData = useMemo(() => {
2679
+ if (!hasPagination || isServerPagination)
2680
+ return sortedData;
2681
+ const start = (currentPage - 1) * pageSize;
2682
+ return sortedData.slice(start, start + pageSize);
2683
+ }, [sortedData, hasPagination, isServerPagination, currentPage, pageSize]);
2684
+ const displayData = hasPagination ? pagedData : sortedData;
2685
+ const goToPage = (page) => {
2686
+ const clamped = Math.max(1, Math.min(page, totalPages));
2687
+ if (isServerPagination) {
2688
+ pagination.onPageChange?.(clamped);
2689
+ }
2690
+ else {
2691
+ setInternalPage(clamped);
2692
+ }
2693
+ };
2694
+ const changePageSize = (size) => {
2695
+ setInternalPageSize(size);
2696
+ setInternalPage(1);
2697
+ if (isServerPagination) {
2698
+ pagination.onPageSizeChange?.(size);
2699
+ pagination.onPageChange?.(1);
2700
+ }
2701
+ };
2702
+ // Selection
2703
+ const allKeys = displayData.map(keyExtractor);
2704
+ const allSelected = allKeys.length > 0 && allKeys.every((k) => selectedKeys.includes(k));
2705
+ const someSelected = allKeys.some((k) => selectedKeys.includes(k)) && !allSelected;
2706
+ const toggleAll = () => {
2707
+ if (allSelected) {
2708
+ onSelectionChange?.(selectedKeys.filter((k) => !allKeys.includes(k)));
2709
+ }
2710
+ else {
2711
+ onSelectionChange?.([...new Set([...selectedKeys, ...allKeys])]);
2712
+ }
2713
+ };
2714
+ const toggleRow = (key) => {
2715
+ if (selectedKeys.includes(key)) {
2716
+ onSelectionChange?.(selectedKeys.filter((k) => k !== key));
2717
+ }
2718
+ else {
2719
+ onSelectionChange?.([...selectedKeys, key]);
2720
+ }
2721
+ };
2722
+ const cellPad = compact ? 'px-3 py-2' : 'px-4 py-3';
2723
+ const startRow = hasPagination ? (currentPage - 1) * pageSize + 1 : 1;
2724
+ const endRow = hasPagination ? Math.min(currentPage * pageSize, totalRows) : totalRows;
2725
+ return (jsxs("div", { className: cn('flex flex-col gap-0 w-full', className), children: [toolbar && (jsx("div", { className: "flex items-center justify-between gap-3 px-1 pb-3", children: toolbar })), jsx("div", { className: cn('w-full overflow-auto rounded-lg', bordered && 'border border-border'), children: jsxs("table", { className: "w-full text-sm border-collapse", children: [caption && jsx("caption", { className: "text-xs text-text-muted pb-2 text-left", children: caption }), jsx("thead", { className: cn(stickyHeader && 'sticky top-0 z-10'), children: jsxs("tr", { className: "bg-surface-hover border-b border-border", children: [selectable && (jsx("th", { className: cn('w-10', cellPad), children: jsx(Checkbox, { checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: toggleAll, "aria-label": "Select all rows" }) })), columns.map((col) => (jsx("th", { className: cn('text-left font-semibold text-text-muted whitespace-nowrap', cellPad, col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.sortable && 'cursor-pointer select-none hover:text-text', col.className), style: { width: col.width }, onClick: col.sortable ? () => handleSort(col.key) : undefined, children: jsxs("span", { className: "inline-flex items-center gap-1", children: [col.header, col.sortable && (jsx("span", { className: "flex-shrink-0", children: effectiveSortKey === col.key ? (effectiveSortDir === 'asc' ? (jsx(ChevronUp, { size: 14 })) : effectiveSortDir === 'desc' ? (jsx(ChevronDown, { size: 14 })) : (jsx(ChevronsUpDown, { size: 14, className: "opacity-40" }))) : (jsx(ChevronsUpDown, { size: 14, className: "opacity-40" })) }))] }) }, col.key))), rowActions && (jsx("th", { className: cn('text-right font-semibold text-text-muted whitespace-nowrap', cellPad), children: "Actions" }))] }) }), jsx("tbody", { children: loading ? (Array.from({ length: pageSize > 5 ? 5 : pageSize }).map((_, i) => (jsxs("tr", { className: "border-b border-border", children: [selectable && jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 w-4 rounded bg-surface-hover animate-pulse" }) }), columns.map((col) => (jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 rounded bg-surface-hover animate-pulse", style: { width: `${60 + (col.key.length * 7) % 40}%` } }) }, col.key))), rowActions && jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 w-16 rounded bg-surface-hover animate-pulse ml-auto" }) })] }, i)))) : displayData.length === 0 ? (jsx("tr", { children: jsx("td", { colSpan: columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0), className: "text-center py-12 text-text-muted", children: emptyMessage }) })) : (displayData.map((row, i) => {
2726
+ const key = keyExtractor(row, i);
2727
+ const isSelected = selectedKeys.includes(key);
2728
+ return (jsxs("tr", { className: cn('border-b border-border transition-colors', striped && i % 2 === 1 && 'bg-surface-hover/50', hoverable && 'hover:bg-surface-hover', isSelected && 'bg-primary/5'), children: [selectable && (jsx("td", { className: cellPad, children: jsx(Checkbox, { checked: isSelected, onCheckedChange: () => toggleRow(key), "aria-label": `Select row ${i + 1}` }) })), columns.map((col) => (jsx("td", { className: cn('text-text', cellPad, col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.className), children: col.accessor
2729
+ ? col.accessor(row)
2730
+ : String(row[col.key] ?? '') }, col.key))), rowActions && (jsx("td", { className: cn(cellPad, 'text-right'), children: rowActions(row) }))] }, key));
2731
+ })) })] }) }), hasPagination && (jsxs("div", { className: "flex items-center justify-between gap-4 px-1 pt-3 flex-wrap", children: [jsxs("div", { className: "flex items-center gap-2 text-sm text-text-muted", children: [jsx("span", { children: "Rows per page:" }), jsx("select", { value: pageSize, onChange: (e) => changePageSize(Number(e.target.value)), className: "h-7 px-2 rounded-md border border-border bg-surface text-text text-xs outline-none focus:border-border-focus", children: pageSizeOptions.map((s) => (jsx("option", { value: s, children: s }, s))) })] }), jsxs("div", { className: "flex items-center gap-1 text-sm text-text-muted", children: [jsx("span", { className: "mr-2", children: loading ? '…' : `${startRow}–${endRow} of ${totalRows}` }), jsx("button", { onClick: () => goToPage(1), disabled: currentPage === 1 || loading, className: "p-1 rounded hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed transition-colors", "aria-label": "First page", children: jsx(ChevronsLeft, { size: 16 }) }), jsx("button", { onClick: () => goToPage(currentPage - 1), disabled: currentPage === 1 || loading, className: "p-1 rounded hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed transition-colors", "aria-label": "Previous page", children: jsx(ChevronLeft, { size: 16 }) }), jsx("div", { className: "flex items-center gap-0.5", children: Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
2732
+ let page;
2733
+ if (totalPages <= 5) {
2734
+ page = i + 1;
2735
+ }
2736
+ else if (currentPage <= 3) {
2737
+ page = i + 1;
2738
+ }
2739
+ else if (currentPage >= totalPages - 2) {
2740
+ page = totalPages - 4 + i;
2741
+ }
2742
+ else {
2743
+ page = currentPage - 2 + i;
2744
+ }
2745
+ return (jsx("button", { onClick: () => goToPage(page), disabled: loading, className: cn('w-7 h-7 rounded text-xs font-medium transition-colors', page === currentPage
2746
+ ? 'bg-primary text-primary-foreground'
2747
+ : 'hover:bg-surface-hover text-text-muted'), children: page }, page));
2748
+ }) }), jsx("button", { onClick: () => goToPage(currentPage + 1), disabled: currentPage === totalPages || loading, className: "p-1 rounded hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed transition-colors", "aria-label": "Next page", children: jsx(ChevronRight, { size: 16 }) }), jsx("button", { onClick: () => goToPage(totalPages), disabled: currentPage === totalPages || loading, className: "p-1 rounded hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed transition-colors", "aria-label": "Last page", children: jsx(ChevronsRight, { size: 16 }) })] })] }))] }));
2749
+ }
2750
+
2751
+ function ListItem({ primary, secondary, leading, trailing, onClick, active, disabled, divider, className, }) {
2752
+ return (jsxs("li", { className: cn('flex items-center gap-3 px-3 py-2.5', onClick && !disabled && 'cursor-pointer hover:bg-surface-hover transition-colors', active && 'bg-primary/5 text-primary', disabled && 'opacity-50 cursor-not-allowed', divider && 'border-b border-border', className), onClick: !disabled ? onClick : undefined, role: onClick ? 'button' : undefined, tabIndex: onClick && !disabled ? 0 : undefined, children: [leading && (jsx("div", { className: "flex-shrink-0 flex items-center", children: leading })), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("p", { className: "text-sm font-medium text-text truncate", children: primary }), secondary && (jsx("p", { className: "text-xs text-text-muted truncate mt-0.5", children: secondary }))] }), trailing && (jsx("div", { className: "flex-shrink-0 flex items-center", children: trailing }))] }));
2753
+ }
2754
+ function List({ items, ordered = false, divided = false, hoverable = true, compact = false, children, className, }) {
2755
+ const Tag = ordered ? 'ol' : 'ul';
2756
+ return (jsx(Tag, { className: cn('rounded-lg bg-surface border border-border overflow-hidden', divided && '[&>li]:border-b [&>li]:border-border [&>li:last-child]:border-0', compact && '[&>li]:py-1.5', className), children: items
2757
+ ? items.map((item, i) => (jsx(ListItem, { ...item, className: cn(!hoverable && 'cursor-default hover:bg-transparent', item.className) }, item.id ?? i)))
2758
+ : children }));
2759
+ }
2760
+
2761
+ const variantClasses$3 = {
2762
+ default: 'bg-surface-hover text-text-muted border-border',
2763
+ success: 'bg-success-bg text-success border-success/30',
2764
+ error: 'bg-error-bg text-error border-error/30',
2765
+ warning: 'bg-warning-bg text-warning border-warning/30',
2766
+ info: 'bg-info-bg text-info border-info/30',
2767
+ };
2768
+ const connectorVariant = {
2769
+ default: 'bg-border',
2770
+ success: 'bg-success/30',
2771
+ error: 'bg-error/30',
2772
+ warning: 'bg-warning/30',
2773
+ info: 'bg-info/30',
2774
+ };
2775
+ function Timeline({ items, orientation = 'vertical', className }) {
2776
+ return (jsx("ol", { className: cn('flex', orientation === 'vertical' ? 'flex-col' : 'flex-row', className), children: items.map((item, index) => {
2777
+ const isLast = index === items.length - 1;
2778
+ const variant = item.variant ?? 'default';
2779
+ return (jsxs("li", { className: cn('flex', orientation === 'vertical' ? 'flex-row gap-4' : 'flex-col items-center flex-1'), children: [jsxs("div", { className: cn('flex flex-shrink-0', orientation === 'vertical' ? 'flex-col items-center' : 'flex-row items-center w-full'), children: [jsx("div", { className: cn('flex items-center justify-center w-8 h-8 rounded-full border flex-shrink-0', variantClasses$3[variant]), children: item.icon ?? (jsx("span", { className: "w-2 h-2 rounded-full bg-current" })) }), !isLast && (jsx("div", { className: cn(connectorVariant[variant], orientation === 'vertical' ? 'w-0.5 flex-1 my-1 min-h-[1.5rem]' : 'h-0.5 flex-1 mx-1') }))] }), jsxs("div", { className: cn('pb-6', isLast && 'pb-0', orientation === 'horizontal' && 'text-center mt-2 px-2'), children: [jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [jsx("p", { className: "text-sm font-medium text-text", children: item.title }), item.timestamp && (jsx("span", { className: "text-xs text-text-muted", children: item.timestamp }))] }), item.description && (jsx("p", { className: "text-sm text-text-muted mt-0.5", children: item.description }))] })] }, item.id));
2780
+ }) }));
2781
+ }
2782
+
2783
+ const iconBgClasses = {
2784
+ primary: 'bg-primary/10 text-primary',
2785
+ success: 'bg-success-bg text-success',
2786
+ error: 'bg-error-bg text-error',
2787
+ warning: 'bg-warning-bg text-warning',
2788
+ info: 'bg-info-bg text-info',
2789
+ };
2790
+ function Stat({ label, value, delta, deltaLabel, icon, iconBg = 'primary', description, loading = false, className, }) {
2791
+ const isPositive = delta !== undefined && delta > 0;
2792
+ const isNegative = delta !== undefined && delta < 0;
2793
+ return (jsxs("div", { className: cn('flex flex-col gap-3 p-4 rounded-lg bg-surface border border-border shadow-sm', className), children: [jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsx("p", { className: "text-sm font-medium text-text-muted", children: label }), icon && (jsx("div", { className: cn('flex items-center justify-center w-10 h-10 rounded-lg flex-shrink-0', iconBgClasses[iconBg]), children: icon }))] }), loading ? (jsxs("div", { className: "space-y-2", children: [jsx("div", { className: "h-8 w-24 rounded bg-surface-hover animate-pulse" }), jsx("div", { className: "h-4 w-16 rounded bg-surface-hover animate-pulse" })] })) : (jsxs(Fragment, { children: [jsx("p", { className: "text-2xl font-bold text-text", children: value }), delta !== undefined && (jsxs("div", { className: "flex items-center gap-1.5", children: [jsxs("span", { className: cn('flex items-center gap-0.5 text-xs font-medium', isPositive && 'text-success', isNegative && 'text-error', !isPositive && !isNegative && 'text-text-muted'), children: [isPositive ? (jsx(TrendingUp, { size: 12 })) : isNegative ? (jsx(TrendingDown, { size: 12 })) : (jsx(Minus, { size: 12 })), Math.abs(delta), "%"] }), deltaLabel && (jsx("span", { className: "text-xs text-text-muted", children: deltaLabel }))] })), description && (jsx("p", { className: "text-xs text-text-muted", children: description }))] }))] }));
2794
+ }
2795
+
2796
+ function EmptyState({ title = 'No data found', description, illustration, icon, action, className, }) {
2797
+ return (jsxs("div", { className: cn('flex flex-col items-center justify-center gap-4 py-16 px-6 text-center', className), children: [illustration ? (jsx("div", { className: "mb-2", children: illustration })) : (jsx("div", { className: "flex items-center justify-center w-16 h-16 rounded-full bg-surface-hover text-text-muted", children: icon ?? jsx(Inbox, { size: 28 }) })), jsxs("div", { className: "flex flex-col gap-1.5 max-w-sm", children: [jsx("h3", { className: "text-base font-semibold text-text", children: title }), description && jsx("p", { className: "text-sm text-text-muted", children: description })] }), action && jsx("div", { className: "mt-2", children: action })] }));
2798
+ }
2799
+
2800
+ function MarkdownReader({ children, className, codeTheme = 'dark', showLineNumbers = false, }) {
2801
+ const prismTheme = codeTheme === 'dark' ? themes.oneDark : themes.oneLight;
2802
+ return (jsx("div", { className: cn('markdown-reader w-full', className), children: jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components: {
2803
+ // Headings
2804
+ h1: ({ children: c }) => (jsx("h1", { className: "text-3xl font-bold text-text mt-8 mb-4 pb-2 border-b border-border first:mt-0", children: c })),
2805
+ h2: ({ children: c }) => (jsx("h2", { className: "text-2xl font-semibold text-text mt-7 mb-3 pb-2 border-b border-border", children: c })),
2806
+ h3: ({ children: c }) => (jsx("h3", { className: "text-xl font-semibold text-text mt-6 mb-2", children: c })),
2807
+ h4: ({ children: c }) => (jsx("h4", { className: "text-lg font-semibold text-text mt-5 mb-2", children: c })),
2808
+ h5: ({ children: c }) => (jsx("h5", { className: "text-base font-semibold text-text mt-4 mb-1", children: c })),
2809
+ h6: ({ children: c }) => (jsx("h6", { className: "text-sm font-semibold text-text-muted mt-4 mb-1", children: c })),
2810
+ // Paragraph
2811
+ p: ({ children: c }) => (jsx("p", { className: "text-text leading-7 mb-4 last:mb-0", children: c })),
2812
+ // Links
2813
+ a: ({ href, children: c }) => (jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", className: "text-primary underline underline-offset-4 hover:opacity-80 transition-opacity", children: c })),
2814
+ // Lists
2815
+ ul: ({ children: c }) => (jsx("ul", { className: "list-disc list-outside pl-6 mb-4 space-y-1 text-text", children: c })),
2816
+ ol: ({ children: c }) => (jsx("ol", { className: "list-decimal list-outside pl-6 mb-4 space-y-1 text-text", children: c })),
2817
+ li: ({ children: c }) => (jsx("li", { className: "leading-7", children: c })),
2818
+ // Blockquote
2819
+ blockquote: ({ children: c }) => (jsx("blockquote", { className: "border-l-4 border-primary/40 pl-4 py-1 my-4 bg-primary/5 rounded-r-md text-text-muted italic", children: c })),
2820
+ // Inline code
2821
+ code: ({ children: c, className: cls }) => {
2822
+ const match = /language-(\w+)/.exec(cls ?? '');
2823
+ const language = match ? match[1] : null;
2824
+ const code = String(c).trimEnd();
2825
+ if (language) {
2826
+ return (jsxs("div", { className: "my-4 rounded-lg border border-border overflow-hidden", children: [jsx("div", { className: "flex items-center px-4 py-2 bg-surface border-b border-border", children: jsx("span", { className: "text-xs font-mono text-text-muted uppercase tracking-wide", children: language }) }), jsx(Highlight, { theme: prismTheme, code: code, language: language, children: ({ className: hlClass, style, tokens, getLineProps, getTokenProps }) => (jsx("pre", { className: cn('overflow-x-auto p-4 text-sm leading-relaxed m-0', hlClass), style: style, children: tokens.map((line, i) => (jsxs("div", { ...getLineProps({ line }), children: [showLineNumbers && (jsx("span", { className: "select-none pr-4 text-right inline-block w-8 opacity-40 text-xs", children: i + 1 })), line.map((token, key) => (jsx("span", { ...getTokenProps({ token }) }, key)))] }, i))) })) })] }));
2827
+ }
2828
+ return (jsx("code", { className: "inline rounded px-1.5 py-0.5 bg-surface-hover border border-border text-sm font-mono text-text", children: c }));
2829
+ },
2830
+ // Pre — handled by code above, suppress default pre wrapper
2831
+ pre: ({ children: c }) => jsx(Fragment, { children: c }),
2832
+ // Horizontal rule
2833
+ hr: () => jsx("hr", { className: "my-6 border-border" }),
2834
+ // Strong / em
2835
+ strong: ({ children: c }) => jsx("strong", { className: "font-semibold text-text", children: c }),
2836
+ em: ({ children: c }) => jsx("em", { className: "italic text-text", children: c }),
2837
+ del: ({ children: c }) => jsx("del", { className: "line-through text-text-muted", children: c }),
2838
+ // Table (GFM)
2839
+ table: ({ children: c }) => (jsx("div", { className: "my-4 w-full overflow-x-auto rounded-lg border border-border", children: jsx("table", { className: "w-full text-sm border-collapse", children: c }) })),
2840
+ thead: ({ children: c }) => (jsx("thead", { className: "bg-surface-hover border-b border-border", children: c })),
2841
+ tbody: ({ children: c }) => jsx("tbody", { children: c }),
2842
+ tr: ({ children: c }) => (jsx("tr", { className: "border-b border-border last:border-0 hover:bg-surface-hover/50 transition-colors", children: c })),
2843
+ th: ({ children: c }) => (jsx("th", { className: "px-4 py-2.5 text-left font-semibold text-text-muted whitespace-nowrap", children: c })),
2844
+ td: ({ children: c }) => (jsx("td", { className: "px-4 py-2.5 text-text", children: c })),
2845
+ // Task list items (GFM checkboxes)
2846
+ input: ({ type, checked }) => {
2847
+ if (type === 'checkbox') {
2848
+ return (jsx("input", { type: "checkbox", checked: checked, readOnly: true, className: "mr-2 accent-primary" }));
2849
+ }
2850
+ return null;
2851
+ },
2852
+ // Image
2853
+ img: ({ src, alt }) => (jsx("img", { src: src, alt: alt ?? '', className: "my-4 max-w-full rounded-lg border border-border" })),
2854
+ }, children: children }) }));
2855
+ }
2856
+
2857
+ function Collapsible({ title, children, defaultOpen = false, open: controlledOpen, onOpenChange, disabled = false, icon, variant = 'default', className, triggerClassName, contentClassName, }) {
2858
+ const id = useId();
2859
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
2860
+ const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
2861
+ const toggle = () => {
2862
+ if (disabled)
2863
+ return;
2864
+ const next = !open;
2865
+ setInternalOpen(next);
2866
+ onOpenChange?.(next);
2867
+ };
2868
+ const variantClasses = {
2869
+ default: '',
2870
+ bordered: 'border border-border rounded-lg overflow-hidden',
2871
+ filled: 'bg-surface rounded-lg overflow-hidden',
2872
+ };
2873
+ const triggerVariant = {
2874
+ default: 'hover:bg-surface-hover rounded-md',
2875
+ bordered: 'border-b border-border bg-surface-hover hover:bg-surface-active',
2876
+ filled: 'hover:bg-surface-active',
2877
+ };
2878
+ return (jsxs("div", { className: cn(variantClasses[variant], className), children: [jsxs("button", { type: "button", id: `${id}-trigger`, "aria-expanded": open, "aria-controls": `${id}-content`, onClick: toggle, disabled: disabled, className: cn('w-full flex items-center justify-between gap-3 px-3 py-2.5 text-left', 'transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-border-focus/40', 'disabled:opacity-50 disabled:cursor-not-allowed', triggerVariant[variant], triggerClassName), children: [jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [icon && jsx("span", { className: "flex-shrink-0 text-text-muted", children: icon }), jsx("span", { className: "text-sm font-medium text-text truncate", children: title })] }), jsx(ChevronDown, { size: 16, className: cn('flex-shrink-0 text-text-muted transition-transform duration-200', open && 'rotate-180') })] }), jsx("div", { id: `${id}-content`, role: "region", "aria-labelledby": `${id}-trigger`, className: cn('overflow-hidden transition-all duration-200', open ? 'max-h-[9999px] opacity-100' : 'max-h-0 opacity-0'), children: jsx("div", { className: cn('px-3 py-3', contentClassName), children: children }) })] }));
2879
+ }
2880
+
2881
+ function TreeNode({ item, depth, indent, selectedId, selectedIds, expandedIds, onSelect, onToggle, multiSelect, }) {
2882
+ const hasChildren = item.children && item.children.length > 0;
2883
+ const isExpanded = expandedIds.includes(item.id);
2884
+ const isSelected = multiSelect
2885
+ ? (selectedIds ?? []).includes(item.id)
2886
+ : selectedId === item.id;
2887
+ return (jsxs("li", { role: "treeitem", "aria-expanded": hasChildren ? isExpanded : undefined, "aria-selected": isSelected, children: [jsxs("div", { className: cn('flex items-center gap-1.5 px-2 py-1 rounded-md cursor-pointer select-none', 'transition-colors text-sm', isSelected
2888
+ ? 'bg-primary text-primary-foreground'
2889
+ : 'text-text hover:bg-surface-hover', item.disabled && 'opacity-50 cursor-not-allowed pointer-events-none'), style: { paddingLeft: `${depth * indent + 8}px` }, onClick: () => {
2890
+ if (item.disabled)
2891
+ return;
2892
+ if (hasChildren)
2893
+ onToggle(item.id);
2894
+ onSelect?.(item.id, item);
2895
+ }, onKeyDown: (e) => {
2896
+ if (e.key === 'Enter' || e.key === ' ') {
2897
+ e.preventDefault();
2898
+ if (item.disabled)
2899
+ return;
2900
+ if (hasChildren)
2901
+ onToggle(item.id);
2902
+ onSelect?.(item.id, item);
2903
+ }
2904
+ }, tabIndex: item.disabled ? -1 : 0, role: "button", children: [hasChildren ? (jsx("span", { className: "flex-shrink-0 text-text-muted", children: isExpanded ? jsx(ChevronDown, { size: 14 }) : jsx(ChevronRight, { size: 14 }) })) : (jsx("span", { className: "w-3.5 flex-shrink-0" })), item.icon && (jsx("span", { className: cn('flex-shrink-0', isSelected ? 'text-primary-foreground' : 'text-text-muted'), children: item.icon })), jsx("span", { className: "flex-1 truncate", children: item.label }), item.badge && jsx("span", { className: "flex-shrink-0", children: item.badge })] }), hasChildren && isExpanded && (jsx("ul", { role: "group", className: "mt-0.5", children: item.children.map((child) => (jsx(TreeNode, { item: child, depth: depth + 1, indent: indent, selectedId: selectedId, selectedIds: selectedIds, expandedIds: expandedIds, onSelect: onSelect, onToggle: onToggle, multiSelect: multiSelect }, child.id))) }))] }));
2905
+ }
2906
+ function TreeView({ items, selectedId, onSelect, defaultExpandedIds = [], expandedIds: controlledExpandedIds, onExpandedChange, multiSelect = false, selectedIds, onMultiSelect, indent = 16, className, }) {
2907
+ const [internalExpandedIds, setInternalExpandedIds] = useState(defaultExpandedIds);
2908
+ const expandedIds = controlledExpandedIds ?? internalExpandedIds;
2909
+ const handleToggle = (id) => {
2910
+ const next = expandedIds.includes(id)
2911
+ ? expandedIds.filter((e) => e !== id)
2912
+ : [...expandedIds, id];
2913
+ setInternalExpandedIds(next);
2914
+ onExpandedChange?.(next);
2915
+ };
2916
+ const handleSelect = (id, item) => {
2917
+ if (multiSelect && onMultiSelect) {
2918
+ const current = selectedIds ?? [];
2919
+ const next = current.includes(id)
2920
+ ? current.filter((s) => s !== id)
2921
+ : [...current, id];
2922
+ onMultiSelect(next);
2923
+ }
2924
+ else {
2925
+ onSelect?.(id, item);
2926
+ }
2927
+ };
2928
+ return (jsx("ul", { role: "tree", className: cn('space-y-0.5', className), children: items.map((item) => (jsx(TreeNode, { item: item, depth: 0, indent: indent, selectedId: selectedId, selectedIds: selectedIds, expandedIds: expandedIds, onSelect: handleSelect, onToggle: handleToggle, multiSelect: multiSelect }, item.id))) }));
2929
+ }
2930
+
2931
+ const alertVariants = cva('flex gap-3 rounded-lg border p-4 text-sm', {
2932
+ variants: {
2933
+ variant: {
2934
+ info: 'bg-info-bg border-info/30 text-info',
2935
+ success: 'bg-success-bg border-success/30 text-success',
2936
+ warning: 'bg-warning-bg border-warning/30 text-warning',
2937
+ error: 'bg-error-bg border-error/30 text-error',
2938
+ },
2939
+ },
2940
+ defaultVariants: { variant: 'info' },
2941
+ });
2942
+ const icons$1 = {
2943
+ info: Info,
2944
+ success: CheckCircle,
2945
+ warning: AlertTriangle,
2946
+ error: AlertCircle,
2947
+ };
2948
+ function Alert({ variant = 'info', title, children, dismissible = false, onDismiss, icon, className, }) {
2949
+ const [dismissed, setDismissed] = useState(false);
2950
+ if (dismissed)
2951
+ return null;
2952
+ const IconComponent = icons$1[variant ?? 'info'];
2953
+ const handleDismiss = () => {
2954
+ setDismissed(true);
2955
+ onDismiss?.();
2956
+ };
2957
+ return (jsxs("div", { role: "alert", className: cn(alertVariants({ variant }), className), children: [jsx("span", { className: "flex-shrink-0 mt-0.5", children: icon ?? jsx(IconComponent, { size: 16 }) }), jsxs("div", { className: "flex-1 min-w-0", children: [title && jsx("p", { className: "font-semibold mb-0.5", children: title }), children && jsx("div", { className: "opacity-90", children: children })] }), dismissible && (jsx("button", { type: "button", onClick: handleDismiss, className: "flex-shrink-0 flex items-center justify-center w-5 h-5 rounded hover:opacity-70 transition-opacity mt-0.5", "aria-label": "Dismiss alert", children: jsx(X, { size: 14 }) }))] }));
2958
+ }
2959
+
2960
+ const variantClasses$2 = {
2961
+ info: 'border-info/30 bg-info-bg text-info',
2962
+ success: 'border-success/30 bg-success-bg text-success',
2963
+ warning: 'border-warning/30 bg-warning-bg text-warning',
2964
+ error: 'border-error/30 bg-error-bg text-error',
2965
+ };
2966
+ const icons = {
2967
+ info: Info,
2968
+ success: CheckCircle,
2969
+ warning: AlertTriangle,
2970
+ error: AlertCircle,
2971
+ };
2972
+ const positionClasses = {
2973
+ 'top-left': 'top-4 left-4',
2974
+ 'top-center': 'top-4 left-1/2 -translate-x-1/2',
2975
+ 'top-right': 'top-4 right-4',
2976
+ 'bottom-left': 'bottom-4 left-4',
2977
+ 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
2978
+ 'bottom-right': 'bottom-4 right-4',
2979
+ };
2980
+ function ToastProvider({ toasts, onDismiss, position = 'bottom-right' }) {
2981
+ return (jsxs(RadixToast.Provider, { swipeDirection: "right", children: [toasts.map((toast) => {
2982
+ const variant = toast.variant ?? 'info';
2983
+ const IconComponent = icons[variant];
2984
+ return (jsxs(RadixToast.Root, { open: true, onOpenChange: (open) => { if (!open)
2985
+ onDismiss(toast.id); }, duration: toast.duration, className: cn('flex items-start gap-3 p-4 rounded-lg border shadow-lg w-80 max-w-[calc(100vw-2rem)]', 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-2', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-right-full', variantClasses$2[variant]), children: [jsx("span", { className: "flex-shrink-0 mt-0.5", children: jsx(IconComponent, { size: 16 }) }), jsxs("div", { className: "flex-1 min-w-0", children: [toast.title && (jsx(RadixToast.Title, { className: "font-semibold text-sm", children: toast.title })), toast.description && (jsx(RadixToast.Description, { className: "text-sm opacity-90 mt-0.5", children: toast.description }))] }), jsx(RadixToast.Close, { asChild: true, children: jsx("button", { type: "button", className: "flex-shrink-0 flex items-center justify-center w-5 h-5 rounded hover:opacity-70 transition-opacity", "aria-label": "Close notification", children: jsx(X, { size: 14 }) }) })] }, toast.id));
2986
+ }), jsx(RadixToast.Viewport, { className: cn('fixed flex flex-col gap-2 z-toast', positionClasses[position]) })] }));
2987
+ }
2988
+
2989
+ const sizeMap = {
2990
+ xs: 12,
2991
+ sm: 16,
2992
+ md: 24,
2993
+ lg: 32,
2994
+ xl: 48,
2995
+ };
2996
+ const colorClasses$3 = {
2997
+ primary: 'text-primary',
2998
+ secondary: 'text-secondary',
2999
+ white: 'text-white',
3000
+ current: 'text-current',
3001
+ };
3002
+ function Spinner({ size = 'md', color = 'primary', label = 'Loading...', overlay = false, className }) {
3003
+ const spinner = (jsxs("div", { role: "status", "aria-label": label, className: cn('inline-flex items-center justify-center', colorClasses$3[color], className), children: [jsx(Loader2, { size: sizeMap[size], className: "animate-spin" }), jsx("span", { className: "sr-only", children: label })] }));
3004
+ if (overlay) {
3005
+ return (jsx("div", { className: "fixed inset-0 flex items-center justify-center bg-overlay z-modal", children: spinner }));
3006
+ }
3007
+ return spinner;
3008
+ }
3009
+
3010
+ function Skeleton({ variant = 'rect', width, height, lines = 1, className, animated = true, }) {
3011
+ const base = cn('bg-surface-hover', animated && 'animate-pulse', variant === 'circle' && 'rounded-full', variant === 'text' && 'rounded', variant === 'rect' && 'rounded-md', className);
3012
+ const style = {
3013
+ width: width !== undefined ? (typeof width === 'number' ? `${width}px` : width) : undefined,
3014
+ height: height !== undefined ? (typeof height === 'number' ? `${height}px` : height) : undefined,
3015
+ };
3016
+ if (variant === 'text' && lines > 1) {
3017
+ return (jsx("div", { className: "flex flex-col gap-2", children: Array.from({ length: lines }).map((_, i) => (jsx("div", { className: cn(base, 'h-4'), style: { ...style, width: i === lines - 1 ? '75%' : '100%' } }, i))) }));
3018
+ }
3019
+ return (jsx("div", { className: cn(base, variant === 'text' && 'h-4', variant === 'circle' && 'w-10 h-10'), style: style }));
3020
+ }
3021
+ function SkeletonCard({ showAvatar = true, lines = 3, className }) {
3022
+ return (jsxs("div", { className: cn('p-4 rounded-lg border border-border bg-surface space-y-3', className), children: [showAvatar && (jsxs("div", { className: "flex items-center gap-3", children: [jsx(Skeleton, { variant: "circle", width: 40, height: 40 }), jsxs("div", { className: "flex-1 space-y-2", children: [jsx(Skeleton, { variant: "text", width: "60%" }), jsx(Skeleton, { variant: "text", width: "40%" })] })] })), jsx(Skeleton, { variant: "text", lines: lines })] }));
3023
+ }
3024
+
3025
+ const variantClasses$1 = {
3026
+ primary: 'bg-primary',
3027
+ success: 'bg-success',
3028
+ error: 'bg-error',
3029
+ warning: 'bg-warning',
3030
+ info: 'bg-info',
3031
+ };
3032
+ const sizeClasses$7 = {
3033
+ xs: 'h-1',
3034
+ sm: 'h-1.5',
3035
+ md: 'h-2.5',
3036
+ lg: 'h-4',
3037
+ };
3038
+ function ProgressBar({ value = 0, max = 100, variant = 'primary', size = 'md', label, showValue = false, striped = false, animated = false, indeterminate = false, className, }) {
3039
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
3040
+ return (jsxs("div", { className: cn('flex flex-col gap-1', className), children: [(label || showValue) && (jsxs("div", { className: "flex items-center justify-between", children: [label && jsx("span", { className: "text-sm font-medium text-text", children: label }), showValue && !indeterminate && (jsxs("span", { className: "text-xs text-text-muted", children: [Math.round(percentage), "%"] }))] })), jsx("div", { className: cn('w-full bg-surface-hover rounded-full overflow-hidden', sizeClasses$7[size]), role: "progressbar", "aria-valuenow": indeterminate ? undefined : value, "aria-valuemin": 0, "aria-valuemax": max, "aria-label": label, children: jsx("div", { className: cn('h-full rounded-full transition-all duration-300', variantClasses$1[variant], striped && 'bg-stripes', indeterminate && 'animate-indeterminate w-1/3', !indeterminate && 'transition-[width]'), style: indeterminate ? undefined : { width: `${percentage}%` } }) })] }));
3041
+ }
3042
+
3043
+ class ErrorBoundary extends Component {
3044
+ constructor(props) {
3045
+ super(props);
3046
+ Object.defineProperty(this, "reset", {
3047
+ enumerable: true,
3048
+ configurable: true,
3049
+ writable: true,
3050
+ value: () => {
3051
+ this.setState({ hasError: false, error: null });
3052
+ }
3053
+ });
3054
+ this.state = { hasError: false, error: null };
3055
+ }
3056
+ static getDerivedStateFromError(error) {
3057
+ return { hasError: true, error };
3058
+ }
3059
+ componentDidCatch(error, errorInfo) {
3060
+ this.props.onError?.(error, errorInfo);
3061
+ }
3062
+ render() {
3063
+ if (this.state.hasError && this.state.error) {
3064
+ if (this.props.fallback) {
3065
+ if (typeof this.props.fallback === 'function') {
3066
+ return this.props.fallback(this.state.error, this.reset);
3067
+ }
3068
+ return this.props.fallback;
3069
+ }
3070
+ return (jsxs("div", { className: "flex flex-col items-center justify-center gap-4 p-8 rounded-lg border border-error/30 bg-error-bg text-error", children: [jsx(AlertCircle, { size: 32 }), jsxs("div", { className: "text-center", children: [jsx("h3", { className: "font-semibold text-base mb-1", children: "Something went wrong" }), jsx("p", { className: "text-sm opacity-80 max-w-sm", children: this.state.error.message })] }), jsxs("button", { type: "button", onClick: this.reset, className: "flex items-center gap-2 px-4 py-2 rounded-md border border-error/30 text-sm font-medium hover:bg-error/10 transition-colors", children: [jsx(RefreshCw, { size: 14 }), "Try again"] })] }));
3071
+ }
3072
+ return this.props.children;
3073
+ }
3074
+ }
3075
+
3076
+ const FullScreenLoaderContext = createContext(null);
3077
+ function FullScreenLoaderProvider({ children, defaultText = 'Loading…', defaultBlur = true, }) {
3078
+ const [state, setState] = useState({
3079
+ visible: false,
3080
+ text: defaultText,
3081
+ blur: defaultBlur,
3082
+ });
3083
+ const show = useCallback((options) => {
3084
+ setState((prev) => ({
3085
+ ...prev,
3086
+ ...options,
3087
+ text: options?.text ?? defaultText,
3088
+ blur: options?.blur ?? defaultBlur,
3089
+ visible: true,
3090
+ }));
3091
+ }, [defaultText, defaultBlur]);
3092
+ const hide = useCallback(() => {
3093
+ setState((prev) => ({ ...prev, visible: false }));
3094
+ }, []);
3095
+ const update = useCallback((options) => {
3096
+ setState((prev) => ({ ...prev, ...options }));
3097
+ }, []);
3098
+ return (jsxs(FullScreenLoaderContext.Provider, { value: { show, hide, update }, children: [children, jsx(FullScreenLoader, { ...state })] }));
3099
+ }
3100
+ function useFullScreenLoader() {
3101
+ const ctx = useContext(FullScreenLoaderContext);
3102
+ if (!ctx) {
3103
+ throw new Error('useFullScreenLoader must be used within a FullScreenLoaderProvider');
3104
+ }
3105
+ return ctx;
3106
+ }
3107
+ function FullScreenLoader({ visible = false, text = 'Loading…', icon, blur = true, className, }) {
3108
+ if (!visible)
3109
+ return null;
3110
+ const content = (jsx("div", { className: cn('fixed inset-0 z-[9999] flex flex-col items-center justify-center gap-4', blur ? 'backdrop-blur-sm bg-background/70' : 'bg-background/80', className), role: "status", "aria-live": "polite", "aria-label": text, children: jsxs("div", { className: "flex flex-col items-center gap-4 p-8 rounded-2xl bg-surface shadow-xl border border-border", children: [jsx("div", { className: "text-primary", children: icon ?? jsx(Spinner, { size: "xl" }) }), text && (jsx("p", { className: "text-sm font-medium text-text-muted animate-pulse", children: text }))] }) }));
3111
+ return createPortal(content, document.body);
3112
+ }
3113
+
3114
+ const sizeClasses$6 = {
3115
+ sm: 'max-w-sm',
3116
+ md: 'max-w-md',
3117
+ lg: 'max-w-lg',
3118
+ xl: 'max-w-xl',
3119
+ full: 'max-w-[calc(100vw-2rem)] h-[calc(100vh-2rem)]',
3120
+ };
3121
+ function Modal({ open, defaultOpen, onOpenChange, title, description, footer, size = 'md', showClose = true, closeOnOverlayClick = true, children, trigger, className, }) {
3122
+ return (jsxs(RadixDialog.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, children: [trigger && jsx(RadixDialog.Trigger, { asChild: true, children: trigger }), jsxs(RadixDialog.Portal, { children: [jsx(RadixDialog.Overlay, { className: cn('fixed inset-0 bg-overlay z-modal', 'data-[state=open]:animate-in data-[state=open]:fade-in-0', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0'), onClick: closeOnOverlayClick ? undefined : (e) => e.stopPropagation() }), jsxs(RadixDialog.Content, { className: cn('fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2', 'w-full bg-surface rounded-lg shadow-xl border border-border', 'flex flex-col z-modal', 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95', 'focus:outline-none', sizeClasses$6[size], className), children: [(title || showClose) && (jsxs("div", { className: "flex items-start justify-between gap-4 px-6 py-4 border-b border-border flex-shrink-0", children: [jsxs("div", { className: "flex-1 min-w-0", children: [title && (jsx(RadixDialog.Title, { className: "text-base font-semibold text-text", children: title })), description && (jsx(RadixDialog.Description, { className: "text-sm text-text-muted mt-0.5", children: description }))] }), showClose && (jsx(RadixDialog.Close, { asChild: true, children: jsx("button", { type: "button", className: "flex-shrink-0 flex items-center justify-center w-7 h-7 rounded-md text-text-muted hover:text-text hover:bg-surface-hover transition-colors", "aria-label": "Close modal", children: jsx(X, { size: 16 }) }) }))] })), jsx("div", { className: "flex-1 overflow-auto px-6 py-4", children: children }), footer && (jsx("div", { className: "flex-shrink-0 px-6 py-4 border-t border-border", children: footer }))] })] })] }));
3123
+ }
3124
+
3125
+ const sideClasses = {
3126
+ left: 'left-0 top-0 bottom-0 h-full data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left',
3127
+ right: 'right-0 top-0 bottom-0 h-full data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right',
3128
+ top: 'top-0 left-0 right-0 w-full data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top',
3129
+ bottom: 'bottom-0 left-0 right-0 w-full data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom',
3130
+ };
3131
+ const sizeClasses$5 = {
3132
+ left: { sm: 'w-64', md: 'w-80', lg: 'w-96', xl: 'w-[32rem]' },
3133
+ right: { sm: 'w-64', md: 'w-80', lg: 'w-96', xl: 'w-[32rem]' },
3134
+ top: { sm: 'h-48', md: 'h-64', lg: 'h-80', xl: 'h-96' },
3135
+ bottom: { sm: 'h-48', md: 'h-64', lg: 'h-80', xl: 'h-96' },
3136
+ };
3137
+ function Drawer({ open, defaultOpen, onOpenChange, side = 'right', size = 'md', title, description, footer, showClose = true, children, trigger, className, }) {
3138
+ return (jsxs(RadixDialog.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, children: [trigger && jsx(RadixDialog.Trigger, { asChild: true, children: trigger }), jsxs(RadixDialog.Portal, { children: [jsx(RadixDialog.Overlay, { className: cn('fixed inset-0 bg-overlay z-sidebar', 'data-[state=open]:animate-in data-[state=open]:fade-in-0', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0') }), jsxs(RadixDialog.Content, { className: cn('fixed bg-surface shadow-xl border-border flex flex-col z-sidebar', 'data-[state=open]:animate-in data-[state=closed]:animate-out', 'data-[state=open]:duration-300 data-[state=closed]:duration-200', 'focus:outline-none', sideClasses[side], sizeClasses$5[side][size], side === 'left' && 'border-r', side === 'right' && 'border-l', side === 'top' && 'border-b', side === 'bottom' && 'border-t', className), children: [(title || showClose) && (jsxs("div", { className: "flex items-start justify-between gap-4 px-5 py-4 border-b border-border flex-shrink-0", children: [jsxs("div", { className: "flex-1 min-w-0", children: [title && (jsx(RadixDialog.Title, { className: "text-base font-semibold text-text", children: title })), description && (jsx(RadixDialog.Description, { className: "text-sm text-text-muted mt-0.5", children: description }))] }), showClose && (jsx(RadixDialog.Close, { asChild: true, children: jsx("button", { type: "button", className: "flex-shrink-0 flex items-center justify-center w-7 h-7 rounded-md text-text-muted hover:text-text hover:bg-surface-hover transition-colors", "aria-label": "Close drawer", children: jsx(X, { size: 16 }) }) }))] })), jsx("div", { className: cn('flex-1 overflow-auto p-5'), children: children }), footer && (jsx("div", { className: "flex-shrink-0 px-5 py-4 border-t border-border", children: footer }))] })] })] }));
3139
+ }
3140
+
3141
+ function Tooltip({ content, children, placement = 'top', delay = 200, arrow = true, className, contentClassName, disabled = false, }) {
3142
+ if (disabled)
3143
+ return jsx(Fragment, { children: children });
3144
+ return (jsx(RadixTooltip.Provider, { delayDuration: delay, children: jsxs(RadixTooltip.Root, { children: [jsx(RadixTooltip.Trigger, { asChild: true, className: className, children: children }), jsx(RadixTooltip.Portal, { children: jsxs(RadixTooltip.Content, { side: placement, sideOffset: 6, className: cn('z-tooltip px-2.5 py-1.5 rounded-md text-xs font-medium', 'bg-text text-surface shadow-md max-w-xs', 'data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95', contentClassName), children: [content, arrow && (jsx(RadixTooltip.Arrow, { className: "fill-text" }))] }) })] }) }));
3145
+ }
3146
+
3147
+ function Popover({ content, children, placement = 'bottom', open, defaultOpen, onOpenChange, showClose = false, arrow = true, className, contentClassName, }) {
3148
+ const [side, align] = placement.split('-');
3149
+ return (jsxs(RadixPopover.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, children: [jsx(RadixPopover.Trigger, { asChild: true, className: className, children: children }), jsx(RadixPopover.Portal, { children: jsxs(RadixPopover.Content, { side: side, align: align ?? 'center', sideOffset: 8, className: cn('z-dropdown bg-surface border border-border rounded-lg shadow-lg p-4 max-w-sm', 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95', 'focus:outline-none', contentClassName), children: [showClose && (jsx(RadixPopover.Close, { asChild: true, children: jsx("button", { type: "button", className: "absolute top-2 right-2 flex items-center justify-center w-6 h-6 rounded text-text-muted hover:text-text hover:bg-surface-hover transition-colors", "aria-label": "Close popover", children: jsx(X, { size: 14 }) }) })), content, arrow && jsx(RadixPopover.Arrow, { className: "fill-border" })] }) })] }));
3150
+ }
3151
+
3152
+ const itemBase = cn('flex items-center gap-2 px-2 py-1.5 text-sm rounded-md cursor-pointer outline-none select-none', 'transition-colors', 'data-[highlighted]:bg-surface-hover data-[highlighted]:text-text', 'data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none');
3153
+ function renderItems(items) {
3154
+ return items.map((item, i) => {
3155
+ if (item.type === 'separator') {
3156
+ return jsx(RadixContextMenu.Separator, { className: "my-1 h-px bg-border" }, i);
3157
+ }
3158
+ if (item.type === 'label') {
3159
+ return (jsx(RadixContextMenu.Label, { className: "px-2 py-1 text-xs font-semibold text-text-muted uppercase tracking-wide", children: item.label }, i));
3160
+ }
3161
+ if (item.type === 'checkbox') {
3162
+ return (jsxs(RadixContextMenu.CheckboxItem, { checked: item.checked, onCheckedChange: item.onCheckedChange, disabled: item.disabled, className: cn(itemBase, 'pl-7 relative', item.destructive && 'text-error'), children: [jsx(RadixContextMenu.ItemIndicator, { className: "absolute left-2", children: jsx(Check, { size: 12 }) }), item.label] }, i));
3163
+ }
3164
+ if (item.children && item.children.length > 0) {
3165
+ return (jsxs(RadixContextMenu.Sub, { children: [jsxs(RadixContextMenu.SubTrigger, { disabled: item.disabled, className: cn(itemBase, item.destructive && 'text-error'), children: [item.icon && jsx("span", { className: "w-4 h-4 flex items-center", children: item.icon }), jsx("span", { className: "flex-1", children: item.label }), jsx(ChevronRight, { size: 12, className: "ml-auto text-text-muted" })] }), jsx(RadixContextMenu.Portal, { children: jsx(RadixContextMenu.SubContent, { className: "z-dropdown bg-surface border border-border rounded-lg shadow-lg p-1 min-w-[10rem]", sideOffset: 2, alignOffset: -4, children: renderItems(item.children) }) })] }, i));
3166
+ }
3167
+ return (jsxs(RadixContextMenu.Item, { disabled: item.disabled, onSelect: item.onClick, className: cn(itemBase, item.destructive ? 'text-error' : 'text-text'), children: [item.icon && jsx("span", { className: "w-4 h-4 flex items-center", children: item.icon }), jsx("span", { className: "flex-1", children: item.label }), item.shortcut && (jsx("span", { className: "ml-auto text-xs text-text-muted", children: item.shortcut }))] }, i));
3168
+ });
3169
+ }
3170
+ function ContextMenu({ items, children, className }) {
3171
+ return (jsxs(RadixContextMenu.Root, { children: [jsx(RadixContextMenu.Trigger, { asChild: true, className: className, children: children }), jsx(RadixContextMenu.Portal, { children: jsx(RadixContextMenu.Content, { className: cn('z-dropdown bg-surface border border-border rounded-lg shadow-lg p-1 min-w-[10rem]', 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0'), children: renderItems(items) }) })] }));
3172
+ }
3173
+
3174
+ const ConfirmDialogContext = createContext(null);
3175
+ function useConfirm() {
3176
+ const ctx = useContext(ConfirmDialogContext);
3177
+ if (!ctx) {
3178
+ throw new Error('useConfirm must be used within a ConfirmDialogProvider');
3179
+ }
3180
+ return ctx.confirm;
3181
+ }
3182
+ const variantConfig = {
3183
+ default: {
3184
+ icon: jsx(Info, { size: 20 }),
3185
+ iconBg: 'bg-primary/10 text-primary',
3186
+ confirmVariant: 'primary',
3187
+ defaultTitle: 'Are you sure?',
3188
+ defaultConfirm: 'Confirm',
3189
+ },
3190
+ danger: {
3191
+ icon: jsx(Trash2, { size: 20 }),
3192
+ iconBg: 'bg-error/10 text-error',
3193
+ confirmVariant: 'danger',
3194
+ defaultTitle: 'Delete this item?',
3195
+ defaultConfirm: 'Delete',
3196
+ },
3197
+ warning: {
3198
+ icon: jsx(AlertTriangle, { size: 20 }),
3199
+ iconBg: 'bg-warning/10 text-warning',
3200
+ confirmVariant: 'primary',
3201
+ confirmClassName: 'bg-warning hover:bg-warning/90 text-white',
3202
+ defaultTitle: 'Are you sure?',
3203
+ defaultConfirm: 'Proceed',
3204
+ },
3205
+ };
3206
+ function ConfirmDialogProvider({ children }) {
3207
+ const [state, setState] = useState({
3208
+ open: false,
3209
+ resolve: null,
3210
+ });
3211
+ const confirm = useCallback((options) => {
3212
+ return new Promise((resolve) => {
3213
+ setState({
3214
+ open: true,
3215
+ resolve,
3216
+ ...options,
3217
+ });
3218
+ });
3219
+ }, []);
3220
+ const handleResponse = (value) => {
3221
+ state.resolve?.(value);
3222
+ setState((prev) => ({ ...prev, open: false, resolve: null }));
3223
+ };
3224
+ const variant = state.variant ?? 'default';
3225
+ const config = variantConfig[variant];
3226
+ const title = state.title ?? config.defaultTitle;
3227
+ const description = state.description;
3228
+ const confirmLabel = state.confirmLabel ?? config.defaultConfirm;
3229
+ const cancelLabel = state.cancelLabel ?? 'Cancel';
3230
+ return (jsxs(ConfirmDialogContext.Provider, { value: { confirm }, children: [children, jsx(RadixDialog.Root, { open: state.open, onOpenChange: (open) => { if (!open)
3231
+ handleResponse(false); }, children: jsxs(RadixDialog.Portal, { children: [jsx(RadixDialog.Overlay, { className: "fixed inset-0 z-[1000] bg-black/40 backdrop-blur-sm animate-in fade-in-0" }), jsxs(RadixDialog.Content, { className: cn('fixed z-[1001] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2', 'w-full max-w-md bg-surface rounded-xl shadow-2xl border border-border p-6', 'animate-in fade-in-0 zoom-in-95'), "aria-describedby": description ? 'confirm-desc' : undefined, children: [jsxs("div", { className: "flex items-start gap-4", children: [jsx("div", { className: cn('flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center', config.iconBg), children: config.icon }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx(RadixDialog.Title, { className: "text-base font-semibold text-text", children: title }), description && (jsx("p", { id: "confirm-desc", className: "mt-1 text-sm text-text-muted", children: description }))] }), jsx("button", { onClick: () => handleResponse(false), className: "flex-shrink-0 text-text-muted hover:text-text transition-colors focus:outline-none", "aria-label": "Close", children: jsx(X, { size: 16 }) })] }), jsxs("div", { className: "flex items-center justify-end gap-3 mt-6", children: [jsx(Button, { variant: "outline", size: "sm", onClick: () => handleResponse(false), children: cancelLabel }), jsx(Button, { variant: config.confirmVariant, size: "sm", className: config.confirmClassName, onClick: () => handleResponse(true), children: confirmLabel })] })] })] }) })] }));
3232
+ }
3233
+ function ConfirmDialog({ open, onConfirm, onCancel, title, description, confirmLabel, cancelLabel, variant = 'default', }) {
3234
+ const config = variantConfig[variant];
3235
+ return (jsx(RadixDialog.Root, { open: open, onOpenChange: (o) => { if (!o)
3236
+ onCancel(); }, children: jsxs(RadixDialog.Portal, { children: [jsx(RadixDialog.Overlay, { className: "fixed inset-0 z-[1000] bg-black/40 backdrop-blur-sm animate-in fade-in-0" }), jsxs(RadixDialog.Content, { className: cn('fixed z-[1001] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2', 'w-full max-w-md bg-surface rounded-xl shadow-2xl border border-border p-6', 'animate-in fade-in-0 zoom-in-95'), "aria-describedby": description ? 'confirm-desc-static' : undefined, children: [jsxs("div", { className: "flex items-start gap-4", children: [jsx("div", { className: cn('flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center', config.iconBg), children: config.icon }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx(RadixDialog.Title, { className: "text-base font-semibold text-text", children: title ?? config.defaultTitle }), description && (jsx("p", { id: "confirm-desc-static", className: "mt-1 text-sm text-text-muted", children: description }))] }), jsx("button", { onClick: onCancel, className: "flex-shrink-0 text-text-muted hover:text-text transition-colors focus:outline-none", "aria-label": "Close", children: jsx(X, { size: 16 }) })] }), jsxs("div", { className: "flex items-center justify-end gap-3 mt-6", children: [jsx(Button, { variant: "outline", size: "sm", onClick: onCancel, children: cancelLabel ?? 'Cancel' }), jsx(Button, { variant: config.confirmVariant, size: "sm", className: config.confirmClassName, onClick: onConfirm, children: confirmLabel ?? config.defaultConfirm })] })] })] }) }));
3237
+ }
3238
+
3239
+ const sizeClasses$4 = {
3240
+ sm: 'text-[10px] px-1 py-0.5 min-w-[18px]',
3241
+ md: 'text-xs px-1.5 py-0.5 min-w-[22px]',
3242
+ lg: 'text-sm px-2 py-1 min-w-[28px]',
3243
+ };
3244
+ function Kbd({ children, size = 'md', className }) {
3245
+ return (jsx("kbd", { className: cn('inline-flex items-center justify-center rounded border font-mono font-medium', 'bg-surface-hover border-border text-text-muted', 'shadow-[0_1px_0_1px_var(--color-border)]', sizeClasses$4[size], className), children: children }));
3246
+ }
3247
+
3248
+ function highlight(text, query) {
3249
+ if (!query.trim())
3250
+ return jsx(Fragment, { children: text });
3251
+ const idx = text.toLowerCase().indexOf(query.toLowerCase());
3252
+ if (idx === -1)
3253
+ return jsx(Fragment, { children: text });
3254
+ return (jsxs(Fragment, { children: [text.slice(0, idx), jsx("mark", { className: "bg-primary/20 text-primary rounded-sm", children: text.slice(idx, idx + query.length) }), text.slice(idx + query.length)] }));
3255
+ }
3256
+ function CommandPalette({ open, onClose, items = [], groups = [], placeholder = 'Search commands…', emptyMessage = 'No results found.', loading = false, onSearch, maxHeight = 400, className, }) {
3257
+ const id = useId();
3258
+ const [query, setQuery] = useState('');
3259
+ const [activeIndex, setActiveIndex] = useState(0);
3260
+ const inputRef = useRef(null);
3261
+ const listRef = useRef(null);
3262
+ // Flatten all items for keyboard nav
3263
+ const allItems = React.useMemo(() => {
3264
+ const flatGroups = groups.flatMap((g) => g.items.filter((item) => !item.disabled &&
3265
+ (item.label.toLowerCase().includes(query.toLowerCase()) ||
3266
+ item.description?.toLowerCase().includes(query.toLowerCase()))));
3267
+ const flatItems = items.filter((item) => !item.disabled &&
3268
+ (item.label.toLowerCase().includes(query.toLowerCase()) ||
3269
+ item.description?.toLowerCase().includes(query.toLowerCase())));
3270
+ return [...flatItems, ...flatGroups];
3271
+ }, [items, groups, query]);
3272
+ const filteredGroups = React.useMemo(() => {
3273
+ if (groups.length === 0)
3274
+ return [];
3275
+ return groups
3276
+ .map((g) => ({
3277
+ ...g,
3278
+ items: g.items.filter((item) => item.label.toLowerCase().includes(query.toLowerCase()) ||
3279
+ item.description?.toLowerCase().includes(query.toLowerCase())),
3280
+ }))
3281
+ .filter((g) => g.items.length > 0);
3282
+ }, [groups, query]);
3283
+ const filteredItems = React.useMemo(() => {
3284
+ return items.filter((item) => item.label.toLowerCase().includes(query.toLowerCase()) ||
3285
+ item.description?.toLowerCase().includes(query.toLowerCase()));
3286
+ }, [items, query]);
3287
+ const hasResults = filteredItems.length > 0 || filteredGroups.length > 0;
3288
+ useEffect(() => {
3289
+ if (open) {
3290
+ setQuery('');
3291
+ setActiveIndex(0);
3292
+ setTimeout(() => inputRef.current?.focus(), 10);
3293
+ }
3294
+ }, [open]);
3295
+ useEffect(() => {
3296
+ setActiveIndex(0);
3297
+ }, [query]);
3298
+ useEffect(() => {
3299
+ onSearch?.(query);
3300
+ }, [query, onSearch]);
3301
+ const handleSelect = useCallback((item) => {
3302
+ if (item.disabled)
3303
+ return;
3304
+ item.onSelect();
3305
+ onClose();
3306
+ }, [onClose]);
3307
+ const handleKeyDown = (e) => {
3308
+ if (e.key === 'Escape') {
3309
+ onClose();
3310
+ return;
3311
+ }
3312
+ if (e.key === 'ArrowDown') {
3313
+ e.preventDefault();
3314
+ setActiveIndex((i) => Math.min(i + 1, allItems.length - 1));
3315
+ }
3316
+ else if (e.key === 'ArrowUp') {
3317
+ e.preventDefault();
3318
+ setActiveIndex((i) => Math.max(i - 1, 0));
3319
+ }
3320
+ else if (e.key === 'Enter') {
3321
+ e.preventDefault();
3322
+ const item = allItems[activeIndex];
3323
+ if (item)
3324
+ handleSelect(item);
3325
+ }
3326
+ };
3327
+ // Scroll active item into view
3328
+ useEffect(() => {
3329
+ const el = listRef.current?.querySelector(`[data-active="true"]`);
3330
+ el?.scrollIntoView({ block: 'nearest' });
3331
+ }, [activeIndex]);
3332
+ if (!open)
3333
+ return null;
3334
+ if (typeof document === 'undefined')
3335
+ return null;
3336
+ let globalIndex = 0;
3337
+ const renderItem = (item) => {
3338
+ const idx = globalIndex++;
3339
+ const isActive = idx === activeIndex;
3340
+ return (jsxs("div", { "data-active": isActive, role: "option", "aria-selected": isActive, "aria-disabled": item.disabled, onClick: () => !item.disabled && handleSelect(item), onMouseEnter: () => setActiveIndex(idx), className: cn('flex items-center gap-3 px-3 py-2.5 rounded-md cursor-pointer transition-colors', isActive ? 'bg-primary text-primary-foreground' : 'text-text hover:bg-surface-hover', item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none'), children: [item.icon && (jsx("span", { className: cn('flex-shrink-0', isActive ? 'text-primary-foreground' : 'text-text-muted'), children: item.icon })), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("div", { className: "text-sm font-medium truncate", children: highlight(item.label, query) }), item.description && (jsx("div", { className: cn('text-xs truncate', isActive ? 'text-primary-foreground/70' : 'text-text-muted'), children: highlight(item.description, query) }))] }), item.shortcut && (jsx("div", { className: "flex items-center gap-1 flex-shrink-0", children: item.shortcut.map((k, i) => (jsx(Kbd, { size: "sm", children: k }, i))) })), jsx(ChevronRight, { size: 14, className: cn('flex-shrink-0', isActive ? 'text-primary-foreground/70' : 'text-text-muted') })] }, item.id));
3341
+ };
3342
+ return createPortal(jsxs("div", { className: "fixed inset-0 z-[9999] flex items-start justify-center pt-[15vh] px-4", role: "dialog", "aria-modal": "true", "aria-label": "Command palette", onClick: (e) => e.target === e.currentTarget && onClose(), children: [jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: onClose }), jsxs("div", { className: cn('relative w-full max-w-lg bg-surface rounded-xl shadow-2xl border border-border overflow-hidden', className), onKeyDown: handleKeyDown, children: [jsxs("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-border", children: [loading ? (jsx(Loader2, { size: 18, className: "text-text-muted animate-spin flex-shrink-0" })) : (jsx(Search, { size: 18, className: "text-text-muted flex-shrink-0" })), jsx("input", { ref: inputRef, id: `${id}-input`, type: "text", role: "combobox", "aria-expanded": true, "aria-controls": `${id}-list`, "aria-autocomplete": "list", value: query, onChange: (e) => setQuery(e.target.value), placeholder: placeholder, className: "flex-1 bg-transparent text-text placeholder:text-text-muted outline-none text-sm" }), query && (jsx("button", { type: "button", onClick: () => setQuery(''), className: "text-text-muted hover:text-text transition-colors", "aria-label": "Clear search", children: jsx(X, { size: 16 }) })), jsx(Kbd, { size: "sm", children: "Esc" })] }), jsx("div", { ref: listRef, id: `${id}-list`, role: "listbox", className: "overflow-y-auto p-2", style: { maxHeight }, children: loading && !hasResults ? (jsxs("div", { className: "flex items-center justify-center py-8 text-text-muted text-sm gap-2", children: [jsx(Loader2, { size: 16, className: "animate-spin" }), "Searching\u2026"] })) : !hasResults ? (jsx("div", { className: "py-8 text-center text-text-muted text-sm", children: emptyMessage })) : (jsxs(Fragment, { children: [filteredItems.map(renderItem), filteredGroups.map((group) => (jsxs("div", { children: [jsx("div", { className: "px-3 py-1.5 text-xs font-semibold text-text-muted uppercase tracking-wider", children: group.label }), group.items.map(renderItem)] }, group.id)))] })) }), jsxs("div", { className: "flex items-center gap-3 px-4 py-2 border-t border-border text-xs text-text-muted", children: [jsxs("span", { className: "flex items-center gap-1", children: [jsx(Kbd, { size: "sm", children: "\u2191" }), jsx(Kbd, { size: "sm", children: "\u2193" }), " Navigate"] }), jsxs("span", { className: "flex items-center gap-1", children: [jsx(Kbd, { size: "sm", children: "\u21B5" }), " Select"] }), jsxs("span", { className: "flex items-center gap-1", children: [jsx(Kbd, { size: "sm", children: "Esc" }), " Close"] })] })] })] }), document.body);
3343
+ }
3344
+ function useCommandPalette() {
3345
+ const [open, setOpen] = useState(false);
3346
+ useEffect(() => {
3347
+ const handler = (e) => {
3348
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
3349
+ e.preventDefault();
3350
+ setOpen((v) => !v);
3351
+ }
3352
+ };
3353
+ window.addEventListener('keydown', handler);
3354
+ return () => window.removeEventListener('keydown', handler);
3355
+ }, []);
3356
+ return { open, setOpen, toggle: () => setOpen((v) => !v) };
3357
+ }
3358
+
3359
+ const defaultSizeByLevel = {
3360
+ 1: '4xl',
3361
+ 2: '3xl',
3362
+ 3: '2xl',
3363
+ 4: 'xl',
3364
+ 5: 'lg',
3365
+ 6: 'md',
3366
+ };
3367
+ const sizeClasses$3 = {
3368
+ xs: 'text-xs',
3369
+ sm: 'text-sm',
3370
+ md: 'text-base',
3371
+ lg: 'text-lg',
3372
+ xl: 'text-xl',
3373
+ '2xl': 'text-2xl',
3374
+ '3xl': 'text-3xl',
3375
+ '4xl': 'text-4xl',
3376
+ };
3377
+ const weightClasses$1 = {
3378
+ normal: 'font-normal',
3379
+ medium: 'font-medium',
3380
+ semibold: 'font-semibold',
3381
+ bold: 'font-bold',
3382
+ };
3383
+ const colorClasses$2 = {
3384
+ default: 'text-text',
3385
+ muted: 'text-text-muted',
3386
+ primary: 'text-primary',
3387
+ error: 'text-error',
3388
+ success: 'text-success',
3389
+ warning: 'text-warning',
3390
+ };
3391
+ function Heading({ level = 2, size, weight = 'bold', truncate = false, color = 'default', className, children, ...props }) {
3392
+ const Tag = `h${level}`;
3393
+ const resolvedSize = size ?? defaultSizeByLevel[level];
3394
+ return (jsx(Tag, { className: cn('font-base leading-tight', sizeClasses$3[resolvedSize], weightClasses$1[weight], colorClasses$2[color], truncate && 'truncate', className), ...props, children: children }));
3395
+ }
3396
+
3397
+ const sizeClasses$2 = {
3398
+ xs: 'text-xs',
3399
+ sm: 'text-sm',
3400
+ base: 'text-base',
3401
+ lg: 'text-lg',
3402
+ xl: 'text-xl',
3403
+ };
3404
+ const weightClasses = {
3405
+ normal: 'font-normal',
3406
+ medium: 'font-medium',
3407
+ semibold: 'font-semibold',
3408
+ bold: 'font-bold',
3409
+ };
3410
+ const colorClasses$1 = {
3411
+ default: 'text-text',
3412
+ muted: 'text-text-muted',
3413
+ disabled: 'text-text-disabled',
3414
+ primary: 'text-primary',
3415
+ error: 'text-error',
3416
+ success: 'text-success',
3417
+ warning: 'text-warning',
3418
+ inherit: 'text-inherit',
3419
+ };
3420
+ const clampClasses = {
3421
+ 1: 'line-clamp-1',
3422
+ 2: 'line-clamp-2',
3423
+ 3: 'line-clamp-3',
3424
+ 4: 'line-clamp-4',
3425
+ 5: 'line-clamp-5',
3426
+ };
3427
+ function Text({ as: Tag = 'p', size = 'base', weight = 'normal', color = 'default', truncate = false, clamp, italic = false, underline = false, className, children, ...props }) {
3428
+ return (jsx(Tag, { className: cn('font-base leading-normal', sizeClasses$2[size], weightClasses[weight], colorClasses$1[color], truncate && 'truncate', clamp && clampClasses[clamp], italic && 'italic', underline && 'underline underline-offset-2', className), ...props, children: children }));
3429
+ }
3430
+
3431
+ const sizeClasses$1 = {
3432
+ sm: 'text-xs',
3433
+ md: 'text-sm',
3434
+ lg: 'text-base',
3435
+ };
3436
+ function Label({ required, optional, size = 'md', className, children, ...props }) {
3437
+ return (jsxs(RadixLabel.Root, { className: cn('font-medium text-text leading-none cursor-default', sizeClasses$1[size], className), ...props, children: [children, required && jsx("span", { className: "text-error ml-1", "aria-hidden": "true", children: "*" }), optional && jsx("span", { className: "text-text-muted ml-1 font-normal", children: "(optional)" })] }));
3438
+ }
3439
+
3440
+ function Code({ block = false, language = 'tsx', theme = 'dark', showLineNumbers = false, copyable = true, filename, className, children, ...props }) {
3441
+ const [copied, setCopied] = useState(false);
3442
+ const handleCopy = () => {
3443
+ const text = typeof children === 'string' ? children : String(children ?? '');
3444
+ navigator.clipboard.writeText(text).then(() => {
3445
+ setCopied(true);
3446
+ setTimeout(() => setCopied(false), 2000);
3447
+ });
3448
+ };
3449
+ if (block) {
3450
+ const code = typeof children === 'string' ? children : String(children ?? '');
3451
+ const prismTheme = theme === 'dark' ? themes.oneDark : themes.oneLight;
3452
+ return (jsxs("div", { className: cn('w-full rounded-lg border border-border overflow-hidden', className), children: [(filename || copyable) && (jsxs("div", { className: "flex items-center justify-between px-4 py-2 bg-surface border-b border-border", children: [filename ? (jsx("span", { className: "text-xs font-mono text-text-muted", children: filename })) : (jsx("span", { className: "text-xs font-mono text-text-muted uppercase tracking-wide", children: language })), copyable && (jsxs("button", { type: "button", onClick: handleCopy, className: "flex items-center gap-1.5 text-xs text-text-muted hover:text-text transition-colors", "aria-label": "Copy code", children: [copied ? jsx(Check, { size: 13, className: "text-success" }) : jsx(Copy, { size: 13 }), copied ? 'Copied!' : 'Copy'] }))] })), jsx(Highlight, { theme: prismTheme, code: code.trimEnd(), language: language, children: ({ className: hlClass, style, tokens, getLineProps, getTokenProps }) => (jsx("pre", { className: cn('overflow-x-auto p-4 text-sm leading-relaxed m-0', hlClass), style: style, children: tokens.map((line, i) => (jsxs("div", { ...getLineProps({ line }), children: [showLineNumbers && (jsx("span", { className: "select-none pr-4 text-right inline-block w-8 opacity-40 text-xs", children: i + 1 })), line.map((token, key) => (jsx("span", { ...getTokenProps({ token }) }, key)))] }, i))) })) })] }));
3453
+ }
3454
+ return (jsx("code", { className: cn('inline rounded px-1.5 py-0.5 bg-surface-hover border border-border', 'text-sm font-mono text-text', className), ...props, children: children }));
3455
+ }
3456
+
3457
+ const underlineClasses = {
3458
+ always: 'underline underline-offset-2',
3459
+ hover: 'hover:underline underline-offset-2',
3460
+ none: 'no-underline',
3461
+ };
3462
+ const colorClasses = {
3463
+ primary: 'text-primary hover:text-primary-hover',
3464
+ text: 'text-text hover:text-text-muted',
3465
+ muted: 'text-text-muted hover:text-text',
3466
+ inherit: 'text-inherit',
3467
+ };
3468
+ function Link({ external = false, underline = 'hover', color = 'primary', showExternalIcon = true, className, children, target, rel, ...props }) {
3469
+ const isExternal = external || target === '_blank';
3470
+ return (jsxs("a", { target: isExternal ? '_blank' : target, rel: isExternal ? 'noopener noreferrer' : rel, className: cn('inline-flex items-center gap-1 transition-colors cursor-pointer', colorClasses[color], underlineClasses[underline], className), ...props, children: [children, isExternal && showExternalIcon && (jsx(ExternalLink, { size: 12, className: "flex-shrink-0 opacity-70", "aria-hidden": "true" }))] }));
3471
+ }
3472
+
3473
+ const sizeClasses = {
3474
+ sm: 'h-7 px-2 text-xs gap-1',
3475
+ md: 'h-8 px-2.5 text-sm gap-1.5',
3476
+ lg: 'h-10 px-3 text-sm gap-2',
3477
+ };
3478
+ const iconSize = { sm: 12, md: 14, lg: 16 };
3479
+ const variantClasses = {
3480
+ ghost: 'bg-transparent hover:bg-surface-hover text-text-muted hover:text-text',
3481
+ outline: 'border border-border bg-transparent hover:bg-surface-hover text-text-muted hover:text-text',
3482
+ solid: 'bg-surface-hover hover:bg-surface-active text-text border border-border',
3483
+ };
3484
+ function CopyButton({ value, size = 'md', variant = 'ghost', label, copiedLabel = 'Copied!', timeout = 2000, onCopy, className, disabled = false, }) {
3485
+ const [copied, setCopied] = useState(false);
3486
+ const handleCopy = async () => {
3487
+ if (disabled || copied)
3488
+ return;
3489
+ try {
3490
+ await navigator.clipboard.writeText(value);
3491
+ setCopied(true);
3492
+ onCopy?.(value);
3493
+ setTimeout(() => setCopied(false), timeout);
3494
+ }
3495
+ catch {
3496
+ // fallback for older browsers
3497
+ const el = document.createElement('textarea');
3498
+ el.value = value;
3499
+ document.body.appendChild(el);
3500
+ el.select();
3501
+ document.execCommand('copy');
3502
+ document.body.removeChild(el);
3503
+ setCopied(true);
3504
+ onCopy?.(value);
3505
+ setTimeout(() => setCopied(false), timeout);
3506
+ }
3507
+ };
3508
+ return (jsx("button", { type: "button", onClick: handleCopy, disabled: disabled, "aria-label": copied ? copiedLabel : (label ?? 'Copy to clipboard'), className: cn('inline-flex items-center justify-center rounded-md font-medium transition-all', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-border-focus/40', 'disabled:opacity-50 disabled:cursor-not-allowed', sizeClasses[size], variantClasses[variant], copied && 'text-success', className), children: copied ? (jsxs(Fragment, { children: [jsx(Check, { size: iconSize[size] }), label && jsx("span", { children: copiedLabel })] })) : (jsxs(Fragment, { children: [jsx(Copy, { size: iconSize[size] }), label && jsx("span", { children: label })] })) }));
3509
+ }
3510
+
3511
+ export { Alert, AppShell, Avatar, AvatarGroup, Badge, BadgeAnchor, Box, Breadcrumb, Button, Card, Checkbox, ChipSelect, Code, Collapsible, CommandPalette, ConfirmDialog, ConfirmDialogProvider, Container, ContextMenu, CopyButton, DEFAULT_COUNTRY_CODES, DataTable, DatePicker, Divider, Drawer, EmptyState, ErrorBoundary, FileUpload, FormField, FullScreenLoader, FullScreenLoaderProvider, Grid, Heading, Icon, IconButton, Image, JSONForm, Kbd, Label, Link, List, ListItem, MarkdownReader, Modal, MultiSelect, Navbar, NumberInput, OTPInput, Pagination, PhoneInput, Popover, ProgressBar, RadioGroup, Rating, SVG, SearchSelect, Select, Sidebar, Skeleton, SkeletonCard, Slider, Spacer, Spinner, Stack, Stat, StepIndicator, Switch, Table, Tabs, TabsContent, Tag, TagInput, Text, TextArea, TextField, ThemeProvider, Timeline, ToastProvider, Tooltip, TreeView, buttonVariants, cn, useClickOutside, useClipboard, useCommandPalette, useConfirm, useDebounce, useFullScreenLoader, useIntersectionObserver, useKeyboard, useLocalStorage, useMediaQuery, useTheme, useToast, useWindowSize, v };
3512
+ //# sourceMappingURL=index.esm.js.map