@sypra-ui/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1554 @@
1
+ // src/lib/utils.ts
2
+ var cn = (...values) => values.filter(Boolean).join(" ");
3
+
4
+ // src/components/forms.tsx
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var buttonStyles = {
7
+ primary: "bg-[hsl(var(--rui-primary))] text-[hsl(var(--rui-primary-foreground))] hover:opacity-90",
8
+ secondary: "bg-[hsl(var(--rui-secondary))] text-[hsl(var(--rui-secondary-foreground))] hover:opacity-80",
9
+ outline: "border border-[hsl(var(--rui-border))] bg-transparent hover:bg-[hsl(var(--rui-accent))]",
10
+ ghost: "bg-transparent hover:bg-[hsl(var(--rui-accent))]",
11
+ destructive: "bg-[hsl(var(--rui-destructive))] text-[hsl(var(--rui-destructive-foreground))] hover:opacity-90"
12
+ };
13
+ function Button({
14
+ variant = "primary",
15
+ size = "md",
16
+ loading,
17
+ className,
18
+ children,
19
+ disabled,
20
+ ...props
21
+ }) {
22
+ const padding = size === "sm" ? "h-8 px-3 text-sm" : size === "lg" ? "h-11 px-5 text-base" : "h-10 px-4 text-sm";
23
+ return /* @__PURE__ */ jsxs(
24
+ "button",
25
+ {
26
+ className: cn(
27
+ "rui-focus inline-flex items-center justify-center gap-2 rounded-[var(--rui-radius)] font-medium transition disabled:pointer-events-none disabled:opacity-50",
28
+ padding,
29
+ buttonStyles[variant],
30
+ className
31
+ ),
32
+ disabled: disabled || loading,
33
+ "aria-busy": loading,
34
+ ...props,
35
+ children: [
36
+ loading && /* @__PURE__ */ jsx("span", { className: "size-4 animate-spin rounded-full border-2 border-current border-r-transparent" }),
37
+ " ",
38
+ children
39
+ ]
40
+ }
41
+ );
42
+ }
43
+ function IconButton({
44
+ children,
45
+ "aria-label": label,
46
+ className,
47
+ ...props
48
+ }) {
49
+ return /* @__PURE__ */ jsx(
50
+ Button,
51
+ {
52
+ className: cn("size-10 px-0", className),
53
+ "aria-label": label,
54
+ ...props,
55
+ children
56
+ }
57
+ );
58
+ }
59
+ var Input = ({
60
+ className,
61
+ ...props
62
+ }) => /* @__PURE__ */ jsx(
63
+ "input",
64
+ {
65
+ className: cn(
66
+ "rui-focus h-10 w-full rounded-[var(--rui-radius)] border border-[hsl(var(--rui-input))] bg-transparent px-3 text-sm placeholder:text-[hsl(var(--rui-muted-foreground))] disabled:cursor-not-allowed disabled:opacity-50",
67
+ className
68
+ ),
69
+ ...props
70
+ }
71
+ );
72
+ var Textarea = ({
73
+ className,
74
+ ...props
75
+ }) => /* @__PURE__ */ jsx(
76
+ "textarea",
77
+ {
78
+ className: cn(
79
+ "rui-focus min-h-24 w-full rounded-[var(--rui-radius)] border border-[hsl(var(--rui-input))] bg-transparent px-3 py-2 text-sm placeholder:text-[hsl(var(--rui-muted-foreground))] disabled:cursor-not-allowed disabled:opacity-50",
80
+ className
81
+ ),
82
+ ...props
83
+ }
84
+ );
85
+ var Label = ({
86
+ className,
87
+ ...props
88
+ }) => /* @__PURE__ */ jsx(
89
+ "label",
90
+ {
91
+ className: cn("mb-1.5 block text-sm font-medium", className),
92
+ ...props
93
+ }
94
+ );
95
+ var Checkbox = ({
96
+ className,
97
+ ...props
98
+ }) => /* @__PURE__ */ jsx(
99
+ "input",
100
+ {
101
+ type: "checkbox",
102
+ className: cn(
103
+ "rui-focus size-4 accent-[hsl(var(--rui-primary))]",
104
+ className
105
+ ),
106
+ ...props
107
+ }
108
+ );
109
+ var Radio = ({
110
+ className,
111
+ ...props
112
+ }) => /* @__PURE__ */ jsx(
113
+ "input",
114
+ {
115
+ type: "radio",
116
+ className: cn(
117
+ "rui-focus size-4 accent-[hsl(var(--rui-primary))]",
118
+ className
119
+ ),
120
+ ...props
121
+ }
122
+ );
123
+ var Switch = ({
124
+ checked,
125
+ onChange,
126
+ disabled,
127
+ className,
128
+ ...props
129
+ }) => /* @__PURE__ */ jsxs(
130
+ "label",
131
+ {
132
+ className: cn(
133
+ "inline-flex cursor-pointer items-center",
134
+ disabled && "cursor-not-allowed opacity-50",
135
+ className
136
+ ),
137
+ children: [
138
+ /* @__PURE__ */ jsx(
139
+ "input",
140
+ {
141
+ type: "checkbox",
142
+ className: "peer sr-only",
143
+ checked,
144
+ onChange,
145
+ disabled,
146
+ ...props
147
+ }
148
+ ),
149
+ /* @__PURE__ */ jsx("span", { className: "rui-focus h-6 w-11 rounded-full bg-[hsl(var(--rui-muted))] p-0.5 transition peer-checked:bg-[hsl(var(--rui-primary))]", children: /* @__PURE__ */ jsx("span", { className: "block size-5 rounded-full bg-[hsl(var(--rui-switch-thumb))] transition peer-checked:translate-x-5" }) })
150
+ ]
151
+ }
152
+ );
153
+ var Select = ({
154
+ className,
155
+ children,
156
+ ...props
157
+ }) => /* @__PURE__ */ jsx(
158
+ "select",
159
+ {
160
+ className: cn(
161
+ "rui-focus h-10 w-full rounded-[var(--rui-radius)] border border-[hsl(var(--rui-input))] bg-[hsl(var(--rui-background))] px-3 text-sm",
162
+ className
163
+ ),
164
+ ...props,
165
+ children
166
+ }
167
+ );
168
+
169
+ // src/components/display.tsx
170
+ import { jsx as jsx2 } from "react/jsx-runtime";
171
+ function Card({
172
+ className,
173
+ ...props
174
+ }) {
175
+ return /* @__PURE__ */ jsx2(
176
+ "section",
177
+ {
178
+ className: cn(
179
+ "rounded-[calc(var(--rui-radius)+.15rem)] border border-[hsl(var(--rui-border))] bg-[hsl(var(--rui-card))] p-5 shadow-sm",
180
+ className
181
+ ),
182
+ ...props
183
+ }
184
+ );
185
+ }
186
+ function CardHeader({
187
+ className,
188
+ ...props
189
+ }) {
190
+ return /* @__PURE__ */ jsx2(
191
+ "div",
192
+ {
193
+ className: cn("mb-4 flex items-start justify-between gap-3", className),
194
+ ...props
195
+ }
196
+ );
197
+ }
198
+ function CardTitle({
199
+ className,
200
+ ...props
201
+ }) {
202
+ return /* @__PURE__ */ jsx2("h3", { className: cn("font-semibold leading-none", className), ...props });
203
+ }
204
+ function CardDescription({
205
+ className,
206
+ ...props
207
+ }) {
208
+ return /* @__PURE__ */ jsx2(
209
+ "p",
210
+ {
211
+ className: cn(
212
+ "mt-1 text-sm text-[hsl(var(--rui-muted-foreground))]",
213
+ className
214
+ ),
215
+ ...props
216
+ }
217
+ );
218
+ }
219
+ function Badge({
220
+ className,
221
+ variant = "default",
222
+ ...props
223
+ }) {
224
+ const variants = {
225
+ default: "bg-[hsl(var(--rui-secondary))]",
226
+ success: "bg-[hsl(var(--rui-success-background))] text-[hsl(var(--rui-success-foreground))]",
227
+ warning: "bg-[hsl(var(--rui-warning-background))] text-[hsl(var(--rui-warning-foreground))]",
228
+ destructive: "bg-[hsl(var(--rui-destructive-background))] text-[hsl(var(--rui-destructive-text))]",
229
+ outline: "border border-[hsl(var(--rui-border))]"
230
+ };
231
+ return /* @__PURE__ */ jsx2(
232
+ "span",
233
+ {
234
+ className: cn(
235
+ "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
236
+ variants[variant],
237
+ className
238
+ ),
239
+ children: props.children
240
+ }
241
+ );
242
+ }
243
+ function Avatar({
244
+ src,
245
+ alt = "",
246
+ fallback,
247
+ className
248
+ }) {
249
+ return /* @__PURE__ */ jsx2(
250
+ "span",
251
+ {
252
+ className: cn(
253
+ "inline-flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full bg-[hsl(var(--rui-muted))] text-sm font-medium",
254
+ className
255
+ ),
256
+ children: src ? /* @__PURE__ */ jsx2("img", { className: "size-full object-cover", src, alt }) : fallback ?? alt.slice(0, 2).toUpperCase()
257
+ }
258
+ );
259
+ }
260
+ var Separator = ({
261
+ className,
262
+ ...props
263
+ }) => /* @__PURE__ */ jsx2(
264
+ "div",
265
+ {
266
+ role: "separator",
267
+ className: cn("h-px w-full bg-[hsl(var(--rui-border))]", className),
268
+ ...props
269
+ }
270
+ );
271
+ var Skeleton = ({
272
+ className,
273
+ ...props
274
+ }) => /* @__PURE__ */ jsx2(
275
+ "div",
276
+ {
277
+ className: cn(
278
+ "animate-pulse rounded bg-[hsl(var(--rui-muted))]",
279
+ className
280
+ ),
281
+ ...props
282
+ }
283
+ );
284
+ function Progress({
285
+ value,
286
+ max = 100,
287
+ label,
288
+ className
289
+ }) {
290
+ const percent = Math.max(0, Math.min(100, value / max * 100));
291
+ return /* @__PURE__ */ jsx2(
292
+ "div",
293
+ {
294
+ className,
295
+ role: "progressbar",
296
+ "aria-label": label,
297
+ "aria-valuemin": 0,
298
+ "aria-valuemax": max,
299
+ "aria-valuenow": value,
300
+ children: /* @__PURE__ */ jsx2("div", { className: "h-2 overflow-hidden rounded-full bg-[hsl(var(--rui-muted))]", children: /* @__PURE__ */ jsx2(
301
+ "div",
302
+ {
303
+ className: "h-full rounded-full bg-[hsl(var(--rui-primary))] transition-all",
304
+ style: { width: `${percent}%` }
305
+ }
306
+ ) })
307
+ }
308
+ );
309
+ }
310
+
311
+ // src/components/dialog.tsx
312
+ import {
313
+ cloneElement,
314
+ createContext,
315
+ isValidElement,
316
+ useCallback,
317
+ useContext,
318
+ useEffect,
319
+ useId,
320
+ useRef,
321
+ useState
322
+ } from "react";
323
+ import { createPortal } from "react-dom";
324
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
325
+ var DialogContext = createContext(null);
326
+ var DialogTitleContext = createContext(void 0);
327
+ var useDialog = () => {
328
+ const value = useContext(DialogContext);
329
+ if (!value) throw new Error("Dialog components must be inside Dialog");
330
+ return value;
331
+ };
332
+ function Dialog({
333
+ open: controlled,
334
+ defaultOpen = false,
335
+ onOpenChange,
336
+ children
337
+ }) {
338
+ const [uncontrolled, setUncontrolled] = useState(defaultOpen);
339
+ const open = controlled ?? uncontrolled;
340
+ const setOpen = useCallback(
341
+ (next) => {
342
+ if (controlled === void 0) setUncontrolled(next);
343
+ onOpenChange?.(next);
344
+ },
345
+ [controlled, onOpenChange]
346
+ );
347
+ return /* @__PURE__ */ jsx3(DialogContext.Provider, { value: { open, setOpen }, children });
348
+ }
349
+ function DialogTrigger({
350
+ children,
351
+ asChild = false
352
+ }) {
353
+ const { setOpen } = useDialog();
354
+ if (asChild && isValidElement(children))
355
+ return cloneElement(children, {
356
+ onClick: () => {
357
+ children.props.onClick?.();
358
+ setOpen(true);
359
+ }
360
+ });
361
+ return /* @__PURE__ */ jsx3(Button, { onClick: () => setOpen(true), children });
362
+ }
363
+ function DialogClose({
364
+ children,
365
+ asChild = false
366
+ }) {
367
+ const { setOpen } = useDialog();
368
+ if (asChild && isValidElement(children))
369
+ return cloneElement(children, {
370
+ onClick: () => {
371
+ children.props.onClick?.();
372
+ setOpen(false);
373
+ }
374
+ });
375
+ return /* @__PURE__ */ jsx3(Button, { variant: "ghost", onClick: () => setOpen(false), children });
376
+ }
377
+ function DialogContent({
378
+ children,
379
+ className,
380
+ movable = false,
381
+ constrainToViewport = true,
382
+ style,
383
+ ...props
384
+ }) {
385
+ const { open, setOpen } = useDialog();
386
+ const panel = useRef(null);
387
+ const previousFocus = useRef(null);
388
+ const [drag, setDrag] = useState(null);
389
+ const [offset, setOffset] = useState({ x: 0, y: 0 });
390
+ const titleId = useId();
391
+ useEffect(() => {
392
+ if (!open) return;
393
+ previousFocus.current = document.activeElement;
394
+ const timer = window.setTimeout(() => panel.current?.focus(), 0);
395
+ const key = (event) => {
396
+ if (event.key === "Escape") setOpen(false);
397
+ if (event.key === "Tab" && panel.current) {
398
+ const nodes = panel.current.querySelectorAll(
399
+ 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
400
+ );
401
+ const first = nodes[0], last = nodes[nodes.length - 1];
402
+ if (!first || !last) return;
403
+ if (event.shiftKey && document.activeElement === first) {
404
+ event.preventDefault();
405
+ last.focus();
406
+ } else if (!event.shiftKey && document.activeElement === last) {
407
+ event.preventDefault();
408
+ first.focus();
409
+ }
410
+ }
411
+ };
412
+ document.addEventListener("keydown", key);
413
+ return () => {
414
+ window.clearTimeout(timer);
415
+ document.removeEventListener("keydown", key);
416
+ previousFocus.current?.focus();
417
+ };
418
+ }, [open, setOpen]);
419
+ useEffect(() => {
420
+ if (!drag) return;
421
+ const move = (event) => {
422
+ let x = offset.x + event.clientX - drag.x;
423
+ let y = offset.y + event.clientY - drag.y;
424
+ if (constrainToViewport && panel.current) {
425
+ const rect = panel.current.getBoundingClientRect();
426
+ x = Math.min(
427
+ Math.max(x, -rect.left + 12),
428
+ window.innerWidth - rect.right - 12 + offset.x
429
+ );
430
+ y = Math.min(
431
+ Math.max(y, -rect.top + 12),
432
+ window.innerHeight - rect.bottom - 12 + offset.y
433
+ );
434
+ }
435
+ setOffset({ x, y });
436
+ };
437
+ const up = () => setDrag(null);
438
+ window.addEventListener("pointermove", move);
439
+ window.addEventListener("pointerup", up);
440
+ return () => {
441
+ window.removeEventListener("pointermove", move);
442
+ window.removeEventListener("pointerup", up);
443
+ };
444
+ }, [drag, offset, constrainToViewport]);
445
+ if (!open || typeof document === "undefined") return null;
446
+ return createPortal(
447
+ /* @__PURE__ */ jsxs2(
448
+ "div",
449
+ {
450
+ className: "fixed inset-0 z-50 grid place-items-center p-4",
451
+ role: "presentation",
452
+ children: [
453
+ /* @__PURE__ */ jsx3(
454
+ "button",
455
+ {
456
+ className: "absolute inset-0 cursor-default bg-[hsl(var(--rui-overlay))]/50",
457
+ "aria-label": "Close dialog",
458
+ onClick: () => setOpen(false)
459
+ }
460
+ ),
461
+ /* @__PURE__ */ jsxs2(
462
+ "div",
463
+ {
464
+ ref: panel,
465
+ role: "dialog",
466
+ "aria-modal": "true",
467
+ "aria-labelledby": titleId,
468
+ tabIndex: -1,
469
+ className: cn(
470
+ "relative z-10 max-h-[calc(100vh-2rem)] w-full max-w-lg overflow-auto rounded-[calc(var(--rui-radius)+.2rem)] bg-[hsl(var(--rui-card))] p-6 text-[hsl(var(--rui-foreground))] shadow-2xl outline-none",
471
+ drag && "select-none",
472
+ className
473
+ ),
474
+ style: {
475
+ ...style,
476
+ transform: `translate(${offset.x}px, ${offset.y}px)`
477
+ },
478
+ ...props,
479
+ children: [
480
+ movable && /* @__PURE__ */ jsx3(
481
+ "div",
482
+ {
483
+ onPointerDown: (event) => {
484
+ if (window.innerWidth < 640 || event.target.closest(
485
+ "button,input,select,textarea,a,[role=button]"
486
+ ))
487
+ return;
488
+ event.preventDefault();
489
+ setDrag({ x: event.clientX, y: event.clientY });
490
+ },
491
+ className: "-mx-2 -mt-2 mb-3 cursor-grab touch-none rounded px-2 py-1 text-xs text-[hsl(var(--rui-muted-foreground))] active:cursor-grabbing",
492
+ children: "Drag to move"
493
+ }
494
+ ),
495
+ /* @__PURE__ */ jsx3(DialogTitleContext.Provider, { value: titleId, children })
496
+ ]
497
+ }
498
+ )
499
+ ]
500
+ }
501
+ ),
502
+ document.body
503
+ );
504
+ }
505
+ var DialogHeader = ({
506
+ className,
507
+ ...props
508
+ }) => /* @__PURE__ */ jsx3("div", { className: cn("mb-5 space-y-1.5", className), ...props });
509
+ var DialogTitle = ({
510
+ className,
511
+ ...props
512
+ }) => {
513
+ const id = useContext(DialogTitleContext);
514
+ return /* @__PURE__ */ jsx3("h2", { id, className: cn("text-lg font-semibold", className), ...props });
515
+ };
516
+ var DialogDescription = ({
517
+ className,
518
+ ...props
519
+ }) => /* @__PURE__ */ jsx3(
520
+ "p",
521
+ {
522
+ className: cn("text-sm text-[hsl(var(--rui-muted-foreground))]", className),
523
+ ...props
524
+ }
525
+ );
526
+ var DialogBody = ({
527
+ className,
528
+ ...props
529
+ }) => /* @__PURE__ */ jsx3("div", { className: cn("space-y-4", className), ...props });
530
+ var DialogFooter = ({
531
+ className,
532
+ ...props
533
+ }) => /* @__PURE__ */ jsx3(
534
+ "div",
535
+ {
536
+ className: cn("mt-6 flex flex-wrap justify-end gap-2", className),
537
+ ...props
538
+ }
539
+ );
540
+ var AlertDialog = Dialog;
541
+ var AlertDialogTrigger = DialogTrigger;
542
+ var AlertDialogContent = DialogContent;
543
+ var AlertDialogHeader = DialogHeader;
544
+ var AlertDialogTitle = DialogTitle;
545
+ var AlertDialogDescription = DialogDescription;
546
+ var AlertDialogFooter = DialogFooter;
547
+ var AlertDialogClose = DialogClose;
548
+ function Sheet({
549
+ children,
550
+ ...props
551
+ }) {
552
+ return /* @__PURE__ */ jsx3(Dialog, { ...props, children });
553
+ }
554
+ var SheetTrigger = DialogTrigger;
555
+ var SheetClose = DialogClose;
556
+ function SheetContent({
557
+ className,
558
+ ...props
559
+ }) {
560
+ return /* @__PURE__ */ jsx3(
561
+ DialogContent,
562
+ {
563
+ className: cn("ml-auto h-full max-h-none rounded-r-none", className),
564
+ ...props
565
+ }
566
+ );
567
+ }
568
+ var Drawer = Sheet;
569
+ var DrawerTrigger = SheetTrigger;
570
+ var DrawerContent = SheetContent;
571
+
572
+ // src/components/overlays.tsx
573
+ import {
574
+ cloneElement as cloneElement2,
575
+ isValidElement as isValidElement2,
576
+ useCallback as useCallback2,
577
+ useEffect as useEffect2,
578
+ useId as useId2,
579
+ useRef as useRef2,
580
+ useState as useState2
581
+ } from "react";
582
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
583
+ function Popover({
584
+ trigger,
585
+ children,
586
+ className,
587
+ open: controlledOpen,
588
+ defaultOpen = false,
589
+ onOpenChange
590
+ }) {
591
+ const [uncontrolledOpen, setUncontrolledOpen] = useState2(defaultOpen);
592
+ const open = controlledOpen ?? uncontrolledOpen;
593
+ const root = useRef2(null);
594
+ const contentId = useId2();
595
+ const setOpen = useCallback2(
596
+ (next) => {
597
+ if (controlledOpen === void 0) setUncontrolledOpen(next);
598
+ onOpenChange?.(next);
599
+ },
600
+ [controlledOpen, onOpenChange]
601
+ );
602
+ useEffect2(() => {
603
+ if (!open) return;
604
+ const dismiss = (event) => {
605
+ if (!root.current?.contains(event.target)) setOpen(false);
606
+ };
607
+ const escape = (event) => {
608
+ if (event.key === "Escape") setOpen(false);
609
+ };
610
+ document.addEventListener("pointerdown", dismiss);
611
+ document.addEventListener("keydown", escape);
612
+ return () => {
613
+ document.removeEventListener("pointerdown", dismiss);
614
+ document.removeEventListener("keydown", escape);
615
+ };
616
+ }, [open, setOpen]);
617
+ const triggerProps = {
618
+ "aria-controls": contentId,
619
+ "aria-expanded": open,
620
+ "aria-haspopup": "dialog",
621
+ onClick: () => setOpen(!open)
622
+ };
623
+ const triggerElement = isValidElement2(trigger) ? cloneElement2(trigger, {
624
+ ...triggerProps,
625
+ onClick: () => {
626
+ trigger.props.onClick?.();
627
+ setOpen(!open);
628
+ }
629
+ }) : /* @__PURE__ */ jsx4("button", { type: "button", ...triggerProps, children: trigger });
630
+ return /* @__PURE__ */ jsxs3("span", { ref: root, className: "relative inline-block", children: [
631
+ triggerElement,
632
+ open && /* @__PURE__ */ jsx4(
633
+ "span",
634
+ {
635
+ id: contentId,
636
+ role: "dialog",
637
+ className: cn(
638
+ "absolute z-30 mt-2 block min-w-52 rounded-[var(--rui-radius)] border border-[hsl(var(--rui-border))] bg-[hsl(var(--rui-card))] p-3 shadow-lg",
639
+ className
640
+ ),
641
+ children
642
+ }
643
+ )
644
+ ] });
645
+ }
646
+ var Tooltip = ({
647
+ content,
648
+ children,
649
+ className
650
+ }) => {
651
+ const [visible, setVisible] = useState2(false);
652
+ const id = useId2();
653
+ return /* @__PURE__ */ jsxs3(
654
+ "span",
655
+ {
656
+ className: "relative inline-flex",
657
+ onMouseEnter: () => setVisible(true),
658
+ onMouseLeave: () => setVisible(false),
659
+ onFocus: () => setVisible(true),
660
+ onBlur: () => setVisible(false),
661
+ "aria-describedby": visible ? id : void 0,
662
+ children: [
663
+ children,
664
+ visible && /* @__PURE__ */ jsx4(
665
+ "span",
666
+ {
667
+ id,
668
+ role: "tooltip",
669
+ className: cn(
670
+ "absolute bottom-full left-1/2 z-30 mb-2 w-max max-w-52 -translate-x-1/2 rounded bg-[hsl(var(--rui-tooltip))] px-2 py-1 text-xs text-[hsl(var(--rui-tooltip-foreground))] shadow",
671
+ className
672
+ ),
673
+ children: content
674
+ }
675
+ )
676
+ ]
677
+ }
678
+ );
679
+ };
680
+ function DropdownMenu({
681
+ trigger,
682
+ children
683
+ }) {
684
+ return /* @__PURE__ */ jsx4(Popover, { trigger, className: "right-0 p-1", children });
685
+ }
686
+ var DropdownMenuItem = ({
687
+ className,
688
+ ...props
689
+ }) => /* @__PURE__ */ jsx4(
690
+ "button",
691
+ {
692
+ className: cn(
693
+ "rui-focus block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-[hsl(var(--rui-accent))]",
694
+ className
695
+ ),
696
+ ...props
697
+ }
698
+ );
699
+
700
+ // src/components/navigation.tsx
701
+ import { useState as useState3 } from "react";
702
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
703
+ function Navbar({
704
+ brand,
705
+ children,
706
+ className,
707
+ label = "Main navigation"
708
+ }) {
709
+ return /* @__PURE__ */ jsxs4(
710
+ "nav",
711
+ {
712
+ "aria-label": label,
713
+ className: cn(
714
+ "flex min-h-14 items-center justify-between border-b border-[hsl(var(--rui-border))] px-4",
715
+ className
716
+ ),
717
+ children: [
718
+ brand,
719
+ /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-2", children })
720
+ ]
721
+ }
722
+ );
723
+ }
724
+ function Sidebar({
725
+ children,
726
+ className,
727
+ label = "Sidebar"
728
+ }) {
729
+ return /* @__PURE__ */ jsx5(
730
+ "aside",
731
+ {
732
+ "aria-label": label,
733
+ className: cn(
734
+ "w-60 shrink-0 border-r border-[hsl(var(--rui-border))] p-3",
735
+ className
736
+ ),
737
+ children
738
+ }
739
+ );
740
+ }
741
+ function MobileNav({
742
+ children,
743
+ className,
744
+ label = "Mobile navigation"
745
+ }) {
746
+ return /* @__PURE__ */ jsx5(
747
+ "nav",
748
+ {
749
+ "aria-label": label,
750
+ className: cn(
751
+ "fixed inset-x-0 bottom-0 z-40 flex justify-around border-t border-[hsl(var(--rui-border))] bg-[hsl(var(--rui-card))] p-2 sm:hidden",
752
+ className
753
+ ),
754
+ children
755
+ }
756
+ );
757
+ }
758
+ function Breadcrumb({
759
+ items
760
+ }) {
761
+ return /* @__PURE__ */ jsx5("nav", { "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx5("ol", { className: "flex flex-wrap items-center gap-2 text-sm text-[hsl(var(--rui-muted-foreground))]", children: items.map((item, index) => /* @__PURE__ */ jsxs4(
762
+ "li",
763
+ {
764
+ className: "flex items-center gap-2",
765
+ children: [
766
+ index > 0 && /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "/" }),
767
+ item.href ? /* @__PURE__ */ jsx5(
768
+ "a",
769
+ {
770
+ className: "hover:text-[hsl(var(--rui-foreground))]",
771
+ href: item.href,
772
+ children: item.label
773
+ }
774
+ ) : /* @__PURE__ */ jsx5(
775
+ "span",
776
+ {
777
+ "aria-current": index === items.length - 1 ? "page" : void 0,
778
+ children: item.label
779
+ }
780
+ )
781
+ ]
782
+ },
783
+ `${item.label}-${index}`
784
+ )) }) });
785
+ }
786
+ function Tabs({
787
+ tabs,
788
+ value,
789
+ defaultValue,
790
+ onValueChange,
791
+ className
792
+ }) {
793
+ const [local, setLocal] = useState3(defaultValue ?? tabs[0]?.value ?? "");
794
+ const selected = value ?? local;
795
+ const choose = (next) => {
796
+ if (value === void 0) setLocal(next);
797
+ onValueChange?.(next);
798
+ };
799
+ const active = tabs.find((tab) => tab.value === selected);
800
+ return /* @__PURE__ */ jsxs4("div", { className, children: [
801
+ /* @__PURE__ */ jsx5(
802
+ "div",
803
+ {
804
+ role: "tablist",
805
+ className: "flex gap-1 border-b border-[hsl(var(--rui-border))]",
806
+ children: tabs.map((tab) => /* @__PURE__ */ jsx5(
807
+ "button",
808
+ {
809
+ role: "tab",
810
+ "aria-selected": tab.value === selected,
811
+ disabled: tab.disabled,
812
+ onClick: () => choose(tab.value),
813
+ className: cn(
814
+ "rui-focus border-b-2 px-3 py-2 text-sm font-medium transition",
815
+ tab.value === selected ? "border-[hsl(var(--rui-primary))]" : "border-transparent text-[hsl(var(--rui-muted-foreground))]"
816
+ ),
817
+ children: tab.label
818
+ },
819
+ tab.value
820
+ ))
821
+ }
822
+ ),
823
+ /* @__PURE__ */ jsx5("div", { role: "tabpanel", className: "py-4", children: active?.content })
824
+ ] });
825
+ }
826
+ function Accordion({
827
+ items,
828
+ type = "single"
829
+ }) {
830
+ const [open, setOpen] = useState3([]);
831
+ const toggle = (value) => setOpen(
832
+ (current) => current.includes(value) ? current.filter((item) => item !== value) : type === "single" ? [value] : [...current, value]
833
+ );
834
+ return /* @__PURE__ */ jsx5("div", { className: "divide-y divide-[hsl(var(--rui-border))]", children: items.map((item) => /* @__PURE__ */ jsxs4("div", { children: [
835
+ /* @__PURE__ */ jsxs4(
836
+ "button",
837
+ {
838
+ className: "rui-focus flex w-full items-center justify-between py-3 text-left font-medium",
839
+ "aria-expanded": open.includes(item.value),
840
+ onClick: () => toggle(item.value),
841
+ children: [
842
+ item.title,
843
+ /* @__PURE__ */ jsx5("span", { children: open.includes(item.value) ? "\u2212" : "+" })
844
+ ]
845
+ }
846
+ ),
847
+ open.includes(item.value) && /* @__PURE__ */ jsx5("div", { className: "pb-3 text-sm text-[hsl(var(--rui-muted-foreground))]", children: item.content })
848
+ ] }, item.value)) });
849
+ }
850
+ function Pagination({
851
+ page,
852
+ pageSize,
853
+ total,
854
+ onPageChange,
855
+ onPageSizeChange,
856
+ pageSizeOptions = [10, 20, 50],
857
+ ariaLabel = "Pagination",
858
+ className
859
+ }) {
860
+ const pages = Math.max(1, Math.ceil(total / pageSize));
861
+ const visible = Array.from(
862
+ { length: Math.min(pages, 5) },
863
+ (_, i) => Math.max(1, Math.min(pages - 4, page - 3)) + i
864
+ );
865
+ return /* @__PURE__ */ jsxs4(
866
+ "nav",
867
+ {
868
+ "aria-label": ariaLabel,
869
+ className: cn("flex flex-wrap items-center gap-2", className),
870
+ children: [
871
+ /* @__PURE__ */ jsx5(
872
+ Button,
873
+ {
874
+ variant: "outline",
875
+ size: "sm",
876
+ disabled: page <= 1,
877
+ onClick: () => onPageChange(page - 1),
878
+ children: "Previous"
879
+ }
880
+ ),
881
+ visible.map((number) => /* @__PURE__ */ jsx5(
882
+ Button,
883
+ {
884
+ variant: number === page ? "primary" : "ghost",
885
+ size: "sm",
886
+ "aria-current": number === page ? "page" : void 0,
887
+ onClick: () => onPageChange(number),
888
+ children: number
889
+ },
890
+ number
891
+ )),
892
+ /* @__PURE__ */ jsx5(
893
+ Button,
894
+ {
895
+ variant: "outline",
896
+ size: "sm",
897
+ disabled: page >= pages,
898
+ onClick: () => onPageChange(page + 1),
899
+ children: "Next"
900
+ }
901
+ ),
902
+ onPageSizeChange && /* @__PURE__ */ jsx5(
903
+ Select,
904
+ {
905
+ "aria-label": "Rows per page",
906
+ className: "ml-auto w-auto",
907
+ value: pageSize,
908
+ onChange: (event) => onPageSizeChange(Number(event.target.value)),
909
+ children: pageSizeOptions.map((size) => /* @__PURE__ */ jsxs4("option", { value: size, children: [
910
+ size,
911
+ " / page"
912
+ ] }, size))
913
+ }
914
+ )
915
+ ]
916
+ }
917
+ );
918
+ }
919
+
920
+ // src/components/feedback.tsx
921
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
922
+ function Alert({
923
+ title,
924
+ children,
925
+ variant = "default",
926
+ className
927
+ }) {
928
+ const color = variant === "destructive" ? "border-[hsl(var(--rui-destructive))] bg-[hsl(var(--rui-destructive-background))] text-[hsl(var(--rui-destructive-text))]" : variant === "success" ? "border-[hsl(var(--rui-success))] bg-[hsl(var(--rui-success-background))] text-[hsl(var(--rui-success-foreground))]" : "border-[hsl(var(--rui-border))] bg-[hsl(var(--rui-muted))]";
929
+ return /* @__PURE__ */ jsxs5(
930
+ "div",
931
+ {
932
+ role: "alert",
933
+ className: cn(
934
+ "rounded-[var(--rui-radius)] border p-3 text-sm",
935
+ color,
936
+ className
937
+ ),
938
+ children: [
939
+ title && /* @__PURE__ */ jsx6("strong", { className: "block", children: title }),
940
+ children
941
+ ]
942
+ }
943
+ );
944
+ }
945
+ function Toast({
946
+ title,
947
+ description,
948
+ action,
949
+ className
950
+ }) {
951
+ return /* @__PURE__ */ jsxs5(
952
+ "div",
953
+ {
954
+ role: "status",
955
+ className: cn(
956
+ "flex max-w-sm items-start gap-4 rounded-[var(--rui-radius)] border border-[hsl(var(--rui-border))] bg-[hsl(var(--rui-card))] p-4 shadow-lg",
957
+ className
958
+ ),
959
+ children: [
960
+ /* @__PURE__ */ jsxs5("div", { className: "flex-1", children: [
961
+ /* @__PURE__ */ jsx6("strong", { className: "text-sm", children: title }),
962
+ description && /* @__PURE__ */ jsx6("p", { className: "mt-1 text-sm text-[hsl(var(--rui-muted-foreground))]", children: description })
963
+ ] }),
964
+ action
965
+ ]
966
+ }
967
+ );
968
+ }
969
+ var EmptyState = ({
970
+ title = "Nothing here yet",
971
+ description,
972
+ action,
973
+ className
974
+ }) => /* @__PURE__ */ jsxs5(
975
+ "div",
976
+ {
977
+ className: cn(
978
+ "rounded-[var(--rui-radius)] border border-dashed border-[hsl(var(--rui-border))] p-10 text-center",
979
+ className
980
+ ),
981
+ children: [
982
+ /* @__PURE__ */ jsx6("h3", { className: "font-semibold", children: title }),
983
+ description && /* @__PURE__ */ jsx6("p", { className: "mt-1 text-sm text-[hsl(var(--rui-muted-foreground))]", children: description }),
984
+ action && /* @__PURE__ */ jsx6("div", { className: "mt-4", children: action })
985
+ ]
986
+ }
987
+ );
988
+ var ErrorState = ({
989
+ title = "Something went wrong",
990
+ onRetry,
991
+ className
992
+ }) => /* @__PURE__ */ jsx6(Alert, { title, variant: "destructive", className, children: onRetry && /* @__PURE__ */ jsx6(Button, { variant: "outline", size: "sm", className: "mt-3", onClick: onRetry, children: "Try again" }) });
993
+ var LoadingState = ({
994
+ label = "Loading",
995
+ className
996
+ }) => /* @__PURE__ */ jsxs5(
997
+ "div",
998
+ {
999
+ role: "status",
1000
+ className: cn(
1001
+ "flex items-center gap-2 p-4 text-sm text-[hsl(var(--rui-muted-foreground))]",
1002
+ className
1003
+ ),
1004
+ children: [
1005
+ /* @__PURE__ */ jsx6("span", { className: "size-4 animate-spin rounded-full border-2 border-current border-r-transparent" }),
1006
+ label
1007
+ ]
1008
+ }
1009
+ );
1010
+
1011
+ // src/components/layout.tsx
1012
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1013
+ var PageShell = ({
1014
+ as: Component = "main",
1015
+ className,
1016
+ ...props
1017
+ }) => /* @__PURE__ */ jsx7(
1018
+ Component,
1019
+ {
1020
+ className: cn(
1021
+ "min-h-screen bg-[hsl(var(--rui-background))] text-[hsl(var(--rui-foreground))]",
1022
+ className
1023
+ ),
1024
+ ...props
1025
+ }
1026
+ );
1027
+ var PageHeader = ({
1028
+ as: Heading = "h1",
1029
+ title,
1030
+ description,
1031
+ actions,
1032
+ className
1033
+ }) => /* @__PURE__ */ jsxs6(
1034
+ "header",
1035
+ {
1036
+ className: cn(
1037
+ "flex flex-wrap items-start justify-between gap-4 border-b border-[hsl(var(--rui-border))] pb-5",
1038
+ className
1039
+ ),
1040
+ children: [
1041
+ /* @__PURE__ */ jsxs6("div", { children: [
1042
+ /* @__PURE__ */ jsx7(Heading, { className: "text-2xl font-bold tracking-tight", children: title }),
1043
+ description && /* @__PURE__ */ jsx7("p", { className: "mt-1 text-sm text-[hsl(var(--rui-muted-foreground))]", children: description })
1044
+ ] }),
1045
+ actions
1046
+ ]
1047
+ }
1048
+ );
1049
+ var PageContent = ({
1050
+ className,
1051
+ ...props
1052
+ }) => /* @__PURE__ */ jsx7("div", { className: cn("py-6", className), ...props });
1053
+ var Section = ({
1054
+ title,
1055
+ description,
1056
+ children,
1057
+ className
1058
+ }) => /* @__PURE__ */ jsxs6("section", { className: cn("space-y-4", className), children: [
1059
+ title && /* @__PURE__ */ jsxs6("div", { children: [
1060
+ /* @__PURE__ */ jsx7("h2", { className: "font-semibold", children: title }),
1061
+ description && /* @__PURE__ */ jsx7("p", { className: "mt-1 text-sm text-[hsl(var(--rui-muted-foreground))]", children: description })
1062
+ ] }),
1063
+ children
1064
+ ] });
1065
+ var Stack = ({
1066
+ gap = 4,
1067
+ className,
1068
+ ...props
1069
+ }) => /* @__PURE__ */ jsx7(
1070
+ "div",
1071
+ {
1072
+ className: cn(
1073
+ "flex flex-col",
1074
+ {
1075
+ 1: "gap-1",
1076
+ 2: "gap-2",
1077
+ 3: "gap-3",
1078
+ 4: "gap-4",
1079
+ 6: "gap-6",
1080
+ 8: "gap-8"
1081
+ }[gap],
1082
+ className
1083
+ ),
1084
+ ...props
1085
+ }
1086
+ );
1087
+ var Container = ({
1088
+ className,
1089
+ ...props
1090
+ }) => /* @__PURE__ */ jsx7(
1091
+ "div",
1092
+ {
1093
+ className: cn("mx-auto w-full max-w-6xl px-4 sm:px-6", className),
1094
+ ...props
1095
+ }
1096
+ );
1097
+
1098
+ // src/components/tables.tsx
1099
+ import {
1100
+ useMemo,
1101
+ useState as useState4
1102
+ } from "react";
1103
+ import { Fragment, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1104
+ var Table = ({
1105
+ className,
1106
+ ...props
1107
+ }) => /* @__PURE__ */ jsx8("div", { className: "w-full overflow-x-auto", children: /* @__PURE__ */ jsx8("table", { className: cn("w-full text-left text-sm", className), ...props }) });
1108
+ var TableHeader = ({
1109
+ className,
1110
+ ...props
1111
+ }) => /* @__PURE__ */ jsx8(
1112
+ "thead",
1113
+ {
1114
+ className: cn(
1115
+ "border-b border-[hsl(var(--rui-border))] text-xs uppercase tracking-wide text-[hsl(var(--rui-muted-foreground))]",
1116
+ className
1117
+ ),
1118
+ ...props
1119
+ }
1120
+ );
1121
+ var TableBody = ({
1122
+ className,
1123
+ ...props
1124
+ }) => /* @__PURE__ */ jsx8(
1125
+ "tbody",
1126
+ {
1127
+ className: cn("divide-y divide-[hsl(var(--rui-border))]", className),
1128
+ ...props
1129
+ }
1130
+ );
1131
+ var TableFooter = ({
1132
+ className,
1133
+ ...props
1134
+ }) => /* @__PURE__ */ jsx8(
1135
+ "tfoot",
1136
+ {
1137
+ className: cn("border-t border-[hsl(var(--rui-border))]", className),
1138
+ ...props
1139
+ }
1140
+ );
1141
+ var TableRow = ({
1142
+ className,
1143
+ ...props
1144
+ }) => /* @__PURE__ */ jsx8(
1145
+ "tr",
1146
+ {
1147
+ className: cn("transition hover:bg-[hsl(var(--rui-muted))]/60", className),
1148
+ ...props
1149
+ }
1150
+ );
1151
+ var TableHead = ({
1152
+ className,
1153
+ ...props
1154
+ }) => /* @__PURE__ */ jsx8(
1155
+ "th",
1156
+ {
1157
+ scope: "col",
1158
+ className: cn("px-4 py-3 font-medium", className),
1159
+ ...props
1160
+ }
1161
+ );
1162
+ var TableCell = ({
1163
+ className,
1164
+ ...props
1165
+ }) => /* @__PURE__ */ jsx8("td", { className: cn("px-4 py-3", className), ...props });
1166
+ var TableCaption = ({
1167
+ className,
1168
+ ...props
1169
+ }) => /* @__PURE__ */ jsx8(
1170
+ "caption",
1171
+ {
1172
+ className: cn(
1173
+ "mt-3 text-left text-sm text-[hsl(var(--rui-muted-foreground))]",
1174
+ className
1175
+ ),
1176
+ ...props
1177
+ }
1178
+ );
1179
+ function DataTable({
1180
+ data,
1181
+ columns,
1182
+ loading,
1183
+ error,
1184
+ pagination,
1185
+ onPageChange,
1186
+ onRowClick,
1187
+ emptyMessage = "No results found.",
1188
+ className
1189
+ }) {
1190
+ const [sort, setSort] = useState4(null);
1191
+ const rows = useMemo(() => {
1192
+ if (!sort) return data;
1193
+ return [...data].sort((a, b) => {
1194
+ const result = String(a[sort.key] ?? "").localeCompare(
1195
+ String(b[sort.key] ?? ""),
1196
+ void 0,
1197
+ { numeric: true }
1198
+ );
1199
+ return sort.direction === "asc" ? result : -result;
1200
+ });
1201
+ }, [data, sort]);
1202
+ const toggle = (column) => {
1203
+ if (!column.sortable) return;
1204
+ const key = String(column.key);
1205
+ setSort(
1206
+ (current) => current?.key === key && current.direction === "asc" ? { key, direction: "desc" } : { key, direction: "asc" }
1207
+ );
1208
+ };
1209
+ if (error) return /* @__PURE__ */ jsx8(ErrorState, { title: error });
1210
+ return /* @__PURE__ */ jsxs7("div", { className, "aria-busy": loading, children: [
1211
+ loading && /* @__PURE__ */ jsx8(LoadingState, { label: "Updating table\u2026" }),
1212
+ !loading && !rows.length ? /* @__PURE__ */ jsx8(EmptyState, { title: emptyMessage }) : /* @__PURE__ */ jsxs7(Fragment, { children: [
1213
+ /* @__PURE__ */ jsxs7(Table, { children: [
1214
+ /* @__PURE__ */ jsx8(TableHeader, { children: /* @__PURE__ */ jsx8(TableRow, { children: columns.map((column) => /* @__PURE__ */ jsx8(
1215
+ TableHead,
1216
+ {
1217
+ className: column.className,
1218
+ children: column.sortable ? /* @__PURE__ */ jsxs7(
1219
+ "button",
1220
+ {
1221
+ className: "rui-focus inline-flex items-center gap-1",
1222
+ onClick: () => toggle(column),
1223
+ children: [
1224
+ column.header,
1225
+ /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: sort?.key === String(column.key) ? sort.direction === "asc" ? "\u2191" : "\u2193" : "\u2195" })
1226
+ ]
1227
+ }
1228
+ ) : column.header
1229
+ },
1230
+ String(column.key)
1231
+ )) }) }),
1232
+ /* @__PURE__ */ jsx8(TableBody, { children: rows.map((row, index) => /* @__PURE__ */ jsx8(
1233
+ TableRow,
1234
+ {
1235
+ tabIndex: onRowClick ? 0 : void 0,
1236
+ onClick: () => onRowClick?.(row),
1237
+ onKeyDown: (event) => {
1238
+ if (onRowClick && (event.key === "Enter" || event.key === " "))
1239
+ onRowClick(row);
1240
+ },
1241
+ className: onRowClick ? "cursor-pointer" : void 0,
1242
+ children: columns.map((column) => /* @__PURE__ */ jsx8(
1243
+ TableCell,
1244
+ {
1245
+ className: column.className,
1246
+ children: column.cell ? column.cell(row) : String(row[column.key] ?? "")
1247
+ },
1248
+ String(column.key)
1249
+ ))
1250
+ },
1251
+ String(row.id ?? index)
1252
+ )) })
1253
+ ] }),
1254
+ pagination && onPageChange && /* @__PURE__ */ jsx8(
1255
+ Pagination,
1256
+ {
1257
+ className: "mt-4",
1258
+ ...pagination,
1259
+ ariaLabel: "Table pagination",
1260
+ onPageChange
1261
+ }
1262
+ )
1263
+ ] })
1264
+ ] });
1265
+ }
1266
+ function SearchableTable({
1267
+ data,
1268
+ columns,
1269
+ searchKeys,
1270
+ ...props
1271
+ }) {
1272
+ const [query, setQuery] = useState4("");
1273
+ const filtered = data.filter(
1274
+ (row) => searchKeys.some(
1275
+ (key) => String(row[key] ?? "").toLowerCase().includes(query.toLowerCase())
1276
+ )
1277
+ );
1278
+ return /* @__PURE__ */ jsxs7("div", { className: "space-y-3", children: [
1279
+ /* @__PURE__ */ jsx8(
1280
+ Input,
1281
+ {
1282
+ "aria-label": "Search table",
1283
+ placeholder: "Search\u2026",
1284
+ value: query,
1285
+ onChange: (event) => setQuery(event.target.value)
1286
+ }
1287
+ ),
1288
+ /* @__PURE__ */ jsx8(DataTable, { data: filtered, columns, ...props })
1289
+ ] });
1290
+ }
1291
+ var PaginatedTable = DataTable;
1292
+
1293
+ // src/components/utilities.tsx
1294
+ import { createPortal as createPortal2 } from "react-dom";
1295
+ import {
1296
+ useEffect as useEffect3,
1297
+ useId as useId3,
1298
+ useState as useState5
1299
+ } from "react";
1300
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1301
+ function Command({
1302
+ open,
1303
+ onOpenChange,
1304
+ items
1305
+ }) {
1306
+ const [query, setQuery] = useState5("");
1307
+ useEffect3(() => {
1308
+ const listener = (event) => {
1309
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
1310
+ event.preventDefault();
1311
+ onOpenChange(!open);
1312
+ }
1313
+ };
1314
+ window.addEventListener("keydown", listener);
1315
+ return () => window.removeEventListener("keydown", listener);
1316
+ }, [open, onOpenChange]);
1317
+ const matches = items.filter(
1318
+ (item) => `${item.label} ${item.keywords?.join(" ") ?? ""}`.toLowerCase().includes(query.toLowerCase())
1319
+ );
1320
+ return /* @__PURE__ */ jsx9(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs8(DialogContent, { className: "max-w-md p-3", children: [
1321
+ /* @__PURE__ */ jsx9(
1322
+ Input,
1323
+ {
1324
+ autoFocus: true,
1325
+ placeholder: "Type a command\u2026",
1326
+ value: query,
1327
+ onChange: (event) => setQuery(event.target.value)
1328
+ }
1329
+ ),
1330
+ " ",
1331
+ /* @__PURE__ */ jsx9("div", { className: "mt-2", children: matches.map((item) => /* @__PURE__ */ jsx9(
1332
+ "button",
1333
+ {
1334
+ className: "rui-focus block w-full rounded p-2 text-left text-sm hover:bg-[hsl(var(--rui-accent))]",
1335
+ onClick: () => {
1336
+ item.onSelect();
1337
+ onOpenChange(false);
1338
+ },
1339
+ children: item.label
1340
+ },
1341
+ item.label
1342
+ )) })
1343
+ ] }) });
1344
+ }
1345
+ function ContextMenu({
1346
+ children,
1347
+ menu
1348
+ }) {
1349
+ const [point, setPoint] = useState5(null);
1350
+ useEffect3(() => {
1351
+ if (!point) return;
1352
+ const close = (event) => {
1353
+ if (event.key === "Escape") setPoint(null);
1354
+ };
1355
+ document.addEventListener("keydown", close);
1356
+ return () => document.removeEventListener("keydown", close);
1357
+ }, [point]);
1358
+ return /* @__PURE__ */ jsxs8(
1359
+ "div",
1360
+ {
1361
+ onContextMenu: (event) => {
1362
+ event.preventDefault();
1363
+ setPoint({
1364
+ x: Math.min(event.clientX, window.innerWidth - 180),
1365
+ y: Math.min(event.clientY, window.innerHeight - 120)
1366
+ });
1367
+ },
1368
+ children: [
1369
+ children,
1370
+ point && createPortal2(
1371
+ /* @__PURE__ */ jsxs8(Fragment2, { children: [
1372
+ /* @__PURE__ */ jsx9(
1373
+ "button",
1374
+ {
1375
+ className: "fixed inset-0 z-40 cursor-default",
1376
+ "aria-label": "Close context menu",
1377
+ onClick: () => setPoint(null)
1378
+ }
1379
+ ),
1380
+ /* @__PURE__ */ jsx9(
1381
+ "div",
1382
+ {
1383
+ role: "menu",
1384
+ className: "fixed z-50 rounded border border-[hsl(var(--rui-border))] bg-[hsl(var(--rui-card))] p-1 shadow-lg",
1385
+ style: { left: point.x, top: point.y },
1386
+ children: menu
1387
+ }
1388
+ )
1389
+ ] }),
1390
+ document.body
1391
+ )
1392
+ ]
1393
+ }
1394
+ );
1395
+ }
1396
+ function CopyButton({
1397
+ value,
1398
+ children = "Copy",
1399
+ onCopy
1400
+ }) {
1401
+ const [copied, setCopied] = useState5(false);
1402
+ return /* @__PURE__ */ jsx9(
1403
+ Button,
1404
+ {
1405
+ variant: "outline",
1406
+ size: "sm",
1407
+ onClick: async () => {
1408
+ await navigator.clipboard.writeText(value);
1409
+ setCopied(true);
1410
+ onCopy?.();
1411
+ window.setTimeout(() => setCopied(false), 1500);
1412
+ },
1413
+ children: copied ? "Copied" : children
1414
+ }
1415
+ );
1416
+ }
1417
+ function PasswordInput({
1418
+ className,
1419
+ ...props
1420
+ }) {
1421
+ const [visible, setVisible] = useState5(false);
1422
+ return /* @__PURE__ */ jsxs8("div", { className: "relative", children: [
1423
+ /* @__PURE__ */ jsx9(
1424
+ Input,
1425
+ {
1426
+ type: visible ? "text" : "password",
1427
+ className: cn("pr-16", className),
1428
+ ...props
1429
+ }
1430
+ ),
1431
+ /* @__PURE__ */ jsx9(
1432
+ "button",
1433
+ {
1434
+ type: "button",
1435
+ className: "rui-focus absolute right-2 top-2 text-xs text-[hsl(var(--rui-muted-foreground))]",
1436
+ onClick: () => setVisible(!visible),
1437
+ children: visible ? "Hide" : "Show"
1438
+ }
1439
+ )
1440
+ ] });
1441
+ }
1442
+ function FileUpload({
1443
+ accept,
1444
+ multiple,
1445
+ onFiles,
1446
+ label = "Choose files"
1447
+ }) {
1448
+ const id = useId3();
1449
+ return /* @__PURE__ */ jsxs8("div", { children: [
1450
+ /* @__PURE__ */ jsx9(
1451
+ "input",
1452
+ {
1453
+ id,
1454
+ className: "sr-only",
1455
+ type: "file",
1456
+ accept,
1457
+ multiple,
1458
+ onChange: (event) => onFiles(Array.from(event.target.files ?? []))
1459
+ }
1460
+ ),
1461
+ /* @__PURE__ */ jsx9(
1462
+ Label,
1463
+ {
1464
+ htmlFor: id,
1465
+ className: "cursor-pointer rounded-[var(--rui-radius)] border border-dashed border-[hsl(var(--rui-border))] p-6 text-center text-sm text-[hsl(var(--rui-muted-foreground))]",
1466
+ children: label
1467
+ }
1468
+ )
1469
+ ] });
1470
+ }
1471
+ export {
1472
+ Accordion,
1473
+ Alert,
1474
+ AlertDialog,
1475
+ AlertDialogClose,
1476
+ AlertDialogContent,
1477
+ AlertDialogDescription,
1478
+ AlertDialogFooter,
1479
+ AlertDialogHeader,
1480
+ AlertDialogTitle,
1481
+ AlertDialogTrigger,
1482
+ Avatar,
1483
+ Badge,
1484
+ Breadcrumb,
1485
+ Button,
1486
+ Card,
1487
+ CardDescription,
1488
+ CardHeader,
1489
+ CardTitle,
1490
+ Checkbox,
1491
+ Command,
1492
+ Container,
1493
+ ContextMenu,
1494
+ CopyButton,
1495
+ DataTable,
1496
+ Dialog,
1497
+ DialogBody,
1498
+ DialogClose,
1499
+ DialogContent,
1500
+ DialogDescription,
1501
+ DialogFooter,
1502
+ DialogHeader,
1503
+ DialogTitle,
1504
+ DialogTrigger,
1505
+ Drawer,
1506
+ DrawerContent,
1507
+ DrawerTrigger,
1508
+ DropdownMenu,
1509
+ DropdownMenuItem,
1510
+ EmptyState,
1511
+ ErrorState,
1512
+ FileUpload,
1513
+ IconButton,
1514
+ Input,
1515
+ Label,
1516
+ LoadingState,
1517
+ MobileNav,
1518
+ Navbar,
1519
+ PageContent,
1520
+ PageHeader,
1521
+ PageShell,
1522
+ PaginatedTable,
1523
+ Pagination,
1524
+ PasswordInput,
1525
+ Popover,
1526
+ Progress,
1527
+ Radio,
1528
+ SearchableTable,
1529
+ Section,
1530
+ Select,
1531
+ Separator,
1532
+ Sheet,
1533
+ SheetClose,
1534
+ SheetContent,
1535
+ SheetTrigger,
1536
+ Sidebar,
1537
+ Skeleton,
1538
+ Stack,
1539
+ Switch,
1540
+ Table,
1541
+ TableBody,
1542
+ TableCaption,
1543
+ TableCell,
1544
+ TableFooter,
1545
+ TableHead,
1546
+ TableHeader,
1547
+ TableRow,
1548
+ Tabs,
1549
+ Textarea,
1550
+ Toast,
1551
+ Tooltip,
1552
+ cn
1553
+ };
1554
+ //# sourceMappingURL=index.js.map