@memelabui/ui 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { forwardRef, useId, useRef, useEffect, useState, useMemo, cloneElement } from 'react';
1
+ import { forwardRef, useId, useRef, useEffect, createContext, useState, useCallback, useMemo, isValidElement, cloneElement, useReducer, useContext } from 'react';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { createPortal } from 'react-dom';
4
4
 
@@ -56,6 +56,94 @@ function focusSafely(el) {
56
56
  } catch {
57
57
  }
58
58
  }
59
+ function useClipboard(timeout = 2e3) {
60
+ const [copied, setCopied] = useState(false);
61
+ const timerRef = useRef(null);
62
+ useEffect(() => {
63
+ return () => {
64
+ if (timerRef.current !== null) {
65
+ clearTimeout(timerRef.current);
66
+ }
67
+ };
68
+ }, []);
69
+ const copy = useCallback(
70
+ async (text) => {
71
+ try {
72
+ await navigator.clipboard.writeText(text);
73
+ } catch {
74
+ return;
75
+ }
76
+ if (timerRef.current !== null) {
77
+ clearTimeout(timerRef.current);
78
+ }
79
+ setCopied(true);
80
+ timerRef.current = setTimeout(() => {
81
+ setCopied(false);
82
+ timerRef.current = null;
83
+ }, timeout);
84
+ },
85
+ [timeout]
86
+ );
87
+ return { copy, copied };
88
+ }
89
+ function useDisclosure(defaultOpen = false) {
90
+ const [isOpen, setIsOpen] = useState(defaultOpen);
91
+ const open = useCallback(() => setIsOpen(true), []);
92
+ const close = useCallback(() => setIsOpen(false), []);
93
+ const toggle = useCallback(() => setIsOpen((prev) => !prev), []);
94
+ return { isOpen, open, close, toggle };
95
+ }
96
+ function useMediaQuery(query) {
97
+ const [matches, setMatches] = useState(() => {
98
+ if (typeof window === "undefined") return false;
99
+ return window.matchMedia(query).matches;
100
+ });
101
+ useEffect(() => {
102
+ if (typeof window === "undefined") return;
103
+ const mediaQueryList = window.matchMedia(query);
104
+ setMatches(mediaQueryList.matches);
105
+ const handler = (event) => {
106
+ setMatches(event.matches);
107
+ };
108
+ mediaQueryList.addEventListener("change", handler);
109
+ return () => {
110
+ mediaQueryList.removeEventListener("change", handler);
111
+ };
112
+ }, [query]);
113
+ return matches;
114
+ }
115
+ function useDebounce(value, delayMs = 300) {
116
+ const [debouncedValue, setDebouncedValue] = useState(value);
117
+ useEffect(() => {
118
+ const timer = setTimeout(() => {
119
+ setDebouncedValue(value);
120
+ }, delayMs);
121
+ return () => {
122
+ clearTimeout(timer);
123
+ };
124
+ }, [value, delayMs]);
125
+ return debouncedValue;
126
+ }
127
+
128
+ // src/tokens/colors.ts
129
+ var colors = {
130
+ bg: "#0a0a0f",
131
+ fg: "#f9fafb",
132
+ surface: {
133
+ 0: "#0a0a0f",
134
+ 50: "#0f0f18",
135
+ 100: "#141420",
136
+ 200: "#1a1a2e",
137
+ 300: "#24243a",
138
+ 400: "#2a2a4a"
139
+ },
140
+ primary: { DEFAULT: "#8b5cf6", light: "#a78bfa", dark: "#7c3aed" },
141
+ accent: { DEFAULT: "#667eea", light: "#8b9cf7", dark: "#4c5fd4" },
142
+ glow: { purple: "#764ba2", pink: "#f093fb" },
143
+ success: "#10b981",
144
+ warning: "#f59e0b",
145
+ danger: "#f43f5e"
146
+ };
59
147
  var base = "inline-flex items-center justify-center gap-2 rounded-xl font-semibold leading-none transition-[transform,background-color,box-shadow,opacity] select-none [-webkit-tap-highlight-color:transparent] active:translate-y-[0.5px] disabled:opacity-60 disabled:pointer-events-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent";
60
148
  var sizeClass = {
61
149
  sm: "px-3 py-2 text-sm",
@@ -63,10 +151,10 @@ var sizeClass = {
63
151
  lg: "px-4 py-3 text-base"
64
152
  };
65
153
  var variantClass = {
66
- primary: "bg-gradient-to-r from-violet-600 to-purple-600 text-white shadow-[0_0_25px_rgba(139,92,246,0.35)] hover:shadow-[0_0_35px_rgba(139,92,246,0.5)] hover:scale-[1.02]",
67
- success: "bg-emerald-600 text-white shadow-[0_10px_18px_rgba(16,185,129,0.22)] hover:bg-emerald-700",
68
- warning: "bg-amber-600 text-white shadow-[0_10px_18px_rgba(245,158,11,0.22)] hover:bg-amber-700",
69
- danger: "bg-rose-600 text-white shadow-[0_10px_18px_rgba(244,63,94,0.22)] hover:bg-rose-700",
154
+ primary: "bg-gradient-to-r from-violet-600 to-purple-600 text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02]",
155
+ success: "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20 hover:bg-emerald-700",
156
+ warning: "bg-amber-600 text-white shadow-lg shadow-amber-600/20 hover:bg-amber-700",
157
+ danger: "bg-rose-600 text-white shadow-lg shadow-rose-600/20 hover:bg-rose-700",
70
158
  secondary: "text-white bg-white/5 ring-1 ring-white/10 hover:bg-white/10 hover:ring-white/20 backdrop-blur-sm",
71
159
  ghost: "text-white/70 hover:text-white hover:bg-white/5"
72
160
  };
@@ -78,6 +166,7 @@ var Button = forwardRef(function Button2({ variant = "secondary", size = "md", l
78
166
  type: type === "submit" ? "submit" : type === "reset" ? "reset" : "button",
79
167
  ...props,
80
168
  disabled: disabled || loading,
169
+ ...loading ? { "aria-busy": true } : {},
81
170
  className: cn(base, sizeClass[size], variantClass[variant], className),
82
171
  children: loading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-center gap-2", children: [
83
172
  /* @__PURE__ */ jsx("span", { className: "w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
@@ -98,9 +187,9 @@ var sizeClass2 = {
98
187
  };
99
188
  var variantClass2 = {
100
189
  primary: "bg-primary text-white shadow-glow hover:brightness-[0.98]",
101
- success: "bg-emerald-600 text-white shadow-[0_10px_18px_rgba(16,185,129,0.22)] hover:bg-emerald-700",
102
- warning: "bg-amber-600 text-white shadow-[0_10px_18px_rgba(245,158,11,0.22)] hover:bg-amber-700",
103
- danger: "bg-rose-600 text-white shadow-[0_10px_18px_rgba(244,63,94,0.22)] hover:bg-rose-700",
190
+ success: "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20 hover:bg-emerald-700",
191
+ warning: "bg-amber-600 text-white shadow-lg shadow-amber-600/20 hover:bg-amber-700",
192
+ danger: "bg-rose-600 text-white shadow-lg shadow-rose-600/20 hover:bg-rose-700",
104
193
  secondary: "text-white bg-white/10 shadow-sm ring-1 ring-white/10",
105
194
  ghost: "text-white hover:bg-white/10"
106
195
  };
@@ -117,69 +206,155 @@ var IconButton = forwardRef(function IconButton2({ icon, variant = "ghost", size
117
206
  }
118
207
  );
119
208
  });
120
- var inputBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus:ring-2 focus:ring-primary/40 transition-shadow";
209
+ var inputBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus-visible:ring-2 focus-visible:ring-primary/40 transition-shadow";
121
210
  var Input = forwardRef(function Input2({ hasError, label, error, helperText, className, id: externalId, ...props }, ref) {
122
211
  const generatedId = useId();
123
212
  const inputId = externalId || generatedId;
124
213
  const showError = hasError || !!error;
214
+ const errorId = error ? `${inputId}-error` : void 0;
215
+ const helperId = helperText && !error ? `${inputId}-helper` : void 0;
125
216
  const input = /* @__PURE__ */ jsx(
126
217
  "input",
127
218
  {
219
+ ...props,
128
220
  ref,
129
221
  id: inputId,
130
- ...props,
222
+ "aria-invalid": showError || void 0,
223
+ "aria-describedby": errorId || helperId || void 0,
131
224
  className: cn(inputBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className)
132
225
  }
133
226
  );
134
227
  if (!label && !error && !helperText) return input;
135
228
  return /* @__PURE__ */ jsxs("div", { children: [
136
- label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/50 mb-1.5", children: label }),
229
+ label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/70 mb-1.5", children: label }),
137
230
  input,
138
- error && /* @__PURE__ */ jsx("p", { className: "text-rose-400 text-xs mt-1", children: error }),
139
- helperText && !error && /* @__PURE__ */ jsx("p", { className: "text-white/40 text-xs mt-1", children: helperText })
231
+ error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1", children: error }),
232
+ helperText && !error && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-white/40 text-xs mt-1", children: helperText })
140
233
  ] });
141
234
  });
142
- var selectBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none focus:ring-2 focus:ring-primary/40 transition-shadow";
143
- var Select = forwardRef(function Select2({ hasError, label, error, className, id: externalId, children, ...props }, ref) {
235
+ var inputBase2 = "w-full rounded-xl pl-10 pr-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus-visible:ring-2 focus-visible:ring-primary/40 transition-shadow";
236
+ var SearchInput = forwardRef(function SearchInput2({ label, onClear, className, id: externalId, placeholder = "Search...", value, ...props }, ref) {
237
+ const generatedId = useId();
238
+ const inputId = externalId || generatedId;
239
+ const hasValue = value !== void 0 && value !== "";
240
+ const wrapper = /* @__PURE__ */ jsxs("div", { className: "relative", children: [
241
+ /* @__PURE__ */ jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-white/40", children: /* @__PURE__ */ jsxs(
242
+ "svg",
243
+ {
244
+ xmlns: "http://www.w3.org/2000/svg",
245
+ width: "16",
246
+ height: "16",
247
+ viewBox: "0 0 24 24",
248
+ fill: "none",
249
+ stroke: "currentColor",
250
+ strokeWidth: "2",
251
+ strokeLinecap: "round",
252
+ strokeLinejoin: "round",
253
+ "aria-hidden": "true",
254
+ children: [
255
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
256
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
257
+ ]
258
+ }
259
+ ) }),
260
+ /* @__PURE__ */ jsx(
261
+ "input",
262
+ {
263
+ ref,
264
+ id: inputId,
265
+ type: "search",
266
+ value,
267
+ placeholder,
268
+ ...props,
269
+ className: cn(inputBase2, hasValue && "pr-9", className)
270
+ }
271
+ ),
272
+ hasValue && onClear && /* @__PURE__ */ jsx(
273
+ "button",
274
+ {
275
+ type: "button",
276
+ onClick: onClear,
277
+ "aria-label": "Clear search",
278
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-white/40 hover:text-white transition-colors",
279
+ children: /* @__PURE__ */ jsxs(
280
+ "svg",
281
+ {
282
+ xmlns: "http://www.w3.org/2000/svg",
283
+ width: "14",
284
+ height: "14",
285
+ viewBox: "0 0 24 24",
286
+ fill: "none",
287
+ stroke: "currentColor",
288
+ strokeWidth: "2",
289
+ strokeLinecap: "round",
290
+ strokeLinejoin: "round",
291
+ "aria-hidden": "true",
292
+ children: [
293
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
294
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
295
+ ]
296
+ }
297
+ )
298
+ }
299
+ )
300
+ ] });
301
+ if (!label) return wrapper;
302
+ return /* @__PURE__ */ jsxs("div", { children: [
303
+ /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/70 mb-1.5", children: label }),
304
+ wrapper
305
+ ] });
306
+ });
307
+ var selectBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-primary/40 transition-shadow";
308
+ var Select = forwardRef(function Select2({ hasError, label, error, helperText, className, id: externalId, children, ...props }, ref) {
144
309
  const generatedId = useId();
145
310
  const selectId = externalId || generatedId;
146
311
  const showError = hasError || !!error;
312
+ const errorId = error ? `${selectId}-error` : void 0;
313
+ const helperId = helperText && !error ? `${selectId}-helper` : void 0;
147
314
  const select = /* @__PURE__ */ jsx(
148
315
  "select",
149
316
  {
317
+ ...props,
150
318
  ref,
151
319
  id: selectId,
152
- ...props,
320
+ "aria-invalid": showError || void 0,
321
+ "aria-describedby": errorId || helperId || void 0,
153
322
  className: cn(selectBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className),
154
323
  children
155
324
  }
156
325
  );
157
- if (!label && !error) return select;
326
+ if (!label && !error && !helperText) return select;
158
327
  return /* @__PURE__ */ jsxs("div", { children: [
159
- label && /* @__PURE__ */ jsx("label", { htmlFor: selectId, className: "block text-sm text-white/50 mb-1.5", children: label }),
328
+ label && /* @__PURE__ */ jsx("label", { htmlFor: selectId, className: "block text-sm text-white/70 mb-1.5", children: label }),
160
329
  select,
161
- error && /* @__PURE__ */ jsx("p", { className: "text-rose-400 text-xs mt-1", children: error })
330
+ error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1", children: error }),
331
+ helperText && !error && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-white/40 text-xs mt-1", children: helperText })
162
332
  ] });
163
333
  });
164
- var textareaBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus:ring-2 focus:ring-primary/40 transition-shadow resize-y";
165
- var Textarea = forwardRef(function Textarea2({ hasError, label, error, className, id: externalId, ...props }, ref) {
334
+ var textareaBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus-visible:ring-2 focus-visible:ring-primary/40 transition-shadow resize-y";
335
+ var Textarea = forwardRef(function Textarea2({ hasError, label, error, helperText, className, id: externalId, ...props }, ref) {
166
336
  const generatedId = useId();
167
337
  const textareaId = externalId || generatedId;
168
338
  const showError = hasError || !!error;
339
+ const errorId = error ? `${textareaId}-error` : void 0;
340
+ const helperId = helperText && !error ? `${textareaId}-helper` : void 0;
169
341
  const textarea = /* @__PURE__ */ jsx(
170
342
  "textarea",
171
343
  {
344
+ ...props,
172
345
  ref,
173
346
  id: textareaId,
174
- ...props,
347
+ "aria-invalid": showError || void 0,
348
+ "aria-describedby": errorId || helperId || void 0,
175
349
  className: cn(textareaBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className)
176
350
  }
177
351
  );
178
- if (!label && !error) return textarea;
352
+ if (!label && !error && !helperText) return textarea;
179
353
  return /* @__PURE__ */ jsxs("div", { children: [
180
- label && /* @__PURE__ */ jsx("label", { htmlFor: textareaId, className: "block text-sm text-white/50 mb-1.5", children: label }),
354
+ label && /* @__PURE__ */ jsx("label", { htmlFor: textareaId, className: "block text-sm text-white/70 mb-1.5", children: label }),
181
355
  textarea,
182
- error && /* @__PURE__ */ jsx("p", { className: "text-rose-400 text-xs mt-1", children: error })
356
+ error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1", children: error }),
357
+ helperText && !error && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-white/40 text-xs mt-1", children: helperText })
183
358
  ] });
184
359
  });
185
360
  var base3 = "inline-flex items-center justify-center rounded-full font-semibold ring-1 ring-white/10";
@@ -209,11 +384,15 @@ var thumbSize = {
209
384
  sm: { base: "w-4 h-4", translate: "translate-x-4" },
210
385
  md: { base: "w-5 h-5", translate: "translate-x-5" }
211
386
  };
212
- function Toggle({ checked, onChange, disabled, label, size = "md", "aria-label": ariaLabel }) {
387
+ var Toggle = forwardRef(function Toggle2({ checked, onChange, disabled, label, size = "md", id: externalId, "aria-label": ariaLabel }, ref) {
388
+ const generatedId = useId();
389
+ const toggleId = externalId || generatedId;
213
390
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
214
391
  /* @__PURE__ */ jsx(
215
392
  "button",
216
393
  {
394
+ ref,
395
+ id: toggleId,
217
396
  type: "button",
218
397
  role: "switch",
219
398
  "aria-checked": checked,
@@ -238,20 +417,247 @@ function Toggle({ checked, onChange, disabled, label, size = "md", "aria-label":
238
417
  )
239
418
  }
240
419
  ),
241
- label && /* @__PURE__ */ jsx("span", { className: "text-sm text-white/60", children: label })
420
+ label && /* @__PURE__ */ jsx("label", { htmlFor: toggleId, className: "text-sm text-white/60 cursor-pointer", children: label })
421
+ ] });
422
+ });
423
+ var Checkbox = forwardRef(function Checkbox2({ label, error, indeterminate, className, id: externalId, disabled, checked, onChange, ...props }, ref) {
424
+ const generatedId = useId();
425
+ const inputId = externalId || generatedId;
426
+ const errorId = error ? `${inputId}-error` : void 0;
427
+ const internalRef = useRef(null);
428
+ useEffect(() => {
429
+ const el = internalRef.current;
430
+ if (el) {
431
+ el.indeterminate = indeterminate ?? false;
432
+ }
433
+ }, [indeterminate]);
434
+ const setRefs = (el) => {
435
+ internalRef.current = el;
436
+ if (typeof ref === "function") {
437
+ ref(el);
438
+ } else if (ref) {
439
+ ref.current = el;
440
+ }
441
+ };
442
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-1", className), children: [
443
+ /* @__PURE__ */ jsxs(
444
+ "label",
445
+ {
446
+ htmlFor: inputId,
447
+ className: cn(
448
+ "inline-flex items-center gap-2.5",
449
+ disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
450
+ ),
451
+ children: [
452
+ /* @__PURE__ */ jsx(
453
+ "input",
454
+ {
455
+ ...props,
456
+ ref: setRefs,
457
+ id: inputId,
458
+ type: "checkbox",
459
+ checked,
460
+ onChange,
461
+ disabled,
462
+ "aria-invalid": !!error || void 0,
463
+ "aria-describedby": errorId,
464
+ className: "sr-only peer"
465
+ }
466
+ ),
467
+ /* @__PURE__ */ jsxs(
468
+ "span",
469
+ {
470
+ className: cn(
471
+ "relative flex items-center justify-center w-5 h-5 rounded-md",
472
+ "bg-white/10 border border-white/20",
473
+ "transition-colors duration-150",
474
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40 peer-focus-visible:ring-offset-1 peer-focus-visible:ring-offset-transparent",
475
+ checked && !indeterminate && "bg-primary border-primary",
476
+ indeterminate && "bg-primary border-primary"
477
+ ),
478
+ "aria-hidden": "true",
479
+ children: [
480
+ checked && !indeterminate && /* @__PURE__ */ jsx(
481
+ "svg",
482
+ {
483
+ viewBox: "0 0 12 10",
484
+ fill: "none",
485
+ stroke: "white",
486
+ strokeWidth: "1.8",
487
+ strokeLinecap: "round",
488
+ strokeLinejoin: "round",
489
+ className: "w-3 h-2.5",
490
+ children: /* @__PURE__ */ jsx("polyline", { points: "1,5 4.5,8.5 11,1" })
491
+ }
492
+ ),
493
+ indeterminate && /* @__PURE__ */ jsx(
494
+ "svg",
495
+ {
496
+ viewBox: "0 0 10 2",
497
+ fill: "none",
498
+ stroke: "white",
499
+ strokeWidth: "2",
500
+ strokeLinecap: "round",
501
+ className: "w-2.5 h-0.5",
502
+ children: /* @__PURE__ */ jsx("line", { x1: "0", y1: "1", x2: "10", y2: "1" })
503
+ }
504
+ )
505
+ ]
506
+ }
507
+ ),
508
+ label && /* @__PURE__ */ jsx("span", { className: "text-sm text-white/80 select-none", children: label })
509
+ ]
510
+ }
511
+ ),
512
+ error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs ml-7", children: error })
242
513
  ] });
514
+ });
515
+ var RadioGroupContext = createContext(null);
516
+ function useRadioGroup() {
517
+ const ctx = useContext(RadioGroupContext);
518
+ if (!ctx) {
519
+ throw new Error("RadioItem must be used inside a RadioGroup");
520
+ }
521
+ return ctx;
522
+ }
523
+ function RadioGroup({
524
+ value: controlledValue,
525
+ defaultValue,
526
+ onValueChange,
527
+ name: externalName,
528
+ disabled = false,
529
+ orientation = "vertical",
530
+ label,
531
+ error,
532
+ children,
533
+ className
534
+ }) {
535
+ const groupId = useId();
536
+ const generatedName = useId();
537
+ const name = externalName ?? generatedName;
538
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
539
+ const isControlled = controlledValue !== void 0;
540
+ const currentValue = isControlled ? controlledValue : uncontrolledValue;
541
+ const handleChange = useCallback(
542
+ (val) => {
543
+ if (!isControlled) setUncontrolledValue(val);
544
+ onValueChange?.(val);
545
+ },
546
+ [isControlled, onValueChange]
547
+ );
548
+ const labelId = label ? `${groupId}-label` : void 0;
549
+ const errorId = error ? `${groupId}-error` : void 0;
550
+ const ctxValue = useMemo(
551
+ () => ({ value: currentValue, onChange: handleChange, name, disabled, groupId }),
552
+ [currentValue, handleChange, name, disabled, groupId]
553
+ );
554
+ return /* @__PURE__ */ jsx(RadioGroupContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxs(
555
+ "fieldset",
556
+ {
557
+ role: "radiogroup",
558
+ "aria-labelledby": labelId,
559
+ "aria-describedby": errorId,
560
+ "aria-disabled": disabled || void 0,
561
+ className: cn("border-none p-0 m-0", className),
562
+ children: [
563
+ label && /* @__PURE__ */ jsx("legend", { id: labelId, className: "text-sm text-white/50 mb-2 float-none p-0", children: label }),
564
+ /* @__PURE__ */ jsx(
565
+ "div",
566
+ {
567
+ className: cn(
568
+ "flex gap-3",
569
+ orientation === "vertical" ? "flex-col" : "flex-row flex-wrap"
570
+ ),
571
+ children
572
+ }
573
+ ),
574
+ error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1.5", children: error })
575
+ ]
576
+ }
577
+ ) });
578
+ }
579
+ function RadioItem({ value, disabled: itemDisabled, children, className }) {
580
+ const { value: groupValue, onChange, name, disabled: groupDisabled, groupId } = useRadioGroup();
581
+ const inputId = useId();
582
+ const inputRef = useRef(null);
583
+ const isDisabled = groupDisabled || itemDisabled;
584
+ const isSelected = groupValue === value;
585
+ const handleKeyDown = (e) => {
586
+ if (e.key !== "ArrowDown" && e.key !== "ArrowUp" && e.key !== "ArrowRight" && e.key !== "ArrowLeft") {
587
+ return;
588
+ }
589
+ e.preventDefault();
590
+ const fieldset = inputRef.current?.closest("fieldset");
591
+ if (!fieldset) return;
592
+ const inputs = Array.from(
593
+ fieldset.querySelectorAll(`input[type="radio"][name="${name}"]:not(:disabled)`)
594
+ );
595
+ const currentIndex = inputs.indexOf(inputRef.current);
596
+ if (currentIndex === -1) return;
597
+ const forward = e.key === "ArrowDown" || e.key === "ArrowRight";
598
+ const nextIndex = forward ? (currentIndex + 1) % inputs.length : (currentIndex - 1 + inputs.length) % inputs.length;
599
+ const nextInput = inputs[nextIndex];
600
+ nextInput.focus();
601
+ onChange(nextInput.value);
602
+ };
603
+ return /* @__PURE__ */ jsxs(
604
+ "label",
605
+ {
606
+ htmlFor: inputId,
607
+ className: cn(
608
+ "inline-flex items-center gap-2.5",
609
+ isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
610
+ className
611
+ ),
612
+ children: [
613
+ /* @__PURE__ */ jsx(
614
+ "input",
615
+ {
616
+ ref: inputRef,
617
+ id: inputId,
618
+ type: "radio",
619
+ name,
620
+ value,
621
+ checked: isSelected,
622
+ disabled: isDisabled,
623
+ onChange: () => onChange(value),
624
+ onKeyDown: handleKeyDown,
625
+ className: "sr-only peer",
626
+ "aria-describedby": void 0
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsx(
630
+ "span",
631
+ {
632
+ className: cn(
633
+ "relative flex items-center justify-center w-5 h-5 rounded-full",
634
+ "bg-white/10 border border-white/20",
635
+ "transition-colors duration-150",
636
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40 peer-focus-visible:ring-offset-1 peer-focus-visible:ring-offset-transparent",
637
+ isSelected && "border-primary"
638
+ ),
639
+ "aria-hidden": "true",
640
+ children: isSelected && /* @__PURE__ */ jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-primary" })
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-white/80 select-none", children })
644
+ ]
645
+ }
646
+ );
243
647
  }
244
648
  var sizeClass4 = {
245
649
  sm: "h-3 w-3 border",
246
650
  md: "h-4 w-4 border-2",
247
651
  lg: "h-6 w-6 border-2"
248
652
  };
249
- function Spinner({ className, size = "md" }) {
653
+ function Spinner({ className, size = "md", label }) {
250
654
  return /* @__PURE__ */ jsx(
251
655
  "span",
252
656
  {
253
657
  className: cn("inline-block rounded-full border-white/15 border-t-primary animate-spin", sizeClass4[size], className),
254
- "aria-hidden": "true"
658
+ role: label ? "status" : void 0,
659
+ "aria-hidden": label ? void 0 : "true",
660
+ "aria-label": label || void 0
255
661
  }
256
662
  );
257
663
  }
@@ -264,14 +670,170 @@ function Skeleton({ className, circle }) {
264
670
  }
265
671
  );
266
672
  }
267
- function Card({ hoverable, variant = "surface", className, ...props }) {
673
+ var TabsContext = createContext(null);
674
+ function useTabsContext() {
675
+ const ctx = useContext(TabsContext);
676
+ if (!ctx) throw new Error("Tabs sub-components must be used inside <Tabs>");
677
+ return ctx;
678
+ }
679
+ function Tabs({
680
+ defaultValue = "",
681
+ value,
682
+ onValueChange,
683
+ variant = "underline",
684
+ children,
685
+ className
686
+ }) {
687
+ const baseId = useId();
688
+ const isControlled = value !== void 0;
689
+ const [internalValue, setInternalValue] = useState(defaultValue);
690
+ const activeValue = isControlled ? value ?? "" : internalValue;
691
+ const setActiveValue = useCallback(
692
+ (next) => {
693
+ if (!isControlled) setInternalValue(next);
694
+ onValueChange?.(next);
695
+ },
696
+ [isControlled, onValueChange]
697
+ );
698
+ const ctxValue = useMemo(
699
+ () => ({ activeValue, setActiveValue, variant, baseId }),
700
+ [activeValue, setActiveValue, variant, baseId]
701
+ );
702
+ return /* @__PURE__ */ jsx(TabsContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx("div", { className: cn("flex flex-col", className), children }) });
703
+ }
704
+ function TabList({ children, className }) {
705
+ const { variant } = useTabsContext();
706
+ const listRef = useRef(null);
707
+ const handleKeyDown = (e) => {
708
+ const list = listRef.current;
709
+ if (!list) return;
710
+ const tabs = Array.from(list.querySelectorAll('[role="tab"]:not([disabled])'));
711
+ if (tabs.length === 0) return;
712
+ const focused = document.activeElement;
713
+ if (!focused || !list.contains(focused)) return;
714
+ const currentIndex = tabs.indexOf(focused);
715
+ let nextIndex = currentIndex;
716
+ switch (e.key) {
717
+ case "ArrowRight":
718
+ e.preventDefault();
719
+ nextIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0;
720
+ break;
721
+ case "ArrowLeft":
722
+ e.preventDefault();
723
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1;
724
+ break;
725
+ case "Home":
726
+ e.preventDefault();
727
+ nextIndex = 0;
728
+ break;
729
+ case "End":
730
+ e.preventDefault();
731
+ nextIndex = tabs.length - 1;
732
+ break;
733
+ case "Enter":
734
+ case " ":
735
+ e.preventDefault();
736
+ focused?.click();
737
+ return;
738
+ default:
739
+ return;
740
+ }
741
+ tabs[nextIndex]?.focus();
742
+ tabs[nextIndex]?.click();
743
+ };
268
744
  return /* @__PURE__ */ jsx(
269
745
  "div",
270
746
  {
747
+ ref: listRef,
748
+ role: "tablist",
749
+ onKeyDown: handleKeyDown,
750
+ className: cn(
751
+ "flex",
752
+ variant === "underline" && "border-b border-white/10",
753
+ variant === "pill" && "gap-1 p-1 rounded-xl bg-white/5 ring-1 ring-white/10 w-fit",
754
+ className
755
+ ),
756
+ children
757
+ }
758
+ );
759
+ }
760
+ function Tab({ value, disabled = false, children, className }) {
761
+ const { activeValue, setActiveValue, variant, baseId } = useTabsContext();
762
+ const isActive = activeValue === value;
763
+ return /* @__PURE__ */ jsx(
764
+ "button",
765
+ {
766
+ type: "button",
767
+ role: "tab",
768
+ id: `${baseId}-tab-${value}`,
769
+ "aria-selected": isActive,
770
+ "aria-controls": `${baseId}-panel-${value}`,
771
+ disabled,
772
+ tabIndex: isActive ? 0 : -1,
773
+ onClick: () => {
774
+ if (!disabled) setActiveValue(value);
775
+ },
776
+ className: cn(
777
+ "relative px-4 py-2.5 text-sm font-medium transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 focus-visible:ring-offset-transparent",
778
+ // underline variant
779
+ variant === "underline" && [
780
+ "border-b-2 -mb-px",
781
+ isActive ? "border-primary text-white" : "border-transparent text-white/40 hover:text-white/70 hover:border-white/20",
782
+ disabled && "opacity-40 cursor-not-allowed hover:text-white/40 hover:border-transparent"
783
+ ],
784
+ // pill variant
785
+ variant === "pill" && [
786
+ "rounded-lg",
787
+ isActive ? "bg-white/10 text-white shadow-sm" : "text-white/50 hover:text-white/80 hover:bg-white/5",
788
+ disabled && "opacity-40 cursor-not-allowed hover:bg-transparent hover:text-white/50"
789
+ ],
790
+ className
791
+ ),
792
+ children
793
+ }
794
+ );
795
+ }
796
+ function TabPanel({ value, children, className }) {
797
+ const { activeValue, baseId } = useTabsContext();
798
+ if (activeValue !== value) return null;
799
+ return /* @__PURE__ */ jsx(
800
+ "div",
801
+ {
802
+ role: "tabpanel",
803
+ id: `${baseId}-panel-${value}`,
804
+ "aria-labelledby": `${baseId}-tab-${value}`,
805
+ tabIndex: 0,
806
+ className: cn("mt-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", className),
807
+ children
808
+ }
809
+ );
810
+ }
811
+ var Card = forwardRef(function Card2({ hoverable, variant = "surface", className, ...props }, ref) {
812
+ return /* @__PURE__ */ jsx(
813
+ "div",
814
+ {
815
+ ref,
271
816
  ...props,
272
817
  className: cn(variant === "glass" ? "glass" : "surface", hoverable && "surface-hover", className)
273
818
  }
274
819
  );
820
+ });
821
+ var scrollLockCount = 0;
822
+ var savedOverflow = "";
823
+ function lockScroll() {
824
+ if (typeof document === "undefined") return;
825
+ if (scrollLockCount === 0) {
826
+ savedOverflow = document.body.style.overflow;
827
+ document.body.style.overflow = "hidden";
828
+ }
829
+ scrollLockCount++;
830
+ }
831
+ function unlockScroll() {
832
+ if (typeof document === "undefined") return;
833
+ scrollLockCount = Math.max(0, scrollLockCount - 1);
834
+ if (scrollLockCount === 0) {
835
+ document.body.style.overflow = savedOverflow;
836
+ }
275
837
  }
276
838
  function Modal({
277
839
  isOpen,
@@ -304,6 +866,11 @@ function Modal({
304
866
  if (lastActive?.isConnected) focusSafely(lastActive);
305
867
  };
306
868
  }, [isOpen]);
869
+ useEffect(() => {
870
+ if (!isOpen) return;
871
+ lockScroll();
872
+ return () => unlockScroll();
873
+ }, [isOpen]);
307
874
  if (!isOpen) return null;
308
875
  return /* @__PURE__ */ jsx(
309
876
  "div",
@@ -329,7 +896,7 @@ function Modal({
329
896
  ref: dialogRef,
330
897
  className: cn(
331
898
  "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 animate-modal-pop focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
332
- useGlass && "glass bg-[#0f0f18]/80",
899
+ useGlass && "glass bg-surface-50/80",
333
900
  contentClassName
334
901
  ),
335
902
  onMouseDown: (e) => e.stopPropagation(),
@@ -442,17 +1009,17 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
442
1009
  const anchorRef = useRef(null);
443
1010
  const [open, setOpen] = useState(false);
444
1011
  const [pos, setPos] = useState(null);
445
- const contentStr = useMemo(() => {
1012
+ useMemo(() => {
446
1013
  if (typeof content === "string") return content.trim();
447
1014
  return "";
448
1015
  }, [content]);
449
- function clearTimer() {
1016
+ const clearTimer = useCallback(() => {
450
1017
  if (openTimerRef.current !== null) {
451
1018
  window.clearTimeout(openTimerRef.current);
452
1019
  openTimerRef.current = null;
453
1020
  }
454
- }
455
- function updatePosition() {
1021
+ }, []);
1022
+ const updatePosition = useCallback(() => {
456
1023
  const el = anchorRef.current;
457
1024
  if (!el) return;
458
1025
  const r = el.getBoundingClientRect();
@@ -467,17 +1034,17 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
467
1034
  top: Math.round(effPlacement === "top" ? topY : bottomY),
468
1035
  placement: effPlacement
469
1036
  });
470
- }
471
- function scheduleOpen() {
1037
+ }, [placement]);
1038
+ const scheduleOpen = useCallback(() => {
472
1039
  clearTimer();
473
1040
  openTimerRef.current = window.setTimeout(() => {
474
1041
  setOpen(true);
475
1042
  }, Math.max(0, delayMs));
476
- }
477
- function close() {
1043
+ }, [clearTimer, delayMs]);
1044
+ const close = useCallback(() => {
478
1045
  clearTimer();
479
1046
  setOpen(false);
480
- }
1047
+ }, [clearTimer]);
481
1048
  useEffect(() => {
482
1049
  if (!open) return;
483
1050
  updatePosition();
@@ -489,45 +1056,52 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
489
1056
  window.removeEventListener("scroll", onScroll, true);
490
1057
  window.removeEventListener("resize", onResize);
491
1058
  };
492
- }, [open]);
1059
+ }, [open, updatePosition]);
493
1060
  useEffect(() => {
494
1061
  return () => clearTimer();
495
- }, []);
1062
+ }, [clearTimer]);
1063
+ if (!isValidElement(children)) return children;
496
1064
  const child = cloneElement(children, {
497
1065
  ref: (node) => {
498
1066
  anchorRef.current = node;
499
- const prevRef = children.ref;
1067
+ const childProps = children.props;
1068
+ const prevRef = childProps.ref;
500
1069
  if (typeof prevRef === "function") prevRef(node);
501
1070
  else if (prevRef && typeof prevRef === "object") prevRef.current = node;
502
1071
  },
503
1072
  onMouseEnter: (e) => {
504
- children.props.onMouseEnter?.(e);
1073
+ const childProps = children.props;
1074
+ if (typeof childProps.onMouseEnter === "function") childProps.onMouseEnter(e);
505
1075
  scheduleOpen();
506
1076
  },
507
1077
  onMouseLeave: (e) => {
508
- children.props.onMouseLeave?.(e);
1078
+ const childProps = children.props;
1079
+ if (typeof childProps.onMouseLeave === "function") childProps.onMouseLeave(e);
509
1080
  close();
510
1081
  },
511
1082
  onFocus: (e) => {
512
- children.props.onFocus?.(e);
1083
+ const childProps = children.props;
1084
+ if (typeof childProps.onFocus === "function") childProps.onFocus(e);
513
1085
  scheduleOpen();
514
1086
  },
515
1087
  onBlur: (e) => {
516
- children.props.onBlur?.(e);
1088
+ const childProps = children.props;
1089
+ if (typeof childProps.onBlur === "function") childProps.onBlur(e);
517
1090
  close();
518
1091
  },
519
- ...contentStr ? { "aria-describedby": tooltipId } : {}
1092
+ ...open ? { "aria-describedby": tooltipId } : {}
520
1093
  });
521
1094
  return /* @__PURE__ */ jsxs(Fragment, { children: [
522
1095
  child,
523
- open && content ? createPortal(
1096
+ open && content && typeof document !== "undefined" ? createPortal(
524
1097
  /* @__PURE__ */ jsx(
525
1098
  "div",
526
1099
  {
527
1100
  id: tooltipId,
528
1101
  role: "tooltip",
529
1102
  className: cn(
530
- "fixed z-[9999] max-w-[320px] rounded-lg bg-gray-900 px-3 py-2 text-xs leading-snug text-white shadow-xl pointer-events-none",
1103
+ "fixed z-[9999] max-w-[320px] rounded-lg px-3 py-2 text-xs leading-snug shadow-xl pointer-events-none",
1104
+ "bg-surface-100 text-white ring-1 ring-white/10",
531
1105
  className
532
1106
  ),
533
1107
  style: pos ? {
@@ -553,14 +1127,18 @@ function EmptyState({ icon: Icon, title, description, actionLabel, onAction, chi
553
1127
  }
554
1128
  function CollapsibleSection({ title, defaultOpen = true, children, right, className }) {
555
1129
  const [isOpen, setIsOpen] = useState(defaultOpen);
1130
+ const contentId = useId();
1131
+ const titleId = `${contentId}-title`;
556
1132
  return /* @__PURE__ */ jsxs("div", { className: cn("rounded-xl ring-1 ring-white/10 bg-white/5 overflow-hidden", className), children: [
557
1133
  /* @__PURE__ */ jsxs(
558
1134
  "button",
559
1135
  {
560
1136
  type: "button",
1137
+ id: titleId,
561
1138
  onClick: () => setIsOpen((prev) => !prev),
562
1139
  className: "flex w-full items-center justify-between gap-3 px-4 py-3 text-left hover:bg-white/[0.03] transition-colors",
563
1140
  "aria-expanded": isOpen,
1141
+ "aria-controls": contentId,
564
1142
  children: [
565
1143
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
566
1144
  /* @__PURE__ */ jsx(
@@ -584,6 +1162,9 @@ function CollapsibleSection({ title, defaultOpen = true, children, right, classN
584
1162
  /* @__PURE__ */ jsx(
585
1163
  "div",
586
1164
  {
1165
+ id: contentId,
1166
+ role: "region",
1167
+ "aria-labelledby": titleId,
587
1168
  className: cn(
588
1169
  "grid transition-[grid-template-rows] duration-200 ease-out",
589
1170
  isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
@@ -593,6 +1174,349 @@ function CollapsibleSection({ title, defaultOpen = true, children, right, classN
593
1174
  )
594
1175
  ] });
595
1176
  }
1177
+ var DropdownContext = createContext(null);
1178
+ function useDropdownContext(component) {
1179
+ const ctx = useContext(DropdownContext);
1180
+ if (!ctx) {
1181
+ throw new Error(`<${component}> must be rendered inside <Dropdown>`);
1182
+ }
1183
+ return ctx;
1184
+ }
1185
+ function Dropdown({ children, className }) {
1186
+ const [open, setOpen] = useState(false);
1187
+ const triggerId = useId();
1188
+ const menuId = useId();
1189
+ const triggerRef = useRef(null);
1190
+ const toggleOpen = useCallback(() => setOpen((prev) => !prev), []);
1191
+ const ctxValue = useMemo(
1192
+ () => ({ open, setOpen, toggleOpen, triggerId, menuId, triggerRef }),
1193
+ [open, setOpen, toggleOpen, triggerId, menuId, triggerRef]
1194
+ );
1195
+ return /* @__PURE__ */ jsx(DropdownContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx("div", { className: cn("relative inline-block", className), children }) });
1196
+ }
1197
+ function DropdownTrigger({ children, className }) {
1198
+ const { open, toggleOpen, triggerId, menuId, triggerRef } = useDropdownContext("DropdownTrigger");
1199
+ if (!isValidElement(children)) return children;
1200
+ return cloneElement(children, {
1201
+ id: triggerId,
1202
+ "aria-haspopup": "menu",
1203
+ "aria-expanded": open,
1204
+ "aria-controls": open ? menuId : void 0,
1205
+ className: cn(children.props.className, className),
1206
+ ref: (node) => {
1207
+ triggerRef.current = node;
1208
+ const childRef = children.ref;
1209
+ if (typeof childRef === "function") childRef(node);
1210
+ else if (childRef && typeof childRef === "object") childRef.current = node;
1211
+ },
1212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1213
+ onClick: (e) => {
1214
+ const childProps = children.props;
1215
+ if (typeof childProps.onClick === "function") childProps.onClick(e);
1216
+ toggleOpen();
1217
+ }
1218
+ });
1219
+ }
1220
+ function DropdownMenu({ children, className, align = "left" }) {
1221
+ const { open, setOpen, menuId, triggerId, triggerRef } = useDropdownContext("DropdownMenu");
1222
+ const menuRef = useRef(null);
1223
+ const [pos, setPos] = useState(null);
1224
+ const [visible, setVisible] = useState(false);
1225
+ const updatePosition = useCallback(() => {
1226
+ const trigger = triggerRef.current;
1227
+ if (!trigger) return;
1228
+ const rect = trigger.getBoundingClientRect();
1229
+ const left = align === "right" ? rect.right : rect.left;
1230
+ setPos({
1231
+ top: Math.round(rect.bottom + 6),
1232
+ left: Math.round(left)
1233
+ });
1234
+ }, [align, triggerRef]);
1235
+ useEffect(() => {
1236
+ if (!open) {
1237
+ setVisible(false);
1238
+ return;
1239
+ }
1240
+ updatePosition();
1241
+ const raf = window.requestAnimationFrame(() => {
1242
+ setVisible(true);
1243
+ const menuEl = menuRef.current;
1244
+ if (!menuEl) return;
1245
+ const items = getFocusableElements(menuEl);
1246
+ focusSafely(items[0] ?? menuEl);
1247
+ });
1248
+ return () => window.cancelAnimationFrame(raf);
1249
+ }, [open, updatePosition]);
1250
+ useEffect(() => {
1251
+ if (!open) return;
1252
+ function handlePointerDown(e) {
1253
+ const target = e.target;
1254
+ if (menuRef.current?.contains(target)) return;
1255
+ if (triggerRef.current?.contains(target)) return;
1256
+ setOpen(false);
1257
+ }
1258
+ document.addEventListener("pointerdown", handlePointerDown, true);
1259
+ return () => document.removeEventListener("pointerdown", handlePointerDown, true);
1260
+ }, [open, setOpen, triggerRef]);
1261
+ useEffect(() => {
1262
+ if (!open) return;
1263
+ const onScroll = () => updatePosition();
1264
+ const onResize = () => updatePosition();
1265
+ window.addEventListener("scroll", onScroll, true);
1266
+ window.addEventListener("resize", onResize);
1267
+ return () => {
1268
+ window.removeEventListener("scroll", onScroll, true);
1269
+ window.removeEventListener("resize", onResize);
1270
+ };
1271
+ }, [open, updatePosition]);
1272
+ if (!open) return null;
1273
+ const panel = /* @__PURE__ */ jsx(
1274
+ "div",
1275
+ {
1276
+ ref: menuRef,
1277
+ id: menuId,
1278
+ role: "menu",
1279
+ "aria-labelledby": triggerId,
1280
+ tabIndex: -1,
1281
+ className: cn(
1282
+ // Base glass panel
1283
+ "fixed z-[9999] min-w-[10rem] py-1",
1284
+ "rounded-xl shadow-xl ring-1 ring-white/10",
1285
+ "bg-surface-50/90 backdrop-blur-md",
1286
+ // Animation
1287
+ "transition-[opacity,transform] duration-150 ease-out",
1288
+ visible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-1",
1289
+ className
1290
+ ),
1291
+ style: pos ? {
1292
+ top: pos.top,
1293
+ ...align === "right" ? { right: `calc(100vw - ${pos.left}px)` } : { left: pos.left }
1294
+ } : { top: 0, left: 0, visibility: "hidden" },
1295
+ onKeyDown: (e) => {
1296
+ const menuEl = menuRef.current;
1297
+ if (!menuEl) return;
1298
+ if (e.key === "Escape" || e.key === "Tab") {
1299
+ if (e.key === "Escape") {
1300
+ e.preventDefault();
1301
+ e.stopPropagation();
1302
+ focusSafely(triggerRef.current);
1303
+ }
1304
+ setOpen(false);
1305
+ return;
1306
+ }
1307
+ const items = getFocusableElements(menuEl).filter(
1308
+ (el) => el.getAttribute("role") === "menuitem" && el.getAttribute("aria-disabled") !== "true"
1309
+ );
1310
+ if (items.length === 0) return;
1311
+ const active = document.activeElement;
1312
+ const currentIndex = items.findIndex((el) => el === active);
1313
+ if (e.key === "ArrowDown") {
1314
+ e.preventDefault();
1315
+ const next = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
1316
+ focusSafely(items[next]);
1317
+ } else if (e.key === "ArrowUp") {
1318
+ e.preventDefault();
1319
+ const prev = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
1320
+ focusSafely(items[prev]);
1321
+ } else if (e.key === "Home") {
1322
+ e.preventDefault();
1323
+ focusSafely(items[0]);
1324
+ } else if (e.key === "End") {
1325
+ e.preventDefault();
1326
+ focusSafely(items[items.length - 1]);
1327
+ }
1328
+ },
1329
+ children
1330
+ }
1331
+ );
1332
+ if (typeof document === "undefined") return null;
1333
+ return createPortal(panel, document.body);
1334
+ }
1335
+ function DropdownItem({ onSelect, disabled = false, children, className }) {
1336
+ const { setOpen, triggerRef } = useDropdownContext("DropdownItem");
1337
+ const handleSelect = useCallback(() => {
1338
+ if (disabled) return;
1339
+ setOpen(false);
1340
+ focusSafely(triggerRef.current);
1341
+ onSelect?.();
1342
+ }, [disabled, onSelect, setOpen, triggerRef]);
1343
+ return /* @__PURE__ */ jsx(
1344
+ "div",
1345
+ {
1346
+ role: "menuitem",
1347
+ tabIndex: disabled ? -1 : 0,
1348
+ "aria-disabled": disabled ? "true" : void 0,
1349
+ className: cn(
1350
+ "flex items-center gap-2 w-full px-3 py-2 text-sm text-left cursor-pointer select-none",
1351
+ "text-white/80 transition-colors duration-100",
1352
+ "focus:outline-none focus-visible:bg-white/10",
1353
+ disabled ? "opacity-40 pointer-events-none cursor-not-allowed" : "hover:bg-white/10 hover:text-white focus-visible:text-white",
1354
+ className
1355
+ ),
1356
+ onClick: handleSelect,
1357
+ onKeyDown: (e) => {
1358
+ if (e.key === "Enter" || e.key === " ") {
1359
+ e.preventDefault();
1360
+ handleSelect();
1361
+ }
1362
+ },
1363
+ children
1364
+ }
1365
+ );
1366
+ }
1367
+ function DropdownSeparator({ className }) {
1368
+ return /* @__PURE__ */ jsx("div", { role: "separator", className: cn("border-t border-white/10 my-1", className) });
1369
+ }
1370
+ function matchesAccept(file, accept) {
1371
+ const types = accept.split(",").map((t) => t.trim().toLowerCase());
1372
+ const fileName = file.name.toLowerCase();
1373
+ const mimeType = file.type.toLowerCase();
1374
+ return types.some((t) => {
1375
+ if (t.startsWith(".")) return fileName.endsWith(t);
1376
+ if (t.endsWith("/*")) return mimeType.startsWith(t.slice(0, -1));
1377
+ return mimeType === t;
1378
+ });
1379
+ }
1380
+ function DropZone({
1381
+ onFilesDropped,
1382
+ accept,
1383
+ maxFiles,
1384
+ maxSize,
1385
+ disabled = false,
1386
+ children,
1387
+ className,
1388
+ "aria-label": ariaLabel = "File drop zone"
1389
+ }) {
1390
+ const [isDragging, setIsDragging] = useState(false);
1391
+ const inputRef = useRef(null);
1392
+ const dragCounterRef = useRef(0);
1393
+ const processFiles = useCallback(
1394
+ (fileList) => {
1395
+ if (!fileList || disabled) return;
1396
+ let files = Array.from(fileList);
1397
+ if (accept) {
1398
+ files = files.filter((f) => matchesAccept(f, accept));
1399
+ }
1400
+ if (maxSize !== void 0) {
1401
+ files = files.filter((f) => f.size <= maxSize);
1402
+ }
1403
+ if (maxFiles !== void 0) {
1404
+ files = files.slice(0, maxFiles);
1405
+ }
1406
+ onFilesDropped(files);
1407
+ },
1408
+ [accept, disabled, maxFiles, maxSize, onFilesDropped]
1409
+ );
1410
+ const handleDragEnter = useCallback(
1411
+ (e) => {
1412
+ e.preventDefault();
1413
+ dragCounterRef.current++;
1414
+ if (!disabled) setIsDragging(true);
1415
+ },
1416
+ [disabled]
1417
+ );
1418
+ const handleDragOver = useCallback(
1419
+ (e) => {
1420
+ e.preventDefault();
1421
+ },
1422
+ []
1423
+ );
1424
+ const handleDragLeave = useCallback((e) => {
1425
+ e.preventDefault();
1426
+ dragCounterRef.current--;
1427
+ if (dragCounterRef.current === 0) setIsDragging(false);
1428
+ }, []);
1429
+ const handleDrop = useCallback(
1430
+ (e) => {
1431
+ e.preventDefault();
1432
+ dragCounterRef.current = 0;
1433
+ setIsDragging(false);
1434
+ if (disabled) return;
1435
+ processFiles(e.dataTransfer.files);
1436
+ },
1437
+ [disabled, processFiles]
1438
+ );
1439
+ const handleClick = useCallback(() => {
1440
+ if (!disabled) inputRef.current?.click();
1441
+ }, [disabled]);
1442
+ const handleKeyDown = useCallback(
1443
+ (e) => {
1444
+ if (disabled) return;
1445
+ if (e.key === "Enter" || e.key === " ") {
1446
+ e.preventDefault();
1447
+ inputRef.current?.click();
1448
+ }
1449
+ },
1450
+ [disabled]
1451
+ );
1452
+ const handleInputChange = useCallback(() => {
1453
+ processFiles(inputRef.current?.files ?? null);
1454
+ if (inputRef.current) inputRef.current.value = "";
1455
+ }, [processFiles]);
1456
+ return /* @__PURE__ */ jsxs(
1457
+ "div",
1458
+ {
1459
+ role: "button",
1460
+ tabIndex: disabled ? -1 : 0,
1461
+ "aria-label": ariaLabel,
1462
+ "aria-disabled": disabled,
1463
+ onClick: handleClick,
1464
+ onKeyDown: handleKeyDown,
1465
+ onDragEnter: handleDragEnter,
1466
+ onDragOver: handleDragOver,
1467
+ onDragLeave: handleDragLeave,
1468
+ onDrop: handleDrop,
1469
+ className: cn(
1470
+ "flex flex-col items-center justify-center gap-3 rounded-2xl border-2 border-dashed px-6 py-10 text-center transition-colors cursor-pointer select-none outline-none",
1471
+ "border-white/20 bg-white/5 backdrop-blur-sm",
1472
+ "hover:border-primary/50 hover:bg-white/[0.08]",
1473
+ "focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent",
1474
+ isDragging && "border-primary bg-primary/10",
1475
+ disabled && "pointer-events-none opacity-50",
1476
+ className
1477
+ ),
1478
+ children: [
1479
+ /* @__PURE__ */ jsx(
1480
+ "input",
1481
+ {
1482
+ ref: inputRef,
1483
+ type: "file",
1484
+ tabIndex: -1,
1485
+ className: "sr-only",
1486
+ accept,
1487
+ multiple: maxFiles !== 1,
1488
+ onChange: handleInputChange,
1489
+ onClick: (e) => e.stopPropagation(),
1490
+ "aria-hidden": "true"
1491
+ }
1492
+ ),
1493
+ children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
1494
+ /* @__PURE__ */ jsx(
1495
+ "svg",
1496
+ {
1497
+ xmlns: "http://www.w3.org/2000/svg",
1498
+ className: "h-10 w-10 text-white/40",
1499
+ fill: "none",
1500
+ viewBox: "0 0 24 24",
1501
+ stroke: "currentColor",
1502
+ strokeWidth: 1.5,
1503
+ "aria-hidden": "true",
1504
+ children: /* @__PURE__ */ jsx(
1505
+ "path",
1506
+ {
1507
+ strokeLinecap: "round",
1508
+ strokeLinejoin: "round",
1509
+ d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
1510
+ }
1511
+ )
1512
+ }
1513
+ ),
1514
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-white/50", children: "Drop files here or click to browse" })
1515
+ ] })
1516
+ ]
1517
+ }
1518
+ );
1519
+ }
596
1520
  var maxWidthClass = {
597
1521
  sm: "max-w-2xl",
598
1522
  md: "max-w-4xl",
@@ -605,7 +1529,7 @@ function defaultBackground() {
605
1529
  return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 overflow-hidden", children: [
606
1530
  /* @__PURE__ */ jsx("div", { className: "absolute -top-32 -left-32 h-[600px] w-[600px] rounded-full bg-violet-600/20 blur-[140px]" }),
607
1531
  /* @__PURE__ */ jsx("div", { className: "absolute top-20 -right-32 h-[500px] w-[500px] rounded-full bg-purple-500/[0.12] blur-[120px]" }),
608
- /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-1/2 h-[400px] w-[500px] -translate-x-1/2 rounded-full bg-indigo-500/[0.10] blur-[120px]" })
1532
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-1/2 h-[400px] w-[500px] -translate-x-1/2 rounded-full bg-accent/[0.10] blur-[120px]" })
609
1533
  ] });
610
1534
  }
611
1535
  function PageShell({
@@ -639,11 +1563,8 @@ function PageShell({
639
1563
  }
640
1564
  function Navbar({ logo, children, className, glass = true }) {
641
1565
  return /* @__PURE__ */ jsx("header", { className: cn("fixed top-0 w-full z-50", glass && "glass", className), children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto px-6 h-16 flex items-center justify-between", children: [
642
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo || /* @__PURE__ */ jsxs(Fragment, { children: [
643
- /* @__PURE__ */ jsx("div", { className: "w-9 h-9 rounded-xl animated-gradient" }),
644
- /* @__PURE__ */ jsx("span", { className: "text-lg font-bold tracking-tight", children: "MemeLab" })
645
- ] }) }),
646
- children && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children })
1566
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo ?? /* @__PURE__ */ jsx("div", { className: "w-9 h-9 rounded-xl animated-gradient" }) }),
1567
+ children && /* @__PURE__ */ jsx("nav", { "aria-label": "Main navigation", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children }) })
647
1568
  ] }) });
648
1569
  }
649
1570
  function Sidebar({ children, collapsed = false, onToggle, className }) {
@@ -681,7 +1602,15 @@ function Sidebar({ children, collapsed = false, onToggle, className }) {
681
1602
  }
682
1603
  );
683
1604
  }
684
- function DashboardLayout({ children, navbar, sidebar, className }) {
1605
+ var maxWidthClass2 = {
1606
+ sm: "max-w-2xl",
1607
+ md: "max-w-4xl",
1608
+ lg: "max-w-5xl",
1609
+ xl: "max-w-7xl",
1610
+ "2xl": "max-w-[92rem]",
1611
+ full: "max-w-full"
1612
+ };
1613
+ function DashboardLayout({ children, navbar, sidebar, className, mainClassName, maxWidth = "lg" }) {
685
1614
  return /* @__PURE__ */ jsxs("div", { className: cn("min-h-screen bg-surface relative overflow-hidden", className), children: [
686
1615
  /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "pointer-events-none fixed inset-0", children: [
687
1616
  /* @__PURE__ */ jsx("div", { className: "orb orb-purple w-[400px] h-[400px] -top-[150px] -left-[150px] opacity-15" }),
@@ -690,9 +1619,238 @@ function DashboardLayout({ children, navbar, sidebar, className }) {
690
1619
  navbar,
691
1620
  /* @__PURE__ */ jsxs("div", { className: cn("flex", navbar && "pt-16"), children: [
692
1621
  sidebar,
693
- /* @__PURE__ */ jsx("main", { className: "flex-1 min-w-0 py-8 px-6", children: /* @__PURE__ */ jsx("div", { className: "max-w-5xl mx-auto", children }) })
1622
+ /* @__PURE__ */ jsx("main", { className: cn("flex-1 min-w-0 py-8 px-6", mainClassName), children: /* @__PURE__ */ jsx("div", { className: cn("mx-auto", maxWidthClass2[maxWidth]), children }) })
694
1623
  ] })
695
1624
  ] });
696
1625
  }
1626
+ var ProgressButton = forwardRef(
1627
+ function ProgressButton2({ isLoading, loadingText, children, disabled, className, ...props }, ref) {
1628
+ return /* @__PURE__ */ jsx(
1629
+ Button,
1630
+ {
1631
+ ref,
1632
+ ...props,
1633
+ disabled: disabled || isLoading,
1634
+ "aria-busy": isLoading || void 0,
1635
+ className: cn("relative overflow-hidden", className),
1636
+ children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
1637
+ /* @__PURE__ */ jsx(
1638
+ "span",
1639
+ {
1640
+ "aria-hidden": "true",
1641
+ className: "absolute inset-0 bg-white/20 animate-[ml-shimmer_2s_ease-in-out_infinite] skew-x-[-20deg] pointer-events-none"
1642
+ }
1643
+ ),
1644
+ /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
1645
+ loadingText ?? children
1646
+ ] }) : children
1647
+ }
1648
+ );
1649
+ }
1650
+ );
1651
+ function reducer(state, action) {
1652
+ switch (action.type) {
1653
+ case "ADD": {
1654
+ const next = [...state, action.toast];
1655
+ return next.length > action.maxToasts ? next.slice(-action.maxToasts) : next;
1656
+ }
1657
+ case "REMOVE":
1658
+ return state.filter((t) => t.id !== action.id);
1659
+ case "REMOVE_ALL":
1660
+ return [];
1661
+ default:
1662
+ return state;
1663
+ }
1664
+ }
1665
+ var ToastContext = createContext(null);
1666
+ var variantBarColor = {
1667
+ success: "bg-emerald-500",
1668
+ error: "bg-rose-500",
1669
+ warning: "bg-amber-500",
1670
+ info: "bg-primary"
1671
+ };
1672
+ var variantProgressColor = {
1673
+ success: "bg-emerald-500",
1674
+ error: "bg-rose-500",
1675
+ warning: "bg-amber-500",
1676
+ info: "bg-primary"
1677
+ };
1678
+ var variantIconLabel = {
1679
+ success: "\u2713",
1680
+ error: "\u2715",
1681
+ warning: "\u26A0",
1682
+ info: "i"
1683
+ };
1684
+ var variantIconColor = {
1685
+ success: "text-emerald-400",
1686
+ error: "text-rose-400",
1687
+ warning: "text-amber-400",
1688
+ info: "text-primary"
1689
+ };
1690
+ var positionClass = {
1691
+ "top-right": "top-4 right-4 items-end",
1692
+ "top-center": "top-4 left-1/2 -translate-x-1/2 items-center",
1693
+ "bottom-right": "bottom-4 right-4 items-end",
1694
+ "bottom-center": "bottom-4 left-1/2 -translate-x-1/2 items-center"
1695
+ };
1696
+ function ToastCard({ toast, onDismiss }) {
1697
+ const duration = toast.duration ?? 5e3;
1698
+ const [visible, setVisible] = useState(false);
1699
+ const [exiting, setExiting] = useState(false);
1700
+ const [started, setStarted] = useState(false);
1701
+ const dismissedRef = useRef(false);
1702
+ const timerRef = useRef(null);
1703
+ const exitTimerRef = useRef(null);
1704
+ useEffect(() => {
1705
+ let innerFrame;
1706
+ const frame = requestAnimationFrame(() => {
1707
+ setVisible(true);
1708
+ innerFrame = requestAnimationFrame(() => setStarted(true));
1709
+ });
1710
+ return () => {
1711
+ cancelAnimationFrame(frame);
1712
+ cancelAnimationFrame(innerFrame);
1713
+ };
1714
+ }, []);
1715
+ useEffect(() => {
1716
+ return () => {
1717
+ if (exitTimerRef.current !== null) clearTimeout(exitTimerRef.current);
1718
+ };
1719
+ }, []);
1720
+ const triggerDismiss = useCallback(() => {
1721
+ if (dismissedRef.current) return;
1722
+ dismissedRef.current = true;
1723
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
1724
+ setExiting(true);
1725
+ exitTimerRef.current = setTimeout(() => onDismiss(toast.id), 280);
1726
+ }, [onDismiss, toast.id]);
1727
+ useEffect(() => {
1728
+ if (duration <= 0) return;
1729
+ timerRef.current = setTimeout(triggerDismiss, duration);
1730
+ return () => {
1731
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
1732
+ };
1733
+ }, [duration, triggerDismiss]);
1734
+ return /* @__PURE__ */ jsxs(
1735
+ "div",
1736
+ {
1737
+ role: "alert",
1738
+ "aria-live": "assertive",
1739
+ "aria-atomic": "true",
1740
+ "data-variant": toast.variant,
1741
+ className: cn(
1742
+ // Base layout
1743
+ "relative flex w-80 max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl shadow-xl",
1744
+ // Glass background
1745
+ "bg-surface-100/95 backdrop-blur-md ring-1 ring-white/10",
1746
+ // Enter / exit transitions
1747
+ "transition-all duration-300",
1748
+ visible && !exiting ? "opacity-100 translate-x-0" : "opacity-0 translate-x-8",
1749
+ exiting && "scale-95"
1750
+ ),
1751
+ children: [
1752
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("w-[3px] shrink-0 self-stretch", variantBarColor[toast.variant]) }),
1753
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-start gap-3 px-4 py-3 pr-9", children: [
1754
+ /* @__PURE__ */ jsx(
1755
+ "span",
1756
+ {
1757
+ "aria-hidden": "true",
1758
+ className: cn(
1759
+ "mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[11px] font-bold ring-1",
1760
+ variantIconColor[toast.variant],
1761
+ "ring-current/30"
1762
+ ),
1763
+ children: variantIconLabel[toast.variant]
1764
+ }
1765
+ ),
1766
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1767
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold leading-snug text-white", children: toast.title }),
1768
+ toast.description ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-relaxed text-white/60", children: toast.description }) : null
1769
+ ] })
1770
+ ] }),
1771
+ /* @__PURE__ */ jsx(
1772
+ "button",
1773
+ {
1774
+ type: "button",
1775
+ "aria-label": "Dismiss notification",
1776
+ onClick: triggerDismiss,
1777
+ className: cn(
1778
+ "absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-lg",
1779
+ "text-white/40 transition-colors hover:bg-white/10 hover:text-white/80",
1780
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
1781
+ ),
1782
+ children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M1 1l8 8M9 1L1 9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
1783
+ }
1784
+ ),
1785
+ duration > 0 && /* @__PURE__ */ jsx(
1786
+ "span",
1787
+ {
1788
+ "aria-hidden": "true",
1789
+ className: cn(
1790
+ "absolute bottom-0 left-0 h-[2px] ease-linear",
1791
+ variantProgressColor[toast.variant],
1792
+ "opacity-60"
1793
+ ),
1794
+ style: {
1795
+ width: started ? "0%" : "100%",
1796
+ transitionProperty: "width",
1797
+ transitionDuration: started ? `${duration}ms` : "0ms"
1798
+ }
1799
+ }
1800
+ )
1801
+ ]
1802
+ }
1803
+ );
1804
+ }
1805
+ function ToastProvider({ children, position = "top-right", maxToasts = 5 }) {
1806
+ const [toasts, dispatch] = useReducer(reducer, []);
1807
+ const counterRef = useRef(0);
1808
+ const maxToastsRef = useRef(maxToasts);
1809
+ maxToastsRef.current = maxToasts;
1810
+ const toast = useCallback(
1811
+ (options) => {
1812
+ const id = `toast-${++counterRef.current}`;
1813
+ const newToast = {
1814
+ id,
1815
+ variant: options.variant,
1816
+ title: options.title,
1817
+ description: options.description,
1818
+ duration: options.duration ?? 5e3
1819
+ };
1820
+ dispatch({ type: "ADD", toast: newToast, maxToasts: maxToastsRef.current });
1821
+ return id;
1822
+ },
1823
+ []
1824
+ );
1825
+ const dismiss = useCallback((id) => {
1826
+ dispatch({ type: "REMOVE", id });
1827
+ }, []);
1828
+ const dismissAll = useCallback(() => {
1829
+ dispatch({ type: "REMOVE_ALL" });
1830
+ }, []);
1831
+ const value = useMemo(() => ({ toast, dismiss, dismissAll }), [toast, dismiss, dismissAll]);
1832
+ const container = typeof document !== "undefined" ? createPortal(
1833
+ /* @__PURE__ */ jsx(
1834
+ "div",
1835
+ {
1836
+ "aria-label": "Notifications",
1837
+ className: cn("fixed z-[9999] flex flex-col gap-3 pointer-events-none", positionClass[position]),
1838
+ children: toasts.map((t) => /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx(ToastCard, { toast: t, onDismiss: dismiss }) }, t.id))
1839
+ }
1840
+ ),
1841
+ document.body
1842
+ ) : null;
1843
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value, children: [
1844
+ children,
1845
+ container
1846
+ ] });
1847
+ }
1848
+ function useToast() {
1849
+ const ctx = useContext(ToastContext);
1850
+ if (!ctx) {
1851
+ throw new Error("useToast must be used inside <ToastProvider>");
1852
+ }
1853
+ return ctx;
1854
+ }
697
1855
 
698
- export { Badge, Button, Card, CollapsibleSection, ConfirmDialog, DashboardLayout, EmptyState, IconButton, Input, Modal, Navbar, PageShell, Pill, Select, Sidebar, Skeleton, Spinner, Textarea, Toggle, Tooltip, cn, focusSafely, getFocusableElements };
1856
+ export { Badge, Button, Card, Checkbox, CollapsibleSection, ConfirmDialog, DashboardLayout, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, IconButton, Input, Modal, Navbar, PageShell, Pill, ProgressButton, RadioGroup, RadioItem, SearchInput, Select, Sidebar, Skeleton, Spinner, Tab, TabList, TabPanel, Tabs, Textarea, ToastProvider, Toggle, Tooltip, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useMediaQuery, useToast };