@kjaniec-dev/ui 0.3.1 → 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,14 +352,15 @@ function Segmented({
350
352
  }
351
353
  );
352
354
  }
353
- var Card = React16.forwardRef(
354
- ({ as: Tag = "div", className, elevated, ...props }, ref) => /* @__PURE__ */ jsx(
355
+ var Card = React17.forwardRef(
356
+ ({ as: Tag = "div", className, elevated, interactive, ...props }, ref) => /* @__PURE__ */ jsx(
355
357
  Tag,
356
358
  {
357
359
  ref,
358
360
  className: cn(
359
361
  "bg-card text-card-foreground rounded-kj-xl overflow-hidden border",
360
362
  elevated ? "border-transparent shadow-kj-lg" : "border-border shadow-kj-sm",
363
+ interactive && "cursor-pointer transition-all duration-300 hover:-translate-y-1 hover:shadow-kj-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
361
364
  className
362
365
  ),
363
366
  ...props
@@ -365,23 +368,23 @@ var Card = React16.forwardRef(
365
368
  )
366
369
  );
367
370
  Card.displayName = "Card";
368
- var CardHeader = React16.forwardRef(
371
+ var CardHeader = React17.forwardRef(
369
372
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("p-[1.35rem] flex flex-col gap-1", className), ...props })
370
373
  );
371
374
  CardHeader.displayName = "CardHeader";
372
- var CardTitle = React16.forwardRef(
375
+ var CardTitle = React17.forwardRef(
373
376
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("h3", { ref, className: cn("m-0 text-base font-bold tracking-[-0.01em]", className), ...props })
374
377
  );
375
378
  CardTitle.displayName = "CardTitle";
376
- var CardDescription = React16.forwardRef(
379
+ var CardDescription = React17.forwardRef(
377
380
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("p", { ref, className: cn("m-0 text-[0.85rem] text-muted-foreground", className), ...props })
378
381
  );
379
382
  CardDescription.displayName = "CardDescription";
380
- var CardContent = React16.forwardRef(
383
+ var CardContent = React17.forwardRef(
381
384
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("px-[1.35rem] pb-[1.35rem]", className), ...props })
382
385
  );
383
386
  CardContent.displayName = "CardContent";
384
- var CardFooter = React16.forwardRef(
387
+ var CardFooter = React17.forwardRef(
385
388
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
386
389
  "div",
387
390
  {
@@ -392,7 +395,7 @@ var CardFooter = React16.forwardRef(
392
395
  )
393
396
  );
394
397
  CardFooter.displayName = "CardFooter";
395
- var Stat = React16.forwardRef(
398
+ var Stat = React17.forwardRef(
396
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: [
397
400
  /* @__PURE__ */ jsx("span", { className: "text-[0.78rem] font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: label }),
398
401
  /* @__PURE__ */ jsx("span", { className: "text-[2rem] font-bold tracking-[-0.03em] tabular-nums leading-none", children: value }),
@@ -432,7 +435,7 @@ var avatarVariants = cva(
432
435
  defaultVariants: { size: "md", tone: "secondary" }
433
436
  }
434
437
  );
435
- var Avatar = React16.forwardRef(
438
+ var Avatar = React17.forwardRef(
436
439
  ({ className, size, tone, src, alt, status, children, ...props }, ref) => {
437
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 });
438
441
  if (!status) return node;
@@ -443,20 +446,20 @@ var Avatar = React16.forwardRef(
443
446
  }
444
447
  );
445
448
  Avatar.displayName = "Avatar";
446
- var AvatarGroup = React16.forwardRef(
449
+ var AvatarGroup = React17.forwardRef(
447
450
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex [&>*:not(:first-child)]:-ml-2.5", className), ...props })
448
451
  );
449
452
  AvatarGroup.displayName = "AvatarGroup";
450
- var TabsContext = React16.createContext(null);
453
+ var TabsContext = React17.createContext(null);
451
454
  function useTabs() {
452
- const ctx = React16.useContext(TabsContext);
455
+ const ctx = React17.useContext(TabsContext);
453
456
  if (!ctx) throw new Error("Tabs.* must be used within <Tabs>");
454
457
  return ctx;
455
458
  }
456
459
  function Tabs({ value, defaultValue, onValueChange, className, children, ...props }) {
457
- const [internal, setInternal] = React16.useState(defaultValue ?? "");
460
+ const [internal, setInternal] = React17.useState(defaultValue ?? "");
458
461
  const current = value ?? internal;
459
- const setValue = React16.useCallback(
462
+ const setValue = React17.useCallback(
460
463
  (v) => {
461
464
  if (value === void 0) setInternal(v);
462
465
  onValueChange?.(v);
@@ -465,11 +468,51 @@ function Tabs({ value, defaultValue, onValueChange, className, children, ...prop
465
468
  );
466
469
  return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { value: current, setValue }, children: /* @__PURE__ */ jsx("div", { className, ...props, children }) });
467
470
  }
468
- var TabsList = React16.forwardRef(
469
- ({ 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
+ }
470
513
  );
471
514
  TabsList.displayName = "TabsList";
472
- var TabsTrigger = React16.forwardRef(
515
+ var TabsTrigger = React17.forwardRef(
473
516
  ({ className, value, children, ...props }, ref) => {
474
517
  const { value: current, setValue } = useTabs();
475
518
  const active = current === value;
@@ -479,10 +522,14 @@ var TabsTrigger = React16.forwardRef(
479
522
  ref,
480
523
  type: "button",
481
524
  role: "tab",
525
+ id: `tabs-trigger-${value}`,
526
+ "aria-controls": `tabs-panel-${value}`,
482
527
  "aria-selected": active,
528
+ tabIndex: active ? 0 : -1,
483
529
  onClick: () => setValue(value),
484
530
  className: cn(
485
- "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",
486
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",
487
534
  active ? "text-foreground after:bg-primary" : "text-muted-foreground hover:text-foreground after:bg-transparent",
488
535
  className
@@ -494,18 +541,30 @@ var TabsTrigger = React16.forwardRef(
494
541
  }
495
542
  );
496
543
  TabsTrigger.displayName = "TabsTrigger";
497
- var TabsContent = React16.forwardRef(
544
+ var TabsContent = React17.forwardRef(
498
545
  ({ className, value, ...props }, ref) => {
499
546
  const { value: current } = useTabs();
500
- if (current !== value) return null;
501
- 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
+ );
502
561
  }
503
562
  );
504
563
  TabsContent.displayName = "TabsContent";
505
- var AccordionContext = React16.createContext(null);
564
+ var AccordionContext = React17.createContext(null);
506
565
  function Accordion({ type = "single", defaultValue = [], className, children, ...props }) {
507
- const [open, setOpen] = React16.useState(defaultValue);
508
- const toggle = React16.useCallback(
566
+ const [open, setOpen] = React17.useState(defaultValue);
567
+ const toggle = React17.useCallback(
509
568
  (v) => {
510
569
  setOpen((prev) => {
511
570
  const isOpen = prev.includes(v);
@@ -517,15 +576,15 @@ function Accordion({ type = "single", defaultValue = [], className, children, ..
517
576
  );
518
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 }) });
519
578
  }
520
- var ItemContext = React16.createContext("");
521
- var AccordionItem = React16.forwardRef(
579
+ var ItemContext = React17.createContext("");
580
+ var AccordionItem = React17.forwardRef(
522
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 }) })
523
582
  );
524
583
  AccordionItem.displayName = "AccordionItem";
525
- var AccordionTrigger = React16.forwardRef(
584
+ var AccordionTrigger = React17.forwardRef(
526
585
  ({ className, children, ...props }, ref) => {
527
- const ctx = React16.useContext(AccordionContext);
528
- const value = React16.useContext(ItemContext);
586
+ const ctx = React17.useContext(AccordionContext);
587
+ const value = React17.useContext(ItemContext);
529
588
  const isOpen = ctx.open.includes(value);
530
589
  return /* @__PURE__ */ jsxs(
531
590
  "button",
@@ -562,10 +621,10 @@ var AccordionTrigger = React16.forwardRef(
562
621
  }
563
622
  );
564
623
  AccordionTrigger.displayName = "AccordionTrigger";
565
- var AccordionContent = React16.forwardRef(
624
+ var AccordionContent = React17.forwardRef(
566
625
  ({ className, children, ...props }, ref) => {
567
- const ctx = React16.useContext(AccordionContext);
568
- const value = React16.useContext(ItemContext);
626
+ const ctx = React17.useContext(AccordionContext);
627
+ const value = React17.useContext(ItemContext);
569
628
  const isOpen = ctx.open.includes(value);
570
629
  return /* @__PURE__ */ jsx(
571
630
  "div",
@@ -579,11 +638,11 @@ var AccordionContent = React16.forwardRef(
579
638
  }
580
639
  );
581
640
  AccordionContent.displayName = "AccordionContent";
582
- var MenuContext = React16.createContext(null);
641
+ var MenuContext = React17.createContext(null);
583
642
  function DropdownMenu({ children, className }) {
584
- const [open, setOpen] = React16.useState(false);
585
- const ref = React16.useRef(null);
586
- React16.useEffect(() => {
643
+ const [open, setOpen] = React17.useState(false);
644
+ const ref = React17.useRef(null);
645
+ React17.useEffect(() => {
587
646
  if (!open) return;
588
647
  const onDoc = (e) => {
589
648
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -598,8 +657,23 @@ function DropdownMenu({ children, className }) {
598
657
  }, [open]);
599
658
  return /* @__PURE__ */ jsx(MenuContext.Provider, { value: { open, setOpen }, children: /* @__PURE__ */ jsx("div", { ref, className: cn("relative inline-block", className), children }) });
600
659
  }
601
- var DropdownMenuTrigger = React16.forwardRef(({ onClick, ...props }, ref) => {
602
- 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
+ }
603
677
  return /* @__PURE__ */ jsx(
604
678
  "button",
605
679
  {
@@ -617,14 +691,46 @@ var DropdownMenuTrigger = React16.forwardRef(({ onClick, ...props }, ref) => {
617
691
  });
618
692
  DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
619
693
  function DropdownMenuContent({ className, align = "start", children, ...props }) {
620
- 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
+ };
621
724
  if (!ctx.open) return null;
622
725
  return /* @__PURE__ */ jsx(
623
726
  "div",
624
727
  {
728
+ ref,
625
729
  role: "menu",
730
+ tabIndex: -1,
731
+ onKeyDown: handleKeyDown,
626
732
  className: cn(
627
- "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",
628
734
  "animate-[kjpop_.12s_ease]",
629
735
  align === "end" ? "right-0" : "left-0",
630
736
  className
@@ -634,9 +740,9 @@ function DropdownMenuContent({ className, align = "start", children, ...props })
634
740
  }
635
741
  );
636
742
  }
637
- var DropdownMenuItem = React16.forwardRef(
743
+ var DropdownMenuItem = React17.forwardRef(
638
744
  ({ className, danger, icon, children, onClick, ...props }, ref) => {
639
- const ctx = React16.useContext(MenuContext);
745
+ const ctx = React17.useContext(MenuContext);
640
746
  return /* @__PURE__ */ jsxs(
641
747
  "button",
642
748
  {
@@ -666,11 +772,11 @@ DropdownMenuItem.displayName = "DropdownMenuItem";
666
772
  function DropdownMenuSeparator({ className }) {
667
773
  return /* @__PURE__ */ jsx("div", { className: cn("h-px bg-border my-1.5", className), role: "separator" });
668
774
  }
669
- var Breadcrumb = React16.forwardRef(
775
+ var Breadcrumb = React17.forwardRef(
670
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 })
671
777
  );
672
778
  Breadcrumb.displayName = "Breadcrumb";
673
- var BreadcrumbItem = React16.forwardRef(
779
+ var BreadcrumbItem = React17.forwardRef(
674
780
  ({ className, current, children, ...props }, ref) => {
675
781
  if (current) {
676
782
  return /* @__PURE__ */ jsx("span", { "aria-current": "page", className: cn("text-foreground font-semibold", className), children });
@@ -717,27 +823,27 @@ function Pagination({ page, pageCount, onPageChange, className }) {
717
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" }) })
718
824
  ] });
719
825
  }
720
- var TableWrap = React16.forwardRef(
826
+ var TableWrap = React17.forwardRef(
721
827
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("border border-border rounded-kj-lg overflow-hidden", className), ...props })
722
828
  );
723
829
  TableWrap.displayName = "TableWrap";
724
- var Table = React16.forwardRef(
830
+ var Table = React17.forwardRef(
725
831
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("table", { ref, className: cn("w-full border-collapse text-[0.85rem]", className), ...props })
726
832
  );
727
833
  Table.displayName = "Table";
728
- var TableHeader = React16.forwardRef(
834
+ var TableHeader = React17.forwardRef(
729
835
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className, ...props })
730
836
  );
731
837
  TableHeader.displayName = "TableHeader";
732
- var TableBody = React16.forwardRef(
838
+ var TableBody = React17.forwardRef(
733
839
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("tbody", { ref, className, ...props })
734
840
  );
735
841
  TableBody.displayName = "TableBody";
736
- var TableRow = React16.forwardRef(
842
+ var TableRow = React17.forwardRef(
737
843
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("tr", { ref, className: cn("transition-colors hover:bg-muted/50", className), ...props })
738
844
  );
739
845
  TableRow.displayName = "TableRow";
740
- var TableHead = React16.forwardRef(
846
+ var TableHead = React17.forwardRef(
741
847
  ({ className, numeric, ...props }, ref) => /* @__PURE__ */ jsx(
742
848
  "th",
743
849
  {
@@ -752,7 +858,7 @@ var TableHead = React16.forwardRef(
752
858
  )
753
859
  );
754
860
  TableHead.displayName = "TableHead";
755
- var TableCell = React16.forwardRef(
861
+ var TableCell = React17.forwardRef(
756
862
  ({ className, numeric, ...props }, ref) => /* @__PURE__ */ jsx(
757
863
  "td",
758
864
  {
@@ -785,14 +891,68 @@ function Tooltip({ content, children, className }) {
785
891
  )
786
892
  ] });
787
893
  }
788
- function Modal({ open, onClose, children, width = 440, className }) {
789
- React16.useEffect(() => {
790
- if (!open) return;
791
- const onEsc = (e) => e.key === "Escape" && onClose();
792
- 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);
793
953
  document.body.style.overflow = "hidden";
794
954
  return () => {
795
- document.removeEventListener("keydown", onEsc);
955
+ document.removeEventListener("keydown", handleKeyDown);
796
956
  document.body.style.overflow = "";
797
957
  };
798
958
  }, [open, onClose]);
@@ -805,35 +965,53 @@ function Modal({ open, onClose, children, width = 440, className }) {
805
965
  "bg-[color-mix(in_oklch,#09090b_55%,transparent)]",
806
966
  open ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
807
967
  ),
808
- children: /* @__PURE__ */ jsx(
968
+ children: /* @__PURE__ */ jsx(ModalContext.Provider, { value: { titleId, descId }, children: /* @__PURE__ */ jsxs(
809
969
  "div",
810
970
  {
971
+ ref: containerRef,
811
972
  role: "dialog",
812
973
  "aria-modal": "true",
974
+ "aria-labelledby": titleId,
975
+ "aria-describedby": descId,
976
+ tabIndex: -1,
813
977
  style: { maxWidth: width },
814
978
  className: cn(
815
- "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",
816
980
  open ? "scale-100 translate-y-0" : "scale-95 translate-y-2",
817
981
  className
818
982
  ),
819
- 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
+ ]
820
996
  }
821
- )
997
+ ) })
822
998
  }
823
999
  );
824
1000
  }
825
- function ModalTitle({ className, ...props }) {
826
- 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 });
827
1004
  }
828
- function ModalDescription({ className, ...props }) {
829
- 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 });
830
1008
  }
831
1009
  function ModalActions({ className, ...props }) {
832
1010
  return /* @__PURE__ */ jsx("div", { className: cn("flex gap-2.5 justify-end mt-6", className), ...props });
833
1011
  }
834
- var ToastContext = React16.createContext(null);
1012
+ var ToastContext = React17.createContext(null);
835
1013
  function useToast() {
836
- const ctx = React16.useContext(ToastContext);
1014
+ const ctx = React17.useContext(ToastContext);
837
1015
  if (!ctx) throw new Error("useToast must be used within <ToastProvider>");
838
1016
  return ctx;
839
1017
  }
@@ -854,13 +1032,13 @@ var toneBorder = {
854
1032
  danger: "border-l-danger"
855
1033
  };
856
1034
  function ToastProvider({ children }) {
857
- const [items, setItems] = React16.useState([]);
858
- const idRef = React16.useRef(0);
859
- const remove = React16.useCallback((id) => {
1035
+ const [items, setItems] = React17.useState([]);
1036
+ const idRef = React17.useRef(0);
1037
+ const remove = React17.useCallback((id) => {
860
1038
  setItems((prev) => prev.map((t) => t.id === id ? { ...t, leaving: true } : t));
861
1039
  setTimeout(() => setItems((prev) => prev.filter((t) => t.id !== id)), 250);
862
1040
  }, []);
863
- const toast = React16.useCallback(
1041
+ const toast = React17.useCallback(
864
1042
  ({ message, tone = "default", duration = 3200 }) => {
865
1043
  const id = ++idRef.current;
866
1044
  setItems((prev) => [...prev, { id, message, tone, duration }]);
@@ -890,7 +1068,18 @@ function ToastProvider({ children }) {
890
1068
  )) })
891
1069
  ] });
892
1070
  }
1071
+ var PageHeader = React17.forwardRef(
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: [
1073
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 max-w-2xl", children: [
1074
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "font-mono text-xs font-bold uppercase tracking-[0.2em] text-primary", children: eyebrow }),
1075
+ /* @__PURE__ */ jsx("h1", { className: "font-sans text-4xl font-bold tracking-tight text-foreground sm:text-5xl md:text-6xl", children: title }),
1076
+ description && /* @__PURE__ */ jsx("p", { className: "text-base leading-relaxed text-muted-foreground sm:text-lg", children: description })
1077
+ ] }),
1078
+ actions && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3", children: actions })
1079
+ ] }) })
1080
+ );
1081
+ PageHeader.displayName = "PageHeader";
893
1082
 
894
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, Avatar, AvatarGroup, Badge, Breadcrumb, BreadcrumbItem, BreadcrumbSeparator, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Field, Hint, Input, Label, Modal, ModalActions, ModalDescription, ModalTitle, Pagination, Progress, Radio, Segmented, Select, Slider, Spinner, Stat, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableWrap, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ToastProvider, Tooltip, badgeVariants, buttonVariants, cn, sliderThumbCSS, useToast };
1083
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, Avatar, AvatarGroup, Badge, Breadcrumb, BreadcrumbItem, BreadcrumbSeparator, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Field, Hint, Input, Label, Modal, ModalActions, ModalDescription, ModalTitle, PageHeader, Pagination, Progress, Radio, Segmented, Select, Slider, Spinner, Stat, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableWrap, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ToastProvider, Tooltip, badgeVariants, buttonVariants, cn, sliderThumbCSS, useToast };
895
1084
  //# sourceMappingURL=index.js.map
896
1085
  //# sourceMappingURL=index.js.map