@kjaniec-dev/ui 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
- import * as React16 from 'react';
3
+ import * as React17 from 'react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
6
 
7
+ // src/lib/cn.ts
7
8
  function cn(...inputs) {
8
9
  return twMerge(clsx(inputs));
9
10
  }
@@ -37,7 +38,7 @@ var buttonVariants = cva(
37
38
  defaultVariants: { variant: "primary", size: "md" }
38
39
  }
39
40
  );
40
- var Button = React16.forwardRef(
41
+ var Button = React17.forwardRef(
41
42
  ({ className, variant, size, loading, leadingIcon, trailingIcon, children, disabled, ...props }, ref) => {
42
43
  return /* @__PURE__ */ jsxs(
43
44
  "button",
@@ -84,7 +85,7 @@ var badgeVariants = cva(
84
85
  defaultVariants: { variant: "neutral" }
85
86
  }
86
87
  );
87
- var Badge = React16.forwardRef(
88
+ var Badge = React17.forwardRef(
88
89
  ({ className, variant, dot, children, ...props }, ref) => /* @__PURE__ */ jsxs("span", { ref, className: cn(badgeVariants({ variant }), className), ...props, children: [
89
90
  dot && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-current", "aria-hidden": true }),
90
91
  children
@@ -111,7 +112,7 @@ var iconColor = {
111
112
  warning: "text-warning",
112
113
  danger: "text-danger"
113
114
  };
114
- var Alert = React16.forwardRef(
115
+ var Alert = React17.forwardRef(
115
116
  ({ className, variant = "info", icon, title, children, ...props }, ref) => /* @__PURE__ */ jsxs("div", { ref, role: "alert", className: cn(alertVariants({ variant }), className), ...props, children: [
116
117
  icon && /* @__PURE__ */ jsx("span", { className: cn("shrink-0 mt-0.5 [&_svg]:h-[1.15rem] [&_svg]:w-[1.15rem]", iconColor[variant ?? "info"]), children: icon }),
117
118
  /* @__PURE__ */ jsxs("div", { className: "text-foreground", children: [
@@ -121,7 +122,7 @@ var Alert = React16.forwardRef(
121
122
  ] })
122
123
  );
123
124
  Alert.displayName = "Alert";
124
- var Spinner = React16.forwardRef(
125
+ var Spinner = React17.forwardRef(
125
126
  ({ className, size = 24, style, ...props }, ref) => /* @__PURE__ */ jsx(
126
127
  "span",
127
128
  {
@@ -138,7 +139,7 @@ var Spinner = React16.forwardRef(
138
139
  )
139
140
  );
140
141
  Spinner.displayName = "Spinner";
141
- var Progress = React16.forwardRef(
142
+ var Progress = React17.forwardRef(
142
143
  ({ className, value = 0, tone = "primary", barClassName, ...props }, ref) => {
143
144
  const pct = Math.max(0, Math.min(100, value));
144
145
  return /* @__PURE__ */ jsx(
@@ -146,6 +147,7 @@ var Progress = React16.forwardRef(
146
147
  {
147
148
  ref,
148
149
  role: "progressbar",
150
+ "aria-label": props["aria-label"] ?? "Progress",
149
151
  "aria-valuenow": pct,
150
152
  "aria-valuemin": 0,
151
153
  "aria-valuemax": 100,
@@ -168,7 +170,7 @@ var Progress = React16.forwardRef(
168
170
  );
169
171
  Progress.displayName = "Progress";
170
172
  var baseField = "w-full font-sans text-sm text-foreground bg-surface border rounded-kj-md px-[0.85rem] transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground focus:outline-none focus:border-ring focus:ring-[3px] focus:ring-ring/30 disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed";
171
- var Input = React16.forwardRef(
173
+ var Input = React17.forwardRef(
172
174
  ({ className, error, leadingIcon, style, ...props }, ref) => {
173
175
  const field = /* @__PURE__ */ jsx(
174
176
  "input",
@@ -193,7 +195,7 @@ var Input = React16.forwardRef(
193
195
  }
194
196
  );
195
197
  Input.displayName = "Input";
196
- var Textarea = React16.forwardRef(
198
+ var Textarea = React17.forwardRef(
197
199
  ({ className, error, ...props }, ref) => /* @__PURE__ */ jsx(
198
200
  "textarea",
199
201
  {
@@ -210,14 +212,14 @@ var Textarea = React16.forwardRef(
210
212
  )
211
213
  );
212
214
  Textarea.displayName = "Textarea";
213
- var Label = React16.forwardRef(
215
+ var Label = React17.forwardRef(
214
216
  ({ className, required, children, ...props }, ref) => /* @__PURE__ */ jsxs("label", { ref, className: cn("text-[0.8rem] font-semibold text-foreground", className), ...props, children: [
215
217
  children,
216
218
  required && /* @__PURE__ */ jsx("span", { className: "text-danger", children: " *" })
217
219
  ] })
218
220
  );
219
221
  Label.displayName = "Label";
220
- var Hint = React16.forwardRef(
222
+ var Hint = React17.forwardRef(
221
223
  ({ className, error, ...props }, ref) => /* @__PURE__ */ jsx(
222
224
  "span",
223
225
  {
@@ -228,12 +230,12 @@ var Hint = React16.forwardRef(
228
230
  )
229
231
  );
230
232
  Hint.displayName = "Hint";
231
- var Field = React16.forwardRef(
233
+ var Field = React17.forwardRef(
232
234
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col gap-1.5", className), ...props })
233
235
  );
234
236
  Field.displayName = "Field";
235
237
  var chevron = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E";
236
- var Select = React16.forwardRef(
238
+ var Select = React17.forwardRef(
237
239
  ({ className, error, style, children, ...props }, ref) => /* @__PURE__ */ jsx(
238
240
  "select",
239
241
  {
@@ -261,9 +263,9 @@ var Select = React16.forwardRef(
261
263
  )
262
264
  );
263
265
  Select.displayName = "Select";
264
- var Checkbox = React16.forwardRef(
266
+ var Checkbox = React17.forwardRef(
265
267
  ({ className, label, id, ...props }, ref) => {
266
- const autoId = React16.useId();
268
+ const autoId = React17.useId();
267
269
  const inputId = id ?? autoId;
268
270
  return /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: cn("inline-flex items-center gap-2.5 cursor-pointer text-sm select-none", className), children: [
269
271
  /* @__PURE__ */ jsx("input", { ref, id: inputId, type: "checkbox", className: "peer sr-only", ...props }),
@@ -273,9 +275,9 @@ var Checkbox = React16.forwardRef(
273
275
  }
274
276
  );
275
277
  Checkbox.displayName = "Checkbox";
276
- var Radio = React16.forwardRef(
278
+ var Radio = React17.forwardRef(
277
279
  ({ className, label, id, ...props }, ref) => {
278
- const autoId = React16.useId();
280
+ const autoId = React17.useId();
279
281
  const inputId = id ?? autoId;
280
282
  return /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: cn("inline-flex items-center gap-2.5 cursor-pointer text-sm select-none", className), children: [
281
283
  /* @__PURE__ */ jsx("input", { ref, id: inputId, type: "radio", className: "peer sr-only", ...props }),
@@ -285,9 +287,9 @@ var Radio = React16.forwardRef(
285
287
  }
286
288
  );
287
289
  Radio.displayName = "Radio";
288
- var Switch = React16.forwardRef(
290
+ var Switch = React17.forwardRef(
289
291
  ({ className, label, id, ...props }, ref) => {
290
- const autoId = React16.useId();
292
+ const autoId = React17.useId();
291
293
  const inputId = id ?? autoId;
292
294
  return /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: cn("inline-flex items-center gap-2.5 cursor-pointer text-sm select-none", className), children: [
293
295
  /* @__PURE__ */ jsx("input", { ref, id: inputId, type: "checkbox", role: "switch", className: "peer sr-only", ...props }),
@@ -297,7 +299,7 @@ var Switch = React16.forwardRef(
297
299
  }
298
300
  );
299
301
  Switch.displayName = "Switch";
300
- var Slider = React16.forwardRef(
302
+ var Slider = React17.forwardRef(
301
303
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
302
304
  "input",
303
305
  {
@@ -350,7 +352,7 @@ function Segmented({
350
352
  }
351
353
  );
352
354
  }
353
- var Card = React16.forwardRef(
355
+ var Card = React17.forwardRef(
354
356
  ({ as: Tag = "div", className, elevated, interactive, ...props }, ref) => /* @__PURE__ */ jsx(
355
357
  Tag,
356
358
  {
@@ -366,23 +368,23 @@ var Card = React16.forwardRef(
366
368
  )
367
369
  );
368
370
  Card.displayName = "Card";
369
- var CardHeader = React16.forwardRef(
371
+ var CardHeader = React17.forwardRef(
370
372
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("p-[1.35rem] flex flex-col gap-1", className), ...props })
371
373
  );
372
374
  CardHeader.displayName = "CardHeader";
373
- var CardTitle = React16.forwardRef(
375
+ var CardTitle = React17.forwardRef(
374
376
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("h3", { ref, className: cn("m-0 text-base font-bold tracking-[-0.01em]", className), ...props })
375
377
  );
376
378
  CardTitle.displayName = "CardTitle";
377
- var CardDescription = React16.forwardRef(
379
+ var CardDescription = React17.forwardRef(
378
380
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("p", { ref, className: cn("m-0 text-[0.85rem] text-muted-foreground", className), ...props })
379
381
  );
380
382
  CardDescription.displayName = "CardDescription";
381
- var CardContent = React16.forwardRef(
383
+ var CardContent = React17.forwardRef(
382
384
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("px-[1.35rem] pb-[1.35rem]", className), ...props })
383
385
  );
384
386
  CardContent.displayName = "CardContent";
385
- var CardFooter = React16.forwardRef(
387
+ var CardFooter = React17.forwardRef(
386
388
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
387
389
  "div",
388
390
  {
@@ -393,7 +395,7 @@ var CardFooter = React16.forwardRef(
393
395
  )
394
396
  );
395
397
  CardFooter.displayName = "CardFooter";
396
- var Stat = React16.forwardRef(
398
+ var Stat = React17.forwardRef(
397
399
  ({ className, label, value, delta, trend, ...props }, ref) => /* @__PURE__ */ jsxs(Card, { ref, className: cn("p-[1.35rem] flex flex-col gap-2", className), ...props, children: [
398
400
  /* @__PURE__ */ jsx("span", { className: "text-[0.78rem] font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: label }),
399
401
  /* @__PURE__ */ jsx("span", { className: "text-[2rem] font-bold tracking-[-0.03em] tabular-nums leading-none", children: value }),
@@ -433,7 +435,7 @@ var avatarVariants = cva(
433
435
  defaultVariants: { size: "md", tone: "secondary" }
434
436
  }
435
437
  );
436
- var Avatar = React16.forwardRef(
438
+ var Avatar = React17.forwardRef(
437
439
  ({ className, size, tone, src, alt, status, children, ...props }, ref) => {
438
440
  const node = /* @__PURE__ */ jsx("span", { ref, className: cn(avatarVariants({ size, tone }), "overflow-hidden", className), ...props, children: src ? /* @__PURE__ */ jsx("img", { src, alt, className: "h-full w-full object-cover" }) : children });
439
441
  if (!status) return node;
@@ -444,20 +446,20 @@ var Avatar = React16.forwardRef(
444
446
  }
445
447
  );
446
448
  Avatar.displayName = "Avatar";
447
- var AvatarGroup = React16.forwardRef(
449
+ var AvatarGroup = React17.forwardRef(
448
450
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex [&>*:not(:first-child)]:-ml-2.5", className), ...props })
449
451
  );
450
452
  AvatarGroup.displayName = "AvatarGroup";
451
- var TabsContext = React16.createContext(null);
453
+ var TabsContext = React17.createContext(null);
452
454
  function useTabs() {
453
- const ctx = React16.useContext(TabsContext);
455
+ const ctx = React17.useContext(TabsContext);
454
456
  if (!ctx) throw new Error("Tabs.* must be used within <Tabs>");
455
457
  return ctx;
456
458
  }
457
459
  function Tabs({ value, defaultValue, onValueChange, className, children, ...props }) {
458
- const [internal, setInternal] = React16.useState(defaultValue ?? "");
460
+ const [internal, setInternal] = React17.useState(defaultValue ?? "");
459
461
  const current = value ?? internal;
460
- const setValue = React16.useCallback(
462
+ const setValue = React17.useCallback(
461
463
  (v) => {
462
464
  if (value === void 0) setInternal(v);
463
465
  onValueChange?.(v);
@@ -466,11 +468,51 @@ function Tabs({ value, defaultValue, onValueChange, className, children, ...prop
466
468
  );
467
469
  return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { value: current, setValue }, children: /* @__PURE__ */ jsx("div", { className, ...props, children }) });
468
470
  }
469
- var TabsList = React16.forwardRef(
470
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, role: "tablist", className: cn("flex gap-1 border-b border-border", className), ...props })
471
+ var TabsList = React17.forwardRef(
472
+ ({ className, onKeyDown, ...props }, ref) => {
473
+ const handleKeyDown = (e) => {
474
+ const container = e.currentTarget;
475
+ const tabs = Array.from(container.querySelectorAll('[role="tab"]'));
476
+ if (tabs.length === 0) return;
477
+ const activeIndex = tabs.findIndex((tab) => tab.getAttribute("aria-selected") === "true");
478
+ let newIndex = activeIndex;
479
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
480
+ e.preventDefault();
481
+ newIndex = (activeIndex + 1) % tabs.length;
482
+ tabs[newIndex].focus();
483
+ tabs[newIndex].click();
484
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
485
+ e.preventDefault();
486
+ newIndex = (activeIndex - 1 + tabs.length) % tabs.length;
487
+ tabs[newIndex].focus();
488
+ tabs[newIndex].click();
489
+ } else if (e.key === "Home") {
490
+ e.preventDefault();
491
+ newIndex = 0;
492
+ tabs[newIndex].focus();
493
+ tabs[newIndex].click();
494
+ } else if (e.key === "End") {
495
+ e.preventDefault();
496
+ newIndex = tabs.length - 1;
497
+ tabs[newIndex].focus();
498
+ tabs[newIndex].click();
499
+ }
500
+ onKeyDown?.(e);
501
+ };
502
+ return /* @__PURE__ */ jsx(
503
+ "div",
504
+ {
505
+ ref,
506
+ role: "tablist",
507
+ onKeyDown: handleKeyDown,
508
+ className: cn("flex gap-1 border-b border-border", className),
509
+ ...props
510
+ }
511
+ );
512
+ }
471
513
  );
472
514
  TabsList.displayName = "TabsList";
473
- var TabsTrigger = React16.forwardRef(
515
+ var TabsTrigger = React17.forwardRef(
474
516
  ({ className, value, children, ...props }, ref) => {
475
517
  const { value: current, setValue } = useTabs();
476
518
  const active = current === value;
@@ -480,10 +522,14 @@ var TabsTrigger = React16.forwardRef(
480
522
  ref,
481
523
  type: "button",
482
524
  role: "tab",
525
+ id: `tabs-trigger-${value}`,
526
+ "aria-controls": `tabs-panel-${value}`,
483
527
  "aria-selected": active,
528
+ tabIndex: active ? 0 : -1,
484
529
  onClick: () => setValue(value),
485
530
  className: cn(
486
- "relative px-[0.9rem] py-2.5 text-sm font-semibold cursor-pointer transition-colors duration-150",
531
+ "relative px-[0.9rem] py-2.5 text-sm font-semibold cursor-pointer transition-colors duration-150 outline-none",
532
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:rounded-sm",
487
533
  "after:content-[''] after:absolute after:left-[0.9rem] after:right-[0.9rem] after:-bottom-px after:h-0.5 after:rounded-sm after:transition-colors",
488
534
  active ? "text-foreground after:bg-primary" : "text-muted-foreground hover:text-foreground after:bg-transparent",
489
535
  className
@@ -495,18 +541,30 @@ var TabsTrigger = React16.forwardRef(
495
541
  }
496
542
  );
497
543
  TabsTrigger.displayName = "TabsTrigger";
498
- var TabsContent = React16.forwardRef(
544
+ var TabsContent = React17.forwardRef(
499
545
  ({ className, value, ...props }, ref) => {
500
546
  const { value: current } = useTabs();
501
- if (current !== value) return null;
502
- return /* @__PURE__ */ jsx("div", { ref, role: "tabpanel", className: cn("pt-5 text-[0.9rem] text-muted-foreground", className), ...props });
547
+ const active = current === value;
548
+ if (!active) return null;
549
+ return /* @__PURE__ */ jsx(
550
+ "div",
551
+ {
552
+ ref,
553
+ role: "tabpanel",
554
+ id: `tabs-panel-${value}`,
555
+ "aria-labelledby": `tabs-trigger-${value}`,
556
+ tabIndex: 0,
557
+ className: cn("pt-5 text-[0.9rem] text-muted-foreground outline-none", className),
558
+ ...props
559
+ }
560
+ );
503
561
  }
504
562
  );
505
563
  TabsContent.displayName = "TabsContent";
506
- var AccordionContext = React16.createContext(null);
564
+ var AccordionContext = React17.createContext(null);
507
565
  function Accordion({ type = "single", defaultValue = [], className, children, ...props }) {
508
- const [open, setOpen] = React16.useState(defaultValue);
509
- const toggle = React16.useCallback(
566
+ const [open, setOpen] = React17.useState(defaultValue);
567
+ const toggle = React17.useCallback(
510
568
  (v) => {
511
569
  setOpen((prev) => {
512
570
  const isOpen = prev.includes(v);
@@ -518,15 +576,15 @@ function Accordion({ type = "single", defaultValue = [], className, children, ..
518
576
  );
519
577
  return /* @__PURE__ */ jsx(AccordionContext.Provider, { value: { open, toggle }, children: /* @__PURE__ */ jsx("div", { className: cn("border border-border rounded-kj-lg overflow-hidden", className), ...props, children }) });
520
578
  }
521
- var ItemContext = React16.createContext("");
522
- var AccordionItem = React16.forwardRef(
579
+ var ItemContext = React17.createContext("");
580
+ var AccordionItem = React17.forwardRef(
523
581
  ({ className, value, ...props }, ref) => /* @__PURE__ */ jsx(ItemContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { ref, className: cn("border-t border-border first:border-t-0", className), ...props }) })
524
582
  );
525
583
  AccordionItem.displayName = "AccordionItem";
526
- var AccordionTrigger = React16.forwardRef(
584
+ var AccordionTrigger = React17.forwardRef(
527
585
  ({ className, children, ...props }, ref) => {
528
- const ctx = React16.useContext(AccordionContext);
529
- const value = React16.useContext(ItemContext);
586
+ const ctx = React17.useContext(AccordionContext);
587
+ const value = React17.useContext(ItemContext);
530
588
  const isOpen = ctx.open.includes(value);
531
589
  return /* @__PURE__ */ jsxs(
532
590
  "button",
@@ -563,10 +621,10 @@ var AccordionTrigger = React16.forwardRef(
563
621
  }
564
622
  );
565
623
  AccordionTrigger.displayName = "AccordionTrigger";
566
- var AccordionContent = React16.forwardRef(
624
+ var AccordionContent = React17.forwardRef(
567
625
  ({ className, children, ...props }, ref) => {
568
- const ctx = React16.useContext(AccordionContext);
569
- const value = React16.useContext(ItemContext);
626
+ const ctx = React17.useContext(AccordionContext);
627
+ const value = React17.useContext(ItemContext);
570
628
  const isOpen = ctx.open.includes(value);
571
629
  return /* @__PURE__ */ jsx(
572
630
  "div",
@@ -580,11 +638,11 @@ var AccordionContent = React16.forwardRef(
580
638
  }
581
639
  );
582
640
  AccordionContent.displayName = "AccordionContent";
583
- var MenuContext = React16.createContext(null);
641
+ var MenuContext = React17.createContext(null);
584
642
  function DropdownMenu({ children, className }) {
585
- const [open, setOpen] = React16.useState(false);
586
- const ref = React16.useRef(null);
587
- React16.useEffect(() => {
643
+ const [open, setOpen] = React17.useState(false);
644
+ const ref = React17.useRef(null);
645
+ React17.useEffect(() => {
588
646
  if (!open) return;
589
647
  const onDoc = (e) => {
590
648
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -599,8 +657,23 @@ function DropdownMenu({ children, className }) {
599
657
  }, [open]);
600
658
  return /* @__PURE__ */ jsx(MenuContext.Provider, { value: { open, setOpen }, children: /* @__PURE__ */ jsx("div", { ref, className: cn("relative inline-block", className), children }) });
601
659
  }
602
- var DropdownMenuTrigger = React16.forwardRef(({ onClick, ...props }, ref) => {
603
- const ctx = React16.useContext(MenuContext);
660
+ var DropdownMenuTrigger = React17.forwardRef(({ onClick, asChild, ...props }, ref) => {
661
+ const ctx = React17.useContext(MenuContext);
662
+ if (asChild) {
663
+ const child = React17.Children.only(props.children);
664
+ return React17.cloneElement(child, {
665
+ ref,
666
+ ...props,
667
+ ...child.props,
668
+ onClick: (e) => {
669
+ ctx.setOpen(!ctx.open);
670
+ onClick?.(e);
671
+ child.props.onClick?.(e);
672
+ },
673
+ "aria-haspopup": "menu",
674
+ "aria-expanded": ctx.open
675
+ });
676
+ }
604
677
  return /* @__PURE__ */ jsx(
605
678
  "button",
606
679
  {
@@ -618,14 +691,46 @@ var DropdownMenuTrigger = React16.forwardRef(({ onClick, ...props }, ref) => {
618
691
  });
619
692
  DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
620
693
  function DropdownMenuContent({ className, align = "start", children, ...props }) {
621
- const ctx = React16.useContext(MenuContext);
694
+ const ctx = React17.useContext(MenuContext);
695
+ const ref = React17.useRef(null);
696
+ React17.useEffect(() => {
697
+ if (!ctx.open) return;
698
+ const container = ref.current;
699
+ if (container) {
700
+ const items = Array.from(container.querySelectorAll('[role="menuitem"]'));
701
+ if (items.length > 0) {
702
+ setTimeout(() => items[0].focus(), 50);
703
+ }
704
+ }
705
+ }, [ctx.open]);
706
+ const handleKeyDown = (e) => {
707
+ const container = ref.current;
708
+ if (!container) return;
709
+ const items = Array.from(container.querySelectorAll('[role="menuitem"]'));
710
+ if (items.length === 0) return;
711
+ const currentIndex = items.indexOf(document.activeElement);
712
+ if (e.key === "ArrowDown") {
713
+ e.preventDefault();
714
+ const nextIndex = (currentIndex + 1) % items.length;
715
+ items[nextIndex].focus();
716
+ } else if (e.key === "ArrowUp") {
717
+ e.preventDefault();
718
+ const prevIndex = (currentIndex - 1 + items.length) % items.length;
719
+ items[prevIndex].focus();
720
+ } else if (e.key === "Tab" || e.key === "Escape") {
721
+ ctx.setOpen(false);
722
+ }
723
+ };
622
724
  if (!ctx.open) return null;
623
725
  return /* @__PURE__ */ jsx(
624
726
  "div",
625
727
  {
728
+ ref,
626
729
  role: "menu",
730
+ tabIndex: -1,
731
+ onKeyDown: handleKeyDown,
627
732
  className: cn(
628
- "absolute top-[calc(100%+6px)] z-40 min-w-[200px] p-1.5 rounded-kj-md bg-surface border border-border shadow-kj-lg",
733
+ "absolute top-[calc(100%+6px)] z-40 min-w-[200px] p-1.5 rounded-kj-md bg-surface border border-border shadow-kj-lg outline-none",
629
734
  "animate-[kjpop_.12s_ease]",
630
735
  align === "end" ? "right-0" : "left-0",
631
736
  className
@@ -635,9 +740,9 @@ function DropdownMenuContent({ className, align = "start", children, ...props })
635
740
  }
636
741
  );
637
742
  }
638
- var DropdownMenuItem = React16.forwardRef(
743
+ var DropdownMenuItem = React17.forwardRef(
639
744
  ({ className, danger, icon, children, onClick, ...props }, ref) => {
640
- const ctx = React16.useContext(MenuContext);
745
+ const ctx = React17.useContext(MenuContext);
641
746
  return /* @__PURE__ */ jsxs(
642
747
  "button",
643
748
  {
@@ -667,11 +772,11 @@ DropdownMenuItem.displayName = "DropdownMenuItem";
667
772
  function DropdownMenuSeparator({ className }) {
668
773
  return /* @__PURE__ */ jsx("div", { className: cn("h-px bg-border my-1.5", className), role: "separator" });
669
774
  }
670
- var Breadcrumb = React16.forwardRef(
775
+ var Breadcrumb = React17.forwardRef(
671
776
  ({ className, children, ...props }, ref) => /* @__PURE__ */ jsx("nav", { ref, "aria-label": "Breadcrumb", className: cn("flex items-center gap-1.5 text-[0.85rem] text-muted-foreground", className), ...props, children })
672
777
  );
673
778
  Breadcrumb.displayName = "Breadcrumb";
674
- var BreadcrumbItem = React16.forwardRef(
779
+ var BreadcrumbItem = React17.forwardRef(
675
780
  ({ className, current, children, ...props }, ref) => {
676
781
  if (current) {
677
782
  return /* @__PURE__ */ jsx("span", { "aria-current": "page", className: cn("text-foreground font-semibold", className), children });
@@ -718,27 +823,27 @@ function Pagination({ page, pageCount, onPageChange, className }) {
718
823
  /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next", disabled: page >= pageCount, onClick: () => onPageChange(page + 1), children: /* @__PURE__ */ jsx(Chevron, { dir: "right" }) })
719
824
  ] });
720
825
  }
721
- var TableWrap = React16.forwardRef(
826
+ var TableWrap = React17.forwardRef(
722
827
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("border border-border rounded-kj-lg overflow-hidden", className), ...props })
723
828
  );
724
829
  TableWrap.displayName = "TableWrap";
725
- var Table = React16.forwardRef(
830
+ var Table = React17.forwardRef(
726
831
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("table", { ref, className: cn("w-full border-collapse text-[0.85rem]", className), ...props })
727
832
  );
728
833
  Table.displayName = "Table";
729
- var TableHeader = React16.forwardRef(
834
+ var TableHeader = React17.forwardRef(
730
835
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className, ...props })
731
836
  );
732
837
  TableHeader.displayName = "TableHeader";
733
- var TableBody = React16.forwardRef(
838
+ var TableBody = React17.forwardRef(
734
839
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("tbody", { ref, className, ...props })
735
840
  );
736
841
  TableBody.displayName = "TableBody";
737
- var TableRow = React16.forwardRef(
842
+ var TableRow = React17.forwardRef(
738
843
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("tr", { ref, className: cn("transition-colors hover:bg-muted/50", className), ...props })
739
844
  );
740
845
  TableRow.displayName = "TableRow";
741
- var TableHead = React16.forwardRef(
846
+ var TableHead = React17.forwardRef(
742
847
  ({ className, numeric, ...props }, ref) => /* @__PURE__ */ jsx(
743
848
  "th",
744
849
  {
@@ -753,7 +858,7 @@ var TableHead = React16.forwardRef(
753
858
  )
754
859
  );
755
860
  TableHead.displayName = "TableHead";
756
- var TableCell = React16.forwardRef(
861
+ var TableCell = React17.forwardRef(
757
862
  ({ className, numeric, ...props }, ref) => /* @__PURE__ */ jsx(
758
863
  "td",
759
864
  {
@@ -786,14 +891,68 @@ function Tooltip({ content, children, className }) {
786
891
  )
787
892
  ] });
788
893
  }
789
- function Modal({ open, onClose, children, width = 440, className }) {
790
- React16.useEffect(() => {
791
- if (!open) return;
792
- const onEsc = (e) => e.key === "Escape" && onClose();
793
- document.addEventListener("keydown", onEsc);
894
+ var ModalContext = React17.createContext(null);
895
+ function Modal({ open, onClose, children, width = 440, className, showClose = true }) {
896
+ const titleId = React17.useId();
897
+ const descId = React17.useId();
898
+ const containerRef = React17.useRef(null);
899
+ const lastActiveElement = React17.useRef(null);
900
+ React17.useEffect(() => {
901
+ if (!open) {
902
+ if (lastActiveElement.current) {
903
+ lastActiveElement.current.focus();
904
+ lastActiveElement.current = null;
905
+ }
906
+ return;
907
+ }
908
+ lastActiveElement.current = document.activeElement;
909
+ const container = containerRef.current;
910
+ if (container) {
911
+ const focusables = container.querySelectorAll(
912
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
913
+ );
914
+ if (focusables.length > 0) {
915
+ setTimeout(() => focusables[0].focus(), 50);
916
+ } else {
917
+ container.focus();
918
+ }
919
+ }
920
+ const handleKeyDown = (e) => {
921
+ if (e.key === "Escape") {
922
+ onClose();
923
+ return;
924
+ }
925
+ if (e.key === "Tab") {
926
+ const container2 = containerRef.current;
927
+ if (!container2) return;
928
+ const focusables = Array.from(
929
+ container2.querySelectorAll(
930
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
931
+ )
932
+ );
933
+ if (focusables.length === 0) {
934
+ e.preventDefault();
935
+ return;
936
+ }
937
+ const first = focusables[0];
938
+ const last = focusables[focusables.length - 1];
939
+ if (e.shiftKey) {
940
+ if (document.activeElement === first || document.activeElement === container2) {
941
+ last.focus();
942
+ e.preventDefault();
943
+ }
944
+ } else {
945
+ if (document.activeElement === last) {
946
+ first.focus();
947
+ e.preventDefault();
948
+ }
949
+ }
950
+ }
951
+ };
952
+ document.addEventListener("keydown", handleKeyDown);
794
953
  document.body.style.overflow = "hidden";
795
954
  return () => {
796
- document.removeEventListener("keydown", onEsc);
955
+ document.removeEventListener("keydown", handleKeyDown);
797
956
  document.body.style.overflow = "";
798
957
  };
799
958
  }, [open, onClose]);
@@ -806,35 +965,53 @@ function Modal({ open, onClose, children, width = 440, className }) {
806
965
  "bg-[color-mix(in_oklch,#09090b_55%,transparent)]",
807
966
  open ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
808
967
  ),
809
- children: /* @__PURE__ */ jsx(
968
+ children: /* @__PURE__ */ jsx(ModalContext.Provider, { value: { titleId, descId }, children: /* @__PURE__ */ jsxs(
810
969
  "div",
811
970
  {
971
+ ref: containerRef,
812
972
  role: "dialog",
813
973
  "aria-modal": "true",
974
+ "aria-labelledby": titleId,
975
+ "aria-describedby": descId,
976
+ tabIndex: -1,
814
977
  style: { maxWidth: width },
815
978
  className: cn(
816
- "w-full p-7 rounded-kj-2xl bg-surface border border-border shadow-kj-lg transition-transform duration-200",
979
+ "relative w-full p-7 rounded-kj-2xl bg-surface border border-border shadow-kj-lg transition-transform duration-200 outline-none",
817
980
  open ? "scale-100 translate-y-0" : "scale-95 translate-y-2",
818
981
  className
819
982
  ),
820
- children
983
+ children: [
984
+ showClose && /* @__PURE__ */ jsx(
985
+ "button",
986
+ {
987
+ type: "button",
988
+ onClick: onClose,
989
+ "aria-label": "Close",
990
+ className: "absolute top-4 right-4 p-1.5 rounded-kj-sm text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer",
991
+ children: /* @__PURE__ */ jsx("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
992
+ }
993
+ ),
994
+ children
995
+ ]
821
996
  }
822
- )
997
+ ) })
823
998
  }
824
999
  );
825
1000
  }
826
- function ModalTitle({ className, ...props }) {
827
- return /* @__PURE__ */ jsx("h3", { className: cn("m-0 mb-2 text-[1.15rem] font-bold tracking-[-0.01em]", className), ...props });
1001
+ function ModalTitle({ className, id, ...props }) {
1002
+ const ctx = React17.useContext(ModalContext);
1003
+ return /* @__PURE__ */ jsx("h3", { id: id ?? ctx?.titleId, className: cn("m-0 mb-2 text-[1.15rem] font-bold tracking-[-0.01em]", className), ...props });
828
1004
  }
829
- function ModalDescription({ className, ...props }) {
830
- return /* @__PURE__ */ jsx("p", { className: cn("m-0 text-[0.9rem] text-muted-foreground", className), ...props });
1005
+ function ModalDescription({ className, id, ...props }) {
1006
+ const ctx = React17.useContext(ModalContext);
1007
+ return /* @__PURE__ */ jsx("p", { id: id ?? ctx?.descId, className: cn("m-0 text-[0.9rem] text-muted-foreground", className), ...props });
831
1008
  }
832
1009
  function ModalActions({ className, ...props }) {
833
1010
  return /* @__PURE__ */ jsx("div", { className: cn("flex gap-2.5 justify-end mt-6", className), ...props });
834
1011
  }
835
- var ToastContext = React16.createContext(null);
1012
+ var ToastContext = React17.createContext(null);
836
1013
  function useToast() {
837
- const ctx = React16.useContext(ToastContext);
1014
+ const ctx = React17.useContext(ToastContext);
838
1015
  if (!ctx) throw new Error("useToast must be used within <ToastProvider>");
839
1016
  return ctx;
840
1017
  }
@@ -855,13 +1032,13 @@ var toneBorder = {
855
1032
  danger: "border-l-danger"
856
1033
  };
857
1034
  function ToastProvider({ children }) {
858
- const [items, setItems] = React16.useState([]);
859
- const idRef = React16.useRef(0);
860
- const remove = React16.useCallback((id) => {
1035
+ const [items, setItems] = React17.useState([]);
1036
+ const idRef = React17.useRef(0);
1037
+ const remove = React17.useCallback((id) => {
861
1038
  setItems((prev) => prev.map((t) => t.id === id ? { ...t, leaving: true } : t));
862
1039
  setTimeout(() => setItems((prev) => prev.filter((t) => t.id !== id)), 250);
863
1040
  }, []);
864
- const toast = React16.useCallback(
1041
+ const toast = React17.useCallback(
865
1042
  ({ message, tone = "default", duration = 3200 }) => {
866
1043
  const id = ++idRef.current;
867
1044
  setItems((prev) => [...prev, { id, message, tone, duration }]);
@@ -891,7 +1068,7 @@ function ToastProvider({ children }) {
891
1068
  )) })
892
1069
  ] });
893
1070
  }
894
- var PageHeader = React16.forwardRef(
1071
+ var PageHeader = React17.forwardRef(
895
1072
  ({ eyebrow, title, description, actions }, ref) => /* @__PURE__ */ jsx("header", { ref, className: "py-12 md:py-16", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 md:flex-row md:items-end md:justify-between", children: [
896
1073
  /* @__PURE__ */ jsxs("div", { className: "space-y-3 max-w-2xl", children: [
897
1074
  eyebrow && /* @__PURE__ */ jsx("p", { className: "font-mono text-xs font-bold uppercase tracking-[0.2em] text-primary", children: eyebrow }),