@kjaniec-dev/ui 0.7.2 → 0.9.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
- import * as React18 from 'react';
3
+ import * as React52 from 'react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
6
 
@@ -38,7 +38,7 @@ var buttonVariants = cva(
38
38
  defaultVariants: { variant: "primary", size: "md" }
39
39
  }
40
40
  );
41
- var Button = React18.forwardRef(
41
+ var Button = React52.forwardRef(
42
42
  ({ className, variant, size, loading, leadingIcon, trailingIcon, children, disabled, ...props }, ref) => {
43
43
  return /* @__PURE__ */ jsxs(
44
44
  "button",
@@ -85,7 +85,7 @@ var badgeVariants = cva(
85
85
  defaultVariants: { variant: "neutral" }
86
86
  }
87
87
  );
88
- var Badge = React18.forwardRef(
88
+ var Badge = React52.forwardRef(
89
89
  ({ className, variant, dot, children, ...props }, ref) => /* @__PURE__ */ jsxs("span", { ref, className: cn(badgeVariants({ variant }), className), ...props, children: [
90
90
  dot && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-current", "aria-hidden": true }),
91
91
  children
@@ -112,7 +112,7 @@ var iconColor = {
112
112
  warning: "text-warning",
113
113
  danger: "text-danger"
114
114
  };
115
- var Alert = React18.forwardRef(
115
+ var Alert = React52.forwardRef(
116
116
  ({ className, variant = "info", icon, title, children, ...props }, ref) => /* @__PURE__ */ jsxs("div", { ref, role: "alert", className: cn(alertVariants({ variant }), className), ...props, children: [
117
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 }),
118
118
  /* @__PURE__ */ jsxs("div", { className: "text-foreground", children: [
@@ -122,7 +122,7 @@ var Alert = React18.forwardRef(
122
122
  ] })
123
123
  );
124
124
  Alert.displayName = "Alert";
125
- var Spinner = React18.forwardRef(
125
+ var Spinner = React52.forwardRef(
126
126
  ({ className, size = 24, style, ...props }, ref) => /* @__PURE__ */ jsx(
127
127
  "span",
128
128
  {
@@ -139,7 +139,7 @@ var Spinner = React18.forwardRef(
139
139
  )
140
140
  );
141
141
  Spinner.displayName = "Spinner";
142
- var Progress = React18.forwardRef(
142
+ var Progress = React52.forwardRef(
143
143
  ({ className, value = 0, tone = "primary", barClassName, ...props }, ref) => {
144
144
  const pct = Math.max(0, Math.min(100, value));
145
145
  return /* @__PURE__ */ jsx(
@@ -169,14 +169,14 @@ var Progress = React18.forwardRef(
169
169
  }
170
170
  );
171
171
  Progress.displayName = "Progress";
172
- var Label = React18.forwardRef(
172
+ var Label = React52.forwardRef(
173
173
  ({ className, required, children, ...props }, ref) => /* @__PURE__ */ jsxs("label", { ref, className: cn("text-[0.8rem] font-semibold text-foreground", className), ...props, children: [
174
174
  children,
175
175
  required && /* @__PURE__ */ jsx("span", { className: "text-danger", children: " *" })
176
176
  ] })
177
177
  );
178
178
  Label.displayName = "Label";
179
- var Hint = React18.forwardRef(
179
+ var Hint = React52.forwardRef(
180
180
  ({ className, error, ...props }, ref) => /* @__PURE__ */ jsx(
181
181
  "span",
182
182
  {
@@ -187,17 +187,17 @@ var Hint = React18.forwardRef(
187
187
  )
188
188
  );
189
189
  Hint.displayName = "Hint";
190
- var Field = React18.forwardRef(
190
+ var Field = React52.forwardRef(
191
191
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col gap-1.5", className), ...props })
192
192
  );
193
193
  Field.displayName = "Field";
194
- var FormField = React18.forwardRef(
194
+ var FormField = React52.forwardRef(
195
195
  ({ className, label, hint, error, required, children, ...props }, ref) => {
196
- const id = React18.useId();
196
+ const id = React52.useId();
197
197
  const hintId = `${id}-hint`;
198
198
  const errorId = `${id}-error`;
199
- const child = React18.Children.only(children);
200
- const clonedChild = React18.cloneElement(child, {
199
+ const child = React52.Children.only(children);
200
+ const clonedChild = React52.cloneElement(child, {
201
201
  id: child.props.id ?? id,
202
202
  "aria-describedby": cn(
203
203
  hint && hintId,
@@ -216,9 +216,136 @@ var FormField = React18.forwardRef(
216
216
  }
217
217
  );
218
218
  FormField.displayName = "FormField";
219
+ var sizeDimensions = {
220
+ sm: 36,
221
+ md: 64,
222
+ lg: 96,
223
+ xl: 128
224
+ };
225
+ var defaultStrokeWidths = {
226
+ sm: 3.5,
227
+ md: 6,
228
+ lg: 8,
229
+ xl: 10
230
+ };
231
+ var textSizeClasses = {
232
+ sm: "text-[0.65rem] font-semibold leading-none",
233
+ md: "text-xs font-semibold leading-none",
234
+ lg: "text-base font-bold leading-none",
235
+ xl: "text-xl font-bold leading-none"
236
+ };
237
+ var toneClasses = {
238
+ primary: "text-primary stroke-current",
239
+ secondary: "text-secondary stroke-current",
240
+ success: "text-success stroke-current",
241
+ warning: "text-warning stroke-current",
242
+ danger: "text-danger stroke-current",
243
+ info: "text-info stroke-current"
244
+ };
245
+ var ProgressRing = React52.forwardRef(
246
+ ({
247
+ className,
248
+ style,
249
+ value = 0,
250
+ min = 0,
251
+ max = 100,
252
+ size = "md",
253
+ strokeWidth,
254
+ tone = "primary",
255
+ showValue = false,
256
+ formatValue,
257
+ trackClassName,
258
+ indicatorClassName,
259
+ children,
260
+ "aria-label": ariaLabel,
261
+ ...props
262
+ }, ref) => {
263
+ const range2 = max > min ? max - min : 100;
264
+ const clampedValue = Math.max(min, Math.min(max, value));
265
+ const percentage = Math.max(0, Math.min(100, (clampedValue - min) / range2 * 100));
266
+ const dimension = sizeDimensions[size];
267
+ const strokeWidthVal = strokeWidth ?? defaultStrokeWidths[size];
268
+ const radius = Math.max(0, (dimension - strokeWidthVal) / 2);
269
+ const circumference = 2 * Math.PI * radius;
270
+ const strokeDashoffset = circumference - percentage / 100 * circumference;
271
+ const computedAriaLabel = ariaLabel ?? (props["aria-labelledby"] ? void 0 : "Progress");
272
+ return /* @__PURE__ */ jsxs(
273
+ "div",
274
+ {
275
+ ref,
276
+ role: "progressbar",
277
+ "aria-label": computedAriaLabel,
278
+ "aria-valuenow": clampedValue,
279
+ "aria-valuemin": min,
280
+ "aria-valuemax": max,
281
+ className: cn("relative inline-flex items-center justify-center shrink-0", className),
282
+ style: { width: dimension, height: dimension, ...style },
283
+ ...props,
284
+ children: [
285
+ /* @__PURE__ */ jsxs(
286
+ "svg",
287
+ {
288
+ width: dimension,
289
+ height: dimension,
290
+ viewBox: `0 0 ${dimension} ${dimension}`,
291
+ className: "-rotate-90 transform",
292
+ children: [
293
+ /* @__PURE__ */ jsx(
294
+ "circle",
295
+ {
296
+ cx: dimension / 2,
297
+ cy: dimension / 2,
298
+ r: radius,
299
+ strokeWidth: strokeWidthVal,
300
+ className: cn("stroke-muted/30 fill-none", trackClassName)
301
+ }
302
+ ),
303
+ /* @__PURE__ */ jsx(
304
+ "circle",
305
+ {
306
+ cx: dimension / 2,
307
+ cy: dimension / 2,
308
+ r: radius,
309
+ strokeWidth: strokeWidthVal,
310
+ strokeDasharray: circumference,
311
+ strokeDashoffset,
312
+ strokeLinecap: percentage === 0 ? "butt" : "round",
313
+ className: cn(
314
+ "fill-none transition-[stroke-dashoffset,opacity] duration-500 ease-in-out",
315
+ percentage === 0 && "opacity-0",
316
+ toneClasses[tone],
317
+ indicatorClassName
318
+ )
319
+ }
320
+ )
321
+ ]
322
+ }
323
+ ),
324
+ (children || showValue) && /* @__PURE__ */ jsx(
325
+ "div",
326
+ {
327
+ className: cn(
328
+ "absolute inset-0 flex flex-col items-center justify-center text-center text-foreground p-1",
329
+ textSizeClasses[size]
330
+ ),
331
+ children: children ?? (formatValue ? formatValue(clampedValue, Math.round(percentage)) : `${Math.round(percentage)}%`)
332
+ }
333
+ )
334
+ ]
335
+ }
336
+ );
337
+ }
338
+ );
339
+ ProgressRing.displayName = "ProgressRing";
340
+ var ProgressRingField = React52.forwardRef(
341
+ ({ label, hint, error, required, fieldClassName, ...ringProps }, ref) => {
342
+ return /* @__PURE__ */ jsx(FormField, { label, hint, error, required, className: fieldClassName, children: /* @__PURE__ */ jsx(ProgressRing, { ref, "aria-label": ringProps["aria-label"] ?? label, ...ringProps }) });
343
+ }
344
+ );
345
+ ProgressRingField.displayName = "ProgressRingField";
219
346
  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";
220
- var Input = React18.forwardRef(
221
- ({ className, error, leadingIcon, style, ...props }, ref) => {
347
+ var Input = React52.forwardRef(
348
+ ({ className, error, leadingIcon, ...props }, ref) => {
222
349
  const field = /* @__PURE__ */ jsx(
223
350
  "input",
224
351
  {
@@ -242,7 +369,7 @@ var Input = React18.forwardRef(
242
369
  }
243
370
  );
244
371
  Input.displayName = "Input";
245
- var Textarea = React18.forwardRef(
372
+ var Textarea = React52.forwardRef(
246
373
  ({ className, error, ...props }, ref) => /* @__PURE__ */ jsx(
247
374
  "textarea",
248
375
  {
@@ -259,14 +386,14 @@ var Textarea = React18.forwardRef(
259
386
  )
260
387
  );
261
388
  Textarea.displayName = "Textarea";
262
- var TextField = React18.forwardRef(
389
+ var TextField = React52.forwardRef(
263
390
  ({ label, hint, error, required, className, ...props }, ref) => {
264
391
  return /* @__PURE__ */ jsx(FormField, { label, hint, error, required, className, children: /* @__PURE__ */ jsx(Input, { ref, error: !!error, ...props }) });
265
392
  }
266
393
  );
267
394
  TextField.displayName = "TextField";
268
395
  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";
269
- var Select = React18.forwardRef(
396
+ var Select = React52.forwardRef(
270
397
  ({ className, error, style, children, ...props }, ref) => /* @__PURE__ */ jsx(
271
398
  "select",
272
399
  {
@@ -294,15 +421,15 @@ var Select = React18.forwardRef(
294
421
  )
295
422
  );
296
423
  Select.displayName = "Select";
297
- var SelectField = React18.forwardRef(
424
+ var SelectField = React52.forwardRef(
298
425
  ({ label, hint, error, required, className, children, ...props }, ref) => {
299
426
  return /* @__PURE__ */ jsx(FormField, { label, hint, error, required, className, children: /* @__PURE__ */ jsx(Select, { ref, error: !!error, ...props, children }) });
300
427
  }
301
428
  );
302
429
  SelectField.displayName = "SelectField";
303
- var Checkbox = React18.forwardRef(
430
+ var Checkbox = React52.forwardRef(
304
431
  ({ className, label, id, ...props }, ref) => {
305
- const autoId = React18.useId();
432
+ const autoId = React52.useId();
306
433
  const inputId = id ?? autoId;
307
434
  return /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: cn("inline-flex items-center gap-2.5 cursor-pointer text-sm select-none", className), children: [
308
435
  /* @__PURE__ */ jsx("input", { ref, id: inputId, type: "checkbox", className: "peer sr-only", ...props }),
@@ -312,9 +439,9 @@ var Checkbox = React18.forwardRef(
312
439
  }
313
440
  );
314
441
  Checkbox.displayName = "Checkbox";
315
- var Radio = React18.forwardRef(
442
+ var Radio = React52.forwardRef(
316
443
  ({ className, label, id, ...props }, ref) => {
317
- const autoId = React18.useId();
444
+ const autoId = React52.useId();
318
445
  const inputId = id ?? autoId;
319
446
  return /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: cn("inline-flex items-center gap-2.5 cursor-pointer text-sm select-none", className), children: [
320
447
  /* @__PURE__ */ jsx("input", { ref, id: inputId, type: "radio", className: "peer sr-only", ...props }),
@@ -324,9 +451,9 @@ var Radio = React18.forwardRef(
324
451
  }
325
452
  );
326
453
  Radio.displayName = "Radio";
327
- var CheckboxField = React18.forwardRef(
454
+ var CheckboxField = React52.forwardRef(
328
455
  ({ label, hint, error, className, ...props }, ref) => {
329
- const id = React18.useId();
456
+ const id = React52.useId();
330
457
  const hintId = `${id}-hint`;
331
458
  const errorId = `${id}-error`;
332
459
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1 w-full", className), children: [
@@ -347,9 +474,9 @@ var CheckboxField = React18.forwardRef(
347
474
  }
348
475
  );
349
476
  CheckboxField.displayName = "CheckboxField";
350
- var Switch = React18.forwardRef(
477
+ var Switch = React52.forwardRef(
351
478
  ({ className, label, id, ...props }, ref) => {
352
- const autoId = React18.useId();
479
+ const autoId = React52.useId();
353
480
  const inputId = id ?? autoId;
354
481
  return /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: cn("inline-flex items-center gap-2.5 cursor-pointer text-sm select-none", className), children: [
355
482
  /* @__PURE__ */ jsx("input", { ref, id: inputId, type: "checkbox", role: "switch", className: "peer sr-only", ...props }),
@@ -359,19 +486,47 @@ var Switch = React18.forwardRef(
359
486
  }
360
487
  );
361
488
  Switch.displayName = "Switch";
362
- var Slider = React18.forwardRef(
363
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
364
- "input",
365
- {
366
- ref,
367
- type: "range",
368
- className: cn(
369
- "kj-slider w-full h-1.5 rounded-full bg-input appearance-none cursor-pointer",
370
- className
371
- ),
372
- ...props
373
- }
374
- )
489
+ var Slider = React52.forwardRef(
490
+ ({ className, style, value, defaultValue, min = 0, max = 100, onChange, ...props }, ref) => {
491
+ const [localValue, setLocalValue] = React52.useState(
492
+ Number(defaultValue ?? value ?? 50)
493
+ );
494
+ const isControlled = value !== void 0;
495
+ const currentVal = isControlled ? Number(value) : localValue;
496
+ const numMin = Number(min);
497
+ const numMax = Number(max);
498
+ const percentage = Math.min(
499
+ 100,
500
+ Math.max(0, (currentVal - numMin) / (numMax - numMin) * 100)
501
+ );
502
+ const handleChange = (e) => {
503
+ if (!isControlled) {
504
+ setLocalValue(Number(e.target.value));
505
+ }
506
+ onChange?.(e);
507
+ };
508
+ return /* @__PURE__ */ jsx(
509
+ "input",
510
+ {
511
+ ref,
512
+ type: "range",
513
+ value,
514
+ defaultValue,
515
+ min,
516
+ max,
517
+ onChange: handleChange,
518
+ style: {
519
+ background: `linear-gradient(to right, var(--kj-primary) ${percentage}%, var(--kj-border, rgba(120, 120, 128, 0.2)) ${percentage}%)`,
520
+ ...style
521
+ },
522
+ className: cn(
523
+ "kj-slider w-full h-1.5 rounded-full appearance-none cursor-pointer accent-primary",
524
+ className
525
+ ),
526
+ ...props
527
+ }
528
+ );
529
+ }
375
530
  );
376
531
  Slider.displayName = "Slider";
377
532
  var sliderThumbCSS = `
@@ -412,7 +567,47 @@ function Segmented({
412
567
  }
413
568
  );
414
569
  }
415
- var Card = React18.forwardRef(
570
+ function ToggleGroup({
571
+ options,
572
+ value,
573
+ onChange,
574
+ disabled,
575
+ className,
576
+ ...props
577
+ }) {
578
+ const toggle = (optionValue) => {
579
+ if (disabled) return;
580
+ const next = value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue];
581
+ onChange(next);
582
+ };
583
+ return /* @__PURE__ */ jsx(
584
+ "div",
585
+ {
586
+ role: "group",
587
+ className: cn("inline-flex gap-0.5 p-[3px] rounded-kj-md bg-muted", className),
588
+ ...props,
589
+ children: options.map((opt) => {
590
+ const active = value.includes(opt.value);
591
+ return /* @__PURE__ */ jsx(
592
+ "button",
593
+ {
594
+ type: "button",
595
+ "aria-pressed": active,
596
+ disabled,
597
+ onClick: () => toggle(opt.value),
598
+ className: cn(
599
+ "px-3.5 py-1.5 text-[0.82rem] font-semibold rounded-[calc(var(--kj-radius-md)-3px)] cursor-pointer transition-all duration-150 disabled:cursor-not-allowed disabled:opacity-50",
600
+ active ? "bg-surface text-foreground shadow-kj-xs" : "bg-transparent text-muted-foreground hover:text-foreground"
601
+ ),
602
+ children: opt.label
603
+ },
604
+ opt.value
605
+ );
606
+ })
607
+ }
608
+ );
609
+ }
610
+ var Card = React52.forwardRef(
416
611
  ({ as: Tag = "div", className, elevated, interactive, ...props }, ref) => /* @__PURE__ */ jsx(
417
612
  Tag,
418
613
  {
@@ -428,23 +623,23 @@ var Card = React18.forwardRef(
428
623
  )
429
624
  );
430
625
  Card.displayName = "Card";
431
- var CardHeader = React18.forwardRef(
626
+ var CardHeader = React52.forwardRef(
432
627
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("p-[1.35rem] flex flex-col gap-1", className), ...props })
433
628
  );
434
629
  CardHeader.displayName = "CardHeader";
435
- var CardTitle = React18.forwardRef(
436
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("h3", { ref, className: cn("m-0 text-base font-bold tracking-[-0.01em]", className), ...props })
630
+ var CardTitle = React52.forwardRef(
631
+ ({ className, children, ...props }, ref) => /* @__PURE__ */ jsx("h3", { ref, className: cn("m-0 text-base font-bold tracking-[-0.01em]", className), ...props, children })
437
632
  );
438
633
  CardTitle.displayName = "CardTitle";
439
- var CardDescription = React18.forwardRef(
634
+ var CardDescription = React52.forwardRef(
440
635
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("p", { ref, className: cn("m-0 text-[0.85rem] text-muted-foreground", className), ...props })
441
636
  );
442
637
  CardDescription.displayName = "CardDescription";
443
- var CardContent = React18.forwardRef(
638
+ var CardContent = React52.forwardRef(
444
639
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("px-[1.35rem] pb-[1.35rem]", className), ...props })
445
640
  );
446
641
  CardContent.displayName = "CardContent";
447
- var CardFooter = React18.forwardRef(
642
+ var CardFooter = React52.forwardRef(
448
643
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
449
644
  "div",
450
645
  {
@@ -455,7 +650,7 @@ var CardFooter = React18.forwardRef(
455
650
  )
456
651
  );
457
652
  CardFooter.displayName = "CardFooter";
458
- var Stat = React18.forwardRef(
653
+ var Stat = React52.forwardRef(
459
654
  ({ className, label, value, delta, trend, ...props }, ref) => /* @__PURE__ */ jsxs(Card, { ref, className: cn("p-[1.35rem] flex flex-col gap-2", className), ...props, children: [
460
655
  /* @__PURE__ */ jsx("span", { className: "text-[0.78rem] font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: label }),
461
656
  /* @__PURE__ */ jsx("span", { className: "text-[2rem] font-bold tracking-[-0.03em] tabular-nums leading-none", children: value }),
@@ -495,7 +690,7 @@ var avatarVariants = cva(
495
690
  defaultVariants: { size: "md", tone: "secondary" }
496
691
  }
497
692
  );
498
- var Avatar = React18.forwardRef(
693
+ var Avatar = React52.forwardRef(
499
694
  ({ className, size, tone, src, alt, status, children, ...props }, ref) => {
500
695
  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 });
501
696
  if (!status) return node;
@@ -506,29 +701,30 @@ var Avatar = React18.forwardRef(
506
701
  }
507
702
  );
508
703
  Avatar.displayName = "Avatar";
509
- var AvatarGroup = React18.forwardRef(
704
+ var AvatarGroup = React52.forwardRef(
510
705
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex [&>*:not(:first-child)]:-ml-2.5", className), ...props })
511
706
  );
512
707
  AvatarGroup.displayName = "AvatarGroup";
513
- var TabsContext = React18.createContext(null);
708
+ var TabsContext = React52.createContext(null);
514
709
  function useTabs() {
515
- const ctx = React18.useContext(TabsContext);
710
+ const ctx = React52.useContext(TabsContext);
516
711
  if (!ctx) throw new Error("Tabs.* must be used within <Tabs>");
517
712
  return ctx;
518
713
  }
519
714
  function Tabs({ value, defaultValue, onValueChange, className, children, ...props }) {
520
- const [internal, setInternal] = React18.useState(defaultValue ?? "");
715
+ const [internal, setInternal] = React52.useState(defaultValue ?? "");
716
+ const uid = React52.useId();
521
717
  const current = value ?? internal;
522
- const setValue = React18.useCallback(
718
+ const setValue = React52.useCallback(
523
719
  (v) => {
524
720
  if (value === void 0) setInternal(v);
525
721
  onValueChange?.(v);
526
722
  },
527
723
  [value, onValueChange]
528
724
  );
529
- return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { value: current, setValue }, children: /* @__PURE__ */ jsx("div", { className, ...props, children }) });
725
+ return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { value: current, setValue, uid }, children: /* @__PURE__ */ jsx("div", { className, ...props, children }) });
530
726
  }
531
- var TabsList = React18.forwardRef(
727
+ var TabsList = React52.forwardRef(
532
728
  ({ className, onKeyDown, ...props }, ref) => {
533
729
  const handleKeyDown = (e) => {
534
730
  const container = e.currentTarget;
@@ -575,9 +771,9 @@ var TabsList = React18.forwardRef(
575
771
  }
576
772
  );
577
773
  TabsList.displayName = "TabsList";
578
- var TabsTrigger = React18.forwardRef(
774
+ var TabsTrigger = React52.forwardRef(
579
775
  ({ className, value, children, ...props }, ref) => {
580
- const { value: current, setValue } = useTabs();
776
+ const { value: current, setValue, uid } = useTabs();
581
777
  const active = current === value;
582
778
  return /* @__PURE__ */ jsx(
583
779
  "button",
@@ -585,8 +781,8 @@ var TabsTrigger = React18.forwardRef(
585
781
  ref,
586
782
  type: "button",
587
783
  role: "tab",
588
- id: `tabs-trigger-${value}`,
589
- "aria-controls": `tabs-panel-${value}`,
784
+ id: `tabs-trigger-${uid}-${value}`,
785
+ "aria-controls": `tabs-panel-${uid}-${value}`,
590
786
  "aria-selected": active,
591
787
  tabIndex: active ? 0 : -1,
592
788
  onClick: () => setValue(value),
@@ -604,9 +800,9 @@ var TabsTrigger = React18.forwardRef(
604
800
  }
605
801
  );
606
802
  TabsTrigger.displayName = "TabsTrigger";
607
- var TabsContent = React18.forwardRef(
803
+ var TabsContent = React52.forwardRef(
608
804
  ({ className, value, ...props }, ref) => {
609
- const { value: current } = useTabs();
805
+ const { value: current, uid } = useTabs();
610
806
  const active = current === value;
611
807
  if (!active) return null;
612
808
  return /* @__PURE__ */ jsx(
@@ -614,8 +810,8 @@ var TabsContent = React18.forwardRef(
614
810
  {
615
811
  ref,
616
812
  role: "tabpanel",
617
- id: `tabs-panel-${value}`,
618
- "aria-labelledby": `tabs-trigger-${value}`,
813
+ id: `tabs-panel-${uid}-${value}`,
814
+ "aria-labelledby": `tabs-trigger-${uid}-${value}`,
619
815
  tabIndex: 0,
620
816
  className: cn("pt-5 text-[0.9rem] text-muted-foreground outline-none", className),
621
817
  ...props
@@ -624,10 +820,10 @@ var TabsContent = React18.forwardRef(
624
820
  }
625
821
  );
626
822
  TabsContent.displayName = "TabsContent";
627
- var AccordionContext = React18.createContext(null);
823
+ var AccordionContext = React52.createContext(null);
628
824
  function Accordion({ type = "single", defaultValue = [], className, children, ...props }) {
629
- const [open, setOpen] = React18.useState(defaultValue);
630
- const toggle = React18.useCallback(
825
+ const [open, setOpen] = React52.useState(defaultValue);
826
+ const toggle = React52.useCallback(
631
827
  (v) => {
632
828
  setOpen((prev) => {
633
829
  const isOpen = prev.includes(v);
@@ -639,15 +835,15 @@ function Accordion({ type = "single", defaultValue = [], className, children, ..
639
835
  );
640
836
  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 }) });
641
837
  }
642
- var ItemContext = React18.createContext("");
643
- var AccordionItem = React18.forwardRef(
838
+ var ItemContext = React52.createContext("");
839
+ var AccordionItem = React52.forwardRef(
644
840
  ({ 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 }) })
645
841
  );
646
842
  AccordionItem.displayName = "AccordionItem";
647
- var AccordionTrigger = React18.forwardRef(
843
+ var AccordionTrigger = React52.forwardRef(
648
844
  ({ className, children, ...props }, ref) => {
649
- const ctx = React18.useContext(AccordionContext);
650
- const value = React18.useContext(ItemContext);
845
+ const ctx = React52.useContext(AccordionContext);
846
+ const value = React52.useContext(ItemContext);
651
847
  const isOpen = ctx.open.includes(value);
652
848
  return /* @__PURE__ */ jsxs(
653
849
  "button",
@@ -684,10 +880,10 @@ var AccordionTrigger = React18.forwardRef(
684
880
  }
685
881
  );
686
882
  AccordionTrigger.displayName = "AccordionTrigger";
687
- var AccordionContent = React18.forwardRef(
883
+ var AccordionContent = React52.forwardRef(
688
884
  ({ className, children, ...props }, ref) => {
689
- const ctx = React18.useContext(AccordionContext);
690
- const value = React18.useContext(ItemContext);
885
+ const ctx = React52.useContext(AccordionContext);
886
+ const value = React52.useContext(ItemContext);
691
887
  const isOpen = ctx.open.includes(value);
692
888
  return /* @__PURE__ */ jsx(
693
889
  "div",
@@ -701,11 +897,11 @@ var AccordionContent = React18.forwardRef(
701
897
  }
702
898
  );
703
899
  AccordionContent.displayName = "AccordionContent";
704
- var MenuContext = React18.createContext(null);
900
+ var MenuContext = React52.createContext(null);
705
901
  function DropdownMenu({ children, className }) {
706
- const [open, setOpen] = React18.useState(false);
707
- const ref = React18.useRef(null);
708
- React18.useEffect(() => {
902
+ const [open, setOpen] = React52.useState(false);
903
+ const ref = React52.useRef(null);
904
+ React52.useEffect(() => {
709
905
  if (!open) return;
710
906
  const onDoc = (e) => {
711
907
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -720,11 +916,11 @@ function DropdownMenu({ children, className }) {
720
916
  }, [open]);
721
917
  return /* @__PURE__ */ jsx(MenuContext.Provider, { value: { open, setOpen }, children: /* @__PURE__ */ jsx("div", { ref, className: cn("relative inline-block", className), children }) });
722
918
  }
723
- var DropdownMenuTrigger = React18.forwardRef(({ onClick, asChild, ...props }, ref) => {
724
- const ctx = React18.useContext(MenuContext);
919
+ var DropdownMenuTrigger = React52.forwardRef(({ onClick, asChild, ...props }, ref) => {
920
+ const ctx = React52.useContext(MenuContext);
725
921
  if (asChild) {
726
- const child = React18.Children.only(props.children);
727
- return React18.cloneElement(child, {
922
+ const child = React52.Children.only(props.children);
923
+ return React52.cloneElement(child, {
728
924
  ref,
729
925
  ...props,
730
926
  ...child.props,
@@ -754,9 +950,9 @@ var DropdownMenuTrigger = React18.forwardRef(({ onClick, asChild, ...props }, re
754
950
  });
755
951
  DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
756
952
  function DropdownMenuContent({ className, align = "start", children, ...props }) {
757
- const ctx = React18.useContext(MenuContext);
758
- const ref = React18.useRef(null);
759
- React18.useEffect(() => {
953
+ const ctx = React52.useContext(MenuContext);
954
+ const ref = React52.useRef(null);
955
+ React52.useEffect(() => {
760
956
  if (!ctx.open) return;
761
957
  const container = ref.current;
762
958
  if (container) {
@@ -803,9 +999,9 @@ function DropdownMenuContent({ className, align = "start", children, ...props })
803
999
  }
804
1000
  );
805
1001
  }
806
- var DropdownMenuItem = React18.forwardRef(
1002
+ var DropdownMenuItem = React52.forwardRef(
807
1003
  ({ className, danger, icon, children, onClick, ...props }, ref) => {
808
- const ctx = React18.useContext(MenuContext);
1004
+ const ctx = React52.useContext(MenuContext);
809
1005
  return /* @__PURE__ */ jsxs(
810
1006
  "button",
811
1007
  {
@@ -835,11 +1031,11 @@ DropdownMenuItem.displayName = "DropdownMenuItem";
835
1031
  function DropdownMenuSeparator({ className }) {
836
1032
  return /* @__PURE__ */ jsx("div", { className: cn("h-px bg-border my-1.5", className), role: "separator" });
837
1033
  }
838
- var Breadcrumb = React18.forwardRef(
1034
+ var Breadcrumb = React52.forwardRef(
839
1035
  ({ 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 })
840
1036
  );
841
1037
  Breadcrumb.displayName = "Breadcrumb";
842
- var BreadcrumbItem = React18.forwardRef(
1038
+ var BreadcrumbItem = React52.forwardRef(
843
1039
  ({ className, current, children, ...props }, ref) => {
844
1040
  if (current) {
845
1041
  return /* @__PURE__ */ jsx("span", { "aria-current": "page", className: cn("text-foreground font-semibold", className), children });
@@ -886,7 +1082,7 @@ function Pagination({ page, pageCount, onPageChange, className }) {
886
1082
  /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next", disabled: page >= pageCount, onClick: () => onPageChange(page + 1), children: /* @__PURE__ */ jsx(Chevron, { dir: "right" }) })
887
1083
  ] });
888
1084
  }
889
- var BottomNavigation = React18.forwardRef(
1085
+ var BottomNavigation = React52.forwardRef(
890
1086
  ({ className, items, showLabels = "always", fixed = true, ...props }, ref) => {
891
1087
  return /* @__PURE__ */ jsx(
892
1088
  "nav",
@@ -958,27 +1154,27 @@ var BottomNavigation = React18.forwardRef(
958
1154
  }
959
1155
  );
960
1156
  BottomNavigation.displayName = "BottomNavigation";
961
- var TableWrap = React18.forwardRef(
1157
+ var TableWrap = React52.forwardRef(
962
1158
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("border border-border rounded-kj-lg overflow-x-auto w-full", className), ...props })
963
1159
  );
964
1160
  TableWrap.displayName = "TableWrap";
965
- var Table = React18.forwardRef(
1161
+ var Table = React52.forwardRef(
966
1162
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("table", { ref, className: cn("w-full border-collapse text-[0.85rem]", className), ...props })
967
1163
  );
968
1164
  Table.displayName = "Table";
969
- var TableHeader = React18.forwardRef(
1165
+ var TableHeader = React52.forwardRef(
970
1166
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className, ...props })
971
1167
  );
972
1168
  TableHeader.displayName = "TableHeader";
973
- var TableBody = React18.forwardRef(
1169
+ var TableBody = React52.forwardRef(
974
1170
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("tbody", { ref, className, ...props })
975
1171
  );
976
1172
  TableBody.displayName = "TableBody";
977
- var TableRow = React18.forwardRef(
1173
+ var TableRow = React52.forwardRef(
978
1174
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx("tr", { ref, className: cn("transition-colors hover:bg-muted/50", className), ...props })
979
1175
  );
980
1176
  TableRow.displayName = "TableRow";
981
- var TableHead = React18.forwardRef(
1177
+ var TableHead = React52.forwardRef(
982
1178
  ({ className, numeric, ...props }, ref) => /* @__PURE__ */ jsx(
983
1179
  "th",
984
1180
  {
@@ -993,7 +1189,7 @@ var TableHead = React18.forwardRef(
993
1189
  )
994
1190
  );
995
1191
  TableHead.displayName = "TableHead";
996
- var TableCell = React18.forwardRef(
1192
+ var TableCell = React52.forwardRef(
997
1193
  ({ className, numeric, ...props }, ref) => /* @__PURE__ */ jsx(
998
1194
  "td",
999
1195
  {
@@ -1009,30 +1205,33 @@ var TableCell = React18.forwardRef(
1009
1205
  );
1010
1206
  TableCell.displayName = "TableCell";
1011
1207
  function Tooltip({ content, children, className }) {
1012
- return /* @__PURE__ */ jsxs("span", { className: cn("relative inline-flex group", className), tabIndex: 0, children: [
1013
- children,
1014
- /* @__PURE__ */ jsx(
1015
- "span",
1016
- {
1017
- role: "tooltip",
1018
- className: cn(
1019
- "pointer-events-none absolute bottom-[calc(100%+8px)] left-1/2 -translate-x-1/2 translate-y-1 whitespace-nowrap",
1020
- "rounded-kj-sm bg-foreground text-background text-xs font-medium px-2.5 py-1.5 shadow-kj-md",
1021
- "opacity-0 transition-all duration-150 group-hover:opacity-100 group-hover:translate-y-0 group-focus-visible:opacity-100 group-focus-visible:translate-y-0",
1022
- "after:content-[''] after:absolute after:top-full after:left-1/2 after:-translate-x-1/2 after:border-[5px] after:border-transparent after:border-t-foreground"
1023
- ),
1024
- children: content
1025
- }
1026
- )
1027
- ] });
1208
+ return (
1209
+ /* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
1210
+ /* @__PURE__ */ jsxs("span", { className: cn("relative inline-flex group", className), tabIndex: 0, children: [
1211
+ children,
1212
+ /* @__PURE__ */ jsx(
1213
+ "span",
1214
+ {
1215
+ role: "tooltip",
1216
+ className: cn(
1217
+ "pointer-events-none absolute bottom-[calc(100%+8px)] left-1/2 -translate-x-1/2 translate-y-1 whitespace-nowrap",
1218
+ "rounded-kj-sm bg-foreground text-background text-xs font-medium px-2.5 py-1.5 shadow-kj-md",
1219
+ "opacity-0 transition-all duration-150 group-hover:opacity-100 group-hover:translate-y-0 group-focus-visible:opacity-100 group-focus-visible:translate-y-0",
1220
+ "after:content-[''] after:absolute after:top-full after:left-1/2 after:-translate-x-1/2 after:border-[5px] after:border-transparent after:border-t-foreground"
1221
+ ),
1222
+ children: content
1223
+ }
1224
+ )
1225
+ ] })
1226
+ );
1028
1227
  }
1029
- var ModalContext = React18.createContext(null);
1228
+ var ModalContext = React52.createContext(null);
1030
1229
  function Modal({ open, onClose, children, width = 440, className, showClose = true }) {
1031
- const titleId = React18.useId();
1032
- const descId = React18.useId();
1033
- const containerRef = React18.useRef(null);
1034
- const lastActiveElement = React18.useRef(null);
1035
- React18.useEffect(() => {
1230
+ const titleId = React52.useId();
1231
+ const descId = React52.useId();
1232
+ const containerRef = React52.useRef(null);
1233
+ const lastActiveElement = React52.useRef(null);
1234
+ React52.useEffect(() => {
1036
1235
  if (!open) {
1037
1236
  if (lastActiveElement.current) {
1038
1237
  lastActiveElement.current.focus();
@@ -1133,20 +1332,20 @@ function Modal({ open, onClose, children, width = 440, className, showClose = tr
1133
1332
  }
1134
1333
  );
1135
1334
  }
1136
- function ModalTitle({ className, id, ...props }) {
1137
- const ctx = React18.useContext(ModalContext);
1138
- return /* @__PURE__ */ jsx("h3", { id: id ?? ctx?.titleId, className: cn("m-0 mb-2 text-[1.15rem] font-bold tracking-[-0.01em]", className), ...props });
1335
+ function ModalTitle({ className, id, children, ...props }) {
1336
+ const ctx = React52.useContext(ModalContext);
1337
+ return /* @__PURE__ */ jsx("h3", { id: id ?? ctx?.titleId, className: cn("m-0 mb-2 text-[1.15rem] font-bold tracking-[-0.01em]", className), ...props, children });
1139
1338
  }
1140
1339
  function ModalDescription({ className, id, ...props }) {
1141
- const ctx = React18.useContext(ModalContext);
1340
+ const ctx = React52.useContext(ModalContext);
1142
1341
  return /* @__PURE__ */ jsx("p", { id: id ?? ctx?.descId, className: cn("m-0 text-[0.9rem] text-muted-foreground", className), ...props });
1143
1342
  }
1144
1343
  function ModalActions({ className, ...props }) {
1145
1344
  return /* @__PURE__ */ jsx("div", { className: cn("flex gap-2.5 justify-end mt-6", className), ...props });
1146
1345
  }
1147
- var ToastContext = React18.createContext(null);
1346
+ var ToastContext = React52.createContext(null);
1148
1347
  function useToast() {
1149
- const ctx = React18.useContext(ToastContext);
1348
+ const ctx = React52.useContext(ToastContext);
1150
1349
  if (!ctx) throw new Error("useToast must be used within <ToastProvider>");
1151
1350
  return ctx;
1152
1351
  }
@@ -1167,13 +1366,13 @@ var toneBorder = {
1167
1366
  danger: "border-l-danger"
1168
1367
  };
1169
1368
  function ToastProvider({ children }) {
1170
- const [items, setItems] = React18.useState([]);
1171
- const idRef = React18.useRef(0);
1172
- const remove = React18.useCallback((id) => {
1369
+ const [items, setItems] = React52.useState([]);
1370
+ const idRef = React52.useRef(0);
1371
+ const remove = React52.useCallback((id) => {
1173
1372
  setItems((prev) => prev.map((t) => t.id === id ? { ...t, leaving: true } : t));
1174
1373
  setTimeout(() => setItems((prev) => prev.filter((t) => t.id !== id)), 250);
1175
1374
  }, []);
1176
- const toast = React18.useCallback(
1375
+ const toast = React52.useCallback(
1177
1376
  ({ message, tone = "default", duration = 3200 }) => {
1178
1377
  const id = ++idRef.current;
1179
1378
  setItems((prev) => [...prev, { id, message, tone, duration }]);
@@ -1203,7 +1402,7 @@ function ToastProvider({ children }) {
1203
1402
  )) })
1204
1403
  ] });
1205
1404
  }
1206
- var PageHeader = React18.forwardRef(
1405
+ var PageHeader = React52.forwardRef(
1207
1406
  ({ 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: [
1208
1407
  /* @__PURE__ */ jsxs("div", { className: "space-y-3 max-w-2xl", children: [
1209
1408
  eyebrow && /* @__PURE__ */ jsx("p", { className: "font-mono text-xs font-bold uppercase tracking-[0.2em] text-primary", children: eyebrow }),
@@ -1214,7 +1413,111 @@ var PageHeader = React18.forwardRef(
1214
1413
  ] }) })
1215
1414
  );
1216
1415
  PageHeader.displayName = "PageHeader";
1217
- var EmptyState = React18.forwardRef(
1416
+ var SectionHeaderKicker = React52.forwardRef(
1417
+ ({ className, children, ...props }, ref) => {
1418
+ if (!children) return null;
1419
+ return /* @__PURE__ */ jsx(
1420
+ "p",
1421
+ {
1422
+ ref,
1423
+ className: cn(
1424
+ "font-mono text-xs font-bold uppercase tracking-[0.2em] text-primary",
1425
+ className
1426
+ ),
1427
+ ...props,
1428
+ children
1429
+ }
1430
+ );
1431
+ }
1432
+ );
1433
+ SectionHeaderKicker.displayName = "SectionHeaderKicker";
1434
+ var SectionHeaderTitle = React52.forwardRef(
1435
+ ({ className, as: Component = "h2", children, ...props }, ref) => {
1436
+ if (!children) return null;
1437
+ return /* @__PURE__ */ jsx(
1438
+ Component,
1439
+ {
1440
+ ref,
1441
+ className: cn("text-2xl font-bold tracking-tight text-foreground sm:text-3xl", className),
1442
+ ...props,
1443
+ children
1444
+ }
1445
+ );
1446
+ }
1447
+ );
1448
+ SectionHeaderTitle.displayName = "SectionHeaderTitle";
1449
+ var SectionHeaderDescription = React52.forwardRef(({ className, children, ...props }, ref) => {
1450
+ if (!children) return null;
1451
+ return /* @__PURE__ */ jsx(
1452
+ "p",
1453
+ {
1454
+ ref,
1455
+ className: cn("text-base leading-relaxed text-muted-foreground max-w-2xl", className),
1456
+ ...props,
1457
+ children
1458
+ }
1459
+ );
1460
+ });
1461
+ SectionHeaderDescription.displayName = "SectionHeaderDescription";
1462
+ var SectionHeaderActions = React52.forwardRef(
1463
+ ({ className, children, ...props }, ref) => {
1464
+ if (!children) return null;
1465
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-wrap items-center gap-3", className), ...props, children });
1466
+ }
1467
+ );
1468
+ SectionHeaderActions.displayName = "SectionHeaderActions";
1469
+ var SectionHeader = React52.forwardRef(
1470
+ ({
1471
+ kicker,
1472
+ title,
1473
+ description,
1474
+ actions,
1475
+ align = "left",
1476
+ headingLevel = "h2",
1477
+ divider = false,
1478
+ className,
1479
+ children,
1480
+ ...props
1481
+ }, ref) => {
1482
+ const isCentered = align === "center";
1483
+ return /* @__PURE__ */ jsx(
1484
+ "div",
1485
+ {
1486
+ ref,
1487
+ className: cn(
1488
+ "space-y-4",
1489
+ isCentered ? "text-center items-center mx-auto max-w-3xl" : "md:flex md:items-end md:justify-between md:space-y-0",
1490
+ divider && "border-b border-border pb-6",
1491
+ className
1492
+ ),
1493
+ ...props,
1494
+ children: children ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
1495
+ /* @__PURE__ */ jsxs(
1496
+ "div",
1497
+ {
1498
+ className: cn(
1499
+ "space-y-2",
1500
+ isCentered && "flex flex-col items-center"
1501
+ ),
1502
+ children: [
1503
+ kicker && /* @__PURE__ */ jsx(SectionHeaderKicker, { children: kicker }),
1504
+ title && /* @__PURE__ */ jsx(SectionHeaderTitle, { as: headingLevel, children: title }),
1505
+ description && /* @__PURE__ */ jsx(SectionHeaderDescription, { children: description })
1506
+ ]
1507
+ }
1508
+ ),
1509
+ actions && /* @__PURE__ */ jsx(SectionHeaderActions, { className: cn(isCentered && "justify-center mt-4"), children: actions })
1510
+ ] })
1511
+ }
1512
+ );
1513
+ }
1514
+ );
1515
+ SectionHeader.displayName = "SectionHeader";
1516
+ SectionHeader.Kicker = SectionHeaderKicker;
1517
+ SectionHeader.Title = SectionHeaderTitle;
1518
+ SectionHeader.Description = SectionHeaderDescription;
1519
+ SectionHeader.Actions = SectionHeaderActions;
1520
+ var EmptyState = React52.forwardRef(
1218
1521
  ({ className, icon, title, description, action, ...props }, ref) => /* @__PURE__ */ jsxs(
1219
1522
  "div",
1220
1523
  {
@@ -1234,7 +1537,7 @@ var EmptyState = React18.forwardRef(
1234
1537
  )
1235
1538
  );
1236
1539
  EmptyState.displayName = "EmptyState";
1237
- var MetricCard = React18.forwardRef(
1540
+ var MetricCard = React52.forwardRef(
1238
1541
  ({ className, title, value, trend, trendDirection = "neutral", description, icon, ...props }, ref) => {
1239
1542
  const trendColor = {
1240
1543
  up: "text-success bg-success-surface border-success/20",
@@ -1275,22 +1578,22 @@ function DataTable({
1275
1578
  onSort,
1276
1579
  ...props
1277
1580
  }) {
1278
- const getRowId = React18.useCallback(
1581
+ const getRowId = React52.useCallback(
1279
1582
  (row, index) => getRowKey ? getRowKey(row, index) : index,
1280
1583
  [getRowKey]
1281
1584
  );
1282
1585
  const isSelectionActive = selectedRows !== void 0 && onSelectionChange !== void 0;
1283
- const allSelected = React18.useMemo(() => {
1586
+ const allSelected = React52.useMemo(() => {
1284
1587
  if (!isSelectionActive || data.length === 0) return false;
1285
1588
  return data.every((row, idx) => selectedRows.has(getRowId(row, idx)));
1286
1589
  }, [isSelectionActive, data, selectedRows, getRowId]);
1287
- const someSelected = React18.useMemo(() => {
1590
+ const someSelected = React52.useMemo(() => {
1288
1591
  if (!isSelectionActive || data.length === 0) return false;
1289
1592
  const selectedCount = data.filter((row, idx) => selectedRows.has(getRowId(row, idx))).length;
1290
1593
  return selectedCount > 0 && selectedCount < data.length;
1291
1594
  }, [isSelectionActive, data, selectedRows, getRowId]);
1292
- const headerCheckboxRef = React18.useRef(null);
1293
- React18.useEffect(() => {
1595
+ const headerCheckboxRef = React52.useRef(null);
1596
+ React52.useEffect(() => {
1294
1597
  if (headerCheckboxRef.current) {
1295
1598
  headerCheckboxRef.current.indeterminate = someSelected;
1296
1599
  }
@@ -1444,7 +1747,7 @@ function DataTable({
1444
1747
  ] });
1445
1748
  }
1446
1749
  DataTable.displayName = "DataTable";
1447
- var ErrorState = React18.forwardRef(
1750
+ var ErrorState = React52.forwardRef(
1448
1751
  ({ className, title = "Wyst\u0105pi\u0142 b\u0142\u0105d", message, onRetry, retryLabel = "Spr\xF3buj ponownie", ...props }, ref) => /* @__PURE__ */ jsxs(
1449
1752
  "div",
1450
1753
  {
@@ -1464,7 +1767,7 @@ var ErrorState = React18.forwardRef(
1464
1767
  )
1465
1768
  );
1466
1769
  ErrorState.displayName = "ErrorState";
1467
- var Skeleton = React18.forwardRef(
1770
+ var Skeleton = React52.forwardRef(
1468
1771
  ({ className, variant = "rectangular", width, height, style, ...props }, ref) => /* @__PURE__ */ jsx(
1469
1772
  "div",
1470
1773
  {
@@ -1486,7 +1789,7 @@ var Skeleton = React18.forwardRef(
1486
1789
  )
1487
1790
  );
1488
1791
  Skeleton.displayName = "Skeleton";
1489
- var DashboardShell = React18.forwardRef(
1792
+ var DashboardShell = React52.forwardRef(
1490
1793
  ({
1491
1794
  className,
1492
1795
  sidebar,
@@ -1498,7 +1801,7 @@ var DashboardShell = React18.forwardRef(
1498
1801
  mobileSidebar,
1499
1802
  ...props
1500
1803
  }, ref) => {
1501
- const [mobileOpen, setMobileOpen] = React18.useState(false);
1804
+ const [mobileOpen, setMobileOpen] = React52.useState(false);
1502
1805
  const contentWidthClass = {
1503
1806
  default: "max-w-7xl w-full mx-auto",
1504
1807
  wide: "max-w-screen-2xl w-full mx-auto",
@@ -1589,7 +1892,7 @@ var DashboardShell = React18.forwardRef(
1589
1892
  }
1590
1893
  );
1591
1894
  DashboardShell.displayName = "DashboardShell";
1592
- var SettingsLayout = React18.forwardRef(
1895
+ var SettingsLayout = React52.forwardRef(
1593
1896
  ({ className, sidebar, children, title, description, ...props }, ref) => {
1594
1897
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("space-y-6 w-full", className), ...props, children: [
1595
1898
  (title || description) && /* @__PURE__ */ jsxs("div", { className: "border-b border-border pb-4", children: [
@@ -1604,7 +1907,7 @@ var SettingsLayout = React18.forwardRef(
1604
1907
  }
1605
1908
  );
1606
1909
  SettingsLayout.displayName = "SettingsLayout";
1607
- var DetailPageLayout = React18.forwardRef(
1910
+ var DetailPageLayout = React52.forwardRef(
1608
1911
  ({ className, title, description, backHref, backLabel = "Back", onBackClick, actions, aside, children, ...props }, ref) => {
1609
1912
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("space-y-6 w-full", className), ...props, children: [
1610
1913
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
@@ -1650,7 +1953,7 @@ var DetailPageLayout = React18.forwardRef(
1650
1953
  }
1651
1954
  );
1652
1955
  DetailPageLayout.displayName = "DetailPageLayout";
1653
- var TableToolbar = React18.forwardRef(
1956
+ var TableToolbar = React52.forwardRef(
1654
1957
  ({ className, searchQuery, onSearchChange, searchPlaceholder = "Search...", actions, filters, ...props }, ref) => {
1655
1958
  return /* @__PURE__ */ jsxs(
1656
1959
  "div",
@@ -1719,9 +2022,9 @@ function Drawer({
1719
2022
  width = "max-w-md",
1720
2023
  side = "right"
1721
2024
  }) {
1722
- const containerRef = React18.useRef(null);
1723
- const lastActiveElement = React18.useRef(null);
1724
- React18.useEffect(() => {
2025
+ const containerRef = React52.useRef(null);
2026
+ const lastActiveElement = React52.useRef(null);
2027
+ React52.useEffect(() => {
1725
2028
  if (!open) {
1726
2029
  if (lastActiveElement.current) {
1727
2030
  lastActiveElement.current.focus();
@@ -1840,11 +2143,11 @@ function Drawer({
1840
2143
  }
1841
2144
  Drawer.displayName = "Drawer";
1842
2145
  function CommandPalette({ open, onClose, items, placeholder = "Type a command or search..." }) {
1843
- const [search, setSearch] = React18.useState("");
1844
- const [activeIndex, setActiveIndex] = React18.useState(0);
1845
- const containerRef = React18.useRef(null);
1846
- const listRef = React18.useRef(null);
1847
- React18.useEffect(() => {
2146
+ const [search, setSearch] = React52.useState("");
2147
+ const [activeIndex, setActiveIndex] = React52.useState(0);
2148
+ const containerRef = React52.useRef(null);
2149
+ const listRef = React52.useRef(null);
2150
+ React52.useEffect(() => {
1848
2151
  if (open) {
1849
2152
  setSearch("");
1850
2153
  setActiveIndex(0);
@@ -1853,17 +2156,17 @@ function CommandPalette({ open, onClose, items, placeholder = "Type a command or
1853
2156
  document.body.style.overflow = "";
1854
2157
  }
1855
2158
  }, [open]);
1856
- const filteredItems = React18.useMemo(() => {
2159
+ const filteredItems = React52.useMemo(() => {
1857
2160
  if (!search) return items;
1858
2161
  const cleanSearch = search.toLowerCase();
1859
2162
  return items.filter(
1860
2163
  (item) => item.title.toLowerCase().includes(cleanSearch) || item.subtitle?.toLowerCase().includes(cleanSearch) || item.category?.toLowerCase().includes(cleanSearch)
1861
2164
  );
1862
2165
  }, [search, items]);
1863
- React18.useEffect(() => {
2166
+ React52.useEffect(() => {
1864
2167
  setActiveIndex(0);
1865
2168
  }, [filteredItems]);
1866
- React18.useEffect(() => {
2169
+ React52.useEffect(() => {
1867
2170
  if (!open) return;
1868
2171
  const handleKeyDown = (e) => {
1869
2172
  if (e.key === "Escape") {
@@ -1887,7 +2190,7 @@ function CommandPalette({ open, onClose, items, placeholder = "Type a command or
1887
2190
  document.addEventListener("keydown", handleKeyDown);
1888
2191
  return () => document.removeEventListener("keydown", handleKeyDown);
1889
2192
  }, [open, filteredItems, activeIndex, onClose]);
1890
- React18.useEffect(() => {
2193
+ React52.useEffect(() => {
1891
2194
  if (listRef.current) {
1892
2195
  const activeEl = listRef.current.querySelector('[aria-selected="true"]');
1893
2196
  if (activeEl) {
@@ -2057,7 +2360,3705 @@ function SidebarNav({ className, groups, currentHref, ...props }) {
2057
2360
  ] }, groupIdx)) });
2058
2361
  }
2059
2362
  SidebarNav.displayName = "SidebarNav";
2363
+ var fabVariants = cva(
2364
+ [
2365
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap select-none",
2366
+ "font-sans font-semibold leading-none",
2367
+ "border border-transparent rounded-full cursor-pointer shadow-lg",
2368
+ "transition-[background-color,color,border-color,box-shadow,transform] duration-150",
2369
+ "active:translate-y-px active:shadow-md",
2370
+ "focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45",
2371
+ "disabled:opacity-50 disabled:pointer-events-none"
2372
+ ],
2373
+ {
2374
+ variants: {
2375
+ variant: {
2376
+ primary: "bg-primary text-primary-foreground hover:bg-primary-hover",
2377
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover",
2378
+ outline: "bg-transparent text-foreground border-border hover:bg-muted hover:border-muted-foreground",
2379
+ danger: "bg-danger text-white hover:brightness-95"
2380
+ },
2381
+ size: {
2382
+ sm: "h-10 w-10 p-0 text-sm",
2383
+ md: "h-14 w-14 p-0 text-base",
2384
+ lg: "h-16 w-16 p-0 text-lg"
2385
+ },
2386
+ position: {
2387
+ "bottom-right": "fixed bottom-6 right-6 z-50",
2388
+ "bottom-left": "fixed bottom-6 left-6 z-50",
2389
+ "bottom-center": "fixed bottom-6 left-1/2 -translate-x-1/2 z-50",
2390
+ none: "relative"
2391
+ }
2392
+ },
2393
+ defaultVariants: { variant: "primary", size: "md", position: "bottom-right" }
2394
+ }
2395
+ );
2396
+ var Fab = React52.forwardRef(
2397
+ ({ className, variant, size, position, mobileOnly, icon, label, loading, disabled, ...props }, ref) => {
2398
+ return /* @__PURE__ */ jsx(
2399
+ "button",
2400
+ {
2401
+ ref,
2402
+ className: cn(
2403
+ fabVariants({ variant, size, position }),
2404
+ mobileOnly && "md:hidden",
2405
+ className
2406
+ ),
2407
+ ...props,
2408
+ disabled: disabled || loading,
2409
+ "aria-label": label,
2410
+ "aria-busy": loading || void 0,
2411
+ children: loading ? /* @__PURE__ */ jsx(
2412
+ "span",
2413
+ {
2414
+ "aria-hidden": true,
2415
+ className: "h-[1.2em] w-[1.2em] rounded-full border-2 border-current border-t-transparent animate-spin"
2416
+ }
2417
+ ) : /* @__PURE__ */ jsx("span", { className: "[&_svg]:h-[1.35em] [&_svg]:w-[1.35em] flex items-center justify-center", children: icon })
2418
+ }
2419
+ );
2420
+ }
2421
+ );
2422
+ Fab.displayName = "Fab";
2423
+ var BottomSheetContext = React52.createContext(null);
2424
+ function BottomSheet({
2425
+ open,
2426
+ onClose,
2427
+ children,
2428
+ maxWidth = "max-w-lg",
2429
+ className,
2430
+ showClose = true
2431
+ }) {
2432
+ const titleId = React52.useId();
2433
+ const descId = React52.useId();
2434
+ const containerRef = React52.useRef(null);
2435
+ const lastActiveElement = React52.useRef(null);
2436
+ const [mounted, setMounted] = React52.useState(open);
2437
+ React52.useEffect(() => {
2438
+ if (open) {
2439
+ setMounted(true);
2440
+ } else {
2441
+ const timer = setTimeout(() => setMounted(false), 200);
2442
+ return () => clearTimeout(timer);
2443
+ }
2444
+ }, [open]);
2445
+ React52.useEffect(() => {
2446
+ if (!open) return;
2447
+ lastActiveElement.current = document.activeElement;
2448
+ const container = containerRef.current;
2449
+ if (container) {
2450
+ const focusables = container.querySelectorAll(
2451
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
2452
+ );
2453
+ if (focusables.length > 0) {
2454
+ focusables[0].focus();
2455
+ setTimeout(() => {
2456
+ if (container.contains(document.activeElement)) return;
2457
+ focusables[0].focus();
2458
+ }, 50);
2459
+ } else {
2460
+ container.focus();
2461
+ }
2462
+ }
2463
+ const handleKeyDown = (e) => {
2464
+ if (e.key === "Escape") {
2465
+ onClose();
2466
+ return;
2467
+ }
2468
+ if (e.key === "Tab") {
2469
+ const container2 = containerRef.current;
2470
+ if (!container2) return;
2471
+ const focusables = Array.from(
2472
+ container2.querySelectorAll(
2473
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
2474
+ )
2475
+ );
2476
+ if (focusables.length === 0) {
2477
+ e.preventDefault();
2478
+ return;
2479
+ }
2480
+ const first = focusables[0];
2481
+ const last = focusables[focusables.length - 1];
2482
+ if (!container2.contains(document.activeElement)) {
2483
+ e.preventDefault();
2484
+ if (e.shiftKey) {
2485
+ last.focus();
2486
+ } else {
2487
+ first.focus();
2488
+ }
2489
+ return;
2490
+ }
2491
+ if (e.shiftKey) {
2492
+ if (document.activeElement === first || document.activeElement === container2) {
2493
+ last.focus();
2494
+ e.preventDefault();
2495
+ }
2496
+ } else {
2497
+ if (document.activeElement === last) {
2498
+ first.focus();
2499
+ e.preventDefault();
2500
+ }
2501
+ }
2502
+ }
2503
+ };
2504
+ window.addEventListener("keydown", handleKeyDown);
2505
+ document.body.style.overflow = "hidden";
2506
+ return () => {
2507
+ window.removeEventListener("keydown", handleKeyDown);
2508
+ document.body.style.overflow = "";
2509
+ if (lastActiveElement.current) {
2510
+ lastActiveElement.current.focus();
2511
+ lastActiveElement.current = null;
2512
+ }
2513
+ };
2514
+ }, [open, onClose]);
2515
+ if (!open && !mounted) return null;
2516
+ return /* @__PURE__ */ jsx(BottomSheetContext.Provider, { value: { titleId, descId }, children: /* @__PURE__ */ jsx(
2517
+ "div",
2518
+ {
2519
+ role: "presentation",
2520
+ onClick: (e) => e.target === e.currentTarget && onClose(),
2521
+ className: cn(
2522
+ "fixed inset-0 z-[100] grid items-end sm:place-items-center p-0 sm:p-6 backdrop-blur-[3px] transition-all duration-200",
2523
+ "bg-[color-mix(in_oklch,#09090b_55%,transparent)]",
2524
+ open ? "opacity-100 pointer-events-auto visible" : "opacity-0 pointer-events-none invisible"
2525
+ ),
2526
+ children: /* @__PURE__ */ jsxs(
2527
+ "div",
2528
+ {
2529
+ ref: containerRef,
2530
+ role: "dialog",
2531
+ "aria-modal": "true",
2532
+ "aria-labelledby": titleId,
2533
+ "aria-describedby": descId,
2534
+ tabIndex: -1,
2535
+ className: cn(
2536
+ "relative w-full bg-surface border-t sm:border border-border shadow-kj-lg transition-all duration-200 outline-none",
2537
+ "rounded-t-kj-2xl sm:rounded-kj-2xl flex flex-col max-h-[85vh] sm:max-h-[90vh]",
2538
+ // Mobile sliding & Desktop scale/opacity transitions
2539
+ open ? "translate-y-0 sm:scale-100 sm:opacity-100" : "translate-y-full sm:scale-95 sm:opacity-0",
2540
+ // Mobile styling vs Desktop styling
2541
+ "bottom-0 sm:bottom-auto",
2542
+ maxWidth,
2543
+ className
2544
+ ),
2545
+ children: [
2546
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center py-2 sm:hidden cursor-grab", children: /* @__PURE__ */ jsx("div", { className: "w-12 h-1.5 rounded-full bg-muted-foreground/20" }) }),
2547
+ showClose && /* @__PURE__ */ jsx(
2548
+ "button",
2549
+ {
2550
+ type: "button",
2551
+ onClick: onClose,
2552
+ "aria-label": "Close",
2553
+ 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 z-10",
2554
+ 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" }) })
2555
+ }
2556
+ ),
2557
+ children
2558
+ ]
2559
+ }
2560
+ )
2561
+ }
2562
+ ) });
2563
+ }
2564
+ function BottomSheetHeader({ className, ...props }) {
2565
+ return /* @__PURE__ */ jsx("div", { className: cn("p-6 flex flex-col gap-1.5 border-b border-border", className), ...props });
2566
+ }
2567
+ function BottomSheetTitle({ className, id, children, ...props }) {
2568
+ const ctx = React52.useContext(BottomSheetContext);
2569
+ return /* @__PURE__ */ jsx("h2", { id: id ?? ctx?.titleId, className: cn("m-0 text-[1.15rem] font-bold tracking-[-0.01em]", className), ...props, children });
2570
+ }
2571
+ function BottomSheetDescription({ className, id, ...props }) {
2572
+ const ctx = React52.useContext(BottomSheetContext);
2573
+ return /* @__PURE__ */ jsx("p", { id: id ?? ctx?.descId, className: cn("m-0 text-[0.9rem] text-muted-foreground", className), ...props });
2574
+ }
2575
+ function BottomSheetContent({ className, ...props }) {
2576
+ return /* @__PURE__ */ jsx("div", { className: cn("p-6 overflow-y-auto flex-1", className), ...props });
2577
+ }
2578
+ function BottomSheetFooter({ className, ...props }) {
2579
+ return /* @__PURE__ */ jsx("div", { className: cn("p-6 border-t border-border flex gap-2.5 justify-end mt-auto", className), ...props });
2580
+ }
2581
+ var kbdVariants = cva(
2582
+ "inline-flex items-center justify-center font-mono font-medium text-muted-foreground bg-muted border border-border border-b-2 rounded-kj-sm select-none",
2583
+ {
2584
+ variants: {
2585
+ size: {
2586
+ sm: "h-5 min-w-5 px-1 text-[0.68rem]",
2587
+ md: "h-6 min-w-6 px-1.5 text-[0.78rem]"
2588
+ }
2589
+ },
2590
+ defaultVariants: { size: "sm" }
2591
+ }
2592
+ );
2593
+ var Kbd = React52.forwardRef(
2594
+ ({ className, size, keys, children, ...props }, ref) => {
2595
+ if (keys && keys.length > 0) {
2596
+ return /* @__PURE__ */ jsx(
2597
+ "span",
2598
+ {
2599
+ ref,
2600
+ className: cn("inline-flex items-center gap-1", className),
2601
+ ...props,
2602
+ children: keys.map((k, i) => /* @__PURE__ */ jsx("kbd", { className: kbdVariants({ size }), children: k }, i))
2603
+ }
2604
+ );
2605
+ }
2606
+ return /* @__PURE__ */ jsx("kbd", { ref, className: cn(kbdVariants({ size }), className), ...props, children });
2607
+ }
2608
+ );
2609
+ Kbd.displayName = "Kbd";
2610
+ var CodeBlock = React52.forwardRef(
2611
+ ({ className, code, language, filename, copyable = true, maxHeight, ...props }, ref) => {
2612
+ const [copied, setCopied] = React52.useState(false);
2613
+ const canCopy = copyable && typeof navigator !== "undefined" && !!navigator.clipboard;
2614
+ const onCopy = () => {
2615
+ navigator.clipboard.writeText(code).then(
2616
+ () => {
2617
+ setCopied(true);
2618
+ window.setTimeout(() => setCopied(false), 1500);
2619
+ },
2620
+ () => {
2621
+ }
2622
+ );
2623
+ };
2624
+ const label = filename ?? language;
2625
+ return /* @__PURE__ */ jsxs(
2626
+ "div",
2627
+ {
2628
+ ref,
2629
+ className: cn("rounded-kj-md border border-border bg-muted/50 overflow-hidden", className),
2630
+ ...props,
2631
+ children: [
2632
+ (label || canCopy) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3.5 py-2 border-b border-border", children: [
2633
+ /* @__PURE__ */ jsx("span", { className: "text-[0.72rem] font-mono text-muted-foreground", children: label }),
2634
+ canCopy && /* @__PURE__ */ jsx(
2635
+ "button",
2636
+ {
2637
+ type: "button",
2638
+ onClick: onCopy,
2639
+ className: "text-[0.72rem] font-medium text-muted-foreground hover:text-foreground transition-colors cursor-pointer bg-transparent border-0 p-0",
2640
+ children: copied ? "Copied" : "Copy"
2641
+ }
2642
+ )
2643
+ ] }),
2644
+ /* @__PURE__ */ jsx(
2645
+ "pre",
2646
+ {
2647
+ className: "m-0 px-3.5 py-3 overflow-x-auto text-[0.8rem] leading-relaxed font-mono text-foreground",
2648
+ style: maxHeight ? { maxHeight, overflowY: "auto" } : void 0,
2649
+ children: /* @__PURE__ */ jsx("code", { children: code })
2650
+ }
2651
+ )
2652
+ ]
2653
+ }
2654
+ );
2655
+ }
2656
+ );
2657
+ CodeBlock.displayName = "CodeBlock";
2658
+ function Separator({ orientation = "horizontal", decorative = true, className }) {
2659
+ const semanticProps = decorative ? { role: "none", "aria-hidden": true } : { role: "separator", "aria-orientation": orientation };
2660
+ return /* @__PURE__ */ jsx(
2661
+ "div",
2662
+ {
2663
+ className: cn(
2664
+ "shrink-0 bg-border",
2665
+ orientation === "horizontal" ? "h-px w-full" : "w-px h-full",
2666
+ className
2667
+ ),
2668
+ ...semanticProps
2669
+ }
2670
+ );
2671
+ }
2672
+ var PopoverContext = React52.createContext(null);
2673
+ function usePopoverContext(part) {
2674
+ const ctx = React52.useContext(PopoverContext);
2675
+ if (!ctx) throw new Error(`${part} must be used inside <Popover>`);
2676
+ return ctx;
2677
+ }
2678
+ function Popover({ children, className, open: openProp, onOpenChange }) {
2679
+ const [openState, setOpenState] = React52.useState(false);
2680
+ const controlled = openProp !== void 0;
2681
+ const open = controlled ? openProp : openState;
2682
+ const triggerRef = React52.useRef(null);
2683
+ const ref = React52.useRef(null);
2684
+ const setOpen = React52.useCallback(
2685
+ (v) => {
2686
+ if (!controlled) setOpenState(v);
2687
+ onOpenChange?.(v);
2688
+ },
2689
+ [controlled, onOpenChange]
2690
+ );
2691
+ React52.useEffect(() => {
2692
+ if (!open) return;
2693
+ const onDoc = (e) => {
2694
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
2695
+ };
2696
+ const onEsc = (e) => {
2697
+ if (e.key === "Escape") {
2698
+ setOpen(false);
2699
+ triggerRef.current?.focus();
2700
+ }
2701
+ };
2702
+ document.addEventListener("mousedown", onDoc);
2703
+ document.addEventListener("keydown", onEsc);
2704
+ return () => {
2705
+ document.removeEventListener("mousedown", onDoc);
2706
+ document.removeEventListener("keydown", onEsc);
2707
+ };
2708
+ }, [open, setOpen]);
2709
+ return /* @__PURE__ */ jsx(PopoverContext.Provider, { value: { open, setOpen, triggerRef }, children: /* @__PURE__ */ jsx("div", { ref, className: cn("relative inline-block", className), children }) });
2710
+ }
2711
+ var PopoverTrigger = React52.forwardRef(({ onClick, asChild, ...props }, ref) => {
2712
+ const ctx = usePopoverContext("PopoverTrigger");
2713
+ const setRefs = (node) => {
2714
+ ctx.triggerRef.current = node;
2715
+ if (typeof ref === "function") ref(node);
2716
+ else if (ref) ref.current = node;
2717
+ };
2718
+ if (asChild) {
2719
+ const child = React52.Children.only(props.children);
2720
+ return React52.cloneElement(child, {
2721
+ ref: setRefs,
2722
+ ...props,
2723
+ ...child.props,
2724
+ onClick: (e) => {
2725
+ ctx.setOpen(!ctx.open);
2726
+ onClick?.(e);
2727
+ child.props.onClick?.(e);
2728
+ },
2729
+ "aria-haspopup": "dialog",
2730
+ "aria-expanded": ctx.open
2731
+ });
2732
+ }
2733
+ return /* @__PURE__ */ jsx(
2734
+ "button",
2735
+ {
2736
+ ref: setRefs,
2737
+ ...props,
2738
+ type: "button",
2739
+ "aria-haspopup": "dialog",
2740
+ "aria-expanded": ctx.open,
2741
+ onClick: (e) => {
2742
+ ctx.setOpen(!ctx.open);
2743
+ onClick?.(e);
2744
+ }
2745
+ }
2746
+ );
2747
+ });
2748
+ PopoverTrigger.displayName = "PopoverTrigger";
2749
+ var sideAlignClasses = {
2750
+ "bottom-start": "top-[calc(100%+6px)] left-0",
2751
+ "bottom-center": "top-[calc(100%+6px)] left-1/2 -translate-x-1/2",
2752
+ "bottom-end": "top-[calc(100%+6px)] right-0",
2753
+ "top-start": "bottom-[calc(100%+6px)] left-0",
2754
+ "top-center": "bottom-[calc(100%+6px)] left-1/2 -translate-x-1/2",
2755
+ "top-end": "bottom-[calc(100%+6px)] right-0",
2756
+ "left-start": "right-[calc(100%+6px)] top-0",
2757
+ "left-center": "right-[calc(100%+6px)] top-1/2 -translate-y-1/2",
2758
+ "left-end": "right-[calc(100%+6px)] bottom-0",
2759
+ "right-start": "left-[calc(100%+6px)] top-0",
2760
+ "right-center": "left-[calc(100%+6px)] top-1/2 -translate-y-1/2",
2761
+ "right-end": "left-[calc(100%+6px)] bottom-0"
2762
+ };
2763
+ function PopoverContent({
2764
+ className,
2765
+ side = "bottom",
2766
+ align = "center",
2767
+ children,
2768
+ ...props
2769
+ }) {
2770
+ const ctx = usePopoverContext("PopoverContent");
2771
+ const ref = React52.useRef(null);
2772
+ React52.useEffect(() => {
2773
+ if (ctx.open) ref.current?.focus();
2774
+ }, [ctx.open]);
2775
+ if (!ctx.open) return null;
2776
+ return /* @__PURE__ */ jsx(
2777
+ "div",
2778
+ {
2779
+ ref,
2780
+ role: "dialog",
2781
+ tabIndex: -1,
2782
+ className: cn(
2783
+ "absolute z-40 min-w-[200px] p-4 rounded-kj-md bg-surface border border-border shadow-kj-lg outline-none",
2784
+ "animate-[kjpop_.12s_ease]",
2785
+ sideAlignClasses[`${side}-${align}`],
2786
+ className
2787
+ ),
2788
+ ...props,
2789
+ children
2790
+ }
2791
+ );
2792
+ }
2793
+ function toArray(v) {
2794
+ if (v == null) return [];
2795
+ return Array.isArray(v) ? v : [v];
2796
+ }
2797
+ var Combobox = React52.forwardRef(
2798
+ function Combobox2(props, ref) {
2799
+ const {
2800
+ options,
2801
+ placeholder = "Select\u2026",
2802
+ searchPlaceholder = "Search\u2026",
2803
+ emptyMessage = "No results",
2804
+ disabled = false,
2805
+ error = false,
2806
+ required = false,
2807
+ className,
2808
+ id,
2809
+ name,
2810
+ "aria-describedby": describedBy
2811
+ } = props;
2812
+ const multiple = props.multiple ?? false;
2813
+ const reactId = React52.useId();
2814
+ const baseId = id ?? reactId;
2815
+ const listId = `${baseId}-listbox`;
2816
+ const optionId = (i) => `${baseId}-opt-${i}`;
2817
+ const controlled = props.value !== void 0;
2818
+ const [internal, setInternal] = React52.useState(() => toArray(props.defaultValue));
2819
+ const selected = controlled ? toArray(props.value) : internal;
2820
+ const [open, setOpen] = React52.useState(false);
2821
+ const [search, setSearch] = React52.useState("");
2822
+ const [activeIndex, setActiveIndex] = React52.useState(0);
2823
+ const containerRef = React52.useRef(null);
2824
+ const triggerRef = React52.useRef(null);
2825
+ const inputRef = React52.useRef(null);
2826
+ const listRef = React52.useRef(null);
2827
+ const setTriggerRef = (node) => {
2828
+ triggerRef.current = node;
2829
+ if (typeof ref === "function") ref(node);
2830
+ else if (ref) ref.current = node;
2831
+ };
2832
+ const filtered = React52.useMemo(() => {
2833
+ const q = search.trim().toLowerCase();
2834
+ return q ? options.filter((o) => o.label.toLowerCase().includes(q)) : options;
2835
+ }, [search, options]);
2836
+ const firstEnabled = React52.useCallback(() => {
2837
+ const i = filtered.findIndex((o) => !o.disabled);
2838
+ return i === -1 ? 0 : i;
2839
+ }, [filtered]);
2840
+ const labelFor = (v) => options.find((o) => o.value === v)?.label ?? v;
2841
+ const emit = (next) => {
2842
+ if (!controlled) setInternal(next);
2843
+ if (props.multiple) {
2844
+ props.onChange?.(next);
2845
+ } else {
2846
+ props.onChange?.(next[0] ?? "");
2847
+ }
2848
+ };
2849
+ const closePopup = React52.useCallback(() => {
2850
+ setOpen(false);
2851
+ setSearch("");
2852
+ }, []);
2853
+ const openPopup = () => {
2854
+ if (!disabled) setOpen(true);
2855
+ };
2856
+ const handleSelect = (opt) => {
2857
+ if (opt.disabled) return;
2858
+ if (multiple) {
2859
+ const next = selected.includes(opt.value) ? selected.filter((v) => v !== opt.value) : [...selected, opt.value];
2860
+ emit(next);
2861
+ inputRef.current?.focus();
2862
+ } else {
2863
+ emit([opt.value]);
2864
+ closePopup();
2865
+ triggerRef.current?.focus();
2866
+ }
2867
+ };
2868
+ const removeValue = (value) => {
2869
+ emit(selected.filter((v) => v !== value));
2870
+ };
2871
+ React52.useEffect(() => {
2872
+ if (open) setActiveIndex(firstEnabled());
2873
+ }, [open, firstEnabled]);
2874
+ React52.useEffect(() => {
2875
+ if (open) inputRef.current?.focus();
2876
+ }, [open]);
2877
+ React52.useEffect(() => {
2878
+ if (!open) return;
2879
+ const onDown = (e) => {
2880
+ if (containerRef.current && !containerRef.current.contains(e.target)) closePopup();
2881
+ };
2882
+ document.addEventListener("mousedown", onDown);
2883
+ return () => document.removeEventListener("mousedown", onDown);
2884
+ }, [open, closePopup]);
2885
+ React52.useEffect(() => {
2886
+ const el = listRef.current?.querySelector('[data-active="true"]');
2887
+ el?.scrollIntoView({ block: "nearest" });
2888
+ }, [activeIndex]);
2889
+ const moveActive = (dir) => {
2890
+ if (filtered.length === 0) return;
2891
+ setActiveIndex((prev) => {
2892
+ let i = prev;
2893
+ for (let step = 0; step < filtered.length; step++) {
2894
+ i = (i + dir + filtered.length) % filtered.length;
2895
+ if (!filtered[i]?.disabled) return i;
2896
+ }
2897
+ return prev;
2898
+ });
2899
+ };
2900
+ const onInputKeyDown = (e) => {
2901
+ switch (e.key) {
2902
+ case "ArrowDown":
2903
+ e.preventDefault();
2904
+ moveActive(1);
2905
+ break;
2906
+ case "ArrowUp":
2907
+ e.preventDefault();
2908
+ moveActive(-1);
2909
+ break;
2910
+ case "Home":
2911
+ e.preventDefault();
2912
+ setActiveIndex(firstEnabled());
2913
+ break;
2914
+ case "End":
2915
+ e.preventDefault();
2916
+ for (let i = filtered.length - 1; i >= 0; i--) {
2917
+ if (!filtered[i].disabled) {
2918
+ setActiveIndex(i);
2919
+ break;
2920
+ }
2921
+ }
2922
+ break;
2923
+ case "Enter":
2924
+ e.preventDefault();
2925
+ if (filtered[activeIndex]) handleSelect(filtered[activeIndex]);
2926
+ break;
2927
+ case "Escape":
2928
+ e.preventDefault();
2929
+ closePopup();
2930
+ triggerRef.current?.focus();
2931
+ break;
2932
+ case "Backspace":
2933
+ if (multiple && search === "" && selected.length > 0) {
2934
+ removeValue(selected[selected.length - 1]);
2935
+ }
2936
+ break;
2937
+ }
2938
+ };
2939
+ const onTriggerKeyDown = (e) => {
2940
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
2941
+ e.preventDefault();
2942
+ openPopup();
2943
+ }
2944
+ };
2945
+ const showPlaceholder = multiple ? selected.length === 0 : !selected[0];
2946
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative w-full", className), children: [
2947
+ /* @__PURE__ */ jsxs(
2948
+ "div",
2949
+ {
2950
+ className: cn(
2951
+ "relative flex w-full items-center gap-1.5 flex-wrap border rounded-kj-md bg-surface",
2952
+ "pl-[0.85rem] pr-9 py-[calc(0.45rem*var(--kj-density,1))] min-h-[calc(2.5rem*var(--kj-density,1))]",
2953
+ "transition-[border-color,box-shadow] duration-150",
2954
+ "focus-within:outline-none focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/30",
2955
+ disabled && "bg-muted cursor-not-allowed",
2956
+ error ? "border-danger" : "border-input"
2957
+ ),
2958
+ children: [
2959
+ multiple && selected.map((value) => /* @__PURE__ */ jsxs(
2960
+ "span",
2961
+ {
2962
+ className: "inline-flex items-center gap-1 rounded-kj-sm bg-primary/10 text-primary text-xs font-medium pl-2 pr-1 py-0.5",
2963
+ children: [
2964
+ labelFor(value),
2965
+ /* @__PURE__ */ jsx(
2966
+ "button",
2967
+ {
2968
+ type: "button",
2969
+ "aria-label": `Remove ${labelFor(value)}`,
2970
+ disabled,
2971
+ onClick: (e) => {
2972
+ e.stopPropagation();
2973
+ removeValue(value);
2974
+ },
2975
+ className: "rounded-full p-0.5 leading-none hover:bg-primary/20",
2976
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-3 w-3", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18" }) })
2977
+ }
2978
+ )
2979
+ ]
2980
+ },
2981
+ value
2982
+ )),
2983
+ /* @__PURE__ */ jsx(
2984
+ "button",
2985
+ {
2986
+ ref: setTriggerRef,
2987
+ type: "button",
2988
+ id,
2989
+ disabled,
2990
+ "aria-haspopup": "listbox",
2991
+ "aria-expanded": open,
2992
+ "aria-controls": open && filtered.length > 0 ? listId : void 0,
2993
+ "aria-invalid": error || void 0,
2994
+ "aria-required": required || void 0,
2995
+ "aria-describedby": describedBy,
2996
+ onClick: () => open ? closePopup() : openPopup(),
2997
+ onKeyDown: onTriggerKeyDown,
2998
+ className: cn(
2999
+ "flex-1 min-w-[60px] text-left bg-transparent border-none outline-none cursor-pointer text-sm disabled:cursor-not-allowed",
3000
+ showPlaceholder && "text-muted-foreground"
3001
+ ),
3002
+ children: multiple ? selected.length === 0 ? placeholder : "" : (selected[0] ? labelFor(selected[0]) : "") || placeholder
3003
+ }
3004
+ ),
3005
+ name && multiple && selected.map((v) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: v }, v)),
3006
+ name && !multiple && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected[0] ?? "" }),
3007
+ /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) }) })
3008
+ ]
3009
+ }
3010
+ ),
3011
+ open && /* @__PURE__ */ jsxs("div", { className: "absolute z-40 top-[calc(100%+6px)] left-0 w-full min-w-[12rem] bg-surface border border-border rounded-kj-md shadow-kj-lg overflow-hidden animate-[kjpop_.12s_ease]", children: [
3012
+ /* @__PURE__ */ jsx("div", { className: "p-2 border-b border-border", children: /* @__PURE__ */ jsx(
3013
+ "input",
3014
+ {
3015
+ ref: inputRef,
3016
+ type: "text",
3017
+ role: "combobox",
3018
+ "aria-autocomplete": "list",
3019
+ "aria-expanded": open,
3020
+ "aria-controls": filtered.length > 0 ? listId : void 0,
3021
+ "aria-activedescendant": filtered[activeIndex] ? optionId(activeIndex) : void 0,
3022
+ "aria-label": searchPlaceholder,
3023
+ placeholder: searchPlaceholder,
3024
+ value: search,
3025
+ onChange: (e) => setSearch(e.target.value),
3026
+ onKeyDown: onInputKeyDown,
3027
+ className: "w-full bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-none px-2 py-1"
3028
+ }
3029
+ ) }),
3030
+ filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx(
3031
+ "div",
3032
+ {
3033
+ ref: listRef,
3034
+ id: listId,
3035
+ role: "listbox",
3036
+ "aria-multiselectable": multiple || void 0,
3037
+ "aria-label": placeholder,
3038
+ className: "max-h-60 overflow-y-auto p-1",
3039
+ children: filtered.map((opt, i) => {
3040
+ const isSelected = selected.includes(opt.value);
3041
+ const isActive = i === activeIndex;
3042
+ return /* @__PURE__ */ jsxs(
3043
+ "div",
3044
+ {
3045
+ id: optionId(i),
3046
+ role: "option",
3047
+ "aria-selected": isSelected,
3048
+ "aria-disabled": opt.disabled || void 0,
3049
+ "data-active": isActive || void 0,
3050
+ onMouseEnter: () => !opt.disabled && setActiveIndex(i),
3051
+ onClick: () => handleSelect(opt),
3052
+ className: cn(
3053
+ "flex items-center justify-between gap-2 px-3 py-2 rounded-kj-sm text-sm cursor-pointer select-none",
3054
+ opt.disabled && "text-muted-foreground opacity-50 cursor-not-allowed",
3055
+ !opt.disabled && isActive && "bg-primary/10 text-primary",
3056
+ !opt.disabled && !isActive && "text-foreground hover:bg-muted"
3057
+ ),
3058
+ children: [
3059
+ /* @__PURE__ */ jsx("span", { children: opt.label }),
3060
+ isSelected && /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4 text-primary shrink-0", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m5 12 5 5L20 7" }) })
3061
+ ]
3062
+ },
3063
+ opt.value
3064
+ );
3065
+ })
3066
+ }
3067
+ )
3068
+ ] })
3069
+ ] });
3070
+ }
3071
+ );
3072
+ Combobox.displayName = "Combobox";
3073
+ var ComboboxField = React52.forwardRef(
3074
+ function ComboboxField2({ label, hint, error, required, className, ...props }, ref) {
3075
+ return /* @__PURE__ */ jsx(FormField, { label, hint, error, required, className, children: /* @__PURE__ */ jsx(Combobox, { ref, error: !!error, ...props }) });
3076
+ }
3077
+ );
3078
+ ComboboxField.displayName = "ComboboxField";
3079
+
3080
+ // src/components/calendar-internals.ts
3081
+ function startOfDay(d) {
3082
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
3083
+ }
3084
+ function isSameDay(a, b) {
3085
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
3086
+ }
3087
+ function addDays(d, n) {
3088
+ const r = new Date(d);
3089
+ r.setDate(r.getDate() + n);
3090
+ return r;
3091
+ }
3092
+ function addMonths(d, n) {
3093
+ const r = new Date(d);
3094
+ r.setMonth(r.getMonth() + n);
3095
+ return r;
3096
+ }
3097
+ function clampToMonth(year, month, day) {
3098
+ const daysInTarget = new Date(year, month + 1, 0).getDate();
3099
+ return new Date(year, month, Math.min(day, daysInTarget));
3100
+ }
3101
+ function addMonthsClamped(d, n) {
3102
+ return clampToMonth(d.getFullYear(), d.getMonth() + n, d.getDate());
3103
+ }
3104
+ function addYearsClamped(d, n) {
3105
+ return clampToMonth(d.getFullYear() + n, d.getMonth(), d.getDate());
3106
+ }
3107
+ function startOfMonth(d) {
3108
+ return new Date(d.getFullYear(), d.getMonth(), 1);
3109
+ }
3110
+ function startOfWeek(d) {
3111
+ const r = startOfDay(d);
3112
+ r.setDate(r.getDate() - r.getDay());
3113
+ return r;
3114
+ }
3115
+ function buildGridDays(viewMonth) {
3116
+ const gridStart = startOfWeek(startOfMonth(viewMonth));
3117
+ return Array.from({ length: 42 }, (_, i) => addDays(gridStart, i));
3118
+ }
3119
+ function dateKey(d) {
3120
+ return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
3121
+ }
3122
+ function toISODateString(d) {
3123
+ const y = d.getFullYear();
3124
+ const m = String(d.getMonth() + 1).padStart(2, "0");
3125
+ const day = String(d.getDate()).padStart(2, "0");
3126
+ return `${y}-${m}-${day}`;
3127
+ }
3128
+ var monthLabelFormat = new Intl.DateTimeFormat(void 0, { month: "long", year: "numeric" });
3129
+ var fullDateFormat = new Intl.DateTimeFormat(void 0, { dateStyle: "full" });
3130
+ var weekdayShortFormat = new Intl.DateTimeFormat(void 0, { weekday: "short" });
3131
+ var weekdayLongFormat = new Intl.DateTimeFormat(void 0, { weekday: "long" });
3132
+ var weekdayLabels = Array.from({ length: 7 }, (_, i) => {
3133
+ const d = addDays(startOfWeek(/* @__PURE__ */ new Date()), i);
3134
+ return { key: i, short: weekdayShortFormat.format(d), long: weekdayLongFormat.format(d) };
3135
+ });
3136
+ var CalendarGrid = React52.forwardRef(function CalendarGrid2({ viewMonth, isDayDisabled, cellState, onSelectDay, onFocusDay, initialFocusDate, className }, ref) {
3137
+ const gridDays = React52.useMemo(() => buildGridDays(viewMonth), [viewMonth]);
3138
+ const inViewMonth = (d) => d.getMonth() === viewMonth.getMonth() && d.getFullYear() === viewMonth.getFullYear();
3139
+ const [focusedDate, setFocusedDate] = React52.useState(() => startOfDay(initialFocusDate));
3140
+ const pendingFocusRef = React52.useRef(false);
3141
+ const gridRef = React52.useRef(null);
3142
+ const rovingTarget = inViewMonth(focusedDate) && !isDayDisabled(focusedDate) ? focusedDate : gridDays.find((d) => inViewMonth(d) && !isDayDisabled(d)) ?? gridDays.find((d) => inViewMonth(d)) ?? gridDays[0];
3143
+ const moveFocusTo = (next, viaKeyboard) => {
3144
+ pendingFocusRef.current = viaKeyboard;
3145
+ setFocusedDate(next);
3146
+ onFocusDay?.(next);
3147
+ };
3148
+ React52.useEffect(() => {
3149
+ if (!pendingFocusRef.current) return;
3150
+ pendingFocusRef.current = false;
3151
+ const el = gridRef.current?.querySelector(`[data-date="${dateKey(rovingTarget)}"]`);
3152
+ el?.focus();
3153
+ }, [rovingTarget]);
3154
+ const step = (from, delta) => {
3155
+ let next = addDays(from, delta);
3156
+ let guard = 0;
3157
+ while (isDayDisabled(next) && guard < 1e3) {
3158
+ next = addDays(next, delta);
3159
+ guard++;
3160
+ }
3161
+ return isDayDisabled(next) ? from : next;
3162
+ };
3163
+ const selectDay = (d) => {
3164
+ if (isDayDisabled(d)) return;
3165
+ moveFocusTo(d, false);
3166
+ onSelectDay(d);
3167
+ };
3168
+ const onGridKeyDown = (e) => {
3169
+ switch (e.key) {
3170
+ case "ArrowRight":
3171
+ e.preventDefault();
3172
+ moveFocusTo(step(rovingTarget, 1), true);
3173
+ break;
3174
+ case "ArrowLeft":
3175
+ e.preventDefault();
3176
+ moveFocusTo(step(rovingTarget, -1), true);
3177
+ break;
3178
+ case "ArrowDown":
3179
+ e.preventDefault();
3180
+ moveFocusTo(step(rovingTarget, 7), true);
3181
+ break;
3182
+ case "ArrowUp":
3183
+ e.preventDefault();
3184
+ moveFocusTo(step(rovingTarget, -7), true);
3185
+ break;
3186
+ case "Home": {
3187
+ e.preventDefault();
3188
+ const target = startOfWeek(rovingTarget);
3189
+ moveFocusTo(isDayDisabled(target) ? step(target, 1) : target, true);
3190
+ break;
3191
+ }
3192
+ case "End": {
3193
+ e.preventDefault();
3194
+ const target = addDays(startOfWeek(rovingTarget), 6);
3195
+ moveFocusTo(isDayDisabled(target) ? step(target, -1) : target, true);
3196
+ break;
3197
+ }
3198
+ case "PageUp": {
3199
+ e.preventDefault();
3200
+ const target = e.shiftKey ? addYearsClamped(rovingTarget, -1) : addMonthsClamped(rovingTarget, -1);
3201
+ moveFocusTo(isDayDisabled(target) ? step(target, -1) : target, true);
3202
+ break;
3203
+ }
3204
+ case "PageDown": {
3205
+ e.preventDefault();
3206
+ const target = e.shiftKey ? addYearsClamped(rovingTarget, 1) : addMonthsClamped(rovingTarget, 1);
3207
+ moveFocusTo(isDayDisabled(target) ? step(target, 1) : target, true);
3208
+ break;
3209
+ }
3210
+ case "Enter":
3211
+ case " ":
3212
+ e.preventDefault();
3213
+ selectDay(rovingTarget);
3214
+ break;
3215
+ }
3216
+ };
3217
+ return /* @__PURE__ */ jsx("div", { ref, className: cn(className), children: /* @__PURE__ */ jsxs("div", { ref: gridRef, role: "grid", "aria-label": monthLabelFormat.format(viewMonth), onKeyDown: onGridKeyDown, children: [
3218
+ /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7", children: weekdayLabels.map((w) => /* @__PURE__ */ jsx(
3219
+ "div",
3220
+ {
3221
+ role: "columnheader",
3222
+ "aria-label": w.long,
3223
+ className: "h-8 flex items-center justify-center text-xs font-medium text-muted-foreground",
3224
+ children: w.short
3225
+ },
3226
+ w.key
3227
+ )) }),
3228
+ Array.from({ length: 6 }, (_, week) => /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7", children: gridDays.slice(week * 7, week * 7 + 7).map((day) => {
3229
+ if (!inViewMonth(day)) {
3230
+ return /* @__PURE__ */ jsx("div", { role: "gridcell", "aria-hidden": "true", className: "h-9" }, dateKey(day));
3231
+ }
3232
+ const disabled = isDayDisabled(day);
3233
+ const state = cellState(day);
3234
+ const isRoving = isSameDay(day, rovingTarget);
3235
+ const isEndpoint = !!(state.selected || state.rangeStart || state.rangeEnd);
3236
+ return /* @__PURE__ */ jsx(
3237
+ "button",
3238
+ {
3239
+ type: "button",
3240
+ role: "gridcell",
3241
+ "data-date": dateKey(day),
3242
+ tabIndex: isRoving ? 0 : -1,
3243
+ "aria-selected": isEndpoint,
3244
+ "aria-disabled": disabled || void 0,
3245
+ "aria-label": fullDateFormat.format(day),
3246
+ onMouseEnter: () => onFocusDay?.(day),
3247
+ onClick: () => selectDay(day),
3248
+ className: cn(
3249
+ "h-9 w-9 mx-auto flex items-center justify-center text-sm",
3250
+ state.inRange && !isEndpoint ? "rounded-none" : "rounded-full",
3251
+ "focus:outline-none focus:ring-[3px] focus:ring-ring/30",
3252
+ disabled && "text-muted-foreground opacity-40 cursor-not-allowed",
3253
+ !disabled && isEndpoint && "bg-primary text-primary-foreground",
3254
+ !disabled && state.inRange && !isEndpoint && "bg-primary/15 text-foreground",
3255
+ !disabled && !isEndpoint && !state.inRange && state.today && "ring-1 ring-primary text-primary font-medium",
3256
+ !disabled && !isEndpoint && !state.inRange && !state.today && "text-foreground hover:bg-muted"
3257
+ ),
3258
+ children: day.getDate()
3259
+ },
3260
+ dateKey(day)
3261
+ );
3262
+ }) }, week))
3263
+ ] }) });
3264
+ });
3265
+ CalendarGrid.displayName = "CalendarGrid";
3266
+ var Calendar = React52.forwardRef(
3267
+ function Calendar2({ value, defaultValue, onChange, month, defaultMonth, onMonthChange, min, max, disabledDates, className }, ref) {
3268
+ const controlled = value !== void 0;
3269
+ const [internalValue, setInternalValue] = React52.useState(defaultValue);
3270
+ const selected = controlled ? value : internalValue;
3271
+ const monthControlled = month !== void 0;
3272
+ const [internalMonth, setInternalMonth] = React52.useState(
3273
+ () => startOfMonth(month ?? defaultMonth ?? selected ?? /* @__PURE__ */ new Date())
3274
+ );
3275
+ const viewMonth = monthControlled ? startOfMonth(month) : internalMonth;
3276
+ const setViewMonth = (next) => {
3277
+ const normalized = startOfMonth(next);
3278
+ if (!monthControlled) setInternalMonth(normalized);
3279
+ onMonthChange?.(normalized);
3280
+ };
3281
+ const isDayDisabled = React52.useCallback(
3282
+ (d) => {
3283
+ if (min && d < startOfDay(min)) return true;
3284
+ if (max && d > startOfDay(max)) return true;
3285
+ return disabledDates?.(d) ?? false;
3286
+ },
3287
+ [min, max, disabledDates]
3288
+ );
3289
+ const commitValue = (d) => {
3290
+ if (!controlled) setInternalValue(d);
3291
+ onChange?.(d);
3292
+ };
3293
+ const today = /* @__PURE__ */ new Date();
3294
+ const cellState = (d) => ({
3295
+ selected: selected ? isSameDay(d, selected) : false,
3296
+ today: isSameDay(d, today)
3297
+ });
3298
+ const inViewMonth = (d) => d.getMonth() === viewMonth.getMonth() && d.getFullYear() === viewMonth.getFullYear();
3299
+ const handleFocusDay = (d) => {
3300
+ if (!inViewMonth(d)) setViewMonth(d);
3301
+ };
3302
+ return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-[280px] select-none", className), children: [
3303
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
3304
+ /* @__PURE__ */ jsx(
3305
+ "button",
3306
+ {
3307
+ type: "button",
3308
+ "aria-label": "Previous month",
3309
+ onClick: () => setViewMonth(addMonths(viewMonth, -1)),
3310
+ className: "p-1.5 rounded-kj-sm text-muted-foreground hover:bg-muted hover:text-foreground",
3311
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) })
3312
+ }
3313
+ ),
3314
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: monthLabelFormat.format(viewMonth) }),
3315
+ /* @__PURE__ */ jsx(
3316
+ "button",
3317
+ {
3318
+ type: "button",
3319
+ "aria-label": "Next month",
3320
+ onClick: () => setViewMonth(addMonths(viewMonth, 1)),
3321
+ className: "p-1.5 rounded-kj-sm text-muted-foreground hover:bg-muted hover:text-foreground",
3322
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) })
3323
+ }
3324
+ )
3325
+ ] }),
3326
+ /* @__PURE__ */ jsx(
3327
+ CalendarGrid,
3328
+ {
3329
+ viewMonth,
3330
+ isDayDisabled,
3331
+ cellState,
3332
+ onSelectDay: commitValue,
3333
+ onFocusDay: handleFocusDay,
3334
+ initialFocusDate: selected ?? /* @__PURE__ */ new Date()
3335
+ }
3336
+ )
3337
+ ] });
3338
+ }
3339
+ );
3340
+ Calendar.displayName = "Calendar";
3341
+ var displayFormat = new Intl.DateTimeFormat(void 0, { dateStyle: "medium" });
3342
+ var DatePicker = React52.forwardRef(
3343
+ function DatePicker2({
3344
+ value,
3345
+ defaultValue,
3346
+ onChange,
3347
+ min,
3348
+ max,
3349
+ disabledDates,
3350
+ placeholder = "Pick a date\u2026",
3351
+ disabled = false,
3352
+ error = false,
3353
+ required = false,
3354
+ className,
3355
+ id,
3356
+ name,
3357
+ "aria-describedby": describedBy
3358
+ }, ref) {
3359
+ const controlled = value !== void 0;
3360
+ const [internalValue, setInternalValue] = React52.useState(defaultValue);
3361
+ const selected = controlled ? value : internalValue;
3362
+ const [open, setOpen] = React52.useState(false);
3363
+ const containerRef = React52.useRef(null);
3364
+ const triggerRef = React52.useRef(null);
3365
+ const setTriggerRef = (node) => {
3366
+ triggerRef.current = node;
3367
+ if (typeof ref === "function") ref(node);
3368
+ else if (ref) ref.current = node;
3369
+ };
3370
+ const commitValue = (d) => {
3371
+ if (!controlled) setInternalValue(d);
3372
+ onChange?.(d);
3373
+ };
3374
+ const closePopup = React52.useCallback(() => setOpen(false), []);
3375
+ const openPopup = () => {
3376
+ if (!disabled) setOpen(true);
3377
+ };
3378
+ const handleCalendarChange = (d) => {
3379
+ commitValue(d);
3380
+ closePopup();
3381
+ triggerRef.current?.focus();
3382
+ };
3383
+ React52.useEffect(() => {
3384
+ if (!open) return;
3385
+ const onDown = (e) => {
3386
+ if (containerRef.current && !containerRef.current.contains(e.target)) closePopup();
3387
+ };
3388
+ document.addEventListener("mousedown", onDown);
3389
+ return () => document.removeEventListener("mousedown", onDown);
3390
+ }, [open, closePopup]);
3391
+ const onTriggerKeyDown = (e) => {
3392
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
3393
+ e.preventDefault();
3394
+ openPopup();
3395
+ } else if (e.key === "Escape" && open) {
3396
+ e.preventDefault();
3397
+ closePopup();
3398
+ }
3399
+ };
3400
+ const onPanelKeyDown = (e) => {
3401
+ if (e.key === "Escape") {
3402
+ e.preventDefault();
3403
+ closePopup();
3404
+ triggerRef.current?.focus();
3405
+ }
3406
+ };
3407
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative inline-block w-full", className), children: [
3408
+ /* @__PURE__ */ jsxs(
3409
+ "button",
3410
+ {
3411
+ ref: setTriggerRef,
3412
+ type: "button",
3413
+ id,
3414
+ disabled,
3415
+ "aria-haspopup": "grid",
3416
+ "aria-expanded": open,
3417
+ "aria-invalid": error || void 0,
3418
+ "aria-required": required || void 0,
3419
+ "aria-describedby": describedBy,
3420
+ onClick: () => open ? closePopup() : openPopup(),
3421
+ onKeyDown: onTriggerKeyDown,
3422
+ className: cn(
3423
+ "w-full flex items-center justify-between gap-2 border rounded-kj-md bg-surface text-left text-sm",
3424
+ "pl-[0.85rem] pr-3 py-[calc(0.6rem*var(--kj-density,1))]",
3425
+ "transition-[border-color,box-shadow] duration-150",
3426
+ "focus:outline-none focus:border-ring focus:ring-[3px] focus:ring-ring/30",
3427
+ disabled && "bg-muted text-muted-foreground cursor-not-allowed",
3428
+ error ? "border-danger" : "border-input",
3429
+ !selected && "text-muted-foreground"
3430
+ ),
3431
+ children: [
3432
+ /* @__PURE__ */ jsx("span", { children: selected ? displayFormat.format(selected) : placeholder }),
3433
+ /* @__PURE__ */ jsxs(
3434
+ "svg",
3435
+ {
3436
+ viewBox: "0 0 24 24",
3437
+ className: "h-4 w-4 text-muted-foreground shrink-0",
3438
+ fill: "none",
3439
+ stroke: "currentColor",
3440
+ strokeWidth: "2",
3441
+ strokeLinecap: "round",
3442
+ strokeLinejoin: "round",
3443
+ children: [
3444
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
3445
+ /* @__PURE__ */ jsx("path", { d: "M3 10h18M8 2v4M16 2v4" })
3446
+ ]
3447
+ }
3448
+ )
3449
+ ]
3450
+ }
3451
+ ),
3452
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected ? toISODateString(selected) : "" }),
3453
+ open && /* @__PURE__ */ jsx(
3454
+ "div",
3455
+ {
3456
+ onKeyDown: onPanelKeyDown,
3457
+ className: "absolute z-40 top-[calc(100%+6px)] left-0 bg-surface border border-border rounded-kj-md shadow-kj-lg p-3 animate-[kjpop_.12s_ease]",
3458
+ children: /* @__PURE__ */ jsx(Calendar, { value: selected, onChange: handleCalendarChange, min, max, disabledDates })
3459
+ }
3460
+ )
3461
+ ] });
3462
+ }
3463
+ );
3464
+ DatePicker.displayName = "DatePicker";
3465
+ var DatePickerField = React52.forwardRef(
3466
+ function DatePickerField2({ label, hint, error, required, className, ...props }, ref) {
3467
+ return /* @__PURE__ */ jsx(FormField, { label, hint, error, required, className, children: /* @__PURE__ */ jsx(DatePicker, { ref, error: !!error, ...props }) });
3468
+ }
3469
+ );
3470
+ DatePickerField.displayName = "DatePickerField";
3471
+ function isBetweenExclusive(d, lo, hi) {
3472
+ const t = startOfDay(d).getTime();
3473
+ return t > startOfDay(lo).getTime() && t < startOfDay(hi).getTime();
3474
+ }
3475
+ var RangeCalendar = React52.forwardRef(
3476
+ function RangeCalendar2({ value, defaultValue, onChange, month, defaultMonth, onMonthChange, min, max, disabledDates, className }, ref) {
3477
+ const controlled = value !== void 0;
3478
+ const [internalValue, setInternalValue] = React52.useState(defaultValue ?? {});
3479
+ const range2 = controlled ? value : internalValue;
3480
+ const monthControlled = month !== void 0;
3481
+ const [internalMonth, setInternalMonth] = React52.useState(
3482
+ () => startOfMonth(month ?? defaultMonth ?? range2.start ?? /* @__PURE__ */ new Date())
3483
+ );
3484
+ const leftMonth = monthControlled ? startOfMonth(month) : internalMonth;
3485
+ const rightMonth = addMonths(leftMonth, 1);
3486
+ const setViewMonth = (next) => {
3487
+ const normalized = startOfMonth(next);
3488
+ if (!monthControlled) setInternalMonth(normalized);
3489
+ onMonthChange?.(normalized);
3490
+ };
3491
+ const isDayDisabled = React52.useCallback(
3492
+ (d) => {
3493
+ if (min && d < startOfDay(min)) return true;
3494
+ if (max && d > startOfDay(max)) return true;
3495
+ return disabledDates?.(d) ?? false;
3496
+ },
3497
+ [min, max, disabledDates]
3498
+ );
3499
+ const [pendingStart, setPendingStart] = React52.useState(void 0);
3500
+ const [hoverDate, setHoverDate] = React52.useState(void 0);
3501
+ const commitRange = (next) => {
3502
+ if (!controlled) setInternalValue(next);
3503
+ onChange?.(next);
3504
+ };
3505
+ const handleSelectDay = (d) => {
3506
+ const day = startOfDay(d);
3507
+ if (pendingStart === void 0) {
3508
+ setPendingStart(day);
3509
+ setHoverDate(void 0);
3510
+ return;
3511
+ }
3512
+ if (day < startOfDay(pendingStart)) {
3513
+ setPendingStart(day);
3514
+ setHoverDate(void 0);
3515
+ return;
3516
+ }
3517
+ commitRange({ start: pendingStart, end: day });
3518
+ setPendingStart(void 0);
3519
+ setHoverDate(void 0);
3520
+ };
3521
+ const today = /* @__PURE__ */ new Date();
3522
+ const previewEnd = pendingStart !== void 0 ? hoverDate : void 0;
3523
+ const cellState = (d) => {
3524
+ const isToday = isSameDay(d, today);
3525
+ if (pendingStart !== void 0) {
3526
+ if (previewEnd === void 0) {
3527
+ return { rangeStart: isSameDay(d, pendingStart), today: isToday };
3528
+ }
3529
+ if (startOfDay(previewEnd) < startOfDay(pendingStart)) {
3530
+ return { rangeStart: isSameDay(d, previewEnd), today: isToday };
3531
+ }
3532
+ return {
3533
+ rangeStart: isSameDay(d, pendingStart),
3534
+ rangeEnd: isSameDay(d, previewEnd),
3535
+ inRange: isBetweenExclusive(d, pendingStart, previewEnd),
3536
+ today: isToday
3537
+ };
3538
+ }
3539
+ if (range2.start && range2.end) {
3540
+ return {
3541
+ rangeStart: isSameDay(d, range2.start),
3542
+ rangeEnd: isSameDay(d, range2.end),
3543
+ inRange: isBetweenExclusive(d, range2.start, range2.end),
3544
+ today: isToday
3545
+ };
3546
+ }
3547
+ return { rangeStart: range2.start ? isSameDay(d, range2.start) : false, today: isToday };
3548
+ };
3549
+ const inLockedRange = (d) => d.getMonth() === leftMonth.getMonth() && d.getFullYear() === leftMonth.getFullYear() || d.getMonth() === rightMonth.getMonth() && d.getFullYear() === rightMonth.getFullYear();
3550
+ const handleFocusDay = (d) => {
3551
+ if (pendingStart !== void 0) setHoverDate(d);
3552
+ if (!inLockedRange(d)) setViewMonth(d);
3553
+ };
3554
+ const navButton = (direction) => /* @__PURE__ */ jsx(
3555
+ "button",
3556
+ {
3557
+ type: "button",
3558
+ "aria-label": direction === "prev" ? "Previous month" : "Next month",
3559
+ onClick: () => setViewMonth(addMonths(leftMonth, direction === "prev" ? -1 : 1)),
3560
+ className: "p-1.5 rounded-kj-sm text-muted-foreground hover:bg-muted hover:text-foreground",
3561
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: direction === "prev" ? /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) : /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) })
3562
+ }
3563
+ );
3564
+ return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex gap-6 select-none", className), children: [
3565
+ /* @__PURE__ */ jsxs("div", { className: "w-[280px]", children: [
3566
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
3567
+ navButton("prev"),
3568
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: monthLabelFormat.format(leftMonth) }),
3569
+ /* @__PURE__ */ jsx("span", { className: "w-[26px]", "aria-hidden": "true" })
3570
+ ] }),
3571
+ /* @__PURE__ */ jsx(
3572
+ CalendarGrid,
3573
+ {
3574
+ viewMonth: leftMonth,
3575
+ isDayDisabled,
3576
+ cellState,
3577
+ onSelectDay: handleSelectDay,
3578
+ onFocusDay: handleFocusDay,
3579
+ initialFocusDate: pendingStart ?? range2.start ?? /* @__PURE__ */ new Date()
3580
+ }
3581
+ )
3582
+ ] }),
3583
+ /* @__PURE__ */ jsxs("div", { className: "w-[280px]", children: [
3584
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
3585
+ /* @__PURE__ */ jsx("span", { className: "w-[26px]", "aria-hidden": "true" }),
3586
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: monthLabelFormat.format(rightMonth) }),
3587
+ navButton("next")
3588
+ ] }),
3589
+ /* @__PURE__ */ jsx(
3590
+ CalendarGrid,
3591
+ {
3592
+ viewMonth: rightMonth,
3593
+ isDayDisabled,
3594
+ cellState,
3595
+ onSelectDay: handleSelectDay,
3596
+ onFocusDay: handleFocusDay,
3597
+ initialFocusDate: pendingStart ?? range2.start ?? /* @__PURE__ */ new Date()
3598
+ }
3599
+ )
3600
+ ] })
3601
+ ] });
3602
+ }
3603
+ );
3604
+ RangeCalendar.displayName = "RangeCalendar";
3605
+ var displayFormat2 = new Intl.DateTimeFormat(void 0, { dateStyle: "medium" });
3606
+ function formatRangeLabel(range2) {
3607
+ if (!range2.start || !range2.end) return void 0;
3608
+ return `${displayFormat2.format(range2.start)} \u2013 ${displayFormat2.format(range2.end)}`;
3609
+ }
3610
+ var DateRangePicker = React52.forwardRef(
3611
+ function DateRangePicker2({
3612
+ value,
3613
+ defaultValue,
3614
+ onChange,
3615
+ min,
3616
+ max,
3617
+ disabledDates,
3618
+ placeholder = "Pick a date range\u2026",
3619
+ disabled = false,
3620
+ error = false,
3621
+ required = false,
3622
+ className,
3623
+ id,
3624
+ name,
3625
+ "aria-describedby": describedBy
3626
+ }, ref) {
3627
+ const controlled = value !== void 0;
3628
+ const [internalValue, setInternalValue] = React52.useState(defaultValue ?? {});
3629
+ const selected = controlled ? value : internalValue;
3630
+ const [open, setOpen] = React52.useState(false);
3631
+ const containerRef = React52.useRef(null);
3632
+ const triggerRef = React52.useRef(null);
3633
+ const setTriggerRef = (node) => {
3634
+ triggerRef.current = node;
3635
+ if (typeof ref === "function") ref(node);
3636
+ else if (ref) ref.current = node;
3637
+ };
3638
+ const commitValue = (r) => {
3639
+ if (!controlled) setInternalValue(r);
3640
+ onChange?.(r);
3641
+ };
3642
+ const closePopup = React52.useCallback(() => setOpen(false), []);
3643
+ const openPopup = () => {
3644
+ if (!disabled) setOpen(true);
3645
+ };
3646
+ const handleRangeChange = (r) => {
3647
+ commitValue(r);
3648
+ closePopup();
3649
+ triggerRef.current?.focus();
3650
+ };
3651
+ React52.useEffect(() => {
3652
+ if (!open) return;
3653
+ const onDown = (e) => {
3654
+ if (containerRef.current && !containerRef.current.contains(e.target)) closePopup();
3655
+ };
3656
+ document.addEventListener("mousedown", onDown);
3657
+ return () => document.removeEventListener("mousedown", onDown);
3658
+ }, [open, closePopup]);
3659
+ const onTriggerKeyDown = (e) => {
3660
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
3661
+ e.preventDefault();
3662
+ openPopup();
3663
+ } else if (e.key === "Escape" && open) {
3664
+ e.preventDefault();
3665
+ closePopup();
3666
+ triggerRef.current?.focus();
3667
+ }
3668
+ };
3669
+ const onPanelKeyDown = (e) => {
3670
+ if (e.key === "Escape") {
3671
+ e.preventDefault();
3672
+ closePopup();
3673
+ triggerRef.current?.focus();
3674
+ }
3675
+ };
3676
+ const label = formatRangeLabel(selected);
3677
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative inline-block w-full", className), children: [
3678
+ /* @__PURE__ */ jsxs(
3679
+ "button",
3680
+ {
3681
+ ref: setTriggerRef,
3682
+ type: "button",
3683
+ id,
3684
+ disabled,
3685
+ "aria-haspopup": "dialog",
3686
+ "aria-expanded": open,
3687
+ "aria-invalid": error || void 0,
3688
+ "aria-required": required || void 0,
3689
+ "aria-describedby": describedBy,
3690
+ onClick: () => open ? closePopup() : openPopup(),
3691
+ onKeyDown: onTriggerKeyDown,
3692
+ className: cn(
3693
+ "w-full flex items-center justify-between gap-2 border rounded-kj-md bg-surface text-left text-sm",
3694
+ "pl-[0.85rem] pr-3 py-[calc(0.6rem*var(--kj-density,1))]",
3695
+ "transition-[border-color,box-shadow] duration-150",
3696
+ "focus:outline-none focus:border-ring focus:ring-[3px] focus:ring-ring/30",
3697
+ disabled && "bg-muted text-muted-foreground cursor-not-allowed",
3698
+ error ? "border-danger" : "border-input",
3699
+ !label && "text-muted-foreground"
3700
+ ),
3701
+ children: [
3702
+ /* @__PURE__ */ jsx("span", { children: label ?? placeholder }),
3703
+ /* @__PURE__ */ jsxs(
3704
+ "svg",
3705
+ {
3706
+ viewBox: "0 0 24 24",
3707
+ className: "h-4 w-4 text-muted-foreground shrink-0",
3708
+ fill: "none",
3709
+ stroke: "currentColor",
3710
+ strokeWidth: "2",
3711
+ strokeLinecap: "round",
3712
+ strokeLinejoin: "round",
3713
+ children: [
3714
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
3715
+ /* @__PURE__ */ jsx("path", { d: "M3 10h18M8 2v4M16 2v4" })
3716
+ ]
3717
+ }
3718
+ )
3719
+ ]
3720
+ }
3721
+ ),
3722
+ name && /* @__PURE__ */ jsxs(Fragment, { children: [
3723
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: `${name}Start`, value: selected.start ? toISODateString(selected.start) : "" }),
3724
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: `${name}End`, value: selected.end ? toISODateString(selected.end) : "" })
3725
+ ] }),
3726
+ open && /* @__PURE__ */ jsx(
3727
+ "div",
3728
+ {
3729
+ onKeyDown: onPanelKeyDown,
3730
+ className: "absolute z-40 top-[calc(100%+6px)] left-0 bg-surface border border-border rounded-kj-md shadow-kj-lg p-3 animate-[kjpop_.12s_ease]",
3731
+ children: /* @__PURE__ */ jsx(RangeCalendar, { value: selected, onChange: handleRangeChange, min, max, disabledDates })
3732
+ }
3733
+ )
3734
+ ] });
3735
+ }
3736
+ );
3737
+ DateRangePicker.displayName = "DateRangePicker";
3738
+ var DateRangePickerField = React52.forwardRef(
3739
+ function DateRangePickerField2({ label, hint, error, required, className, ...props }, ref) {
3740
+ return /* @__PURE__ */ jsx(FormField, { label, hint, error, required, className, children: /* @__PURE__ */ jsx(DateRangePicker, { ref, error: !!error, ...props }) });
3741
+ }
3742
+ );
3743
+ DateRangePickerField.displayName = "DateRangePickerField";
3744
+ var Dropzone = React52.forwardRef(
3745
+ function Dropzone2({
3746
+ onFiles,
3747
+ accept,
3748
+ multiple = true,
3749
+ disabled = false,
3750
+ className,
3751
+ children,
3752
+ id,
3753
+ "aria-describedby": describedBy,
3754
+ "aria-invalid": ariaInvalid,
3755
+ "aria-required": ariaRequired
3756
+ }, ref) {
3757
+ const inputRef = React52.useRef(null);
3758
+ const [dragActive, setDragActive] = React52.useState(false);
3759
+ const dragCounter = React52.useRef(0);
3760
+ const invalid = ariaInvalid === true || ariaInvalid === "true";
3761
+ const openPicker = () => {
3762
+ if (!disabled) inputRef.current?.click();
3763
+ };
3764
+ const handleInputChange = (e) => {
3765
+ const files = e.target.files ? Array.from(e.target.files) : [];
3766
+ if (files.length > 0) onFiles(files);
3767
+ e.target.value = "";
3768
+ };
3769
+ const handleDragEnter = (e) => {
3770
+ e.preventDefault();
3771
+ if (disabled) return;
3772
+ dragCounter.current += 1;
3773
+ setDragActive(true);
3774
+ };
3775
+ const handleDragOver = (e) => {
3776
+ e.preventDefault();
3777
+ };
3778
+ const handleDragLeave = (e) => {
3779
+ e.preventDefault();
3780
+ if (disabled) return;
3781
+ dragCounter.current -= 1;
3782
+ if (dragCounter.current <= 0) {
3783
+ dragCounter.current = 0;
3784
+ setDragActive(false);
3785
+ }
3786
+ };
3787
+ const handleDrop = (e) => {
3788
+ e.preventDefault();
3789
+ dragCounter.current = 0;
3790
+ setDragActive(false);
3791
+ if (disabled) return;
3792
+ const files = e.dataTransfer?.files ? Array.from(e.dataTransfer.files) : [];
3793
+ if (files.length > 0) onFiles(files);
3794
+ };
3795
+ return /* @__PURE__ */ jsxs("div", { className: cn("w-full", className), children: [
3796
+ /* @__PURE__ */ jsx(
3797
+ "button",
3798
+ {
3799
+ ref,
3800
+ type: "button",
3801
+ id,
3802
+ disabled,
3803
+ "aria-describedby": describedBy,
3804
+ "aria-invalid": ariaInvalid,
3805
+ "aria-required": ariaRequired,
3806
+ "data-drag-active": dragActive || void 0,
3807
+ onClick: openPicker,
3808
+ onDragEnter: handleDragEnter,
3809
+ onDragOver: handleDragOver,
3810
+ onDragLeave: handleDragLeave,
3811
+ onDrop: handleDrop,
3812
+ className: cn(
3813
+ "w-full flex flex-col items-center justify-center gap-2 rounded-kj-md border-2 border-dashed px-6 py-8 text-center text-sm transition-colors",
3814
+ "focus:outline-none focus:ring-[3px] focus:ring-ring/30",
3815
+ disabled ? "border-input bg-muted text-muted-foreground cursor-not-allowed" : dragActive ? "border-primary bg-primary/5 text-foreground cursor-pointer" : invalid ? "border-danger bg-surface text-muted-foreground hover:text-foreground cursor-pointer" : "border-input bg-surface text-muted-foreground hover:border-primary hover:text-foreground cursor-pointer"
3816
+ ),
3817
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
3818
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", className: "h-7 w-7", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
3819
+ /* @__PURE__ */ jsx("path", { d: "M12 16V4M7 9l5-5 5 5" }),
3820
+ /* @__PURE__ */ jsx("path", { d: "M20 16v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-3" })
3821
+ ] }),
3822
+ /* @__PURE__ */ jsxs("span", { children: [
3823
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Drag and drop files here" }),
3824
+ ", or click to browse"
3825
+ ] })
3826
+ ] })
3827
+ }
3828
+ ),
3829
+ /* @__PURE__ */ jsx(
3830
+ "input",
3831
+ {
3832
+ ref: inputRef,
3833
+ type: "file",
3834
+ accept,
3835
+ multiple,
3836
+ disabled,
3837
+ onChange: handleInputChange,
3838
+ className: "sr-only",
3839
+ tabIndex: -1,
3840
+ "aria-hidden": "true"
3841
+ }
3842
+ )
3843
+ ] });
3844
+ }
3845
+ );
3846
+ Dropzone.displayName = "Dropzone";
3847
+
3848
+ // src/components/file-upload-internals.ts
3849
+ var KB = 1024;
3850
+ function formatBytes(bytes) {
3851
+ if (bytes < KB) return `${bytes} B`;
3852
+ const units = ["KB", "MB", "GB", "TB"];
3853
+ let value = bytes / KB;
3854
+ let unitIndex = 0;
3855
+ while (value >= KB && unitIndex < units.length - 1) {
3856
+ value /= KB;
3857
+ unitIndex++;
3858
+ }
3859
+ return `${value.toFixed(1)} ${units[unitIndex]}`;
3860
+ }
3861
+ function matchesAccept(file, accept) {
3862
+ if (!accept || accept.trim() === "") return true;
3863
+ const tokens = accept.split(",").map((t) => t.trim()).filter(Boolean);
3864
+ if (tokens.length === 0) return true;
3865
+ const fileType = file.type.toLowerCase();
3866
+ const fileName = file.name.toLowerCase();
3867
+ return tokens.some((token) => {
3868
+ const t = token.toLowerCase();
3869
+ if (t.startsWith(".")) return fileName.endsWith(t);
3870
+ if (t.endsWith("/*")) return fileType.startsWith(t.slice(0, -1));
3871
+ return fileType === t;
3872
+ });
3873
+ }
3874
+ var idCounter = 0;
3875
+ function generateId() {
3876
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
3877
+ return crypto.randomUUID();
3878
+ }
3879
+ idCounter += 1;
3880
+ return `file-${Date.now().toString(36)}-${idCounter}-${Math.random().toString(36).slice(2, 8)}`;
3881
+ }
3882
+ function validateFiles(files, { accept, maxSize, maxFiles, existingCount = 0, multiple = true }) {
3883
+ const accepted = [];
3884
+ const rejected = [];
3885
+ const effectiveExisting = multiple ? existingCount : 0;
3886
+ for (const file of files) {
3887
+ if (!matchesAccept(file, accept)) {
3888
+ rejected.push({ file, reason: "type" });
3889
+ continue;
3890
+ }
3891
+ if (maxSize != null && file.size > maxSize) {
3892
+ rejected.push({ file, reason: "size" });
3893
+ continue;
3894
+ }
3895
+ if (!multiple) {
3896
+ if (accepted.length === 0) accepted.push(file);
3897
+ else rejected.push({ file, reason: "count" });
3898
+ continue;
3899
+ }
3900
+ const capacity = maxFiles != null ? maxFiles - effectiveExisting - accepted.length : Infinity;
3901
+ if (capacity <= 0) {
3902
+ rejected.push({ file, reason: "count" });
3903
+ continue;
3904
+ }
3905
+ accepted.push(file);
3906
+ }
3907
+ return { accepted, rejected };
3908
+ }
3909
+ function FileIcon({ className }) {
3910
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", className, fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
3911
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
3912
+ /* @__PURE__ */ jsx("path", { d: "M14 2v6h6" })
3913
+ ] });
3914
+ }
3915
+ var FileUpload = React52.forwardRef(
3916
+ function FileUpload2({
3917
+ value,
3918
+ defaultValue,
3919
+ onChange,
3920
+ onReject,
3921
+ accept,
3922
+ maxSize,
3923
+ maxFiles,
3924
+ multiple = true,
3925
+ disabled = false,
3926
+ error = false,
3927
+ required = false,
3928
+ className,
3929
+ id,
3930
+ "aria-describedby": describedBy
3931
+ }, ref) {
3932
+ const controlled = value !== void 0;
3933
+ const [internalItems, setInternalItems] = React52.useState(defaultValue ?? []);
3934
+ const items = controlled ? value : internalItems;
3935
+ const commit = (next) => {
3936
+ if (!controlled) setInternalItems(next);
3937
+ onChange?.(next);
3938
+ };
3939
+ const handleFiles = (files) => {
3940
+ const { accepted, rejected } = validateFiles(files, {
3941
+ accept,
3942
+ maxSize,
3943
+ maxFiles,
3944
+ existingCount: items.length,
3945
+ multiple
3946
+ });
3947
+ if (rejected.length > 0) onReject?.(rejected);
3948
+ if (accepted.length === 0) return;
3949
+ const newItems = accepted.map((file) => ({
3950
+ id: generateId(),
3951
+ file,
3952
+ status: "pending"
3953
+ }));
3954
+ commit(multiple ? [...items, ...newItems] : newItems.slice(0, 1));
3955
+ };
3956
+ const removeItem = (itemId) => {
3957
+ commit(items.filter((it) => it.id !== itemId));
3958
+ };
3959
+ return /* @__PURE__ */ jsxs("div", { className: cn("w-full flex flex-col gap-3", className), children: [
3960
+ /* @__PURE__ */ jsx(
3961
+ Dropzone,
3962
+ {
3963
+ ref,
3964
+ onFiles: handleFiles,
3965
+ accept,
3966
+ multiple,
3967
+ disabled,
3968
+ id,
3969
+ "aria-describedby": describedBy,
3970
+ "aria-invalid": error || void 0,
3971
+ "aria-required": required || void 0
3972
+ }
3973
+ ),
3974
+ items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-2", children: items.map((item) => /* @__PURE__ */ jsxs(
3975
+ "li",
3976
+ {
3977
+ className: "flex items-center gap-3 rounded-kj-md border border-border bg-surface px-3 py-2",
3978
+ children: [
3979
+ /* @__PURE__ */ jsx(FileIcon, { className: "h-5 w-5 shrink-0 text-muted-foreground" }),
3980
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3981
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
3982
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-foreground", children: item.file.name }),
3983
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatBytes(item.file.size) })
3984
+ ] }),
3985
+ item.status === "uploading" && item.progress != null && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex items-center gap-2", children: [
3986
+ /* @__PURE__ */ jsx(Progress, { value: item.progress, className: "h-1.5" }),
3987
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs text-muted-foreground", children: [
3988
+ Math.round(item.progress),
3989
+ "%"
3990
+ ] })
3991
+ ] }),
3992
+ item.status === "success" && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-success", children: "Uploaded" }),
3993
+ item.status === "error" && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-danger", children: item.error ?? "Upload failed" })
3994
+ ] }),
3995
+ !disabled && /* @__PURE__ */ jsx(
3996
+ "button",
3997
+ {
3998
+ type: "button",
3999
+ "aria-label": `Remove ${item.file.name}`,
4000
+ onClick: () => removeItem(item.id),
4001
+ className: "shrink-0 rounded-kj-sm p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
4002
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
4003
+ }
4004
+ )
4005
+ ]
4006
+ },
4007
+ item.id
4008
+ )) })
4009
+ ] });
4010
+ }
4011
+ );
4012
+ FileUpload.displayName = "FileUpload";
4013
+ var FileUploadField = React52.forwardRef(
4014
+ function FileUploadField2({ label, hint, error, required, className, ...props }, ref) {
4015
+ return /* @__PURE__ */ jsx(FormField, { label, hint, error, required, className, children: /* @__PURE__ */ jsx(FileUpload, { ref, error: !!error, ...props }) });
4016
+ }
4017
+ );
4018
+ FileUploadField.displayName = "FileUploadField";
4019
+ var TimelineContext = React52.createContext(null);
4020
+ var TimelineItemContext = React52.createContext(null);
4021
+ var Timeline = React52.forwardRef(
4022
+ ({ align = "left", className, children, ...props }, ref) => {
4023
+ const validChildren = React52.Children.toArray(children).filter(React52.isValidElement);
4024
+ const count = validChildren.length;
4025
+ const childrenWithIndex = validChildren.map((child, index) => {
4026
+ return React52.cloneElement(child, {
4027
+ index
4028
+ });
4029
+ });
4030
+ return /* @__PURE__ */ jsx(TimelineContext.Provider, { value: { align, count }, children: /* @__PURE__ */ jsx(
4031
+ "div",
4032
+ {
4033
+ ref,
4034
+ role: "list",
4035
+ className: cn("flex flex-col gap-6 w-full", className),
4036
+ ...props,
4037
+ children: childrenWithIndex
4038
+ }
4039
+ ) });
4040
+ }
4041
+ );
4042
+ Timeline.displayName = "Timeline";
4043
+ var TimelineItem = React52.forwardRef(
4044
+ ({ className, index = 0, children, ...props }, ref) => {
4045
+ const ctx = React52.useContext(TimelineContext);
4046
+ if (!ctx) throw new Error("TimelineItem must be used within a Timeline");
4047
+ const { align, count } = ctx;
4048
+ const isEven = index % 2 === 0;
4049
+ const isLast = index === count - 1;
4050
+ const gridClass = cn(
4051
+ "grid w-full gap-4 items-start group/timeline-item",
4052
+ align === "left" && "grid-cols-[auto_1fr]",
4053
+ align === "right" && "grid-cols-[1fr_auto] text-right",
4054
+ align === "alternate" && "md:grid-cols-[1fr_auto_1fr] grid-cols-[auto_1fr]"
4055
+ );
4056
+ return /* @__PURE__ */ jsx(TimelineItemContext.Provider, { value: { isEven, align, isLast }, children: /* @__PURE__ */ jsx(
4057
+ "div",
4058
+ {
4059
+ ref,
4060
+ role: "listitem",
4061
+ className: cn(gridClass, className),
4062
+ ...props,
4063
+ children
4064
+ }
4065
+ ) });
4066
+ }
4067
+ );
4068
+ TimelineItem.displayName = "TimelineItem";
4069
+ var TimelineSeparator = React52.forwardRef(
4070
+ ({ className, align: propAlign, ...props }, ref) => {
4071
+ const ctx = React52.useContext(TimelineItemContext);
4072
+ const align = propAlign ?? ctx?.align ?? "left";
4073
+ const separatorClass = cn(
4074
+ "flex flex-col items-center justify-self-center h-full min-h-[40px]",
4075
+ align === "alternate" && "md:col-start-2 col-start-1",
4076
+ align === "left" && "col-start-1",
4077
+ align === "right" && "col-start-2",
4078
+ className
4079
+ );
4080
+ return /* @__PURE__ */ jsx("div", { ref, className: separatorClass, ...props });
4081
+ }
4082
+ );
4083
+ TimelineSeparator.displayName = "TimelineSeparator";
4084
+ var TimelineConnector = React52.forwardRef(
4085
+ ({ className, dashed, ...props }, ref) => {
4086
+ const ctx = React52.useContext(TimelineItemContext);
4087
+ const isLast = ctx?.isLast ?? false;
4088
+ const connectorClass = cn(
4089
+ "grow",
4090
+ isLast && "hidden",
4091
+ dashed ? "w-0 border-l border-dashed border-border" : "w-0.5 bg-border",
4092
+ className
4093
+ );
4094
+ return /* @__PURE__ */ jsx("div", { ref, className: connectorClass, ...props });
4095
+ }
4096
+ );
4097
+ TimelineConnector.displayName = "TimelineConnector";
4098
+ var TimelineDot = React52.forwardRef(
4099
+ ({ className, variant = "default", size = "md", ...props }, ref) => {
4100
+ const sizeClasses2 = {
4101
+ sm: "w-2.5 h-2.5 rounded-full my-1.5",
4102
+ md: "w-4 h-4 rounded-full my-1 border-2 bg-background border-border",
4103
+ lg: "w-8 h-8 rounded-full border flex items-center justify-center bg-background text-[0.8rem]"
4104
+ };
4105
+ const variantClasses = {
4106
+ default: "border-border text-muted-foreground",
4107
+ primary: "border-primary bg-primary/5 text-primary",
4108
+ secondary: "border-secondary bg-secondary/5 text-secondary",
4109
+ success: "border-success bg-success-surface text-success",
4110
+ warning: "border-warning bg-warning-surface text-warning",
4111
+ danger: "border-danger bg-danger-surface text-danger"
4112
+ };
4113
+ return /* @__PURE__ */ jsx(
4114
+ "div",
4115
+ {
4116
+ ref,
4117
+ className: cn(
4118
+ "shrink-0 select-none",
4119
+ sizeClasses2[size],
4120
+ variantClasses[variant],
4121
+ className
4122
+ ),
4123
+ ...props
4124
+ }
4125
+ );
4126
+ }
4127
+ );
4128
+ TimelineDot.displayName = "TimelineDot";
4129
+ var TimelineContent = React52.forwardRef(
4130
+ ({ className, isEven: propIsEven, align: propAlign, ...props }, ref) => {
4131
+ const ctx = React52.useContext(TimelineItemContext);
4132
+ const isEven = propIsEven ?? ctx?.isEven ?? false;
4133
+ const align = propAlign ?? ctx?.align ?? "left";
4134
+ const contentClass = cn(
4135
+ "pb-8 flex flex-col gap-1 w-full",
4136
+ align === "left" && "col-start-2 text-left",
4137
+ align === "right" && "col-start-1 text-right",
4138
+ align === "alternate" && (isEven ? "md:col-start-3 col-start-2 text-left" : "md:col-start-1 col-start-2 md:text-right text-left"),
4139
+ className
4140
+ );
4141
+ return /* @__PURE__ */ jsx("div", { ref, className: contentClass, ...props });
4142
+ }
4143
+ );
4144
+ TimelineContent.displayName = "TimelineContent";
4145
+ var TimelineTitle = React52.forwardRef(
4146
+ ({ className, children, ...props }, ref) => /* @__PURE__ */ jsx(
4147
+ "h4",
4148
+ {
4149
+ ref,
4150
+ className: cn("m-0 text-sm font-semibold tracking-[-0.01em] text-foreground", className),
4151
+ ...props,
4152
+ children
4153
+ }
4154
+ )
4155
+ );
4156
+ TimelineTitle.displayName = "TimelineTitle";
4157
+ var TimelineTime = React52.forwardRef(
4158
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
4159
+ "time",
4160
+ {
4161
+ ref,
4162
+ className: cn("text-[0.75rem] text-muted-foreground font-normal", className),
4163
+ ...props
4164
+ }
4165
+ )
4166
+ );
4167
+ TimelineTime.displayName = "TimelineTime";
4168
+ function useStepper({ initialStep = 0, stepsCount }) {
4169
+ const safeStepsCount = Math.max(1, stepsCount);
4170
+ const [activeStep, setActiveStep] = React52.useState(initialStep);
4171
+ const [completedSteps, setCompletedSteps] = React52.useState([]);
4172
+ const setStep = React52.useCallback((step) => {
4173
+ if (step >= 0 && step < safeStepsCount) {
4174
+ setActiveStep(step);
4175
+ }
4176
+ }, [safeStepsCount]);
4177
+ const nextStep = React52.useCallback(() => {
4178
+ setActiveStep((prev) => Math.min(prev + 1, safeStepsCount - 1));
4179
+ }, [safeStepsCount]);
4180
+ const prevStep = React52.useCallback(() => {
4181
+ setActiveStep((prev) => Math.max(prev - 1, 0));
4182
+ }, []);
4183
+ const completeStep = React52.useCallback((step) => {
4184
+ setCompletedSteps((prev) => {
4185
+ if (step < 0 || step >= safeStepsCount) return prev;
4186
+ if (prev.includes(step)) return prev;
4187
+ return [...prev, step].sort((a, b) => a - b);
4188
+ });
4189
+ }, [safeStepsCount]);
4190
+ const uncompleteStep = React52.useCallback((step) => {
4191
+ setCompletedSteps((prev) => prev.filter((s) => s !== step));
4192
+ }, []);
4193
+ const reset = React52.useCallback(() => {
4194
+ setActiveStep(initialStep);
4195
+ setCompletedSteps([]);
4196
+ }, [initialStep]);
4197
+ return {
4198
+ activeStep,
4199
+ completedSteps,
4200
+ setStep,
4201
+ nextStep,
4202
+ prevStep,
4203
+ completeStep,
4204
+ uncompleteStep,
4205
+ reset,
4206
+ isFirstStep: activeStep === 0,
4207
+ isLastStep: activeStep === safeStepsCount - 1
4208
+ };
4209
+ }
4210
+ var StepperContext = React52.createContext(null);
4211
+ var StepperItemContext = React52.createContext(null);
4212
+ var Stepper = React52.forwardRef(
4213
+ ({ value, defaultValue, onValueChange, orientation = "horizontal", linear = true, children, className, ...props }, ref) => {
4214
+ const [localValue, setLocalValue] = React52.useState(defaultValue ?? 0);
4215
+ const isControlled = value !== void 0;
4216
+ const activeStep = isControlled ? value : localValue;
4217
+ const [steps, setSteps] = React52.useState([]);
4218
+ const [completedSteps, setCompletedSteps] = React52.useState([]);
4219
+ const registerStep = React52.useCallback((val) => {
4220
+ setSteps((prev) => prev.includes(val) ? prev : [...prev, val].sort((a, b) => a - b));
4221
+ }, []);
4222
+ const unregisterStep = React52.useCallback((val) => {
4223
+ setSteps((prev) => prev.filter((v) => v !== val));
4224
+ }, []);
4225
+ const setStep = React52.useCallback(
4226
+ (step) => {
4227
+ if (!isControlled) {
4228
+ setLocalValue(step);
4229
+ }
4230
+ onValueChange?.(step);
4231
+ if (linear) {
4232
+ setCompletedSteps((prev) => {
4233
+ const priorSteps = steps.filter((s) => s < step);
4234
+ const nextCompleted = Array.from(/* @__PURE__ */ new Set([...prev, ...priorSteps])).sort((a, b) => a - b);
4235
+ return nextCompleted;
4236
+ });
4237
+ }
4238
+ },
4239
+ [isControlled, onValueChange, linear, steps]
4240
+ );
4241
+ const contextValue = React52.useMemo(
4242
+ () => ({
4243
+ activeStep,
4244
+ orientation,
4245
+ linear,
4246
+ setStep,
4247
+ completedSteps,
4248
+ registerStep,
4249
+ unregisterStep,
4250
+ steps
4251
+ }),
4252
+ [activeStep, orientation, linear, setStep, completedSteps, registerStep, unregisterStep, steps]
4253
+ );
4254
+ return /* @__PURE__ */ jsx(StepperContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
4255
+ "div",
4256
+ {
4257
+ ref,
4258
+ className: cn(
4259
+ "w-full",
4260
+ orientation === "vertical" ? "flex flex-col gap-6" : "space-y-6",
4261
+ className
4262
+ ),
4263
+ ...props,
4264
+ children
4265
+ }
4266
+ ) });
4267
+ }
4268
+ );
4269
+ Stepper.displayName = "Stepper";
4270
+ var StepperContent = React52.forwardRef(
4271
+ ({ value, className, children, ...props }, ref) => {
4272
+ const ctx = React52.useContext(StepperContext);
4273
+ if (!ctx) throw new Error("StepperContent must be used within a Stepper");
4274
+ const isActive = ctx.activeStep === value;
4275
+ if (!isActive) return null;
4276
+ return /* @__PURE__ */ jsx(
4277
+ "div",
4278
+ {
4279
+ ref,
4280
+ role: "tabpanel",
4281
+ tabIndex: 0,
4282
+ className: cn("focus-visible:outline-none w-full", className),
4283
+ ...props,
4284
+ children
4285
+ }
4286
+ );
4287
+ }
4288
+ );
4289
+ StepperContent.displayName = "StepperContent";
4290
+ var StepperList = React52.forwardRef(
4291
+ ({ className, children, ...props }, ref) => {
4292
+ const ctx = React52.useContext(StepperContext);
4293
+ if (!ctx) throw new Error("StepperList must be used within a Stepper");
4294
+ return /* @__PURE__ */ jsx(
4295
+ "div",
4296
+ {
4297
+ ref,
4298
+ role: "tablist",
4299
+ "aria-orientation": ctx.orientation,
4300
+ className: cn(
4301
+ ctx.orientation === "horizontal" ? "flex items-center justify-between w-full relative gap-4" : "flex flex-col items-start gap-6 relative w-full",
4302
+ className
4303
+ ),
4304
+ ...props,
4305
+ children
4306
+ }
4307
+ );
4308
+ }
4309
+ );
4310
+ StepperList.displayName = "StepperList";
4311
+ var StepperItem = React52.forwardRef(
4312
+ ({ value, disabled = false, className, children, ...props }, ref) => {
4313
+ const ctx = React52.useContext(StepperContext);
4314
+ if (!ctx) throw new Error("StepperItem must be used within a Stepper");
4315
+ const { registerStep, unregisterStep } = ctx;
4316
+ React52.useEffect(() => {
4317
+ registerStep(value);
4318
+ return () => unregisterStep(value);
4319
+ }, [value, registerStep, unregisterStep]);
4320
+ const isLast = ctx.steps[ctx.steps.length - 1] === value;
4321
+ return /* @__PURE__ */ jsx(StepperItemContext.Provider, { value: { value, disabled, isLast }, children: /* @__PURE__ */ jsx(
4322
+ "div",
4323
+ {
4324
+ ref,
4325
+ className: cn(
4326
+ "group relative flex items-center gap-3 z-10",
4327
+ ctx.orientation === "vertical" ? "flex items-start w-full" : "",
4328
+ className
4329
+ ),
4330
+ ...props,
4331
+ children
4332
+ }
4333
+ ) });
4334
+ }
4335
+ );
4336
+ StepperItem.displayName = "StepperItem";
4337
+ var StepperSeparator = React52.forwardRef(
4338
+ ({ className, ...props }, ref) => {
4339
+ const ctx = React52.useContext(StepperContext);
4340
+ if (!ctx) throw new Error("StepperSeparator must be used within a Stepper");
4341
+ return /* @__PURE__ */ jsx(
4342
+ "div",
4343
+ {
4344
+ ref,
4345
+ className: cn(
4346
+ ctx.orientation === "horizontal" ? "flex-1 h-[2px] bg-border transition-all duration-300" : "w-[2px] bg-border absolute left-[17px] top-8 bottom-0 -ml-px z-0",
4347
+ className
4348
+ ),
4349
+ ...props
4350
+ }
4351
+ );
4352
+ }
4353
+ );
4354
+ StepperSeparator.displayName = "StepperSeparator";
4355
+ var StepperTrigger = React52.forwardRef(
4356
+ ({ className, children, onClick, onKeyDown, ...props }, ref) => {
4357
+ const ctx = React52.useContext(StepperContext);
4358
+ const itemCtx = React52.useContext(StepperItemContext);
4359
+ if (!ctx || !itemCtx) {
4360
+ throw new Error("StepperTrigger must be used within a StepperItem inside a Stepper");
4361
+ }
4362
+ const isCompleted = ctx.completedSteps.includes(itemCtx.value) || itemCtx.value < ctx.activeStep;
4363
+ const isActive = ctx.activeStep === itemCtx.value;
4364
+ const isClickable = !ctx.linear || isCompleted || isActive;
4365
+ const disabled = itemCtx.disabled || !isClickable;
4366
+ const handleClick = (e) => {
4367
+ if (disabled) {
4368
+ e.preventDefault();
4369
+ return;
4370
+ }
4371
+ ctx.setStep(itemCtx.value);
4372
+ onClick?.(e);
4373
+ };
4374
+ const handleKeyDown = (e) => {
4375
+ onKeyDown?.(e);
4376
+ const triggers = Array.from(
4377
+ e.currentTarget.closest('[role="tablist"]')?.querySelectorAll('[role="tab"]') || []
4378
+ );
4379
+ const currentIndex = triggers.indexOf(e.currentTarget);
4380
+ if (currentIndex === -1) return;
4381
+ let targetIndex = -1;
4382
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
4383
+ targetIndex = Math.min(currentIndex + 1, triggers.length - 1);
4384
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
4385
+ targetIndex = Math.max(currentIndex - 1, 0);
4386
+ }
4387
+ if (targetIndex !== -1 && triggers[targetIndex]) {
4388
+ e.preventDefault();
4389
+ triggers[targetIndex].focus();
4390
+ }
4391
+ };
4392
+ return /* @__PURE__ */ jsx(
4393
+ "button",
4394
+ {
4395
+ ref,
4396
+ type: "button",
4397
+ role: "tab",
4398
+ "aria-selected": isActive,
4399
+ "aria-disabled": disabled,
4400
+ tabIndex: isActive ? 0 : -1,
4401
+ onClick: handleClick,
4402
+ onKeyDown: handleKeyDown,
4403
+ className: cn(
4404
+ "flex items-center gap-3 bg-transparent border-0 p-0 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-md disabled:cursor-not-allowed disabled:opacity-50",
4405
+ ctx.orientation === "vertical" ? "w-full" : "",
4406
+ className
4407
+ ),
4408
+ ...props,
4409
+ children
4410
+ }
4411
+ );
4412
+ }
4413
+ );
4414
+ StepperTrigger.displayName = "StepperTrigger";
4415
+ var StepperIndicator = React52.forwardRef(
4416
+ ({ className, children, ...props }, ref) => {
4417
+ const ctx = React52.useContext(StepperContext);
4418
+ const itemCtx = React52.useContext(StepperItemContext);
4419
+ if (!ctx || !itemCtx) {
4420
+ throw new Error("StepperIndicator must be used within a StepperItem inside a Stepper");
4421
+ }
4422
+ const isCompleted = ctx.completedSteps.includes(itemCtx.value) || itemCtx.value < ctx.activeStep;
4423
+ const isActive = ctx.activeStep === itemCtx.value;
4424
+ return /* @__PURE__ */ jsx(
4425
+ "span",
4426
+ {
4427
+ ref,
4428
+ className: cn(
4429
+ "flex h-9 w-9 shrink-0 items-center justify-center rounded-full border text-sm font-semibold transition-all duration-200",
4430
+ isCompleted ? "bg-primary border-primary text-primary-foreground" : isActive ? "border-primary text-primary bg-primary/10 ring-2 ring-primary/20" : "border-border text-muted-foreground bg-muted/40",
4431
+ className
4432
+ ),
4433
+ ...props,
4434
+ children: children ?? (isCompleted ? "\u2713" : itemCtx.value + 1)
4435
+ }
4436
+ );
4437
+ }
4438
+ );
4439
+ StepperIndicator.displayName = "StepperIndicator";
4440
+ var StepperTitle = React52.forwardRef(
4441
+ ({ className, ...props }, ref) => {
4442
+ const ctx = React52.useContext(StepperContext);
4443
+ const itemCtx = React52.useContext(StepperItemContext);
4444
+ if (!ctx || !itemCtx) {
4445
+ throw new Error("StepperTitle must be used within a StepperItem inside a Stepper");
4446
+ }
4447
+ const isActive = ctx.activeStep === itemCtx.value;
4448
+ return /* @__PURE__ */ jsx(
4449
+ "span",
4450
+ {
4451
+ ref,
4452
+ className: cn(
4453
+ "text-sm font-medium transition-colors duration-200",
4454
+ isActive ? "text-foreground font-semibold" : "text-muted-foreground",
4455
+ className
4456
+ ),
4457
+ ...props
4458
+ }
4459
+ );
4460
+ }
4461
+ );
4462
+ StepperTitle.displayName = "StepperTitle";
4463
+ var StepperDescription = React52.forwardRef(
4464
+ ({ className, ...props }, ref) => {
4465
+ const itemCtx = React52.useContext(StepperItemContext);
4466
+ if (!itemCtx) throw new Error("StepperDescription must be used within a StepperItem");
4467
+ return /* @__PURE__ */ jsx(
4468
+ "span",
4469
+ {
4470
+ ref,
4471
+ className: cn("text-xs text-muted-foreground text-left block", className),
4472
+ ...props
4473
+ }
4474
+ );
4475
+ }
4476
+ );
4477
+ StepperDescription.displayName = "StepperDescription";
4478
+ var AppShellBanner = React52.forwardRef(
4479
+ ({ className, variant = "primary", closable, onClose, children, ...props }, ref) => {
4480
+ const variantClass = {
4481
+ primary: "bg-primary text-primary-foreground",
4482
+ accent: "bg-secondary text-secondary-foreground",
4483
+ muted: "bg-surface border-b border-border text-muted-foreground"
4484
+ }[variant];
4485
+ return /* @__PURE__ */ jsxs(
4486
+ "div",
4487
+ {
4488
+ ref,
4489
+ className: cn("px-4 py-2 text-sm font-medium text-center flex items-center justify-center relative z-30", variantClass, className),
4490
+ ...props,
4491
+ children: [
4492
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children }),
4493
+ closable && /* @__PURE__ */ jsx(
4494
+ "button",
4495
+ {
4496
+ type: "button",
4497
+ onClick: onClose,
4498
+ className: "p-1 rounded opacity-80 hover:opacity-100 transition-opacity ml-2 cursor-pointer",
4499
+ "aria-label": "Close banner",
4500
+ children: /* @__PURE__ */ jsx("svg", { width: 14, height: 14, 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" }) })
4501
+ }
4502
+ )
4503
+ ]
4504
+ }
4505
+ );
4506
+ }
4507
+ );
4508
+ AppShellBanner.displayName = "AppShellBanner";
4509
+ var AppShellHeader = React52.forwardRef(
4510
+ ({
4511
+ className,
4512
+ variant = "glass",
4513
+ position = "sticky",
4514
+ bordered = true,
4515
+ children,
4516
+ mobileNav,
4517
+ ...props
4518
+ }, ref) => {
4519
+ const [mobileOpen, setMobileOpen] = React52.useState(false);
4520
+ React52.useEffect(() => {
4521
+ const handleKeyDown = (e) => {
4522
+ if (e.key === "Escape" && mobileOpen) {
4523
+ setMobileOpen(false);
4524
+ }
4525
+ };
4526
+ window.addEventListener("keydown", handleKeyDown);
4527
+ return () => window.removeEventListener("keydown", handleKeyDown);
4528
+ }, [mobileOpen]);
4529
+ const positionClass = {
4530
+ sticky: "sticky top-0 z-40",
4531
+ fixed: "fixed top-0 left-0 right-0 z-40",
4532
+ static: "relative z-40"
4533
+ }[position];
4534
+ const variantClass = {
4535
+ glass: "bg-surface/80 backdrop-blur-md text-foreground",
4536
+ solid: "bg-surface text-foreground",
4537
+ transparent: "bg-transparent text-foreground"
4538
+ }[variant];
4539
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4540
+ /* @__PURE__ */ jsx(
4541
+ "header",
4542
+ {
4543
+ ref,
4544
+ className: cn(
4545
+ "w-full transition-colors",
4546
+ positionClass,
4547
+ variantClass,
4548
+ bordered && "border-b border-border/60",
4549
+ className
4550
+ ),
4551
+ ...props,
4552
+ children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between gap-4", children: [
4553
+ mobileNav && /* @__PURE__ */ jsx(
4554
+ "button",
4555
+ {
4556
+ type: "button",
4557
+ onClick: () => setMobileOpen(true),
4558
+ className: "md:hidden p-2 -ml-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer",
4559
+ "aria-label": "Open navigation",
4560
+ children: /* @__PURE__ */ jsxs("svg", { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: [
4561
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12" }),
4562
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
4563
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18" })
4564
+ ] })
4565
+ }
4566
+ ),
4567
+ children
4568
+ ] })
4569
+ }
4570
+ ),
4571
+ mobileNav && mobileOpen && /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 md:hidden flex", children: [
4572
+ /* @__PURE__ */ jsx(
4573
+ "div",
4574
+ {
4575
+ "aria-hidden": "true",
4576
+ onClick: () => setMobileOpen(false),
4577
+ className: "fixed inset-0 bg-overlay backdrop-blur-[2px] transition-opacity duration-300"
4578
+ }
4579
+ ),
4580
+ /* @__PURE__ */ jsxs(
4581
+ "aside",
4582
+ {
4583
+ role: "dialog",
4584
+ "aria-modal": "true",
4585
+ "aria-label": "Mobile Navigation",
4586
+ className: "relative z-10 w-72 h-full bg-surface border-r border-border flex flex-col shadow-kj-lg",
4587
+ children: [
4588
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-4 border-b border-border", children: [
4589
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-bold uppercase tracking-wider text-muted-foreground", children: "Navigation" }),
4590
+ /* @__PURE__ */ jsx(
4591
+ "button",
4592
+ {
4593
+ type: "button",
4594
+ onClick: () => setMobileOpen(false),
4595
+ className: "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer",
4596
+ "aria-label": "Close navigation",
4597
+ 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" }) })
4598
+ }
4599
+ )
4600
+ ] }),
4601
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-4", children: mobileNav })
4602
+ ]
4603
+ }
4604
+ )
4605
+ ] })
4606
+ ] });
4607
+ }
4608
+ );
4609
+ AppShellHeader.displayName = "AppShellHeader";
4610
+ var AppShellMain = React52.forwardRef(
4611
+ ({ className, width = "default", padded = true, children, ...props }, ref) => {
4612
+ const widthClass = {
4613
+ default: "max-w-7xl mx-auto w-full",
4614
+ narrow: "max-w-5xl mx-auto w-full",
4615
+ wide: "max-w-screen-2xl mx-auto w-full",
4616
+ full: "max-w-none w-full"
4617
+ }[width];
4618
+ return /* @__PURE__ */ jsx(
4619
+ "main",
4620
+ {
4621
+ ref,
4622
+ className: cn("flex-1", padded && "px-4 sm:px-6 lg:px-8 py-8 md:py-12", widthClass, className),
4623
+ ...props,
4624
+ children
4625
+ }
4626
+ );
4627
+ }
4628
+ );
4629
+ AppShellMain.displayName = "AppShellMain";
4630
+ var AppShellFooter = React52.forwardRef(
4631
+ ({ className, bordered = true, children, ...props }, ref) => {
4632
+ return /* @__PURE__ */ jsx(
4633
+ "footer",
4634
+ {
4635
+ ref,
4636
+ className: cn(
4637
+ "w-full bg-surface text-foreground py-12 mt-auto",
4638
+ bordered && "border-t border-border",
4639
+ className
4640
+ ),
4641
+ ...props,
4642
+ children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children })
4643
+ }
4644
+ );
4645
+ }
4646
+ );
4647
+ AppShellFooter.displayName = "AppShellFooter";
4648
+ var AppShell = React52.forwardRef(
4649
+ ({
4650
+ className,
4651
+ banner,
4652
+ header,
4653
+ children,
4654
+ footer,
4655
+ contentWidth = "default",
4656
+ headerPosition = "sticky",
4657
+ headerVariant = "glass",
4658
+ paddedContent = true,
4659
+ mobileNav,
4660
+ ...props
4661
+ }, ref) => {
4662
+ return /* @__PURE__ */ jsxs(
4663
+ "div",
4664
+ {
4665
+ ref,
4666
+ className: cn("min-h-screen bg-canvas text-foreground flex flex-col antialiased", className),
4667
+ ...props,
4668
+ children: [
4669
+ banner && /* @__PURE__ */ jsx(AppShellBanner, { children: banner }),
4670
+ header && /* @__PURE__ */ jsx(AppShellHeader, { variant: headerVariant, position: headerPosition, mobileNav, children: header }),
4671
+ /* @__PURE__ */ jsx(AppShellMain, { width: contentWidth, padded: paddedContent, children }),
4672
+ footer && /* @__PURE__ */ jsx(AppShellFooter, { children: footer })
4673
+ ]
4674
+ }
4675
+ );
4676
+ }
4677
+ );
4678
+ AppShell.displayName = "AppShell";
4679
+ AppShell.Banner = AppShellBanner;
4680
+ AppShell.Header = AppShellHeader;
4681
+ AppShell.Main = AppShellMain;
4682
+ AppShell.Footer = AppShellFooter;
4683
+ var sizeClasses = {
4684
+ sm: { wrapper: "kj-rating-sm gap-1", star: "w-4 h-4", text: "text-xs" },
4685
+ md: { wrapper: "kj-rating-md gap-1.5", star: "w-5 h-5", text: "text-sm" },
4686
+ lg: { wrapper: "kj-rating-lg gap-2", star: "w-6 h-6", text: "text-base" },
4687
+ xl: { wrapper: "kj-rating-xl gap-2.5", star: "w-8 h-8", text: "text-lg" }
4688
+ };
4689
+ var iconPaths = {
4690
+ star: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
4691
+ heart: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z",
4692
+ flame: "M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z",
4693
+ shield: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67 0C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",
4694
+ thumb: "M7 10v12M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"
4695
+ };
4696
+ function roundToPrecision(value, precision) {
4697
+ const inv = 1 / precision;
4698
+ return Math.round(value * inv) / inv;
4699
+ }
4700
+ function isCssColor(str) {
4701
+ return str.startsWith("#") || str.startsWith("rgb") || str.startsWith("hsl");
4702
+ }
4703
+ var Rating = React52.forwardRef(
4704
+ ({
4705
+ value: controlledValue,
4706
+ defaultValue = 0,
4707
+ max = 5,
4708
+ precision = 1,
4709
+ size = "md",
4710
+ icon = "star",
4711
+ color = "#f59e0b",
4712
+ emptyColor = "#e5e7eb",
4713
+ allowClear = true,
4714
+ disabled = false,
4715
+ readOnly = false,
4716
+ hoverPreview = true,
4717
+ showValue = false,
4718
+ showCount = false,
4719
+ count,
4720
+ name,
4721
+ onChange,
4722
+ onHoverChange,
4723
+ className,
4724
+ onKeyDown,
4725
+ onMouseLeave,
4726
+ "aria-label": ariaLabel,
4727
+ ...props
4728
+ }, ref) => {
4729
+ const isControlled = controlledValue !== void 0;
4730
+ const [internalValue, setInternalValue] = React52.useState(
4731
+ controlledValue ?? defaultValue
4732
+ );
4733
+ const [hoverValue, setHoverValue] = React52.useState(null);
4734
+ const instanceId = React52.useId();
4735
+ React52.useEffect(() => {
4736
+ if (isControlled) {
4737
+ setInternalValue(controlledValue);
4738
+ }
4739
+ }, [isControlled, controlledValue]);
4740
+ const currentValue = isControlled ? controlledValue : internalValue;
4741
+ const displayValue = hoverPreview && hoverValue !== null ? hoverValue : currentValue;
4742
+ const handleValueChange = (newValue) => {
4743
+ if (disabled || readOnly) return;
4744
+ const clamped = Math.max(0, Math.min(max, roundToPrecision(newValue, precision)));
4745
+ if (!isControlled) {
4746
+ setInternalValue(clamped);
4747
+ }
4748
+ onChange?.(clamped);
4749
+ };
4750
+ const getEventValue = (e, starIndex) => {
4751
+ const target = e.currentTarget;
4752
+ const width = target.offsetWidth;
4753
+ const nativeEv = e.nativeEvent;
4754
+ const offsetX = nativeEv?.offsetX ?? nativeEv?.nativeEvent?.offsetX ?? e.offsetX;
4755
+ if (width > 0 && offsetX !== void 0 && offsetX !== null) {
4756
+ const ratio = Math.max(1e-3, Math.min(1, offsetX / width));
4757
+ const stepInStar = Math.ceil(ratio / precision) * precision;
4758
+ return Math.min(max, Math.max(precision, starIndex - 1 + stepInStar));
4759
+ }
4760
+ return starIndex;
4761
+ };
4762
+ const handleStarClick = (e, starIndex) => {
4763
+ if (disabled || readOnly) return;
4764
+ const targetValue = getEventValue(e, starIndex);
4765
+ let nextValue = targetValue;
4766
+ if (allowClear && targetValue === currentValue) {
4767
+ nextValue = 0;
4768
+ }
4769
+ handleValueChange(nextValue);
4770
+ };
4771
+ const handleStarMouseMove = (e, starIndex) => {
4772
+ if (disabled || readOnly) return;
4773
+ if (hoverPreview) {
4774
+ const hoveredVal = getEventValue(e, starIndex);
4775
+ setHoverValue(hoveredVal);
4776
+ onHoverChange?.(hoveredVal);
4777
+ }
4778
+ };
4779
+ const handleKeyDown = (e) => {
4780
+ if (disabled || readOnly) {
4781
+ onKeyDown?.(e);
4782
+ return;
4783
+ }
4784
+ let nextVal = null;
4785
+ switch (e.key) {
4786
+ case "ArrowRight":
4787
+ case "ArrowUp":
4788
+ nextVal = Math.min(max, currentValue + precision);
4789
+ break;
4790
+ case "ArrowLeft":
4791
+ case "ArrowDown":
4792
+ nextVal = Math.max(0, currentValue - precision);
4793
+ break;
4794
+ case "Home":
4795
+ nextVal = 0;
4796
+ break;
4797
+ case "End":
4798
+ nextVal = max;
4799
+ break;
4800
+ }
4801
+ if (nextVal !== null) {
4802
+ e.preventDefault();
4803
+ handleValueChange(nextVal);
4804
+ }
4805
+ onKeyDown?.(e);
4806
+ };
4807
+ const handleContainerMouseLeave = (e) => {
4808
+ if (hoverPreview) {
4809
+ setHoverValue(null);
4810
+ onHoverChange?.(null);
4811
+ }
4812
+ onMouseLeave?.(e);
4813
+ };
4814
+ const sizing = sizeClasses[size] || sizeClasses.md;
4815
+ return /* @__PURE__ */ jsxs(
4816
+ "div",
4817
+ {
4818
+ ref,
4819
+ role: readOnly ? "img" : "radiogroup",
4820
+ tabIndex: disabled || readOnly ? -1 : 0,
4821
+ "aria-valuenow": displayValue,
4822
+ "aria-valuemin": 0,
4823
+ "aria-valuemax": max,
4824
+ "aria-disabled": disabled || void 0,
4825
+ "aria-readonly": readOnly || void 0,
4826
+ "aria-label": ariaLabel || (readOnly ? `Rated ${displayValue} out of ${max}` : "Rating"),
4827
+ onKeyDown: handleKeyDown,
4828
+ onMouseLeave: handleContainerMouseLeave,
4829
+ className: cn(
4830
+ "inline-flex items-center select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-md",
4831
+ disabled && "opacity-50 cursor-not-allowed",
4832
+ readOnly && "cursor-default",
4833
+ !disabled && !readOnly && "cursor-pointer",
4834
+ sizing.wrapper,
4835
+ className
4836
+ ),
4837
+ ...props,
4838
+ children: [
4839
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: currentValue }),
4840
+ /* @__PURE__ */ jsx("div", { className: "inline-flex items-center gap-0.5", children: Array.from({ length: max }, (_, index) => {
4841
+ const starIndex = index + 1;
4842
+ const fillFraction = Math.max(
4843
+ 0,
4844
+ Math.min(1, displayValue - index)
4845
+ );
4846
+ const gradientId = `rating-grad-${instanceId}-${index}-${Math.round(
4847
+ fillFraction * 100
4848
+ )}`;
4849
+ const CustomIconComponent = typeof icon === "function" || typeof icon === "object" ? icon : null;
4850
+ const builtInIconType = typeof icon === "string" ? icon : "star";
4851
+ const pathD = iconPaths[builtInIconType] || iconPaths.star;
4852
+ return /* @__PURE__ */ jsx(
4853
+ "button",
4854
+ {
4855
+ type: "button",
4856
+ tabIndex: -1,
4857
+ "data-testid": "rating-star",
4858
+ role: readOnly ? void 0 : "radio",
4859
+ "aria-checked": readOnly ? void 0 : displayValue >= starIndex,
4860
+ "aria-label": `${starIndex} of ${max} stars`,
4861
+ disabled,
4862
+ onClick: (e) => handleStarClick(e, starIndex),
4863
+ onMouseMove: (e) => handleStarMouseMove(e, starIndex),
4864
+ onMouseEnter: (e) => handleStarMouseMove(e, starIndex),
4865
+ className: cn(
4866
+ "p-0.5 focus:outline-none transition-transform active:scale-95 disabled:scale-100",
4867
+ sizing.star
4868
+ ),
4869
+ children: CustomIconComponent ? /* @__PURE__ */ jsx(
4870
+ CustomIconComponent,
4871
+ {
4872
+ className: cn(
4873
+ "w-full h-full",
4874
+ fillFraction >= 0.5 ? !isCssColor(color) && color : !isCssColor(emptyColor) && emptyColor
4875
+ )
4876
+ }
4877
+ ) : /* @__PURE__ */ jsxs(
4878
+ "svg",
4879
+ {
4880
+ viewBox: "0 0 24 24",
4881
+ className: "w-full h-full",
4882
+ xmlns: "http://www.w3.org/2000/svg",
4883
+ children: [
4884
+ fillFraction > 0 && fillFraction < 1 && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
4885
+ "linearGradient",
4886
+ {
4887
+ id: gradientId,
4888
+ x1: "0%",
4889
+ y1: "0%",
4890
+ x2: "100%",
4891
+ y2: "0%",
4892
+ children: [
4893
+ isCssColor(color) ? /* @__PURE__ */ jsx(
4894
+ "stop",
4895
+ {
4896
+ offset: `${fillFraction * 100}%`,
4897
+ stopColor: color
4898
+ }
4899
+ ) : /* @__PURE__ */ jsx(
4900
+ "stop",
4901
+ {
4902
+ offset: `${fillFraction * 100}%`,
4903
+ stopColor: "currentColor",
4904
+ className: color
4905
+ }
4906
+ ),
4907
+ isCssColor(emptyColor) ? /* @__PURE__ */ jsx(
4908
+ "stop",
4909
+ {
4910
+ offset: `${fillFraction * 100}%`,
4911
+ stopColor: emptyColor
4912
+ }
4913
+ ) : /* @__PURE__ */ jsx(
4914
+ "stop",
4915
+ {
4916
+ offset: `${fillFraction * 100}%`,
4917
+ stopColor: "currentColor",
4918
+ className: emptyColor
4919
+ }
4920
+ )
4921
+ ]
4922
+ }
4923
+ ) }),
4924
+ /* @__PURE__ */ jsx(
4925
+ "path",
4926
+ {
4927
+ d: pathD,
4928
+ fill: fillFraction === 1 ? isCssColor(color) ? color : void 0 : fillFraction === 0 ? isCssColor(emptyColor) ? emptyColor : void 0 : `url(#${gradientId})`,
4929
+ className: fillFraction === 1 ? !isCssColor(color) ? color : void 0 : fillFraction === 0 ? !isCssColor(emptyColor) ? emptyColor : void 0 : void 0,
4930
+ stroke: "none"
4931
+ }
4932
+ )
4933
+ ]
4934
+ }
4935
+ )
4936
+ },
4937
+ index
4938
+ );
4939
+ }) }),
4940
+ showValue && /* @__PURE__ */ jsx("span", { className: cn("font-medium text-foreground ml-1.5", sizing.text), children: displayValue }),
4941
+ showCount && count !== void 0 && /* @__PURE__ */ jsxs("span", { className: cn("text-muted-foreground ml-1", sizing.text), children: [
4942
+ "(",
4943
+ count,
4944
+ ")"
4945
+ ] })
4946
+ ]
4947
+ }
4948
+ );
4949
+ }
4950
+ );
4951
+ Rating.displayName = "Rating";
4952
+ var RatingField = React52.forwardRef(
4953
+ ({
4954
+ id: customId,
4955
+ label,
4956
+ helperText,
4957
+ errorMessage,
4958
+ required,
4959
+ className,
4960
+ containerClassName,
4961
+ "aria-describedby": ariaDescribedBy,
4962
+ ...ratingProps
4963
+ }, ref) => {
4964
+ const generatedId = React52.useId();
4965
+ const id = customId || generatedId;
4966
+ const helperId = helperText ? `${id}-helper` : void 0;
4967
+ const errorId = errorMessage ? `${id}-error` : void 0;
4968
+ const describedBy = [ariaDescribedBy, helperId, errorId].filter(Boolean).join(" ") || void 0;
4969
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1.5", containerClassName), children: [
4970
+ label && /* @__PURE__ */ jsx(Label, { htmlFor: id, required, children: label }),
4971
+ /* @__PURE__ */ jsx(
4972
+ Rating,
4973
+ {
4974
+ ref,
4975
+ id,
4976
+ className,
4977
+ "aria-describedby": describedBy,
4978
+ ...ratingProps
4979
+ }
4980
+ ),
4981
+ helperText && /* @__PURE__ */ jsx(Hint, { id: helperId, children: helperText }),
4982
+ errorMessage && /* @__PURE__ */ jsx(Hint, { id: errorId, error: true, children: errorMessage })
4983
+ ] });
4984
+ }
4985
+ );
4986
+ RatingField.displayName = "RatingField";
4987
+ var RatingSummary = React52.forwardRef(
4988
+ ({
4989
+ average,
4990
+ totalCount,
4991
+ distribution,
4992
+ max = 5,
4993
+ size = "md",
4994
+ icon = "star",
4995
+ className,
4996
+ ...props
4997
+ }, ref) => {
4998
+ const formattedAverage = Number.isInteger(average) ? average.toString() : average.toFixed(1);
4999
+ const formattedTotalCount = totalCount.toLocaleString();
5000
+ return /* @__PURE__ */ jsxs(
5001
+ "div",
5002
+ {
5003
+ ref,
5004
+ className: cn(
5005
+ "flex flex-col gap-6 sm:flex-row sm:items-center sm:gap-8 p-4 rounded-xl border bg-card text-card-foreground shadow-sm",
5006
+ className
5007
+ ),
5008
+ ...props,
5009
+ children: [
5010
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center p-4 rounded-lg bg-muted/40 text-center min-w-[140px]", children: [
5011
+ /* @__PURE__ */ jsx("div", { className: "text-4xl font-bold tracking-tight text-foreground", children: formattedAverage }),
5012
+ /* @__PURE__ */ jsx("div", { className: "my-1.5", children: /* @__PURE__ */ jsx(
5013
+ Rating,
5014
+ {
5015
+ value: average,
5016
+ max,
5017
+ size,
5018
+ icon,
5019
+ readOnly: true,
5020
+ allowClear: false,
5021
+ hoverPreview: false
5022
+ }
5023
+ ) }),
5024
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
5025
+ formattedTotalCount,
5026
+ " total ratings"
5027
+ ] })
5028
+ ] }),
5029
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col gap-2.5", children: distribution.map((item) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
5030
+ /* @__PURE__ */ jsxs("span", { className: "w-12 text-right font-medium text-foreground whitespace-nowrap", children: [
5031
+ item.stars,
5032
+ " star"
5033
+ ] }),
5034
+ /* @__PURE__ */ jsx(
5035
+ Progress,
5036
+ {
5037
+ value: item.percentage,
5038
+ "aria-label": `${item.stars} star ratings: ${item.percentage}%`,
5039
+ className: "h-2.5 flex-1"
5040
+ }
5041
+ ),
5042
+ /* @__PURE__ */ jsxs("span", { className: "w-12 text-right text-xs text-muted-foreground font-mono", children: [
5043
+ item.percentage,
5044
+ "%"
5045
+ ] })
5046
+ ] }, item.stars)) })
5047
+ ]
5048
+ }
5049
+ );
5050
+ }
5051
+ );
5052
+ RatingSummary.displayName = "RatingSummary";
5053
+ var PricingCard = React52.forwardRef(
5054
+ ({
5055
+ className,
5056
+ name,
5057
+ price,
5058
+ period,
5059
+ description,
5060
+ features = [],
5061
+ variant,
5062
+ badge,
5063
+ popular = false,
5064
+ ctaText = "Get Started",
5065
+ onCtaClick,
5066
+ ctaHref,
5067
+ cta,
5068
+ disabled = false,
5069
+ ...props
5070
+ }, ref) => {
5071
+ const resolvedVariant = variant || (popular ? "featured" : "default");
5072
+ const displayBadge = badge || (popular ? "Most Popular" : void 0);
5073
+ const isFeatured = resolvedVariant === "featured";
5074
+ return /* @__PURE__ */ jsxs(
5075
+ Card,
5076
+ {
5077
+ ref,
5078
+ className: cn(
5079
+ "relative flex flex-col p-6 transition-all duration-300",
5080
+ isFeatured ? "border-amber-500/80 shadow-kj-lg ring-1 ring-amber-500/30" : resolvedVariant === "outline" ? "border-border/80 bg-transparent shadow-none" : "border-border shadow-kj-sm",
5081
+ className
5082
+ ),
5083
+ ...props,
5084
+ children: [
5085
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
5086
+ /* @__PURE__ */ jsxs("div", { children: [
5087
+ /* @__PURE__ */ jsx("h3", { className: "text-xl font-bold tracking-tight text-foreground", children: name }),
5088
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: description })
5089
+ ] }),
5090
+ displayBadge && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-amber-500 px-3 py-0.5 text-[0.7rem] font-bold uppercase tracking-wider text-black shadow-sm shrink-0 mt-0.5", children: displayBadge })
5091
+ ] }),
5092
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-baseline gap-1", children: [
5093
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-extrabold tracking-tight text-foreground", children: price }),
5094
+ period && /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: period })
5095
+ ] }),
5096
+ features.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mb-6 flex flex-1 flex-col gap-2.5 p-0 text-xs list-none", children: features.map((feature, idx) => {
5097
+ const text = typeof feature === "string" ? feature : feature.text;
5098
+ const included = typeof feature === "string" ? true : feature.included ?? true;
5099
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2.5", children: [
5100
+ /* @__PURE__ */ jsx(
5101
+ "span",
5102
+ {
5103
+ className: cn(
5104
+ "flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[0.65rem] font-bold",
5105
+ included ? "bg-amber-500/15 text-amber-600 dark:text-amber-400" : "bg-muted text-muted-foreground/60"
5106
+ ),
5107
+ children: included ? "\u2713" : "\u2715"
5108
+ }
5109
+ ),
5110
+ /* @__PURE__ */ jsx("span", { className: cn(included ? "text-foreground" : "text-muted-foreground line-through opacity-70"), children: text })
5111
+ ] }, idx);
5112
+ }) }),
5113
+ /* @__PURE__ */ jsx("div", { className: "mt-auto pt-2", children: cta ? cta : ctaHref ? (
5114
+ /* eslint-disable-next-line jsx-a11y/anchor-is-valid */
5115
+ /* @__PURE__ */ jsx(
5116
+ "a",
5117
+ {
5118
+ href: disabled ? void 0 : ctaHref,
5119
+ "aria-disabled": disabled || void 0,
5120
+ tabIndex: disabled ? -1 : void 0,
5121
+ className: cn(
5122
+ buttonVariants({ variant: isFeatured ? "primary" : "outline" }),
5123
+ "w-full justify-center",
5124
+ disabled && "pointer-events-none opacity-50"
5125
+ ),
5126
+ children: ctaText
5127
+ }
5128
+ )
5129
+ ) : /* @__PURE__ */ jsx(
5130
+ Button,
5131
+ {
5132
+ variant: isFeatured ? "primary" : "outline",
5133
+ className: "w-full justify-center",
5134
+ onClick: onCtaClick,
5135
+ disabled,
5136
+ children: ctaText
5137
+ }
5138
+ ) })
5139
+ ]
5140
+ }
5141
+ );
5142
+ }
5143
+ );
5144
+ PricingCard.displayName = "PricingCard";
5145
+ var BlogCard = React52.forwardRef(
5146
+ ({
5147
+ className,
5148
+ title,
5149
+ description,
5150
+ coverUrl,
5151
+ coverAlt,
5152
+ category,
5153
+ readTime,
5154
+ publishedAt,
5155
+ author,
5156
+ orientation = "vertical",
5157
+ href,
5158
+ ...props
5159
+ }, ref) => {
5160
+ const isHorizontal = orientation === "horizontal";
5161
+ const isInteractive = Boolean(href || props.onClick);
5162
+ return /* @__PURE__ */ jsxs(
5163
+ Card,
5164
+ {
5165
+ ref,
5166
+ as: href ? "a" : "div",
5167
+ href,
5168
+ interactive: isInteractive,
5169
+ className: cn(
5170
+ "group flex overflow-hidden transition-all duration-300",
5171
+ isHorizontal ? "flex-col sm:flex-row" : "flex-col",
5172
+ className
5173
+ ),
5174
+ ...props,
5175
+ children: [
5176
+ coverUrl && /* @__PURE__ */ jsx(
5177
+ "div",
5178
+ {
5179
+ className: cn(
5180
+ "relative overflow-hidden bg-muted shrink-0",
5181
+ isHorizontal ? "w-full sm:w-2/5 min-h-[160px]" : "w-full aspect-[16/9]"
5182
+ ),
5183
+ children: /* @__PURE__ */ jsx(
5184
+ "img",
5185
+ {
5186
+ src: coverUrl,
5187
+ alt: coverAlt || title,
5188
+ className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
5189
+ }
5190
+ )
5191
+ }
5192
+ ),
5193
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col justify-between p-5", children: [
5194
+ /* @__PURE__ */ jsxs("div", { children: [
5195
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2 text-[0.75rem] text-muted-foreground", children: [
5196
+ category && /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: category }),
5197
+ (readTime || publishedAt) && /* @__PURE__ */ jsxs("span", { children: [
5198
+ publishedAt,
5199
+ " ",
5200
+ publishedAt && readTime && "\u2022",
5201
+ " ",
5202
+ readTime
5203
+ ] })
5204
+ ] }),
5205
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-bold tracking-tight text-foreground transition-colors group-hover:text-primary", children: title }),
5206
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-muted-foreground line-clamp-2", children: description })
5207
+ ] }),
5208
+ author && /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2.5 border-t border-border/60 pt-3", children: [
5209
+ /* @__PURE__ */ jsx(Avatar, { src: author.avatarUrl, alt: author.name, size: "sm", children: author.name.charAt(0) }),
5210
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col text-xs", children: [
5211
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: author.name }),
5212
+ author.role && /* @__PURE__ */ jsx("span", { className: "text-[0.7rem] text-muted-foreground", children: author.role })
5213
+ ] })
5214
+ ] })
5215
+ ] })
5216
+ ]
5217
+ }
5218
+ );
5219
+ }
5220
+ );
5221
+ BlogCard.displayName = "BlogCard";
5222
+ var ProjectCard = React52.forwardRef(
5223
+ ({
5224
+ className,
5225
+ title,
5226
+ description,
5227
+ status,
5228
+ techStack = [],
5229
+ metrics = [],
5230
+ updatedAt,
5231
+ actions,
5232
+ href,
5233
+ ...props
5234
+ }, ref) => {
5235
+ const isInteractive = Boolean(href || props.onClick);
5236
+ return /* @__PURE__ */ jsxs(
5237
+ Card,
5238
+ {
5239
+ ref,
5240
+ as: href ? "a" : "div",
5241
+ href,
5242
+ interactive: isInteractive,
5243
+ className: cn(
5244
+ "group flex flex-col justify-between p-5 transition-all duration-300",
5245
+ className
5246
+ ),
5247
+ ...props,
5248
+ children: [
5249
+ /* @__PURE__ */ jsxs("div", { children: [
5250
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
5251
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-bold tracking-tight text-foreground transition-colors group-hover:text-primary", children: title }),
5252
+ status && /* @__PURE__ */ jsx(Badge, { variant: status.variant || "neutral", className: "shrink-0 text-[0.65rem]", children: status.label })
5253
+ ] }),
5254
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-muted-foreground line-clamp-2", children: description }),
5255
+ techStack.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3.5 flex flex-wrap gap-1.5", children: techStack.map((tech) => /* @__PURE__ */ jsx(Badge, { variant: "neutral", className: "text-[0.65rem] py-0 px-2 font-mono", children: tech }, tech)) })
5256
+ ] }),
5257
+ (metrics.length > 0 || updatedAt || actions) && /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center justify-between border-t border-border/60 pt-3 text-[0.75rem] text-muted-foreground", children: [
5258
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
5259
+ metrics.map((metric, idx) => /* @__PURE__ */ jsxs(
5260
+ "span",
5261
+ {
5262
+ className: "flex items-center gap-1 font-mono font-medium",
5263
+ children: [
5264
+ metric.icon && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: metric.icon }),
5265
+ /* @__PURE__ */ jsx("span", { children: metric.value }),
5266
+ metric.label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70", children: metric.label })
5267
+ ]
5268
+ },
5269
+ metric.label ? `${metric.label}-${idx}` : idx
5270
+ )),
5271
+ updatedAt && /* @__PURE__ */ jsx("span", { children: updatedAt })
5272
+ ] }),
5273
+ actions && /* @__PURE__ */ jsx(
5274
+ "div",
5275
+ {
5276
+ onClick: (e) => e.stopPropagation(),
5277
+ className: "flex items-center gap-1 shrink-0",
5278
+ children: actions
5279
+ }
5280
+ )
5281
+ ] })
5282
+ ]
5283
+ }
5284
+ );
5285
+ }
5286
+ );
5287
+ ProjectCard.displayName = "ProjectCard";
5288
+ function formatRelativeTime(date) {
5289
+ const d = typeof date === "string" ? new Date(date) : date;
5290
+ if (isNaN(d.getTime())) return "";
5291
+ const diffMs = Date.now() - d.getTime();
5292
+ const diffMin = Math.floor(diffMs / 6e4);
5293
+ const diffH = Math.floor(diffMs / 36e5);
5294
+ const diffD = Math.floor(diffMs / 864e5);
5295
+ if (diffMin < 1) return "Just now";
5296
+ if (diffMin < 60) return `${diffMin} min ago`;
5297
+ if (diffH < 24) return diffH === 1 ? "1 hour ago" : `${diffH} hours ago`;
5298
+ if (diffD === 1) return "Yesterday";
5299
+ if (diffD < 7) return `${diffD} days ago`;
5300
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
5301
+ }
5302
+ function useInboxState(initial) {
5303
+ const [items, setItems] = React52.useState(initial);
5304
+ const unreadCount = items.filter((i) => !i.read).length;
5305
+ const markRead = React52.useCallback((id) => {
5306
+ setItems(
5307
+ (prev) => prev.map((item) => item.id === id ? { ...item, read: true } : item)
5308
+ );
5309
+ }, []);
5310
+ const markAllRead = React52.useCallback(() => {
5311
+ setItems((prev) => prev.map((item) => ({ ...item, read: true })));
5312
+ }, []);
5313
+ const dismiss = React52.useCallback((id) => {
5314
+ setItems((prev) => prev.filter((item) => item.id !== id));
5315
+ }, []);
5316
+ return { items, unreadCount, markRead, markAllRead, dismiss };
5317
+ }
5318
+ var InboxContext = React52.createContext(null);
5319
+ function useInboxContext(part) {
5320
+ const ctx = React52.useContext(InboxContext);
5321
+ if (!ctx) throw new Error(`${part} must be used inside <InboxPopover>`);
5322
+ return ctx;
5323
+ }
5324
+ function InboxPopover({
5325
+ children,
5326
+ className,
5327
+ open: openProp,
5328
+ onOpenChange
5329
+ }) {
5330
+ const [openState, setOpenState] = React52.useState(false);
5331
+ const controlled = openProp !== void 0;
5332
+ const open = controlled ? openProp : openState;
5333
+ const triggerRef = React52.useRef(null);
5334
+ const ref = React52.useRef(null);
5335
+ const setOpen = React52.useCallback(
5336
+ (v) => {
5337
+ if (!controlled) setOpenState(v);
5338
+ onOpenChange?.(v);
5339
+ },
5340
+ [controlled, onOpenChange]
5341
+ );
5342
+ React52.useEffect(() => {
5343
+ if (!open) return;
5344
+ const onDoc = (e) => {
5345
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
5346
+ };
5347
+ const onEsc = (e) => {
5348
+ if (e.key === "Escape") {
5349
+ setOpen(false);
5350
+ triggerRef.current?.focus();
5351
+ }
5352
+ };
5353
+ document.addEventListener("mousedown", onDoc);
5354
+ document.addEventListener("keydown", onEsc);
5355
+ return () => {
5356
+ document.removeEventListener("mousedown", onDoc);
5357
+ document.removeEventListener("keydown", onEsc);
5358
+ };
5359
+ }, [open, setOpen]);
5360
+ const ctxValue = React52.useMemo(() => ({ open, setOpen, triggerRef }), [open, setOpen]);
5361
+ return /* @__PURE__ */ jsx(InboxContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx("div", { ref, className: cn("relative inline-block", className), children }) });
5362
+ }
5363
+ function InboxTrigger({
5364
+ unreadCount,
5365
+ label = "Notifications",
5366
+ className,
5367
+ onClick,
5368
+ ...props
5369
+ }) {
5370
+ const ctx = useInboxContext("InboxTrigger");
5371
+ const setRef = (node) => {
5372
+ ctx.triggerRef.current = node;
5373
+ };
5374
+ const displayCount = unreadCount == null || unreadCount <= 0 ? null : unreadCount > 99 ? "99+" : String(unreadCount);
5375
+ const defaultLabel = displayCount ? `${label} (${displayCount} unread)` : label;
5376
+ return /* @__PURE__ */ jsxs(
5377
+ "button",
5378
+ {
5379
+ ref: setRef,
5380
+ type: "button",
5381
+ "aria-label": defaultLabel,
5382
+ "aria-haspopup": "dialog",
5383
+ "aria-expanded": ctx.open,
5384
+ className: cn(
5385
+ "relative inline-flex items-center justify-center w-9 h-9 rounded-full",
5386
+ "bg-surface border border-border text-muted-foreground",
5387
+ "hover:bg-muted hover:text-foreground transition-colors",
5388
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
5389
+ className
5390
+ ),
5391
+ onClick: (e) => {
5392
+ ctx.setOpen(!ctx.open);
5393
+ onClick?.(e);
5394
+ },
5395
+ ...props,
5396
+ children: [
5397
+ /* @__PURE__ */ jsxs(
5398
+ "svg",
5399
+ {
5400
+ width: 16,
5401
+ height: 16,
5402
+ viewBox: "0 0 24 24",
5403
+ fill: "none",
5404
+ stroke: "currentColor",
5405
+ strokeWidth: 2,
5406
+ strokeLinecap: "round",
5407
+ strokeLinejoin: "round",
5408
+ "aria-hidden": "true",
5409
+ children: [
5410
+ /* @__PURE__ */ jsx("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
5411
+ /* @__PURE__ */ jsx("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
5412
+ ]
5413
+ }
5414
+ ),
5415
+ displayCount != null && /* @__PURE__ */ jsx(
5416
+ "span",
5417
+ {
5418
+ "aria-hidden": "true",
5419
+ className: cn(
5420
+ "absolute -top-0.5 -right-0.5 min-w-[16px] h-4 px-1",
5421
+ "rounded-full bg-primary text-primary-foreground",
5422
+ "text-[9px] font-bold leading-4 flex items-center justify-center"
5423
+ ),
5424
+ children: displayCount
5425
+ }
5426
+ )
5427
+ ]
5428
+ }
5429
+ );
5430
+ }
5431
+ function NotificationItem({ item, onDismiss, className }) {
5432
+ const leadingSlot = item.avatarSrc ? /* @__PURE__ */ jsx(
5433
+ "img",
5434
+ {
5435
+ src: item.avatarSrc,
5436
+ alt: item.avatarFallback ?? "",
5437
+ className: "w-7 h-7 rounded-full object-cover flex-shrink-0"
5438
+ }
5439
+ ) : item.avatarFallback ? /* @__PURE__ */ jsx("span", { className: "w-7 h-7 rounded-full bg-primary/20 text-primary text-[10px] font-bold flex items-center justify-center flex-shrink-0 select-none", children: item.avatarFallback }) : item.icon ? /* @__PURE__ */ jsx("span", { className: "w-7 h-7 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 text-primary", children: item.icon }) : null;
5440
+ const textBlock = /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
5441
+ /* @__PURE__ */ jsx("p", { className: cn("text-sm leading-snug", item.read ? "text-muted-foreground" : "text-foreground"), children: item.title }),
5442
+ item.body && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: item.body }),
5443
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: formatRelativeTime(item.timestamp) })
5444
+ ] });
5445
+ const sharedClass = cn(
5446
+ "group relative flex items-start gap-2.5 px-3.5 py-2.5 w-full text-left",
5447
+ "border-b border-border last:border-0",
5448
+ !item.read && "bg-primary/[0.04]",
5449
+ "hover:bg-muted/50 transition-colors",
5450
+ className
5451
+ );
5452
+ const mainContent = /* @__PURE__ */ jsxs(Fragment, { children: [
5453
+ leadingSlot,
5454
+ textBlock
5455
+ ] });
5456
+ const mainAction = item.href ? /* @__PURE__ */ jsx(
5457
+ "a",
5458
+ {
5459
+ href: item.href,
5460
+ onClick: () => item.onClick?.(item.id),
5461
+ className: "flex-1 flex items-start gap-2.5 min-w-0 text-left outline-none",
5462
+ children: mainContent
5463
+ }
5464
+ ) : /* @__PURE__ */ jsx(
5465
+ "button",
5466
+ {
5467
+ type: "button",
5468
+ onClick: () => item.onClick?.(item.id),
5469
+ className: "flex-1 flex items-start gap-2.5 min-w-0 text-left outline-none",
5470
+ children: mainContent
5471
+ }
5472
+ );
5473
+ return /* @__PURE__ */ jsxs("div", { className: sharedClass, children: [
5474
+ mainAction,
5475
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 flex-shrink-0 mt-0.5", children: [
5476
+ /* @__PURE__ */ jsx(
5477
+ "button",
5478
+ {
5479
+ type: "button",
5480
+ "aria-label": "Dismiss",
5481
+ onClick: (e) => {
5482
+ e.preventDefault();
5483
+ e.stopPropagation();
5484
+ onDismiss?.(item.id);
5485
+ },
5486
+ className: cn(
5487
+ "text-muted-foreground hover:text-foreground transition-colors",
5488
+ "opacity-0 group-hover:opacity-100 focus:opacity-100",
5489
+ "text-base leading-none p-0.5 rounded"
5490
+ ),
5491
+ children: "\xD7"
5492
+ }
5493
+ ),
5494
+ /* @__PURE__ */ jsx(
5495
+ "span",
5496
+ {
5497
+ "aria-label": item.read ? void 0 : "Unread",
5498
+ "aria-hidden": item.read ? true : void 0,
5499
+ className: cn(
5500
+ "w-[7px] h-[7px] rounded-full flex-shrink-0 mt-1",
5501
+ item.read ? "border border-border" : "bg-primary"
5502
+ )
5503
+ }
5504
+ )
5505
+ ] })
5506
+ ] });
5507
+ }
5508
+ function InboxContent({
5509
+ items,
5510
+ onMarkAllRead,
5511
+ onDismiss,
5512
+ viewAllHref,
5513
+ viewAllLabel = "View all notifications",
5514
+ emptyState,
5515
+ width = 360,
5516
+ maxHeight = 320,
5517
+ className
5518
+ }) {
5519
+ const ctx = useInboxContext("InboxContent");
5520
+ const ref = React52.useRef(null);
5521
+ React52.useEffect(() => {
5522
+ if (ctx.open) ref.current?.focus();
5523
+ }, [ctx.open]);
5524
+ if (!ctx.open) return null;
5525
+ const allRead = items.length === 0 || items.every((i) => i.read);
5526
+ const defaultEmptyState = /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-10 px-5 text-center", children: [
5527
+ /* @__PURE__ */ jsx("span", { className: "w-10 h-10 rounded-full bg-muted flex items-center justify-center mb-3 text-muted-foreground", children: /* @__PURE__ */ jsxs("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
5528
+ /* @__PURE__ */ jsx("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
5529
+ /* @__PURE__ */ jsx("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
5530
+ ] }) }),
5531
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "All caught up" }),
5532
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "No new notifications" })
5533
+ ] });
5534
+ return /* @__PURE__ */ jsxs(
5535
+ "div",
5536
+ {
5537
+ ref,
5538
+ role: "dialog",
5539
+ "aria-label": "Notifications",
5540
+ tabIndex: -1,
5541
+ style: { width },
5542
+ className: cn(
5543
+ "absolute right-0 top-[calc(100%+6px)] z-40",
5544
+ "bg-surface border border-border rounded-kj-md shadow-kj-lg outline-none",
5545
+ "animate-[kjpop_.12s_ease]",
5546
+ className
5547
+ ),
5548
+ children: [
5549
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3.5 py-2.5 border-b border-border", children: [
5550
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: "Notifications" }),
5551
+ /* @__PURE__ */ jsx(
5552
+ "button",
5553
+ {
5554
+ type: "button",
5555
+ disabled: allRead,
5556
+ "aria-disabled": allRead,
5557
+ onClick: allRead ? void 0 : onMarkAllRead,
5558
+ className: cn(
5559
+ "text-xs font-medium transition-colors",
5560
+ allRead ? "text-muted-foreground cursor-not-allowed" : "text-primary hover:text-primary/80"
5561
+ ),
5562
+ children: "Mark all read"
5563
+ }
5564
+ )
5565
+ ] }),
5566
+ items.length === 0 ? emptyState ?? defaultEmptyState : /* @__PURE__ */ jsx(
5567
+ "div",
5568
+ {
5569
+ className: "overflow-y-auto",
5570
+ style: { maxHeight },
5571
+ children: items.map((item) => /* @__PURE__ */ jsx(
5572
+ NotificationItem,
5573
+ {
5574
+ item,
5575
+ onDismiss
5576
+ },
5577
+ item.id
5578
+ ))
5579
+ }
5580
+ ),
5581
+ items.length > 0 && viewAllHref && /* @__PURE__ */ jsx("div", { className: "border-t border-border px-3.5 py-2.5 text-center", children: /* @__PURE__ */ jsxs(
5582
+ "a",
5583
+ {
5584
+ href: viewAllHref,
5585
+ className: "text-xs font-medium text-primary hover:text-primary/80 transition-colors",
5586
+ children: [
5587
+ viewAllLabel,
5588
+ " \u2192"
5589
+ ]
5590
+ }
5591
+ ) })
5592
+ ]
5593
+ }
5594
+ );
5595
+ }
5596
+ var DEFAULT_COLOR_SWATCHES = [
5597
+ "#EF4444",
5598
+ // Red
5599
+ "#F97316",
5600
+ // Orange
5601
+ "#F59E0B",
5602
+ // Amber
5603
+ "#10B981",
5604
+ // Emerald
5605
+ "#14B8A6",
5606
+ // Teal
5607
+ "#06B6D4",
5608
+ // Sky
5609
+ "#3B82F6",
5610
+ // Blue
5611
+ "#6366F1",
5612
+ // Indigo
5613
+ "#8B5CF6",
5614
+ // Purple
5615
+ "#EC4899",
5616
+ // Pink
5617
+ "#64748B",
5618
+ // Slate
5619
+ "#1E293B"
5620
+ // Charcoal
5621
+ ];
5622
+ function isValidHex(hex) {
5623
+ const trimmed = hex.trim();
5624
+ const clean = trimmed.startsWith("#") ? trimmed.slice(1) : trimmed;
5625
+ return /^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(clean);
5626
+ }
5627
+ function normalizeHex(hex) {
5628
+ let clean = hex.trim();
5629
+ if (!clean.startsWith("#")) clean = `#${clean}`;
5630
+ if (/^#[0-9A-Fa-f]{3}$/.test(clean)) {
5631
+ clean = `#${clean[1]}${clean[1]}${clean[2]}${clean[2]}${clean[3]}${clean[3]}`;
5632
+ }
5633
+ return clean.toUpperCase();
5634
+ }
5635
+ function hexToHsl(hex) {
5636
+ if (!isValidHex(hex)) return { h: 0, s: 0, l: 0 };
5637
+ const normalized = normalizeHex(hex);
5638
+ const r = parseInt(normalized.slice(1, 3), 16) / 255;
5639
+ const g = parseInt(normalized.slice(3, 5), 16) / 255;
5640
+ const b = parseInt(normalized.slice(5, 7), 16) / 255;
5641
+ const max = Math.max(r, g, b);
5642
+ const min = Math.min(r, g, b);
5643
+ let h = 0;
5644
+ let s = 0;
5645
+ const l = (max + min) / 2;
5646
+ if (max !== min) {
5647
+ const d = max - min;
5648
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
5649
+ switch (max) {
5650
+ case r:
5651
+ h = (g - b) / d + (g < b ? 6 : 0);
5652
+ break;
5653
+ case g:
5654
+ h = (b - r) / d + 2;
5655
+ break;
5656
+ case b:
5657
+ h = (r - g) / d + 4;
5658
+ break;
5659
+ }
5660
+ h /= 6;
5661
+ }
5662
+ return {
5663
+ h: Math.round(h * 360),
5664
+ s: Math.round(s * 100),
5665
+ l: Math.round(l * 100)
5666
+ };
5667
+ }
5668
+ function hslToHex(h, s, l) {
5669
+ const normalizedH = (h % 360 + 360) % 360;
5670
+ const sPct = s / 100;
5671
+ const lPct = l / 100;
5672
+ const c = (1 - Math.abs(2 * lPct - 1)) * sPct;
5673
+ const x = c * (1 - Math.abs(normalizedH / 60 % 2 - 1));
5674
+ const m = lPct - c / 2;
5675
+ let r = 0;
5676
+ let g = 0;
5677
+ let b = 0;
5678
+ if (0 <= normalizedH && normalizedH < 60) {
5679
+ r = c;
5680
+ g = x;
5681
+ b = 0;
5682
+ } else if (60 <= normalizedH && normalizedH < 120) {
5683
+ r = x;
5684
+ g = c;
5685
+ b = 0;
5686
+ } else if (120 <= normalizedH && normalizedH < 180) {
5687
+ r = 0;
5688
+ g = c;
5689
+ b = x;
5690
+ } else if (180 <= normalizedH && normalizedH < 240) {
5691
+ r = 0;
5692
+ g = x;
5693
+ b = c;
5694
+ } else if (240 <= normalizedH && normalizedH < 300) {
5695
+ r = x;
5696
+ g = 0;
5697
+ b = c;
5698
+ } else if (300 <= normalizedH && normalizedH < 360) {
5699
+ r = c;
5700
+ g = 0;
5701
+ b = x;
5702
+ }
5703
+ const toHex = (n) => {
5704
+ const hex = Math.round((n + m) * 255).toString(16);
5705
+ return hex.length === 1 ? `0${hex}` : hex;
5706
+ };
5707
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
5708
+ }
5709
+ var ColorPickerSwatch = React52.forwardRef(({ color, selected = false, size = "md", className, style, ...props }, ref) => {
5710
+ const sizeClasses2 = {
5711
+ sm: "w-5 h-5",
5712
+ md: "w-7 h-7",
5713
+ lg: "w-9 h-9"
5714
+ };
5715
+ return /* @__PURE__ */ jsx(
5716
+ "button",
5717
+ {
5718
+ ref,
5719
+ type: "button",
5720
+ "aria-pressed": selected,
5721
+ style: { backgroundColor: color, ...style },
5722
+ className: cn(
5723
+ "relative shrink-0 rounded-full border border-border/50 shadow-sm transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary flex items-center justify-center text-white text-xs font-bold",
5724
+ sizeClasses2[size],
5725
+ selected && "ring-2 ring-primary ring-offset-2 ring-offset-surface",
5726
+ className
5727
+ ),
5728
+ ...props,
5729
+ children: selected && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2713" })
5730
+ }
5731
+ );
5732
+ });
5733
+ ColorPickerSwatch.displayName = "ColorPickerSwatch";
5734
+ var ColorPicker = React52.forwardRef(
5735
+ ({
5736
+ value,
5737
+ defaultValue = "#3B82F6",
5738
+ onChange,
5739
+ swatches = DEFAULT_COLOR_SWATCHES,
5740
+ disabled = false,
5741
+ showHexInput = true,
5742
+ className,
5743
+ ...props
5744
+ }, ref) => {
5745
+ const isControlled = value !== void 0;
5746
+ const [localColor, setLocalColor] = React52.useState(
5747
+ normalizeHex(defaultValue)
5748
+ );
5749
+ const color = isControlled ? normalizeHex(value) : localColor;
5750
+ const [hexInputValue, setHexInputValue] = React52.useState(color);
5751
+ React52.useEffect(() => {
5752
+ setHexInputValue(color);
5753
+ }, [color]);
5754
+ const handleColorChange = React52.useCallback(
5755
+ (newHex) => {
5756
+ const normalized = normalizeHex(newHex);
5757
+ if (!isControlled) {
5758
+ setLocalColor(normalized);
5759
+ }
5760
+ onChange?.(normalized);
5761
+ },
5762
+ [isControlled, onChange]
5763
+ );
5764
+ const hsl = React52.useMemo(() => hexToHsl(color), [color]);
5765
+ const handleHueChange = (e) => {
5766
+ const h = Number(e.target.value);
5767
+ const s = hsl.s === 0 ? 100 : hsl.s;
5768
+ const l = hsl.s === 0 ? 50 : hsl.l;
5769
+ const newHex = hslToHex(h, s, l);
5770
+ handleColorChange(newHex);
5771
+ };
5772
+ const handleHexSubmit = () => {
5773
+ if (isValidHex(hexInputValue)) {
5774
+ handleColorChange(hexInputValue);
5775
+ } else {
5776
+ setHexInputValue(color);
5777
+ }
5778
+ };
5779
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("inline-block", className), ...props, children: /* @__PURE__ */ jsxs(Popover, { children: [
5780
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
5781
+ "button",
5782
+ {
5783
+ type: "button",
5784
+ disabled,
5785
+ "aria-label": `Select color, current ${color}`,
5786
+ className: cn(
5787
+ "inline-flex items-center gap-2.5 px-3 py-1.5 rounded-kj-md border border-border bg-surface text-sm font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50"
5788
+ ),
5789
+ children: [
5790
+ /* @__PURE__ */ jsx(
5791
+ "span",
5792
+ {
5793
+ style: { backgroundColor: color },
5794
+ className: "w-5 h-5 rounded-full border border-border/50 shadow-sm shrink-0"
5795
+ }
5796
+ ),
5797
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs uppercase", children: color }),
5798
+ /* @__PURE__ */ jsx(
5799
+ "svg",
5800
+ {
5801
+ width: 12,
5802
+ height: 12,
5803
+ viewBox: "0 0 24 24",
5804
+ fill: "none",
5805
+ stroke: "currentColor",
5806
+ strokeWidth: 2,
5807
+ className: "text-muted-foreground ml-auto",
5808
+ "aria-hidden": "true",
5809
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
5810
+ }
5811
+ )
5812
+ ]
5813
+ }
5814
+ ) }),
5815
+ /* @__PURE__ */ jsxs(PopoverContent, { side: "bottom", align: "start", className: "w-64 p-3.5 space-y-3.5", children: [
5816
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-2 rounded-kj-md bg-muted/40 border border-border", children: [
5817
+ /* @__PURE__ */ jsx(
5818
+ "span",
5819
+ {
5820
+ style: { backgroundColor: color },
5821
+ className: "w-8 h-8 rounded-full border border-border/50 shadow-sm shrink-0"
5822
+ }
5823
+ ),
5824
+ /* @__PURE__ */ jsxs("div", { children: [
5825
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Selected Color" }),
5826
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-bold text-foreground uppercase", children: color })
5827
+ ] })
5828
+ ] }),
5829
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
5830
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-muted-foreground", children: "Preset Swatches" }),
5831
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-6 gap-2", children: swatches.map((swatch) => {
5832
+ const normalizedSwatch = normalizeHex(swatch);
5833
+ return /* @__PURE__ */ jsx(
5834
+ ColorPickerSwatch,
5835
+ {
5836
+ color: swatch,
5837
+ selected: normalizedSwatch === color,
5838
+ "aria-label": `Select color ${swatch}`,
5839
+ onClick: () => handleColorChange(swatch)
5840
+ },
5841
+ swatch
5842
+ );
5843
+ }) })
5844
+ ] }),
5845
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
5846
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-muted-foreground", children: "Hue" }),
5847
+ /* @__PURE__ */ jsx(
5848
+ "input",
5849
+ {
5850
+ type: "range",
5851
+ min: 0,
5852
+ max: 360,
5853
+ value: hsl.h,
5854
+ onChange: handleHueChange,
5855
+ "aria-label": "Hue slider",
5856
+ className: "w-full h-3 rounded-full appearance-none cursor-pointer",
5857
+ style: {
5858
+ background: "linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)"
5859
+ }
5860
+ }
5861
+ )
5862
+ ] }),
5863
+ showHexInput && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
5864
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-muted-foreground", children: "Hex Code" }),
5865
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(
5866
+ "input",
5867
+ {
5868
+ type: "text",
5869
+ "aria-label": "Hex Code",
5870
+ value: hexInputValue,
5871
+ onChange: (e) => setHexInputValue(e.target.value),
5872
+ onBlur: handleHexSubmit,
5873
+ onKeyDown: (e) => {
5874
+ if (e.key === "Enter") handleHexSubmit();
5875
+ },
5876
+ placeholder: "#000000",
5877
+ className: "flex-1 px-2.5 py-1 text-xs font-mono rounded-kj-md border border-border bg-surface text-foreground uppercase focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
5878
+ }
5879
+ ) })
5880
+ ] })
5881
+ ] })
5882
+ ] }) });
5883
+ }
5884
+ );
5885
+ ColorPicker.displayName = "ColorPicker";
5886
+ var ColorPickerField = React52.forwardRef(({ label, hint, error, required, ...props }, ref) => {
5887
+ return /* @__PURE__ */ jsxs(Field, { children: [
5888
+ label && /* @__PURE__ */ jsx(Label, { required, children: label }),
5889
+ /* @__PURE__ */ jsx(ColorPicker, { ref, ...props }),
5890
+ hint && /* @__PURE__ */ jsx(Hint, { children: hint }),
5891
+ error && /* @__PURE__ */ jsx(Hint, { error: true, children: error })
5892
+ ] });
5893
+ });
5894
+ ColorPickerField.displayName = "ColorPickerField";
5895
+ var COLUMN_CLASSES = {
5896
+ 2: "grid-cols-2",
5897
+ 3: "grid-cols-3",
5898
+ 4: "grid-cols-4",
5899
+ 5: "grid-cols-5"
5900
+ };
5901
+ var ASPECT_CLASSES = {
5902
+ square: "aspect-square",
5903
+ video: "aspect-video",
5904
+ "4/3": "aspect-[4/3]",
5905
+ auto: "aspect-auto"
5906
+ };
5907
+ function ImageGallery({
5908
+ images = [],
5909
+ columns = 3,
5910
+ aspectRatio = "square",
5911
+ maxVisible,
5912
+ className,
5913
+ thumbnailClassName,
5914
+ lightboxClassName,
5915
+ onImageClick
5916
+ }) {
5917
+ const [selectedIndex, setSelectedIndex] = React52.useState(null);
5918
+ const columnClass = COLUMN_CLASSES[columns] || "grid-cols-3";
5919
+ const aspectClass = ASPECT_CLASSES[aspectRatio] || (aspectRatio.startsWith("aspect-") ? aspectRatio : `aspect-[${aspectRatio}]`);
5920
+ const hasMaxVisible = typeof maxVisible === "number" && maxVisible > 0 && images.length > maxVisible;
5921
+ const visibleImages = hasMaxVisible ? images.slice(0, maxVisible) : images;
5922
+ const remainingCount = hasMaxVisible ? images.length - maxVisible + 1 : 0;
5923
+ const handleThumbnailClick = (index) => {
5924
+ setSelectedIndex(index);
5925
+ if (onImageClick) {
5926
+ onImageClick(index);
5927
+ }
5928
+ };
5929
+ const handleClose = React52.useCallback(() => {
5930
+ setSelectedIndex(null);
5931
+ }, []);
5932
+ const handleNext = React52.useCallback(() => {
5933
+ setSelectedIndex((prev) => prev === null ? null : (prev + 1) % images.length);
5934
+ }, [images.length]);
5935
+ const handlePrev = React52.useCallback(() => {
5936
+ setSelectedIndex((prev) => prev === null ? null : (prev - 1 + images.length) % images.length);
5937
+ }, [images.length]);
5938
+ React52.useEffect(() => {
5939
+ if (selectedIndex === null) return;
5940
+ const originalOverflow = document.body.style.overflow;
5941
+ const handleKeyDown = (e) => {
5942
+ if (e.key === "Escape") {
5943
+ handleClose();
5944
+ } else if (e.key === "ArrowRight") {
5945
+ handleNext();
5946
+ } else if (e.key === "ArrowLeft") {
5947
+ handlePrev();
5948
+ }
5949
+ };
5950
+ document.addEventListener("keydown", handleKeyDown);
5951
+ document.body.style.overflow = "hidden";
5952
+ return () => {
5953
+ document.removeEventListener("keydown", handleKeyDown);
5954
+ document.body.style.overflow = originalOverflow;
5955
+ };
5956
+ }, [selectedIndex, handleClose, handleNext, handlePrev]);
5957
+ const activeImage = selectedIndex !== null ? images[selectedIndex] : null;
5958
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5959
+ /* @__PURE__ */ jsx("div", { className: cn("grid gap-3 w-full", columnClass, className), children: visibleImages.map((img, idx) => {
5960
+ const isLastOverlay = hasMaxVisible && idx === maxVisible - 1;
5961
+ const thumbSrc = img.thumbnailSrc || img.src;
5962
+ const defaultAlt = img.alt || `Thumbnail ${idx + 1}`;
5963
+ const buttonAriaLabel = isLastOverlay ? `View all ${images.length} images` : defaultAlt;
5964
+ return /* @__PURE__ */ jsxs(
5965
+ "button",
5966
+ {
5967
+ type: "button",
5968
+ "aria-label": buttonAriaLabel,
5969
+ onClick: () => handleThumbnailClick(idx),
5970
+ className: cn(
5971
+ "relative group overflow-hidden rounded-kj-lg bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 cursor-pointer transition-all duration-200",
5972
+ aspectClass,
5973
+ thumbnailClassName
5974
+ ),
5975
+ children: [
5976
+ /* @__PURE__ */ jsx(
5977
+ "img",
5978
+ {
5979
+ src: thumbSrc,
5980
+ alt: img.alt || `Thumbnail ${idx + 1}`,
5981
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
5982
+ }
5983
+ ),
5984
+ isLastOverlay && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 bg-black/60 backdrop-blur-[2px] flex items-center justify-center text-white font-bold text-xl group-hover:bg-black/70 transition-colors", children: [
5985
+ "+",
5986
+ remainingCount
5987
+ ] })
5988
+ ]
5989
+ },
5990
+ idx
5991
+ );
5992
+ }) }),
5993
+ selectedIndex !== null && activeImage && /* @__PURE__ */ jsxs(
5994
+ "div",
5995
+ {
5996
+ role: "dialog",
5997
+ "aria-modal": "true",
5998
+ "aria-label": "Image gallery lightbox",
5999
+ onClick: (e) => e.target === e.currentTarget && handleClose(),
6000
+ className: cn(
6001
+ "fixed inset-0 z-[100] bg-black/85 backdrop-blur-md flex flex-col justify-between p-4 sm:p-6 transition-opacity duration-200 animate-in fade-in-0",
6002
+ lightboxClassName
6003
+ ),
6004
+ children: [
6005
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between w-full text-white z-10", children: [
6006
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-white/80 select-none", children: [
6007
+ selectedIndex + 1,
6008
+ " of ",
6009
+ images.length
6010
+ ] }),
6011
+ /* @__PURE__ */ jsx(
6012
+ "button",
6013
+ {
6014
+ type: "button",
6015
+ onClick: handleClose,
6016
+ "aria-label": "Close",
6017
+ className: "p-2 rounded-full text-white/80 hover:text-white hover:bg-white/10 transition-colors cursor-pointer",
6018
+ children: /* @__PURE__ */ jsx("svg", { width: 20, height: 20, 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" }) })
6019
+ }
6020
+ )
6021
+ ] }),
6022
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1 flex items-center justify-between gap-4 my-auto min-h-0 w-full", children: [
6023
+ images.length > 1 && /* @__PURE__ */ jsx(
6024
+ "button",
6025
+ {
6026
+ type: "button",
6027
+ onClick: handlePrev,
6028
+ "aria-label": "Previous image",
6029
+ className: "p-3 rounded-full text-white/80 hover:text-white hover:bg-white/10 transition-colors cursor-pointer z-10 shrink-0",
6030
+ children: /* @__PURE__ */ jsx("svg", { width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) })
6031
+ }
6032
+ ),
6033
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col items-center justify-center h-full min-h-0 px-2 select-none", children: /* @__PURE__ */ jsx(
6034
+ "img",
6035
+ {
6036
+ src: activeImage.src,
6037
+ alt: activeImage.alt || `Image ${selectedIndex + 1}`,
6038
+ className: "max-h-[70vh] max-w-full object-contain rounded-kj-md shadow-2xl transition-transform"
6039
+ }
6040
+ ) }),
6041
+ images.length > 1 && /* @__PURE__ */ jsx(
6042
+ "button",
6043
+ {
6044
+ type: "button",
6045
+ onClick: handleNext,
6046
+ "aria-label": "Next image",
6047
+ className: "p-3 rounded-full text-white/80 hover:text-white hover:bg-white/10 transition-colors cursor-pointer z-10 shrink-0",
6048
+ children: /* @__PURE__ */ jsx("svg", { width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) })
6049
+ }
6050
+ )
6051
+ ] }),
6052
+ (activeImage.title || activeImage.caption) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center text-center text-white pb-2 z-10", children: [
6053
+ activeImage.title && /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-white", children: activeImage.title }),
6054
+ activeImage.caption && /* @__PURE__ */ jsx("p", { className: "text-xs sm:text-sm text-white/70 max-w-lg mt-0.5", children: activeImage.caption })
6055
+ ] })
6056
+ ]
6057
+ }
6058
+ )
6059
+ ] });
6060
+ }
2060
6061
 
2061
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, Avatar, AvatarGroup, Badge, BottomNavigation, Breadcrumb, BreadcrumbItem, BreadcrumbSeparator, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CheckboxField, CommandPalette, ConfirmDialog, DashboardShell, DataTable, DetailPageLayout, Drawer, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState, ErrorState, Field, FormField, Hint, Input, Label, MetricCard, Modal, ModalActions, ModalDescription, ModalTitle, PageHeader, Pagination, Progress, Radio, Segmented, Select, SelectField, SettingsLayout, SidebarNav, Skeleton, Slider, Spinner, Stat, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableToolbar, TableWrap, Tabs, TabsContent, TabsList, TabsTrigger, TextField, Textarea, ToastProvider, Tooltip, badgeVariants, buttonVariants, cn, sliderThumbCSS, useToast };
6062
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AppShell, AppShellBanner, AppShellFooter, AppShellHeader, AppShellMain, Avatar, AvatarGroup, Badge, BlogCard, BottomNavigation, BottomSheet, BottomSheetContent, BottomSheetDescription, BottomSheetFooter, BottomSheetHeader, BottomSheetTitle, Breadcrumb, BreadcrumbItem, BreadcrumbSeparator, Button, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CheckboxField, CodeBlock, ColorPicker, ColorPickerField, ColorPickerSwatch, Combobox, ComboboxField, CommandPalette, ConfirmDialog, DEFAULT_COLOR_SWATCHES, DashboardShell, DataTable, DatePicker, DatePickerField, DateRangePicker, DateRangePickerField, DetailPageLayout, Drawer, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Dropzone, EmptyState, ErrorState, Fab, Field, FileUpload, FileUploadField, FormField, Hint, ImageGallery, InboxContent, InboxPopover, InboxTrigger, Input, Kbd, Label, MetricCard, Modal, ModalActions, ModalDescription, ModalTitle, NotificationItem, PageHeader, Pagination, Popover, PopoverContent, PopoverTrigger, PricingCard, Progress, ProgressRing, ProgressRingField, ProjectCard, Radio, RangeCalendar, Rating, RatingField, RatingSummary, SectionHeader, Segmented, Select, SelectField, Separator, SettingsLayout, SidebarNav, Skeleton, Slider, Spinner, Stat, Stepper, StepperContent, StepperDescription, StepperIndicator, StepperItem, StepperList, StepperSeparator, StepperTitle, StepperTrigger, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableToolbar, TableWrap, Tabs, TabsContent, TabsList, TabsTrigger, TextField, Textarea, Timeline, TimelineConnector, TimelineContent, TimelineDot, TimelineItem, TimelineSeparator, TimelineTime, TimelineTitle, ToastProvider, ToggleGroup, Tooltip, badgeVariants, buttonVariants, cn, formatRelativeTime, hexToHsl, hslToHex, isValidHex, normalizeHex, sliderThumbCSS, useInboxState, useStepper, useToast };
2062
6063
  //# sourceMappingURL=index.js.map
2063
6064
  //# sourceMappingURL=index.js.map