@assassin1717/aifelib 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1458 @@
1
+ // src/lib/cn.ts
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ // src/components/button/button.tsx
9
+ import * as React from "react";
10
+ import { Loader2 } from "lucide-react";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ var variantClasses = {
13
+ primary: "bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800 focus-visible:ring-blue-500",
14
+ secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 active:bg-gray-300 focus-visible:ring-gray-400",
15
+ ghost: "bg-transparent text-gray-700 hover:bg-gray-100 active:bg-gray-200 focus-visible:ring-gray-400",
16
+ destructive: "bg-red-600 text-white hover:bg-red-700 active:bg-red-800 focus-visible:ring-red-500",
17
+ outline: "border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 active:bg-gray-100 focus-visible:ring-gray-400"
18
+ };
19
+ var sizeClasses = {
20
+ sm: "h-8 px-3 text-xs gap-1.5",
21
+ md: "h-10 px-4 text-sm gap-2",
22
+ lg: "h-12 px-5 text-base gap-2"
23
+ };
24
+ var Button = React.forwardRef(
25
+ ({
26
+ className,
27
+ variant = "primary",
28
+ size = "md",
29
+ loading = false,
30
+ fullWidth = false,
31
+ disabled,
32
+ children,
33
+ ...props
34
+ }, ref) => {
35
+ const isDisabled = disabled || loading;
36
+ return /* @__PURE__ */ jsxs(
37
+ "button",
38
+ {
39
+ ref,
40
+ disabled: isDisabled,
41
+ "aria-busy": loading,
42
+ className: cn(
43
+ // base
44
+ "inline-flex items-center justify-center rounded-md font-medium",
45
+ "transition-colors duration-150",
46
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
47
+ // disabled
48
+ "disabled:pointer-events-none disabled:opacity-50",
49
+ // touch target — mínimo 44px em mobile
50
+ "min-h-[2.75rem] sm:min-h-0",
51
+ variantClasses[variant],
52
+ sizeClasses[size],
53
+ fullWidth && "w-full",
54
+ className
55
+ ),
56
+ ...props,
57
+ children: [
58
+ loading && /* @__PURE__ */ jsx(
59
+ Loader2,
60
+ {
61
+ className: "shrink-0 animate-spin",
62
+ size: size === "sm" ? 14 : size === "lg" ? 18 : 16,
63
+ "aria-hidden": "true"
64
+ }
65
+ ),
66
+ children
67
+ ]
68
+ }
69
+ );
70
+ }
71
+ );
72
+ Button.displayName = "Button";
73
+
74
+ // src/components/input/input.tsx
75
+ import * as React2 from "react";
76
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
77
+ var Input = React2.forwardRef(
78
+ ({ className, error, startIcon, endIcon, ...props }, ref) => {
79
+ if (startIcon || endIcon) {
80
+ return /* @__PURE__ */ jsxs2("div", { className: "relative flex items-center", children: [
81
+ startIcon && /* @__PURE__ */ jsx2("span", { className: "pointer-events-none absolute left-3 flex shrink-0 items-center text-gray-400", children: startIcon }),
82
+ /* @__PURE__ */ jsx2(
83
+ "input",
84
+ {
85
+ ref,
86
+ className: cn(
87
+ inputBase,
88
+ error && inputError,
89
+ startIcon && "pl-9",
90
+ endIcon && "pr-9",
91
+ className
92
+ ),
93
+ "aria-invalid": error ? true : void 0,
94
+ ...props
95
+ }
96
+ ),
97
+ endIcon && /* @__PURE__ */ jsx2("span", { className: "pointer-events-none absolute right-3 flex shrink-0 items-center text-gray-400", children: endIcon })
98
+ ] });
99
+ }
100
+ return /* @__PURE__ */ jsx2(
101
+ "input",
102
+ {
103
+ ref,
104
+ className: cn(inputBase, error && inputError, className),
105
+ "aria-invalid": error ? true : void 0,
106
+ ...props
107
+ }
108
+ );
109
+ }
110
+ );
111
+ Input.displayName = "Input";
112
+ var inputBase = [
113
+ "w-full rounded-md border border-gray-300 bg-white px-3",
114
+ "text-sm text-gray-900 placeholder:text-gray-400",
115
+ // mobile-friendly height
116
+ "h-11 sm:h-10",
117
+ "transition-colors duration-150",
118
+ "focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
119
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-50"
120
+ ].join(" ");
121
+ var inputError = "border-red-500 focus:ring-red-500";
122
+
123
+ // src/components/textarea/textarea.tsx
124
+ import * as React3 from "react";
125
+ import { jsx as jsx3 } from "react/jsx-runtime";
126
+ var Textarea = React3.forwardRef(
127
+ ({ className, error, ...props }, ref) => /* @__PURE__ */ jsx3(
128
+ "textarea",
129
+ {
130
+ ref,
131
+ className: cn(
132
+ "w-full rounded-md border border-gray-300 bg-white px-3 py-2",
133
+ "text-sm text-gray-900 placeholder:text-gray-400",
134
+ "min-h-[80px] resize-y",
135
+ "transition-colors duration-150",
136
+ "focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
137
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-50",
138
+ error && "border-red-500 focus:ring-red-500",
139
+ className
140
+ ),
141
+ "aria-invalid": error ? true : void 0,
142
+ ...props
143
+ }
144
+ )
145
+ );
146
+ Textarea.displayName = "Textarea";
147
+
148
+ // src/components/select/select.tsx
149
+ import * as React4 from "react";
150
+ import { ChevronDown } from "lucide-react";
151
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
152
+ var Select = React4.forwardRef(
153
+ ({ className, options, placeholder, error, ...props }, ref) => /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
154
+ /* @__PURE__ */ jsxs3(
155
+ "select",
156
+ {
157
+ ref,
158
+ className: cn(
159
+ "w-full appearance-none rounded-md border border-gray-300 bg-white px-3 pr-9",
160
+ "text-sm text-gray-900",
161
+ // mobile-friendly height
162
+ "h-11 sm:h-10",
163
+ "transition-colors duration-150",
164
+ "focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
165
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-50",
166
+ error && "border-red-500 focus:ring-red-500",
167
+ className
168
+ ),
169
+ "aria-invalid": error ? true : void 0,
170
+ ...props,
171
+ children: [
172
+ placeholder && /* @__PURE__ */ jsx4("option", { value: "", disabled: true, children: placeholder }),
173
+ options.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))
174
+ ]
175
+ }
176
+ ),
177
+ /* @__PURE__ */ jsx4(
178
+ ChevronDown,
179
+ {
180
+ className: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400",
181
+ size: 16,
182
+ "aria-hidden": "true"
183
+ }
184
+ )
185
+ ] })
186
+ );
187
+ Select.displayName = "Select";
188
+
189
+ // src/components/checkbox/checkbox.tsx
190
+ import * as React5 from "react";
191
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
192
+ var Checkbox = React5.forwardRef(
193
+ ({ className, label, error, id, ...props }, ref) => {
194
+ const generatedId = React5.useId();
195
+ const checkboxId = id ?? generatedId;
196
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-3", children: [
197
+ /* @__PURE__ */ jsx5("span", { className: "flex min-h-[44px] min-w-[44px] items-center justify-center sm:min-h-0 sm:min-w-0", children: /* @__PURE__ */ jsx5(
198
+ "input",
199
+ {
200
+ ref,
201
+ id: checkboxId,
202
+ type: "checkbox",
203
+ className: cn(
204
+ "h-4 w-4 shrink-0 rounded border-gray-300 text-blue-600",
205
+ "focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
206
+ "disabled:cursor-not-allowed disabled:opacity-50",
207
+ "cursor-pointer",
208
+ error && "border-red-500",
209
+ className
210
+ ),
211
+ "aria-invalid": error ? true : void 0,
212
+ ...props
213
+ }
214
+ ) }),
215
+ label && /* @__PURE__ */ jsx5(
216
+ "label",
217
+ {
218
+ htmlFor: checkboxId,
219
+ className: "cursor-pointer select-none pt-0.5 text-sm text-gray-700",
220
+ children: label
221
+ }
222
+ )
223
+ ] });
224
+ }
225
+ );
226
+ Checkbox.displayName = "Checkbox";
227
+
228
+ // src/components/label/label.tsx
229
+ import * as React6 from "react";
230
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
231
+ var Label = React6.forwardRef(
232
+ ({ className, children, required, ...props }, ref) => /* @__PURE__ */ jsxs5(
233
+ "label",
234
+ {
235
+ ref,
236
+ className: cn(
237
+ "block text-sm font-medium text-gray-700 select-none",
238
+ className
239
+ ),
240
+ ...props,
241
+ children: [
242
+ children,
243
+ required && /* @__PURE__ */ jsx6("span", { className: "ml-1 text-red-500", "aria-hidden": "true", children: "*" })
244
+ ]
245
+ }
246
+ )
247
+ );
248
+ Label.displayName = "Label";
249
+
250
+ // src/components/form-field/form-field.tsx
251
+ import * as React7 from "react";
252
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
253
+ function FormField({
254
+ label,
255
+ htmlFor,
256
+ required,
257
+ hint,
258
+ error,
259
+ className,
260
+ children
261
+ }) {
262
+ const errorId = React7.useId();
263
+ const hintId = React7.useId();
264
+ const childrenWithProps = React7.Children.map(children, (child) => {
265
+ if (!React7.isValidElement(child)) return child;
266
+ return React7.cloneElement(child, {
267
+ id: htmlFor ?? child.props.id,
268
+ error: !!error || child.props.error,
269
+ "aria-describedby": [error ? errorId : null, hint ? hintId : null].filter(Boolean).join(" ") || void 0
270
+ });
271
+ });
272
+ return /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-col gap-1.5", className), children: [
273
+ label && /* @__PURE__ */ jsx7(Label, { ...htmlFor !== void 0 ? { htmlFor } : {}, ...required ? { required } : {}, children: label }),
274
+ childrenWithProps,
275
+ hint && !error && /* @__PURE__ */ jsx7("p", { id: hintId, className: "text-xs text-gray-500", children: hint }),
276
+ error && /* @__PURE__ */ jsx7("p", { id: errorId, className: "text-xs text-red-600", role: "alert", children: error })
277
+ ] });
278
+ }
279
+
280
+ // src/components/spinner/spinner.tsx
281
+ import { Loader2 as Loader22 } from "lucide-react";
282
+ import { jsx as jsx8 } from "react/jsx-runtime";
283
+ var sizeClasses2 = { sm: 16, md: 24, lg: 36 };
284
+ function Spinner({ size = "md", label = "Loading\u2026", className }) {
285
+ return /* @__PURE__ */ jsx8("span", { role: "status", "aria-label": label, className: cn("inline-flex", className), children: /* @__PURE__ */ jsx8(
286
+ Loader22,
287
+ {
288
+ size: sizeClasses2[size],
289
+ className: "animate-spin text-current",
290
+ "aria-hidden": "true"
291
+ }
292
+ ) });
293
+ }
294
+
295
+ // src/components/badge/badge.tsx
296
+ import { jsx as jsx9 } from "react/jsx-runtime";
297
+ var variantClasses2 = {
298
+ default: "bg-gray-100 text-gray-700",
299
+ success: "bg-green-100 text-green-700",
300
+ warning: "bg-yellow-100 text-yellow-700",
301
+ destructive: "bg-red-100 text-red-700",
302
+ info: "bg-blue-100 text-blue-700",
303
+ outline: "border border-gray-300 text-gray-700 bg-transparent"
304
+ };
305
+ function Badge({ variant = "default", className, children, ...props }) {
306
+ return /* @__PURE__ */ jsx9(
307
+ "span",
308
+ {
309
+ className: cn(
310
+ "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
311
+ variantClasses2[variant],
312
+ className
313
+ ),
314
+ ...props,
315
+ children
316
+ }
317
+ );
318
+ }
319
+
320
+ // src/components/alert/alert.tsx
321
+ import { CheckCircle2, AlertTriangle, XCircle, Info } from "lucide-react";
322
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
323
+ var config = {
324
+ info: { icon: Info, classes: "bg-blue-50 border-blue-200 text-blue-800" },
325
+ success: { icon: CheckCircle2, classes: "bg-green-50 border-green-200 text-green-800" },
326
+ warning: { icon: AlertTriangle, classes: "bg-yellow-50 border-yellow-200 text-yellow-800" },
327
+ destructive: { icon: XCircle, classes: "bg-red-50 border-red-200 text-red-800" }
328
+ };
329
+ function Alert({ variant = "info", title, children, className, onDismiss }) {
330
+ const { icon: Icon, classes } = config[variant];
331
+ return /* @__PURE__ */ jsxs7(
332
+ "div",
333
+ {
334
+ role: "alert",
335
+ className: cn("flex gap-3 rounded-md border p-4", classes, className),
336
+ children: [
337
+ /* @__PURE__ */ jsx10(Icon, { size: 18, className: "mt-0.5 shrink-0", "aria-hidden": "true" }),
338
+ /* @__PURE__ */ jsxs7("div", { className: "min-w-0 flex-1 text-sm", children: [
339
+ title && /* @__PURE__ */ jsx10("p", { className: "font-medium", children: title }),
340
+ children && /* @__PURE__ */ jsx10("p", { className: cn(title && "mt-1", "opacity-90"), children })
341
+ ] }),
342
+ onDismiss && /* @__PURE__ */ jsx10(
343
+ "button",
344
+ {
345
+ onClick: onDismiss,
346
+ "aria-label": "Dismiss",
347
+ className: "shrink-0 rounded p-0.5 opacity-60 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current",
348
+ children: /* @__PURE__ */ jsx10(XCircle, { size: 16, "aria-hidden": "true" })
349
+ }
350
+ )
351
+ ]
352
+ }
353
+ );
354
+ }
355
+
356
+ // src/components/card/card.tsx
357
+ import { jsx as jsx11 } from "react/jsx-runtime";
358
+ var paddingClasses = { none: "", sm: "p-3", md: "p-5", lg: "p-7" };
359
+ function Card({ className, padding = "md", children, ...props }) {
360
+ return /* @__PURE__ */ jsx11(
361
+ "div",
362
+ {
363
+ className: cn(
364
+ "rounded-lg border border-gray-200 bg-white shadow-sm",
365
+ paddingClasses[padding],
366
+ className
367
+ ),
368
+ ...props,
369
+ children
370
+ }
371
+ );
372
+ }
373
+ function CardHeader({ className, ...props }) {
374
+ return /* @__PURE__ */ jsx11("div", { className: cn("mb-4 flex flex-col gap-1", className), ...props });
375
+ }
376
+ function CardTitle({ className, ...props }) {
377
+ return /* @__PURE__ */ jsx11("h3", { className: cn("text-base font-semibold text-gray-900", className), ...props });
378
+ }
379
+ function CardDescription({ className, ...props }) {
380
+ return /* @__PURE__ */ jsx11("p", { className: cn("text-sm text-gray-500", className), ...props });
381
+ }
382
+ function CardContent({ className, ...props }) {
383
+ return /* @__PURE__ */ jsx11("div", { className: cn("text-sm text-gray-700", className), ...props });
384
+ }
385
+ function CardFooter({ className, ...props }) {
386
+ return /* @__PURE__ */ jsx11(
387
+ "div",
388
+ {
389
+ className: cn("mt-4 flex flex-wrap items-center gap-2 border-t border-gray-100 pt-4", className),
390
+ ...props
391
+ }
392
+ );
393
+ }
394
+
395
+ // src/components/modal/modal.tsx
396
+ import * as React8 from "react";
397
+ import { createPortal } from "react-dom";
398
+ import { X } from "lucide-react";
399
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
400
+ var sizeClasses3 = {
401
+ sm: "sm:max-w-sm",
402
+ md: "sm:max-w-md",
403
+ lg: "sm:max-w-lg",
404
+ xl: "sm:max-w-xl",
405
+ full: "sm:max-w-[95vw]"
406
+ };
407
+ function Modal({
408
+ open,
409
+ onClose,
410
+ title,
411
+ description,
412
+ size = "md",
413
+ className,
414
+ children,
415
+ footer
416
+ }) {
417
+ const overlayRef = React8.useRef(null);
418
+ const titleId = React8.useId();
419
+ const descId = React8.useId();
420
+ React8.useEffect(() => {
421
+ if (!open) return;
422
+ const onKey = (e) => {
423
+ if (e.key === "Escape") onClose();
424
+ };
425
+ document.addEventListener("keydown", onKey);
426
+ return () => document.removeEventListener("keydown", onKey);
427
+ }, [open, onClose]);
428
+ React8.useEffect(() => {
429
+ if (open) {
430
+ document.body.style.overflow = "hidden";
431
+ return () => {
432
+ document.body.style.overflow = "";
433
+ };
434
+ }
435
+ }, [open]);
436
+ React8.useEffect(() => {
437
+ if (!open) return;
438
+ const el = overlayRef.current;
439
+ if (!el) return;
440
+ const focusable = el.querySelectorAll(
441
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
442
+ );
443
+ const first = focusable[0];
444
+ const last = focusable[focusable.length - 1];
445
+ first?.focus();
446
+ const trap = (e) => {
447
+ if (e.key !== "Tab") return;
448
+ if (e.shiftKey) {
449
+ if (document.activeElement === first) {
450
+ e.preventDefault();
451
+ last?.focus();
452
+ }
453
+ } else {
454
+ if (document.activeElement === last) {
455
+ e.preventDefault();
456
+ first?.focus();
457
+ }
458
+ }
459
+ };
460
+ document.addEventListener("keydown", trap);
461
+ return () => document.removeEventListener("keydown", trap);
462
+ }, [open]);
463
+ if (!open) return null;
464
+ return createPortal(
465
+ /* @__PURE__ */ jsxs8(
466
+ "div",
467
+ {
468
+ ref: overlayRef,
469
+ role: "dialog",
470
+ "aria-modal": "true",
471
+ "aria-labelledby": title ? titleId : void 0,
472
+ "aria-describedby": description ? descId : void 0,
473
+ className: "fixed inset-0 z-50 flex items-end justify-center sm:items-center sm:p-4",
474
+ children: [
475
+ /* @__PURE__ */ jsx12(
476
+ "div",
477
+ {
478
+ className: "absolute inset-0 bg-black/50 backdrop-blur-sm",
479
+ "aria-hidden": "true",
480
+ onClick: onClose
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsxs8(
484
+ "div",
485
+ {
486
+ className: cn(
487
+ "relative z-10 flex w-full flex-col bg-white shadow-xl",
488
+ // mobile: slides from bottom, rounded top only
489
+ "rounded-t-2xl sm:rounded-xl",
490
+ // desktop: constrained width
491
+ sizeClasses3[size],
492
+ // max height with scroll
493
+ "max-h-[90dvh] overflow-hidden",
494
+ className
495
+ ),
496
+ children: [
497
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-start justify-between gap-4 border-b border-gray-100 px-5 py-4", children: [
498
+ /* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
499
+ title && /* @__PURE__ */ jsx12("h2", { id: titleId, className: "text-base font-semibold text-gray-900", children: title }),
500
+ description && /* @__PURE__ */ jsx12("p", { id: descId, className: "mt-0.5 text-sm text-gray-500", children: description })
501
+ ] }),
502
+ /* @__PURE__ */ jsx12(
503
+ "button",
504
+ {
505
+ onClick: onClose,
506
+ "aria-label": "Close",
507
+ className: "shrink-0 rounded p-1 text-gray-400 hover:text-gray-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
508
+ children: /* @__PURE__ */ jsx12(X, { size: 18, "aria-hidden": "true" })
509
+ }
510
+ )
511
+ ] }),
512
+ /* @__PURE__ */ jsx12("div", { className: "flex-1 overflow-y-auto px-5 py-4", children }),
513
+ footer && /* @__PURE__ */ jsx12("div", { className: "flex flex-col-reverse gap-2 border-t border-gray-100 px-5 py-4 sm:flex-row sm:justify-end", children: footer })
514
+ ]
515
+ }
516
+ )
517
+ ]
518
+ }
519
+ ),
520
+ document.body
521
+ );
522
+ }
523
+
524
+ // src/components/confirm-dialog/confirm-dialog.tsx
525
+ import { Fragment, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
526
+ function ConfirmDialog({
527
+ open,
528
+ onClose,
529
+ onConfirm,
530
+ title,
531
+ description,
532
+ confirmLabel = "Confirm",
533
+ cancelLabel = "Cancel",
534
+ variant = "primary",
535
+ loading = false
536
+ }) {
537
+ return /* @__PURE__ */ jsx13(
538
+ Modal,
539
+ {
540
+ open,
541
+ onClose,
542
+ title,
543
+ ...description !== void 0 ? { description } : {},
544
+ size: "sm",
545
+ footer: /* @__PURE__ */ jsxs9(Fragment, { children: [
546
+ /* @__PURE__ */ jsx13(Button, { variant: "ghost", onClick: onClose, disabled: loading, fullWidth: true, children: cancelLabel }),
547
+ /* @__PURE__ */ jsx13(Button, { variant, onClick: onConfirm, loading, fullWidth: true, children: confirmLabel })
548
+ ] }),
549
+ children: /* @__PURE__ */ jsx13("span", {})
550
+ }
551
+ );
552
+ }
553
+
554
+ // src/components/toast/toast.tsx
555
+ import { CheckCircle2 as CheckCircle22, AlertTriangle as AlertTriangle2, XCircle as XCircle2, Info as Info2, X as X2 } from "lucide-react";
556
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
557
+ var iconMap = {
558
+ success: CheckCircle22,
559
+ error: XCircle2,
560
+ warning: AlertTriangle2,
561
+ info: Info2
562
+ };
563
+ var styleMap = {
564
+ success: "border-green-200 bg-green-50 text-green-800",
565
+ error: "border-red-200 bg-red-50 text-red-800",
566
+ warning: "border-yellow-200 bg-yellow-50 text-yellow-800",
567
+ info: "border-blue-200 bg-blue-50 text-blue-800"
568
+ };
569
+ function ToastItem({ toast, onDismiss }) {
570
+ const Icon = iconMap[toast.type];
571
+ return /* @__PURE__ */ jsxs10(
572
+ "div",
573
+ {
574
+ role: "status",
575
+ "aria-live": "polite",
576
+ className: cn(
577
+ "flex w-full items-start gap-3 rounded-lg border p-4 shadow-md",
578
+ "animate-in slide-in-from-bottom-2 duration-200",
579
+ styleMap[toast.type]
580
+ ),
581
+ children: [
582
+ /* @__PURE__ */ jsx14(Icon, { size: 18, className: "mt-0.5 shrink-0", "aria-hidden": "true" }),
583
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1 text-sm", children: [
584
+ /* @__PURE__ */ jsx14("p", { className: "font-medium", children: toast.title }),
585
+ toast.description && /* @__PURE__ */ jsx14("p", { className: "mt-0.5 opacity-80", children: toast.description })
586
+ ] }),
587
+ /* @__PURE__ */ jsx14(
588
+ "button",
589
+ {
590
+ onClick: () => onDismiss(toast.id),
591
+ "aria-label": "Dismiss",
592
+ className: "shrink-0 rounded p-0.5 opacity-60 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current",
593
+ children: /* @__PURE__ */ jsx14(X2, { size: 14, "aria-hidden": "true" })
594
+ }
595
+ )
596
+ ]
597
+ }
598
+ );
599
+ }
600
+
601
+ // src/components/toast/toast-context.tsx
602
+ import * as React9 from "react";
603
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
604
+ var ToastContext = React9.createContext(null);
605
+ function ToastProvider({ children }) {
606
+ const [toasts, setToasts] = React9.useState([]);
607
+ const removeToast = React9.useCallback((id) => {
608
+ setToasts((prev) => prev.filter((t) => t.id !== id));
609
+ }, []);
610
+ const addToast = React9.useCallback(
611
+ ({ type, title, description, duration = 4e3 }) => {
612
+ const id = crypto.randomUUID();
613
+ setToasts((prev) => [
614
+ ...prev,
615
+ {
616
+ id,
617
+ type,
618
+ title,
619
+ ...description !== void 0 ? { description } : {},
620
+ ...duration !== void 0 ? { duration } : {}
621
+ }
622
+ ]);
623
+ if (duration > 0) {
624
+ setTimeout(() => removeToast(id), duration);
625
+ }
626
+ },
627
+ [removeToast]
628
+ );
629
+ return /* @__PURE__ */ jsxs11(ToastContext.Provider, { value: { addToast, removeToast }, children: [
630
+ children,
631
+ /* @__PURE__ */ jsx15(
632
+ "div",
633
+ {
634
+ "aria-label": "Notifications",
635
+ className: "fixed bottom-0 left-0 right-0 z-50 flex flex-col gap-2 p-4 sm:bottom-4 sm:left-auto sm:right-4 sm:w-96",
636
+ children: toasts.map((toast) => /* @__PURE__ */ jsx15(ToastItem, { toast, onDismiss: removeToast }, toast.id))
637
+ }
638
+ )
639
+ ] });
640
+ }
641
+
642
+ // src/components/toast/use-toast.ts
643
+ import * as React10 from "react";
644
+ function useToast() {
645
+ const ctx = React10.useContext(ToastContext);
646
+ if (!ctx) {
647
+ throw new Error("useToast must be used inside <ToastProvider>");
648
+ }
649
+ return ctx;
650
+ }
651
+
652
+ // src/components/table/table.tsx
653
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
654
+ function Table({ className, children, ...props }) {
655
+ return /* @__PURE__ */ jsx16("div", { className: "w-full sm:overflow-x-auto sm:rounded-lg sm:border sm:border-gray-200", children: /* @__PURE__ */ jsx16(
656
+ "table",
657
+ {
658
+ className: cn(
659
+ "w-full border-collapse text-sm",
660
+ // desktop keeps min-width so columns never crush
661
+ "sm:min-w-[600px]",
662
+ className
663
+ ),
664
+ ...props,
665
+ children
666
+ }
667
+ ) });
668
+ }
669
+ function TableHeader({ className, ...props }) {
670
+ return /* @__PURE__ */ jsx16(
671
+ "thead",
672
+ {
673
+ className: cn(
674
+ "hidden sm:table-header-group",
675
+ "border-b border-gray-200 bg-gray-50",
676
+ className
677
+ ),
678
+ ...props
679
+ }
680
+ );
681
+ }
682
+ function TableBody({ className, ...props }) {
683
+ return /* @__PURE__ */ jsx16(
684
+ "tbody",
685
+ {
686
+ className: cn(
687
+ // mobile: stack rows as cards
688
+ "flex flex-col gap-3",
689
+ // sm+: normal tbody
690
+ "sm:table-row-group sm:gap-0 sm:divide-y sm:divide-gray-100 sm:bg-white",
691
+ className
692
+ ),
693
+ ...props
694
+ }
695
+ );
696
+ }
697
+ function TableRow({ className, ...props }) {
698
+ return /* @__PURE__ */ jsx16(
699
+ "tr",
700
+ {
701
+ className: cn(
702
+ // mobile: card
703
+ "block rounded-lg border border-gray-200 bg-white p-4 shadow-sm",
704
+ // sm+: normal row
705
+ "sm:table-row sm:rounded-none sm:border-0 sm:p-0 sm:shadow-none sm:transition-colors sm:hover:bg-gray-50",
706
+ className
707
+ ),
708
+ ...props
709
+ }
710
+ );
711
+ }
712
+ function TableHead({ className, align = "left", ...props }) {
713
+ return /* @__PURE__ */ jsx16(
714
+ "th",
715
+ {
716
+ scope: "col",
717
+ className: cn(
718
+ "px-4 py-3 text-xs font-semibold uppercase tracking-wide text-gray-500 whitespace-nowrap",
719
+ align === "center" && "text-center",
720
+ align === "right" && "text-right",
721
+ className
722
+ ),
723
+ ...props
724
+ }
725
+ );
726
+ }
727
+ function TableCell({ className, align = "left", label, children, ...props }) {
728
+ return /* @__PURE__ */ jsxs12(
729
+ "td",
730
+ {
731
+ className: cn(
732
+ // mobile: each cell is a flex row — "Label value"
733
+ "flex items-start justify-between gap-4 py-1.5 text-gray-700",
734
+ "last:pb-0 first:pt-0",
735
+ // sm+: normal table cell
736
+ "sm:table-cell sm:px-4 sm:py-3",
737
+ align === "center" && "sm:text-center",
738
+ align === "right" && "sm:text-right",
739
+ className
740
+ ),
741
+ ...props,
742
+ children: [
743
+ label && /* @__PURE__ */ jsx16("span", { className: "shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400 sm:hidden", children: label }),
744
+ /* @__PURE__ */ jsx16("span", { className: cn("sm:contents", label && "text-right sm:text-left"), children })
745
+ ]
746
+ }
747
+ );
748
+ }
749
+
750
+ // src/components/table/table-toolbar.tsx
751
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
752
+ function TableToolbar({ filters, actions, className }) {
753
+ return /* @__PURE__ */ jsxs13(
754
+ "div",
755
+ {
756
+ className: cn(
757
+ "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
758
+ "mb-3",
759
+ className
760
+ ),
761
+ children: [
762
+ filters && /* @__PURE__ */ jsx17("div", { className: "flex flex-wrap items-center gap-2", children: filters }),
763
+ actions && /* @__PURE__ */ jsx17("div", { className: "flex shrink-0 flex-wrap items-center gap-2 sm:ml-auto", children: actions })
764
+ ]
765
+ }
766
+ );
767
+ }
768
+
769
+ // src/components/table/table-empty-state.tsx
770
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
771
+ function TableEmptyState({
772
+ colSpan,
773
+ title = "No results",
774
+ description,
775
+ action,
776
+ icon,
777
+ className
778
+ }) {
779
+ return /* @__PURE__ */ jsx18("tr", { className: "block sm:table-row", children: /* @__PURE__ */ jsx18("td", { colSpan, className: cn("block px-4 py-12 text-center sm:table-cell", className), children: /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-center gap-3", children: [
780
+ icon && /* @__PURE__ */ jsx18("span", { className: "text-gray-300", "aria-hidden": "true", children: icon }),
781
+ /* @__PURE__ */ jsx18("p", { className: "text-sm font-medium text-gray-600", children: title }),
782
+ description && /* @__PURE__ */ jsx18("p", { className: "max-w-xs text-xs text-gray-400", children: description }),
783
+ action && /* @__PURE__ */ jsx18("div", { className: "mt-1", children: action })
784
+ ] }) }) });
785
+ }
786
+
787
+ // src/components/page-header/page-header.tsx
788
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
789
+ function PageHeader({ title, description, prefix, actions, className }) {
790
+ return /* @__PURE__ */ jsxs15("div", { className: cn("mb-6", className), children: [
791
+ prefix && /* @__PURE__ */ jsx19("div", { className: "mb-2", children: prefix }),
792
+ /* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
793
+ /* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
794
+ /* @__PURE__ */ jsx19("h1", { className: "truncate text-2xl font-bold text-gray-900 sm:text-3xl", children: title }),
795
+ description && /* @__PURE__ */ jsx19("p", { className: "mt-1 text-sm text-gray-500", children: description })
796
+ ] }),
797
+ actions && /* @__PURE__ */ jsx19("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions })
798
+ ] })
799
+ ] });
800
+ }
801
+
802
+ // src/components/empty-state/empty-state.tsx
803
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
804
+ function EmptyState({
805
+ icon,
806
+ title,
807
+ description,
808
+ action,
809
+ secondaryAction,
810
+ className
811
+ }) {
812
+ return /* @__PURE__ */ jsxs16(
813
+ "div",
814
+ {
815
+ className: cn(
816
+ "flex flex-col items-center justify-center gap-4 rounded-lg border border-dashed border-gray-200 bg-gray-50 px-6 py-16 text-center",
817
+ className
818
+ ),
819
+ children: [
820
+ icon && /* @__PURE__ */ jsx20("span", { className: "text-gray-300", "aria-hidden": "true", children: icon }),
821
+ /* @__PURE__ */ jsxs16("div", { className: "max-w-xs", children: [
822
+ /* @__PURE__ */ jsx20("p", { className: "text-sm font-semibold text-gray-700", children: title }),
823
+ description && /* @__PURE__ */ jsx20("p", { className: "mt-1 text-sm text-gray-400", children: description })
824
+ ] }),
825
+ (action || secondaryAction) && /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-center gap-2 sm:flex-row", children: [
826
+ action,
827
+ secondaryAction
828
+ ] })
829
+ ]
830
+ }
831
+ );
832
+ }
833
+
834
+ // src/components/app-shell/app-shell.tsx
835
+ import * as React12 from "react";
836
+
837
+ // src/components/drawer/drawer.tsx
838
+ import * as React11 from "react";
839
+ import { createPortal as createPortal2 } from "react-dom";
840
+ import { X as X3 } from "lucide-react";
841
+ import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
842
+ function Drawer({
843
+ open,
844
+ onClose,
845
+ title,
846
+ description,
847
+ side = "right",
848
+ widthClass = "sm:w-96",
849
+ className,
850
+ children,
851
+ footer
852
+ }) {
853
+ const overlayRef = React11.useRef(null);
854
+ const titleId = React11.useId();
855
+ const descId = React11.useId();
856
+ React11.useEffect(() => {
857
+ if (!open) return;
858
+ const h = (e) => {
859
+ if (e.key === "Escape") onClose();
860
+ };
861
+ document.addEventListener("keydown", h);
862
+ return () => document.removeEventListener("keydown", h);
863
+ }, [open, onClose]);
864
+ React11.useEffect(() => {
865
+ if (open) {
866
+ document.body.style.overflow = "hidden";
867
+ return () => {
868
+ document.body.style.overflow = "";
869
+ };
870
+ }
871
+ }, [open]);
872
+ React11.useEffect(() => {
873
+ if (!open) return;
874
+ const el = overlayRef.current;
875
+ if (!el) return;
876
+ const focusable = el.querySelectorAll(
877
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
878
+ );
879
+ const first = focusable[0];
880
+ const last = focusable[focusable.length - 1];
881
+ first?.focus();
882
+ const trap = (e) => {
883
+ if (e.key !== "Tab") return;
884
+ if (e.shiftKey) {
885
+ if (document.activeElement === first) {
886
+ e.preventDefault();
887
+ last?.focus();
888
+ }
889
+ } else {
890
+ if (document.activeElement === last) {
891
+ e.preventDefault();
892
+ first?.focus();
893
+ }
894
+ }
895
+ };
896
+ document.addEventListener("keydown", trap);
897
+ return () => document.removeEventListener("keydown", trap);
898
+ }, [open]);
899
+ if (!open) return null;
900
+ return createPortal2(
901
+ /* @__PURE__ */ jsxs17(
902
+ "div",
903
+ {
904
+ ref: overlayRef,
905
+ role: "dialog",
906
+ "aria-modal": "true",
907
+ "aria-labelledby": title ? titleId : void 0,
908
+ "aria-describedby": description ? descId : void 0,
909
+ className: "fixed inset-0 z-50 flex",
910
+ children: [
911
+ /* @__PURE__ */ jsx21(
912
+ "div",
913
+ {
914
+ className: "absolute inset-0 bg-black/50 backdrop-blur-sm",
915
+ "aria-hidden": "true",
916
+ onClick: onClose
917
+ }
918
+ ),
919
+ /* @__PURE__ */ jsxs17(
920
+ "div",
921
+ {
922
+ className: cn(
923
+ "absolute bottom-0 left-0 right-0 z-10 flex flex-col bg-white shadow-xl",
924
+ "max-h-[90dvh] rounded-t-2xl",
925
+ // sm+: full height side panel
926
+ "sm:inset-y-0 sm:bottom-auto sm:top-0 sm:max-h-full sm:rounded-none",
927
+ side === "right" ? "sm:right-0 sm:left-auto" : "sm:left-0 sm:right-auto",
928
+ widthClass,
929
+ className
930
+ ),
931
+ children: [
932
+ /* @__PURE__ */ jsxs17("div", { className: "flex items-start justify-between gap-4 border-b border-gray-100 px-5 py-4", children: [
933
+ /* @__PURE__ */ jsxs17("div", { className: "min-w-0", children: [
934
+ title && /* @__PURE__ */ jsx21("h2", { id: titleId, className: "text-base font-semibold text-gray-900", children: title }),
935
+ description && /* @__PURE__ */ jsx21("p", { id: descId, className: "mt-0.5 text-sm text-gray-500", children: description })
936
+ ] }),
937
+ /* @__PURE__ */ jsx21(
938
+ "button",
939
+ {
940
+ onClick: onClose,
941
+ "aria-label": "Close",
942
+ className: "shrink-0 rounded p-1 text-gray-400 hover:text-gray-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
943
+ children: /* @__PURE__ */ jsx21(X3, { size: 18, "aria-hidden": "true" })
944
+ }
945
+ )
946
+ ] }),
947
+ /* @__PURE__ */ jsx21("div", { className: "flex-1 overflow-y-auto px-5 py-4", children }),
948
+ footer && /* @__PURE__ */ jsx21("div", { className: "flex flex-col-reverse gap-2 border-t border-gray-100 px-5 py-4 sm:flex-row sm:justify-end", children: footer })
949
+ ]
950
+ }
951
+ )
952
+ ]
953
+ }
954
+ ),
955
+ document.body
956
+ );
957
+ }
958
+
959
+ // src/components/sidebar/sidebar.tsx
960
+ import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
961
+ function Sidebar({ logo, groups, footer, className }) {
962
+ return /* @__PURE__ */ jsxs18(
963
+ "aside",
964
+ {
965
+ className: cn(
966
+ "flex h-full w-64 flex-col border-r border-gray-200 bg-white",
967
+ className
968
+ ),
969
+ "aria-label": "Sidebar navigation",
970
+ children: [
971
+ logo && /* @__PURE__ */ jsx22("div", { className: "flex h-16 shrink-0 items-center border-b border-gray-100 px-4", children: logo }),
972
+ /* @__PURE__ */ jsx22("nav", { className: "flex-1 overflow-y-auto px-3 py-4", children: groups.map((group, gi) => /* @__PURE__ */ jsxs18("div", { className: cn(gi > 0 && "mt-6"), children: [
973
+ group.title && /* @__PURE__ */ jsx22("p", { className: "mb-1 px-2 text-xs font-semibold uppercase tracking-wider text-gray-400", children: group.title }),
974
+ /* @__PURE__ */ jsx22("ul", { role: "list", className: "flex flex-col gap-0.5", children: group.items.map((item, ii) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(SidebarItem, { item }) }, ii)) })
975
+ ] }, gi)) }),
976
+ footer && /* @__PURE__ */ jsx22("div", { className: "shrink-0 border-t border-gray-100 p-3", children: footer })
977
+ ]
978
+ }
979
+ );
980
+ }
981
+ function SidebarItem({ item }) {
982
+ const Tag = item.href ? "a" : "button";
983
+ return /* @__PURE__ */ jsxs18(
984
+ Tag,
985
+ {
986
+ href: item.href,
987
+ onClick: item.onClick,
988
+ disabled: item.disabled,
989
+ "aria-current": item.active ? "page" : void 0,
990
+ className: cn(
991
+ "group flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium",
992
+ "transition-colors duration-100",
993
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
994
+ // mobile touch target
995
+ "min-h-[44px] sm:min-h-[36px]",
996
+ item.active ? "bg-blue-50 text-blue-700" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900",
997
+ item.disabled && "cursor-not-allowed opacity-40 pointer-events-none"
998
+ ),
999
+ children: [
1000
+ item.icon && /* @__PURE__ */ jsx22(
1001
+ "span",
1002
+ {
1003
+ "aria-hidden": "true",
1004
+ className: cn(
1005
+ "shrink-0",
1006
+ item.active ? "text-blue-600" : "text-gray-400 group-hover:text-gray-600"
1007
+ ),
1008
+ children: item.icon
1009
+ }
1010
+ ),
1011
+ /* @__PURE__ */ jsx22("span", { className: "flex-1 truncate text-left", children: item.label }),
1012
+ item.badge !== void 0 && /* @__PURE__ */ jsx22(
1013
+ "span",
1014
+ {
1015
+ className: cn(
1016
+ "shrink-0 rounded-full px-2 py-0.5 text-xs font-medium",
1017
+ item.active ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-500"
1018
+ ),
1019
+ children: item.badge
1020
+ }
1021
+ )
1022
+ ]
1023
+ }
1024
+ );
1025
+ }
1026
+
1027
+ // src/components/topbar/topbar.tsx
1028
+ import { Menu } from "lucide-react";
1029
+ import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1030
+ function Topbar({
1031
+ onMenuOpen,
1032
+ title,
1033
+ actions,
1034
+ hideMenuButton = false,
1035
+ className
1036
+ }) {
1037
+ return /* @__PURE__ */ jsxs19(
1038
+ "header",
1039
+ {
1040
+ className: cn(
1041
+ "flex h-14 shrink-0 items-center gap-3 border-b border-gray-200 bg-white px-4",
1042
+ className
1043
+ ),
1044
+ children: [
1045
+ !hideMenuButton && onMenuOpen && /* @__PURE__ */ jsx23(
1046
+ "button",
1047
+ {
1048
+ onClick: onMenuOpen,
1049
+ "aria-label": "Open navigation menu",
1050
+ className: cn(
1051
+ "inline-flex h-9 w-9 items-center justify-center rounded-md text-gray-500",
1052
+ "hover:bg-gray-100 hover:text-gray-700",
1053
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
1054
+ // visible on mobile, hidden when sidebar renders on sm+
1055
+ "sm:hidden"
1056
+ ),
1057
+ children: /* @__PURE__ */ jsx23(Menu, { size: 20, "aria-hidden": "true" })
1058
+ }
1059
+ ),
1060
+ title && /* @__PURE__ */ jsx23("div", { className: "flex-1 truncate text-sm font-semibold text-gray-900 sm:text-base", children: title }),
1061
+ !title && /* @__PURE__ */ jsx23("div", { className: "flex-1" }),
1062
+ actions && /* @__PURE__ */ jsx23("div", { className: "flex shrink-0 items-center gap-2", children: actions })
1063
+ ]
1064
+ }
1065
+ );
1066
+ }
1067
+
1068
+ // src/components/app-shell/app-shell.tsx
1069
+ import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
1070
+ function AppShell({ sidebar, topbar, children, className }) {
1071
+ const [mobileOpen, setMobileOpen] = React12.useState(false);
1072
+ return /* @__PURE__ */ jsxs20("div", { className: cn("flex h-dvh overflow-hidden bg-gray-50", className), children: [
1073
+ /* @__PURE__ */ jsx24("div", { className: "hidden sm:flex sm:shrink-0", children: /* @__PURE__ */ jsx24(Sidebar, { ...sidebar }) }),
1074
+ /* @__PURE__ */ jsx24(
1075
+ Drawer,
1076
+ {
1077
+ open: mobileOpen,
1078
+ onClose: () => setMobileOpen(false),
1079
+ side: "left",
1080
+ widthClass: "sm:w-64",
1081
+ className: "p-0 sm:rounded-none",
1082
+ children: /* @__PURE__ */ jsx24("div", { className: "-mx-5 -my-4 h-full", children: /* @__PURE__ */ jsx24(
1083
+ Sidebar,
1084
+ {
1085
+ ...sidebar,
1086
+ className: "h-full border-r-0"
1087
+ }
1088
+ ) })
1089
+ }
1090
+ ),
1091
+ /* @__PURE__ */ jsxs20("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
1092
+ topbar !== void 0 && /* @__PURE__ */ jsx24(
1093
+ Topbar,
1094
+ {
1095
+ ...topbar,
1096
+ onMenuOpen: () => setMobileOpen(true),
1097
+ hideMenuButton: false
1098
+ }
1099
+ ),
1100
+ /* @__PURE__ */ jsx24("main", { className: "flex-1 overflow-y-auto p-4 sm:p-6", children })
1101
+ ] })
1102
+ ] });
1103
+ }
1104
+
1105
+ // src/components/tabs/tabs.tsx
1106
+ import * as React13 from "react";
1107
+ import { jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
1108
+ function Tabs({ tabs, value, onChange, children, className }) {
1109
+ const tabListRef = React13.useRef(null);
1110
+ const onKeyDown = (e, current) => {
1111
+ const enabled = tabs.filter((t) => !t.disabled);
1112
+ const idx = enabled.findIndex((t) => t.value === current);
1113
+ if (e.key === "ArrowRight") {
1114
+ e.preventDefault();
1115
+ const next = enabled[(idx + 1) % enabled.length];
1116
+ if (next) onChange(next.value);
1117
+ }
1118
+ if (e.key === "ArrowLeft") {
1119
+ e.preventDefault();
1120
+ const prev = enabled[(idx - 1 + enabled.length) % enabled.length];
1121
+ if (prev) onChange(prev.value);
1122
+ }
1123
+ };
1124
+ return /* @__PURE__ */ jsxs21("div", { className: cn("w-full", className), children: [
1125
+ /* @__PURE__ */ jsx25(
1126
+ "div",
1127
+ {
1128
+ ref: tabListRef,
1129
+ role: "tablist",
1130
+ className: "flex overflow-x-auto border-b border-gray-200 scrollbar-none",
1131
+ style: { scrollbarWidth: "none" },
1132
+ children: tabs.map((tab) => {
1133
+ const isActive = tab.value === value;
1134
+ return /* @__PURE__ */ jsx25(
1135
+ "button",
1136
+ {
1137
+ role: "tab",
1138
+ id: `tab-${tab.value}`,
1139
+ "aria-selected": isActive,
1140
+ "aria-controls": `panel-${tab.value}`,
1141
+ disabled: tab.disabled,
1142
+ tabIndex: isActive ? 0 : -1,
1143
+ onClick: () => !tab.disabled && onChange(tab.value),
1144
+ onKeyDown: (e) => onKeyDown(e, tab.value),
1145
+ className: cn(
1146
+ "inline-flex shrink-0 items-center whitespace-nowrap border-b-2 px-4 py-3 text-sm font-medium",
1147
+ "transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500",
1148
+ // mobile touch target
1149
+ "min-h-[44px]",
1150
+ isActive ? "border-blue-600 text-blue-600" : "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
1151
+ tab.disabled && "cursor-not-allowed opacity-40"
1152
+ ),
1153
+ children: tab.label
1154
+ },
1155
+ tab.value
1156
+ );
1157
+ })
1158
+ }
1159
+ ),
1160
+ children
1161
+ ] });
1162
+ }
1163
+ function TabPanel({ value, activeValue, children, className }) {
1164
+ const isActive = value === activeValue;
1165
+ return /* @__PURE__ */ jsx25(
1166
+ "div",
1167
+ {
1168
+ role: "tabpanel",
1169
+ id: `panel-${value}`,
1170
+ "aria-labelledby": `tab-${value}`,
1171
+ hidden: !isActive,
1172
+ className: cn("py-4", className),
1173
+ children: isActive ? children : null
1174
+ }
1175
+ );
1176
+ }
1177
+
1178
+ // src/components/dropdown-menu/dropdown-menu.tsx
1179
+ import * as React14 from "react";
1180
+ import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
1181
+ function DropdownMenu({ trigger, items, align = "right", className }) {
1182
+ const [open, setOpen] = React14.useState(false);
1183
+ const containerRef = React14.useRef(null);
1184
+ const menuRef = React14.useRef(null);
1185
+ React14.useEffect(() => {
1186
+ if (!open) return;
1187
+ const handler = (e) => {
1188
+ if (!containerRef.current?.contains(e.target)) setOpen(false);
1189
+ };
1190
+ document.addEventListener("mousedown", handler);
1191
+ return () => document.removeEventListener("mousedown", handler);
1192
+ }, [open]);
1193
+ React14.useEffect(() => {
1194
+ if (!open) return;
1195
+ const handler = (e) => {
1196
+ if (e.key === "Escape") {
1197
+ setOpen(false);
1198
+ containerRef.current?.querySelector("[data-trigger]")?.focus();
1199
+ }
1200
+ };
1201
+ document.addEventListener("keydown", handler);
1202
+ const first = menuRef.current?.querySelector("[role='menuitem']:not([disabled])");
1203
+ first?.focus();
1204
+ return () => document.removeEventListener("keydown", handler);
1205
+ }, [open]);
1206
+ return /* @__PURE__ */ jsxs22("div", { ref: containerRef, className: cn("relative inline-block", className), children: [
1207
+ /* @__PURE__ */ jsx26(
1208
+ "span",
1209
+ {
1210
+ "data-trigger": true,
1211
+ onClick: () => setOpen((v) => !v),
1212
+ "aria-haspopup": "menu",
1213
+ "aria-expanded": open,
1214
+ className: "inline-flex",
1215
+ children: trigger
1216
+ }
1217
+ ),
1218
+ open && /* @__PURE__ */ jsx26(
1219
+ "div",
1220
+ {
1221
+ ref: menuRef,
1222
+ role: "menu",
1223
+ className: cn(
1224
+ "absolute z-50 mt-1 min-w-[160px] overflow-hidden rounded-lg border border-gray-200 bg-white py-1 shadow-lg",
1225
+ align === "right" ? "right-0" : "left-0"
1226
+ ),
1227
+ children: items.map((item, i) => /* @__PURE__ */ jsxs22(React14.Fragment, { children: [
1228
+ item.divider && /* @__PURE__ */ jsx26("div", { className: "my-1 border-t border-gray-100" }),
1229
+ /* @__PURE__ */ jsxs22(
1230
+ "button",
1231
+ {
1232
+ role: "menuitem",
1233
+ disabled: item.disabled,
1234
+ onClick: () => {
1235
+ if (item.disabled) return;
1236
+ item.onClick?.();
1237
+ setOpen(false);
1238
+ },
1239
+ className: cn(
1240
+ "flex w-full items-center gap-2 px-4 py-2 text-left text-sm",
1241
+ "transition-colors duration-100",
1242
+ "focus-visible:outline-none focus-visible:bg-gray-50",
1243
+ item.destructive ? "text-red-600 hover:bg-red-50" : "text-gray-700 hover:bg-gray-50",
1244
+ item.disabled && "cursor-not-allowed opacity-40"
1245
+ ),
1246
+ children: [
1247
+ item.icon && /* @__PURE__ */ jsx26("span", { className: "shrink-0 text-current", "aria-hidden": "true", children: item.icon }),
1248
+ item.label
1249
+ ]
1250
+ }
1251
+ )
1252
+ ] }, i))
1253
+ }
1254
+ )
1255
+ ] });
1256
+ }
1257
+
1258
+ // src/components/pagination/pagination.tsx
1259
+ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
1260
+ import { jsx as jsx27, jsxs as jsxs23 } from "react/jsx-runtime";
1261
+ function getPages(page, total, siblings) {
1262
+ const delta = siblings;
1263
+ const range = [];
1264
+ for (let i = Math.max(2, page - delta); i <= Math.min(total - 1, page + delta); i++) range.push(i);
1265
+ const pages = [1];
1266
+ const rangeFirst = range[0];
1267
+ const rangeLast = range[range.length - 1];
1268
+ if (rangeFirst !== void 0 && rangeFirst > 2) pages.push("\u2026");
1269
+ pages.push(...range);
1270
+ if (rangeLast !== void 0 && rangeLast < total - 1) pages.push("\u2026");
1271
+ if (total > 1) pages.push(total);
1272
+ return pages;
1273
+ }
1274
+ var btnBase = "inline-flex h-9 min-w-[36px] items-center justify-center rounded-md px-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 disabled:pointer-events-none disabled:opacity-40";
1275
+ function Pagination({
1276
+ page,
1277
+ totalPages,
1278
+ onChange,
1279
+ siblingCount = 1,
1280
+ className
1281
+ }) {
1282
+ if (totalPages <= 1) return null;
1283
+ const pages = getPages(page, totalPages, siblingCount);
1284
+ return /* @__PURE__ */ jsxs23(
1285
+ "nav",
1286
+ {
1287
+ "aria-label": "Pagination",
1288
+ className: cn("flex flex-wrap items-center justify-center gap-1", className),
1289
+ children: [
1290
+ /* @__PURE__ */ jsxs23(
1291
+ "button",
1292
+ {
1293
+ onClick: () => onChange(page - 1),
1294
+ disabled: page === 1,
1295
+ "aria-label": "Previous page",
1296
+ className: cn(btnBase, "text-gray-600 hover:bg-gray-100"),
1297
+ children: [
1298
+ /* @__PURE__ */ jsx27(ChevronLeft, { size: 16, "aria-hidden": "true" }),
1299
+ /* @__PURE__ */ jsx27("span", { className: "ml-1 hidden sm:inline", children: "Prev" })
1300
+ ]
1301
+ }
1302
+ ),
1303
+ /* @__PURE__ */ jsx27("div", { className: "hidden sm:flex sm:items-center sm:gap-1", children: pages.map(
1304
+ (p, i) => p === "\u2026" ? /* @__PURE__ */ jsx27("span", { className: "px-1 text-gray-400", children: /* @__PURE__ */ jsx27(MoreHorizontal, { size: 16, "aria-hidden": "true" }) }, `ellipsis-${i}`) : /* @__PURE__ */ jsx27(
1305
+ "button",
1306
+ {
1307
+ onClick: () => onChange(p),
1308
+ "aria-label": `Page ${p}`,
1309
+ "aria-current": p === page ? "page" : void 0,
1310
+ className: cn(
1311
+ btnBase,
1312
+ p === page ? "bg-blue-600 text-white hover:bg-blue-700" : "text-gray-600 hover:bg-gray-100"
1313
+ ),
1314
+ children: p
1315
+ },
1316
+ p
1317
+ )
1318
+ ) }),
1319
+ /* @__PURE__ */ jsxs23("span", { className: "text-sm text-gray-500 sm:hidden", children: [
1320
+ page,
1321
+ " / ",
1322
+ totalPages
1323
+ ] }),
1324
+ /* @__PURE__ */ jsxs23(
1325
+ "button",
1326
+ {
1327
+ onClick: () => onChange(page + 1),
1328
+ disabled: page === totalPages,
1329
+ "aria-label": "Next page",
1330
+ className: cn(btnBase, "text-gray-600 hover:bg-gray-100"),
1331
+ children: [
1332
+ /* @__PURE__ */ jsx27("span", { className: "mr-1 hidden sm:inline", children: "Next" }),
1333
+ /* @__PURE__ */ jsx27(ChevronRight, { size: 16, "aria-hidden": "true" })
1334
+ ]
1335
+ }
1336
+ )
1337
+ ]
1338
+ }
1339
+ );
1340
+ }
1341
+
1342
+ // src/components/tooltip/tooltip.tsx
1343
+ import * as React15 from "react";
1344
+ import { jsx as jsx28, jsxs as jsxs24 } from "react/jsx-runtime";
1345
+ var sideClasses = {
1346
+ top: {
1347
+ wrapper: "bottom-full left-1/2 mb-2 -translate-x-1/2",
1348
+ tip: "top-full left-1/2 -translate-x-1/2 border-t-gray-800 border-x-transparent border-b-transparent"
1349
+ },
1350
+ bottom: {
1351
+ wrapper: "top-full left-1/2 mt-2 -translate-x-1/2",
1352
+ tip: "bottom-full left-1/2 -translate-x-1/2 border-b-gray-800 border-x-transparent border-t-transparent"
1353
+ },
1354
+ left: {
1355
+ wrapper: "right-full top-1/2 mr-2 -translate-y-1/2",
1356
+ tip: "left-full top-1/2 -translate-y-1/2 border-l-gray-800 border-y-transparent border-r-transparent"
1357
+ },
1358
+ right: {
1359
+ wrapper: "left-full top-1/2 ml-2 -translate-y-1/2",
1360
+ tip: "right-full top-1/2 -translate-y-1/2 border-r-gray-800 border-y-transparent border-l-transparent"
1361
+ }
1362
+ };
1363
+ function Tooltip({ content, side = "top", children, className }) {
1364
+ const id = React15.useId();
1365
+ const [visible, setVisible] = React15.useState(false);
1366
+ const show = () => setVisible(true);
1367
+ const hide = () => setVisible(false);
1368
+ const child = React15.cloneElement(children, {
1369
+ "aria-describedby": visible ? id : void 0,
1370
+ onMouseEnter: (e) => {
1371
+ show();
1372
+ children.props.onMouseEnter?.(e);
1373
+ },
1374
+ onMouseLeave: (e) => {
1375
+ hide();
1376
+ children.props.onMouseLeave?.(e);
1377
+ },
1378
+ onFocus: (e) => {
1379
+ show();
1380
+ children.props.onFocus?.(e);
1381
+ },
1382
+ onBlur: (e) => {
1383
+ hide();
1384
+ children.props.onBlur?.(e);
1385
+ }
1386
+ });
1387
+ return /* @__PURE__ */ jsxs24("span", { className: "relative inline-flex", children: [
1388
+ child,
1389
+ visible && /* @__PURE__ */ jsxs24(
1390
+ "span",
1391
+ {
1392
+ id,
1393
+ role: "tooltip",
1394
+ className: cn(
1395
+ "pointer-events-none absolute z-50 whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-xs text-white shadow-sm",
1396
+ sideClasses[side].wrapper,
1397
+ className
1398
+ ),
1399
+ children: [
1400
+ content,
1401
+ /* @__PURE__ */ jsx28(
1402
+ "span",
1403
+ {
1404
+ "aria-hidden": "true",
1405
+ className: cn(
1406
+ "absolute h-0 w-0 border-4",
1407
+ sideClasses[side].tip
1408
+ )
1409
+ }
1410
+ )
1411
+ ]
1412
+ }
1413
+ )
1414
+ ] });
1415
+ }
1416
+ export {
1417
+ Alert,
1418
+ AppShell,
1419
+ Badge,
1420
+ Button,
1421
+ Card,
1422
+ CardContent,
1423
+ CardDescription,
1424
+ CardFooter,
1425
+ CardHeader,
1426
+ CardTitle,
1427
+ Checkbox,
1428
+ ConfirmDialog,
1429
+ Drawer,
1430
+ DropdownMenu,
1431
+ EmptyState,
1432
+ FormField,
1433
+ Input,
1434
+ Label,
1435
+ Modal,
1436
+ PageHeader,
1437
+ Pagination,
1438
+ Select,
1439
+ Sidebar,
1440
+ Spinner,
1441
+ TabPanel,
1442
+ Table,
1443
+ TableBody,
1444
+ TableCell,
1445
+ TableEmptyState,
1446
+ TableHead,
1447
+ TableHeader,
1448
+ TableRow,
1449
+ TableToolbar,
1450
+ Tabs,
1451
+ Textarea,
1452
+ ToastProvider,
1453
+ Tooltip,
1454
+ Topbar,
1455
+ cn,
1456
+ useToast
1457
+ };
1458
+ //# sourceMappingURL=index.js.map