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