@sovereignfs/ui 0.21.3 → 0.33.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.
Files changed (130) hide show
  1. package/dist/Button.module.css +75 -8
  2. package/dist/Calendar.module.css +126 -0
  3. package/dist/Card.module.css +4 -2
  4. package/dist/Checkbox.module.css +109 -0
  5. package/dist/CodeTextarea.module.css +53 -0
  6. package/dist/ConfirmDialog.module.css +109 -0
  7. package/dist/DatePicker.module.css +57 -0
  8. package/dist/Dialog.module.css +73 -46
  9. package/dist/DragHandleRow.module.css +57 -0
  10. package/dist/Drawer.module.css +61 -0
  11. package/dist/FormField.module.css +5 -0
  12. package/dist/Icon.module.css +5 -0
  13. package/dist/Input.module.css +10 -0
  14. package/dist/Menu.module.css +82 -0
  15. package/dist/NavTabs.module.css +4 -2
  16. package/dist/OverlayHeader.module.css +98 -0
  17. package/dist/Popover.module.css +31 -1
  18. package/dist/SegmentedControl.module.css +4 -2
  19. package/dist/Select.module.css +9 -0
  20. package/dist/Sheet.module.css +55 -0
  21. package/dist/SplitPane.module.css +85 -0
  22. package/dist/StatusBadge.module.css +59 -0
  23. package/dist/SystemBanner.module.css +4 -2
  24. package/dist/Tabs.module.css +4 -2
  25. package/dist/TagInput.module.css +141 -0
  26. package/dist/Textarea.module.css +39 -0
  27. package/dist/Toast.module.css +7 -5
  28. package/dist/Toggle.module.css +1 -1
  29. package/dist/Tooltip.module.css +6 -1
  30. package/dist/index.d.ts +549 -20
  31. package/dist/index.js +1496 -193
  32. package/dist/tokens/primitives.css +19 -0
  33. package/dist/tokens/semantic.css +19 -0
  34. package/package.json +1 -1
  35. package/src/__tests__/motion.test.tsx +105 -0
  36. package/src/components/Button/Button.module.css +75 -8
  37. package/src/components/Button/Button.stories.tsx +6 -0
  38. package/src/components/Button/Button.tsx +17 -1
  39. package/src/components/Button/__tests__/Button.test.tsx +19 -0
  40. package/src/components/Calendar/Calendar.module.css +126 -0
  41. package/src/components/Calendar/Calendar.tsx +213 -0
  42. package/src/components/Calendar/__tests__/Calendar.test.tsx +98 -0
  43. package/src/components/Calendar/dateUtils.ts +97 -0
  44. package/src/components/Card/Card.module.css +4 -2
  45. package/src/components/Checkbox/Checkbox.module.css +109 -0
  46. package/src/components/Checkbox/Checkbox.tsx +78 -0
  47. package/src/components/CodeTextarea/CodeTextarea.module.css +53 -0
  48. package/src/components/CodeTextarea/CodeTextarea.stories.tsx +77 -0
  49. package/src/components/CodeTextarea/CodeTextarea.tsx +36 -0
  50. package/src/components/ConfirmDialog/ConfirmDialog.module.css +109 -0
  51. package/src/components/ConfirmDialog/ConfirmDialog.tsx +124 -0
  52. package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +146 -0
  53. package/src/components/DatePicker/DatePicker.module.css +57 -0
  54. package/src/components/DatePicker/DatePicker.tsx +103 -0
  55. package/src/components/DatePicker/__tests__/DatePicker.test.tsx +89 -0
  56. package/src/components/Dialog/Dialog.module.css +73 -46
  57. package/src/components/Dialog/Dialog.stories.tsx +51 -1
  58. package/src/components/Dialog/Dialog.tsx +86 -22
  59. package/src/components/Dialog/__tests__/Dialog.test.tsx +67 -3
  60. package/src/components/DragHandleRow/DragHandleRow.module.css +57 -0
  61. package/src/components/DragHandleRow/DragHandleRow.tsx +61 -0
  62. package/src/components/Drawer/Drawer.module.css +61 -0
  63. package/src/components/Drawer/Drawer.tsx +117 -13
  64. package/src/components/Drawer/__tests__/Drawer.test.tsx +123 -2
  65. package/src/components/FormField/FormField.module.css +5 -0
  66. package/src/components/FormField/FormField.tsx +36 -11
  67. package/src/components/FormField/__tests__/FormField.test.tsx +83 -0
  68. package/src/components/Icon/Icon.module.css +5 -0
  69. package/src/components/Icon/Icon.stories.tsx +4 -3
  70. package/src/components/Icon/Icon.tsx +1 -1
  71. package/src/components/Icon/icons/calendar.tsx +23 -0
  72. package/src/components/Icon/icons/ellipsis-vertical.tsx +22 -0
  73. package/src/components/Icon/icons/index.ts +8 -4
  74. package/src/components/Icon/icons/sliders-horizontal.tsx +9 -9
  75. package/src/components/Input/Input.module.css +10 -0
  76. package/src/components/Menu/Menu.module.css +82 -0
  77. package/src/components/Menu/Menu.tsx +137 -0
  78. package/src/components/Menu/__tests__/Menu.test.tsx +197 -0
  79. package/src/components/NavTabs/NavTabs.module.css +4 -2
  80. package/src/components/OverlayHeader/OverlayHeader.module.css +98 -0
  81. package/src/components/OverlayHeader/OverlayHeader.tsx +62 -0
  82. package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +51 -0
  83. package/src/components/Popover/Popover.module.css +31 -1
  84. package/src/components/Popover/Popover.stories.tsx +126 -1
  85. package/src/components/Popover/Popover.tsx +140 -7
  86. package/src/components/SegmentedControl/SegmentedControl.module.css +4 -2
  87. package/src/components/Select/Select.module.css +9 -0
  88. package/src/components/Select/Select.tsx +10 -2
  89. package/src/components/Sheet/Sheet.module.css +55 -0
  90. package/src/components/Sheet/Sheet.tsx +151 -0
  91. package/src/components/Sheet/__tests__/Sheet.test.tsx +99 -0
  92. package/src/components/SplitPane/SplitPane.module.css +85 -0
  93. package/src/components/SplitPane/SplitPane.stories.tsx +160 -0
  94. package/src/components/SplitPane/SplitPane.tsx +149 -0
  95. package/src/components/SplitPane/__tests__/SplitPane.test.tsx +51 -0
  96. package/src/components/StatusBadge/StatusBadge.module.css +59 -0
  97. package/src/components/StatusBadge/StatusBadge.stories.tsx +74 -0
  98. package/src/components/StatusBadge/StatusBadge.tsx +65 -0
  99. package/src/components/SystemBanner/SystemBanner.module.css +4 -2
  100. package/src/components/Tabs/Tabs.module.css +4 -2
  101. package/src/components/TagInput/TagInput.module.css +141 -0
  102. package/src/components/TagInput/TagInput.stories.tsx +109 -0
  103. package/src/components/TagInput/TagInput.tsx +174 -0
  104. package/src/components/TagInput/__tests__/TagInput.test.tsx +69 -0
  105. package/src/components/Textarea/Textarea.module.css +39 -0
  106. package/src/components/Textarea/Textarea.tsx +15 -0
  107. package/src/components/Textarea/__tests__/Textarea.test.tsx +29 -0
  108. package/src/components/Toast/Toast.module.css +7 -5
  109. package/src/components/Toggle/Toggle.module.css +1 -1
  110. package/src/components/Tooltip/Tooltip.module.css +6 -1
  111. package/src/hooks/__tests__/useDoubleTap.test.tsx +117 -0
  112. package/src/hooks/__tests__/useIsMobile.test.tsx +80 -0
  113. package/src/hooks/__tests__/useLongPress.test.tsx +158 -0
  114. package/src/hooks/index.ts +4 -0
  115. package/src/hooks/useDoubleTap.ts +91 -0
  116. package/src/hooks/useIsMobile.ts +33 -0
  117. package/src/hooks/useLongPress.ts +185 -0
  118. package/src/index.ts +37 -2
  119. package/src/motion.ts +78 -0
  120. package/src/stories/Checkbox.stories.tsx +45 -0
  121. package/src/stories/DesignSystemOverview.stories.tsx +354 -8
  122. package/src/stories/DragHandleRow.stories.tsx +41 -0
  123. package/src/stories/FormField.stories.tsx +8 -8
  124. package/src/stories/InteractionHooks.stories.tsx +388 -0
  125. package/src/stories/MobilePatterns.stories.tsx +4 -4
  126. package/src/stories/Textarea.stories.tsx +51 -0
  127. package/src/stories/TokenGallery.stories.tsx +87 -1
  128. package/src/tokens/primitives.css +19 -0
  129. package/src/tokens/semantic.css +19 -0
  130. package/src/components/Icon/icons/terminal.tsx +0 -19
package/dist/index.js CHANGED
@@ -591,9 +591,9 @@ function AlertTriangleIcon(props) {
591
591
  );
592
592
  }
593
593
 
594
- // src/components/Icon/icons/sliders-horizontal.tsx
594
+ // src/components/Icon/icons/calendar.tsx
595
595
  import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
596
- function SlidersHorizontalIcon(props) {
596
+ function CalendarIcon(props) {
597
597
  return /* @__PURE__ */ jsxs21(
598
598
  "svg",
599
599
  {
@@ -606,23 +606,18 @@ function SlidersHorizontalIcon(props) {
606
606
  strokeLinejoin: "round",
607
607
  ...props,
608
608
  children: [
609
- /* @__PURE__ */ jsx28("line", { x1: "21", x2: "14", y1: "4", y2: "4" }),
610
- /* @__PURE__ */ jsx28("line", { x1: "10", x2: "3", y1: "4", y2: "4" }),
611
- /* @__PURE__ */ jsx28("line", { x1: "21", x2: "12", y1: "12", y2: "12" }),
612
- /* @__PURE__ */ jsx28("line", { x1: "8", x2: "3", y1: "12", y2: "12" }),
613
- /* @__PURE__ */ jsx28("line", { x1: "21", x2: "16", y1: "20", y2: "20" }),
614
- /* @__PURE__ */ jsx28("line", { x1: "12", x2: "3", y1: "20", y2: "20" }),
615
- /* @__PURE__ */ jsx28("line", { x1: "14", x2: "14", y1: "2", y2: "6" }),
616
- /* @__PURE__ */ jsx28("line", { x1: "8", x2: "8", y1: "10", y2: "14" }),
617
- /* @__PURE__ */ jsx28("line", { x1: "16", x2: "16", y1: "18", y2: "22" })
609
+ /* @__PURE__ */ jsx28("path", { d: "M8 2v4" }),
610
+ /* @__PURE__ */ jsx28("path", { d: "M16 2v4" }),
611
+ /* @__PURE__ */ jsx28("rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }),
612
+ /* @__PURE__ */ jsx28("path", { d: "M3 10h18" })
618
613
  ]
619
614
  }
620
615
  );
621
616
  }
622
617
 
623
- // src/components/Icon/icons/terminal.tsx
618
+ // src/components/Icon/icons/sliders-horizontal.tsx
624
619
  import { jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
625
- function TerminalIcon(props) {
620
+ function SlidersHorizontalIcon(props) {
626
621
  return /* @__PURE__ */ jsxs22(
627
622
  "svg",
628
623
  {
@@ -635,8 +630,38 @@ function TerminalIcon(props) {
635
630
  strokeLinejoin: "round",
636
631
  ...props,
637
632
  children: [
638
- /* @__PURE__ */ jsx29("polyline", { points: "4 17 10 11 4 5" }),
639
- /* @__PURE__ */ jsx29("line", { x1: "12", x2: "20", y1: "19", y2: "19" })
633
+ /* @__PURE__ */ jsx29("path", { d: "M10 5H3" }),
634
+ /* @__PURE__ */ jsx29("path", { d: "M12 19H3" }),
635
+ /* @__PURE__ */ jsx29("path", { d: "M14 3v4" }),
636
+ /* @__PURE__ */ jsx29("path", { d: "M16 17v4" }),
637
+ /* @__PURE__ */ jsx29("path", { d: "M21 12h-9" }),
638
+ /* @__PURE__ */ jsx29("path", { d: "M21 19h-5" }),
639
+ /* @__PURE__ */ jsx29("path", { d: "M21 5h-7" }),
640
+ /* @__PURE__ */ jsx29("path", { d: "M8 10v4" }),
641
+ /* @__PURE__ */ jsx29("path", { d: "M8 12H3" })
642
+ ]
643
+ }
644
+ );
645
+ }
646
+
647
+ // src/components/Icon/icons/ellipsis-vertical.tsx
648
+ import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
649
+ function EllipsisVerticalIcon(props) {
650
+ return /* @__PURE__ */ jsxs23(
651
+ "svg",
652
+ {
653
+ xmlns: "http://www.w3.org/2000/svg",
654
+ viewBox: "0 0 24 24",
655
+ fill: "none",
656
+ stroke: "currentColor",
657
+ strokeWidth: "2",
658
+ strokeLinecap: "round",
659
+ strokeLinejoin: "round",
660
+ ...props,
661
+ children: [
662
+ /* @__PURE__ */ jsx30("circle", { cx: "12", cy: "12", r: "1" }),
663
+ /* @__PURE__ */ jsx30("circle", { cx: "12", cy: "5", r: "1" }),
664
+ /* @__PURE__ */ jsx30("circle", { cx: "12", cy: "19", r: "1" })
640
665
  ]
641
666
  }
642
667
  );
@@ -670,13 +695,14 @@ var ICONS = {
670
695
  "grid-2x2": Grid2x2Icon,
671
696
  info: InfoIcon,
672
697
  "alert-triangle": AlertTriangleIcon,
698
+ calendar: CalendarIcon,
673
699
  "sliders-horizontal": SlidersHorizontalIcon,
674
- terminal: TerminalIcon
700
+ "ellipsis-vertical": EllipsisVerticalIcon
675
701
  };
676
702
 
677
703
  // src/components/Icon/Icon.tsx
678
704
  import styles2 from "./Icon.module.css";
679
- import { jsx as jsx30 } from "react/jsx-runtime";
705
+ import { jsx as jsx31 } from "react/jsx-runtime";
680
706
  function Icon({
681
707
  name,
682
708
  size = "md",
@@ -685,14 +711,14 @@ function Icon({
685
711
  }) {
686
712
  const Svg = ICONS[name];
687
713
  const classes = [styles2.root, styles2[size], className].filter(Boolean).join(" ");
688
- return /* @__PURE__ */ jsx30(Svg, { className: classes, role: aria["aria-label"] ? "img" : void 0, ...aria });
714
+ return /* @__PURE__ */ jsx31(Svg, { className: classes, role: aria["aria-label"] ? "img" : void 0, ...aria });
689
715
  }
690
716
 
691
717
  // src/components/Toggle/Toggle.tsx
692
718
  import styles3 from "./Toggle.module.css";
693
- import { jsx as jsx31 } from "react/jsx-runtime";
719
+ import { jsx as jsx32 } from "react/jsx-runtime";
694
720
  function Toggle({ checked, onChange, disabled, className, ...rest }) {
695
- return /* @__PURE__ */ jsx31(
721
+ return /* @__PURE__ */ jsx32(
696
722
  "button",
697
723
  {
698
724
  type: "button",
@@ -702,14 +728,14 @@ function Toggle({ checked, onChange, disabled, className, ...rest }) {
702
728
  className: [styles3.track, checked ? styles3.on : styles3.off, className].filter(Boolean).join(" "),
703
729
  onClick: () => onChange(!checked),
704
730
  ...rest,
705
- children: /* @__PURE__ */ jsx31("span", { className: styles3.thumb })
731
+ children: /* @__PURE__ */ jsx32("span", { className: styles3.thumb })
706
732
  }
707
733
  );
708
734
  }
709
735
 
710
736
  // src/components/SegmentedControl/SegmentedControl.tsx
711
737
  import styles4 from "./SegmentedControl.module.css";
712
- import { jsx as jsx32 } from "react/jsx-runtime";
738
+ import { jsx as jsx33 } from "react/jsx-runtime";
713
739
  function SegmentedControl({
714
740
  value,
715
741
  onChange,
@@ -717,7 +743,7 @@ function SegmentedControl({
717
743
  size = "md",
718
744
  "aria-label": ariaLabel
719
745
  }) {
720
- return /* @__PURE__ */ jsx32(
746
+ return /* @__PURE__ */ jsx33(
721
747
  "div",
722
748
  {
723
749
  role: "radiogroup",
@@ -725,7 +751,7 @@ function SegmentedControl({
725
751
  className: [styles4.track, styles4[size]].join(" "),
726
752
  children: options.map((opt) => {
727
753
  const active = opt.value === value;
728
- return /* @__PURE__ */ jsx32(
754
+ return /* @__PURE__ */ jsx33(
729
755
  "button",
730
756
  {
731
757
  type: "button",
@@ -744,11 +770,11 @@ function SegmentedControl({
744
770
 
745
771
  // src/components/Tabs/Tabs.tsx
746
772
  import styles5 from "./Tabs.module.css";
747
- import { jsx as jsx33 } from "react/jsx-runtime";
773
+ import { jsx as jsx34 } from "react/jsx-runtime";
748
774
  function Tabs({ items, value, onChange, "aria-label": ariaLabel }) {
749
- return /* @__PURE__ */ jsx33("div", { role: "tablist", "aria-label": ariaLabel, className: styles5.tablist, children: items.map((item) => {
775
+ return /* @__PURE__ */ jsx34("div", { role: "tablist", "aria-label": ariaLabel, className: styles5.tablist, children: items.map((item) => {
750
776
  const active = item.value === value;
751
- return /* @__PURE__ */ jsx33(
777
+ return /* @__PURE__ */ jsx34(
752
778
  "button",
753
779
  {
754
780
  type: "button",
@@ -765,15 +791,15 @@ function Tabs({ items, value, onChange, "aria-label": ariaLabel }) {
765
791
 
766
792
  // src/components/Select/Select.tsx
767
793
  import styles6 from "./Select.module.css";
768
- import { jsx as jsx34, jsxs as jsxs23 } from "react/jsx-runtime";
794
+ import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
769
795
  function Select({ className, size = "md", children, ...rest }) {
770
- return /* @__PURE__ */ jsxs23(
796
+ return /* @__PURE__ */ jsxs24(
771
797
  "div",
772
798
  {
773
- className: [styles6.wrapper, size === "sm" ? styles6.sm : void 0].filter(Boolean).join(" "),
799
+ className: [styles6.wrapper, size === "sm" ? styles6.sm : void 0, className].filter(Boolean).join(" "),
774
800
  children: [
775
- /* @__PURE__ */ jsx34("select", { className: [styles6.select, className].filter(Boolean).join(" "), ...rest, children }),
776
- /* @__PURE__ */ jsx34(
801
+ /* @__PURE__ */ jsx35("select", { className: styles6.select, ...rest, children }),
802
+ /* @__PURE__ */ jsx35(
777
803
  "svg",
778
804
  {
779
805
  className: styles6.chevron,
@@ -786,7 +812,7 @@ function Select({ className, size = "md", children, ...rest }) {
786
812
  strokeWidth: "2",
787
813
  strokeLinecap: "round",
788
814
  strokeLinejoin: "round",
789
- children: /* @__PURE__ */ jsx34("polyline", { points: "6 9 12 15 18 9" })
815
+ children: /* @__PURE__ */ jsx35("polyline", { points: "6 9 12 15 18 9" })
790
816
  }
791
817
  )
792
818
  ]
@@ -796,18 +822,18 @@ function Select({ className, size = "md", children, ...rest }) {
796
822
 
797
823
  // src/components/SystemBanner/SystemBanner.tsx
798
824
  import styles7 from "./SystemBanner.module.css";
799
- import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
825
+ import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
800
826
  function SystemBanner({ variant = "info", onDismiss, children }) {
801
- return /* @__PURE__ */ jsxs24("div", { role: "status", "aria-live": "polite", className: [styles7.banner, styles7[variant]].join(" "), children: [
802
- /* @__PURE__ */ jsx35("span", { className: styles7.content, children }),
803
- onDismiss && /* @__PURE__ */ jsx35(
827
+ return /* @__PURE__ */ jsxs25("div", { role: "status", "aria-live": "polite", className: [styles7.banner, styles7[variant]].join(" "), children: [
828
+ /* @__PURE__ */ jsx36("span", { className: styles7.content, children }),
829
+ onDismiss && /* @__PURE__ */ jsx36(
804
830
  "button",
805
831
  {
806
832
  type: "button",
807
833
  className: styles7.dismiss,
808
834
  onClick: onDismiss,
809
835
  "aria-label": "Dismiss banner",
810
- children: /* @__PURE__ */ jsx35(
836
+ children: /* @__PURE__ */ jsx36(
811
837
  "svg",
812
838
  {
813
839
  width: "14",
@@ -816,7 +842,7 @@ function SystemBanner({ variant = "info", onDismiss, children }) {
816
842
  fill: "none",
817
843
  xmlns: "http://www.w3.org/2000/svg",
818
844
  "aria-hidden": true,
819
- children: /* @__PURE__ */ jsx35(
845
+ children: /* @__PURE__ */ jsx36(
820
846
  "path",
821
847
  {
822
848
  d: "M1 1L13 13M13 1L1 13",
@@ -833,19 +859,71 @@ function SystemBanner({ variant = "info", onDismiss, children }) {
833
859
  }
834
860
 
835
861
  // src/components/Popover/Popover.tsx
836
- import { useEffect, useRef } from "react";
862
+ import {
863
+ useEffect,
864
+ useLayoutEffect,
865
+ useRef,
866
+ useState
867
+ } from "react";
837
868
  import styles8 from "./Popover.module.css";
838
- import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
869
+ import { jsx as jsx37, jsxs as jsxs26 } from "react/jsx-runtime";
870
+ var TRIGGER_GAP = 8;
871
+ var VIEWPORT_MARGIN = 4;
839
872
  function Popover({
840
873
  trigger,
841
874
  open,
842
875
  onClose,
843
876
  align = "right",
844
877
  width = 288,
878
+ panelStyle,
845
879
  "aria-label": ariaLabel,
846
880
  children
847
881
  }) {
848
882
  const containerRef = useRef(null);
883
+ const panelRef = useRef(null);
884
+ const [triggerWidth, setTriggerWidth] = useState(null);
885
+ const [openUpward, setOpenUpward] = useState(false);
886
+ const [maxHeight, setMaxHeight] = useState(null);
887
+ useEffect(() => {
888
+ if (width !== "trigger") return;
889
+ const el = containerRef.current?.firstElementChild;
890
+ if (!el) return;
891
+ function recompute() {
892
+ if (el) setTriggerWidth(el.getBoundingClientRect().width);
893
+ }
894
+ recompute();
895
+ const ro = new ResizeObserver(recompute);
896
+ ro.observe(el);
897
+ return () => ro.disconnect();
898
+ }, [width]);
899
+ useLayoutEffect(() => {
900
+ if (!open) {
901
+ setOpenUpward(false);
902
+ setMaxHeight(null);
903
+ return;
904
+ }
905
+ const container = containerRef.current;
906
+ const panel = panelRef.current;
907
+ if (!container || !panel) return;
908
+ const measure = () => {
909
+ const triggerRect = container.getBoundingClientRect();
910
+ const panelHeight2 = panel.getBoundingClientRect().height;
911
+ return {
912
+ panelHeight: panelHeight2,
913
+ spaceBelow: window.innerHeight - triggerRect.bottom - TRIGGER_GAP - VIEWPORT_MARGIN,
914
+ spaceAbove: triggerRect.top - TRIGGER_GAP - VIEWPORT_MARGIN
915
+ };
916
+ };
917
+ let { panelHeight, spaceBelow, spaceAbove } = measure();
918
+ if (panelHeight > spaceBelow && panelHeight > spaceAbove) {
919
+ container.scrollIntoView({ block: "start", behavior: "instant" });
920
+ ({ panelHeight, spaceBelow, spaceAbove } = measure());
921
+ }
922
+ const upward = panelHeight > spaceBelow && spaceAbove > spaceBelow;
923
+ setOpenUpward(upward);
924
+ const available = upward ? spaceAbove : spaceBelow;
925
+ setMaxHeight(panelHeight > available ? Math.max(available, 0) : null);
926
+ }, [open, children]);
849
927
  useEffect(() => {
850
928
  if (!open) return;
851
929
  function handleMouseDown(e) {
@@ -864,16 +942,21 @@ function Popover({
864
942
  document.addEventListener("keydown", handleKeyDown);
865
943
  return () => document.removeEventListener("keydown", handleKeyDown);
866
944
  }, [open, onClose]);
867
- return /* @__PURE__ */ jsxs25("div", { ref: containerRef, className: styles8.container, children: [
945
+ return /* @__PURE__ */ jsxs26("div", { ref: containerRef, className: styles8.container, children: [
868
946
  trigger,
869
- open && /* @__PURE__ */ jsx36(
947
+ open && /* @__PURE__ */ jsx37(
870
948
  "div",
871
949
  {
950
+ ref: panelRef,
872
951
  role: "dialog",
873
952
  "aria-label": ariaLabel,
874
953
  "aria-modal": false,
875
- className: [styles8.panel, styles8[align]].join(" "),
876
- style: { width },
954
+ className: [styles8.panel, styles8[align], openUpward ? styles8.upward : ""].filter(Boolean).join(" "),
955
+ style: {
956
+ width: width === "trigger" ? triggerWidth ?? void 0 : width,
957
+ maxHeight: maxHeight ?? void 0,
958
+ ...panelStyle
959
+ },
877
960
  children
878
961
  }
879
962
  )
@@ -882,29 +965,372 @@ function Popover({
882
965
 
883
966
  // src/components/Button/Button.tsx
884
967
  import styles9 from "./Button.module.css";
885
- import { jsx as jsx37 } from "react/jsx-runtime";
968
+ import { jsx as jsx38, jsxs as jsxs27 } from "react/jsx-runtime";
886
969
  function Button({
887
970
  variant = "primary",
888
971
  size = "md",
889
972
  type = "button",
973
+ loading = false,
974
+ disabled,
890
975
  className,
891
976
  children,
892
977
  ...rest
893
978
  }) {
894
979
  const classes = [styles9.button, styles9[variant], styles9[size], className].filter(Boolean).join(" ");
895
- return /* @__PURE__ */ jsx37("button", { type, className: classes, ...rest, children });
980
+ return /* @__PURE__ */ jsxs27(
981
+ "button",
982
+ {
983
+ type,
984
+ className: classes,
985
+ disabled: disabled || loading,
986
+ "aria-busy": loading || void 0,
987
+ ...rest,
988
+ children: [
989
+ loading && // aria-hidden: the button's own accessible name (its visible children,
990
+ // unchanged below) plus aria-busy already communicate the pending
991
+ // state — an unhidden Spinner-style status region would double-narrate it.
992
+ /* @__PURE__ */ jsx38("span", { className: styles9.spinner, "aria-hidden": "true" }),
993
+ children
994
+ ]
995
+ }
996
+ );
896
997
  }
897
998
 
898
999
  // src/components/Input/Input.tsx
899
1000
  import styles10 from "./Input.module.css";
900
- import { jsx as jsx38 } from "react/jsx-runtime";
1001
+ import { jsx as jsx39 } from "react/jsx-runtime";
901
1002
  function Input({ type = "text", className, ...rest }) {
902
1003
  const classes = [styles10.input, className].filter(Boolean).join(" ");
903
- return /* @__PURE__ */ jsx38("input", { type, className: classes, ...rest });
1004
+ return /* @__PURE__ */ jsx39("input", { type, className: classes, ...rest });
1005
+ }
1006
+
1007
+ // src/components/Textarea/Textarea.tsx
1008
+ import styles11 from "./Textarea.module.css";
1009
+ import { jsx as jsx40 } from "react/jsx-runtime";
1010
+ function Textarea({ className, rows = 4, ...rest }) {
1011
+ const classes = [styles11.textarea, className].filter(Boolean).join(" ");
1012
+ return /* @__PURE__ */ jsx40("textarea", { rows, className: classes, ...rest });
1013
+ }
1014
+
1015
+ // src/components/CodeTextarea/CodeTextarea.tsx
1016
+ import styles12 from "./CodeTextarea.module.css";
1017
+ import { jsx as jsx41 } from "react/jsx-runtime";
1018
+ function CodeTextarea({
1019
+ className,
1020
+ rows = 12,
1021
+ spellCheck = false,
1022
+ wrap = "off",
1023
+ invalid = false,
1024
+ "aria-invalid": ariaInvalid,
1025
+ ...rest
1026
+ }) {
1027
+ const classes = [styles12.textarea, invalid ? styles12.invalid : void 0, className].filter(Boolean).join(" ");
1028
+ return /* @__PURE__ */ jsx41(
1029
+ "textarea",
1030
+ {
1031
+ rows,
1032
+ spellCheck,
1033
+ wrap,
1034
+ className: classes,
1035
+ "aria-invalid": ariaInvalid ?? (invalid || void 0),
1036
+ ...rest
1037
+ }
1038
+ );
1039
+ }
1040
+
1041
+ // src/components/StatusBadge/StatusBadge.tsx
1042
+ import styles13 from "./StatusBadge.module.css";
1043
+ import { jsx as jsx42, jsxs as jsxs28 } from "react/jsx-runtime";
1044
+ var STATUS_CLASS = {
1045
+ unmodified: styles13.neutral,
1046
+ draft: styles13.info,
1047
+ committed: styles13.success,
1048
+ conflict: styles13.error,
1049
+ "pending-delete": styles13.warning,
1050
+ synced: styles13.success,
1051
+ warning: styles13.warning,
1052
+ error: styles13.error
1053
+ };
1054
+ var STATUS_LABEL = {
1055
+ unmodified: "Unmodified",
1056
+ draft: "Draft",
1057
+ committed: "Committed",
1058
+ conflict: "Conflict",
1059
+ "pending-delete": "Pending delete",
1060
+ synced: "Synced",
1061
+ warning: "Warning",
1062
+ error: "Error"
1063
+ };
1064
+ function StatusBadge({
1065
+ status,
1066
+ children,
1067
+ className,
1068
+ "aria-label": ariaLabel
1069
+ }) {
1070
+ const label = children ?? STATUS_LABEL[status];
1071
+ return /* @__PURE__ */ jsxs28(
1072
+ "span",
1073
+ {
1074
+ className: [styles13.badge, STATUS_CLASS[status], className].filter(Boolean).join(" "),
1075
+ "aria-label": ariaLabel,
1076
+ children: [
1077
+ /* @__PURE__ */ jsx42("span", { className: styles13.dot, "aria-hidden": "true" }),
1078
+ /* @__PURE__ */ jsx42("span", { className: styles13.label, children: label })
1079
+ ]
1080
+ }
1081
+ );
1082
+ }
1083
+
1084
+ // src/components/SplitPane/SplitPane.tsx
1085
+ import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
1086
+ import styles14 from "./SplitPane.module.css";
1087
+ import { jsx as jsx43, jsxs as jsxs29 } from "react/jsx-runtime";
1088
+ var clampSize = (value, min, max) => Math.min(Math.max(value, min), max);
1089
+ function SplitPane({
1090
+ primary,
1091
+ secondary,
1092
+ defaultPrimarySize = 50,
1093
+ minPrimarySize = 30,
1094
+ maxPrimarySize = 70,
1095
+ resizable = true,
1096
+ primaryLabel = "Primary pane",
1097
+ secondaryLabel = "Secondary pane",
1098
+ resizeLabel = "Resize panes",
1099
+ className
1100
+ }) {
1101
+ const initialSize = clampSize(defaultPrimarySize, minPrimarySize, maxPrimarySize);
1102
+ const [primarySize, setPrimarySize] = useState2(initialSize);
1103
+ const containerRef = useRef2(null);
1104
+ const dragState = useRef2(null);
1105
+ const updateFromClientX = useCallback(
1106
+ (clientX) => {
1107
+ const rect = containerRef.current?.getBoundingClientRect();
1108
+ if (!rect || rect.width === 0) {
1109
+ return;
1110
+ }
1111
+ const next = (clientX - rect.left) / rect.width * 100;
1112
+ setPrimarySize(clampSize(next, minPrimarySize, maxPrimarySize));
1113
+ },
1114
+ [maxPrimarySize, minPrimarySize]
1115
+ );
1116
+ useEffect2(() => {
1117
+ const handlePointerMove = (event) => {
1118
+ if (!dragState.current || event.pointerId !== dragState.current.pointerId) {
1119
+ return;
1120
+ }
1121
+ updateFromClientX(event.clientX);
1122
+ };
1123
+ const stopDragging = (event) => {
1124
+ if (!dragState.current || event.pointerId !== dragState.current.pointerId) {
1125
+ return;
1126
+ }
1127
+ dragState.current = null;
1128
+ document.body.style.cursor = "";
1129
+ document.body.style.userSelect = "";
1130
+ };
1131
+ window.addEventListener("pointermove", handlePointerMove);
1132
+ window.addEventListener("pointerup", stopDragging);
1133
+ window.addEventListener("pointercancel", stopDragging);
1134
+ return () => {
1135
+ window.removeEventListener("pointermove", handlePointerMove);
1136
+ window.removeEventListener("pointerup", stopDragging);
1137
+ window.removeEventListener("pointercancel", stopDragging);
1138
+ document.body.style.cursor = "";
1139
+ document.body.style.userSelect = "";
1140
+ };
1141
+ }, [updateFromClientX]);
1142
+ const startDragging = (event) => {
1143
+ if (!resizable) {
1144
+ return;
1145
+ }
1146
+ dragState.current = { pointerId: event.pointerId };
1147
+ document.body.style.cursor = "col-resize";
1148
+ document.body.style.userSelect = "none";
1149
+ event.currentTarget.setPointerCapture(event.pointerId);
1150
+ };
1151
+ const resizeWithKeyboard = (event) => {
1152
+ const step = event.shiftKey ? 10 : 5;
1153
+ if (event.key === "ArrowLeft") {
1154
+ event.preventDefault();
1155
+ setPrimarySize((size) => clampSize(size - step, minPrimarySize, maxPrimarySize));
1156
+ }
1157
+ if (event.key === "ArrowRight") {
1158
+ event.preventDefault();
1159
+ setPrimarySize((size) => clampSize(size + step, minPrimarySize, maxPrimarySize));
1160
+ }
1161
+ if (event.key === "Home") {
1162
+ event.preventDefault();
1163
+ setPrimarySize(minPrimarySize);
1164
+ }
1165
+ if (event.key === "End") {
1166
+ event.preventDefault();
1167
+ setPrimarySize(maxPrimarySize);
1168
+ }
1169
+ };
1170
+ const style = {
1171
+ "--split-pane-primary-size": `${primarySize}%`
1172
+ };
1173
+ return /* @__PURE__ */ jsxs29(
1174
+ "div",
1175
+ {
1176
+ ref: containerRef,
1177
+ className: [styles14.splitPane, className].filter(Boolean).join(" "),
1178
+ style,
1179
+ children: [
1180
+ /* @__PURE__ */ jsx43("section", { className: styles14.pane, "aria-label": primaryLabel, children: primary }),
1181
+ resizable && /* @__PURE__ */ jsx43(
1182
+ "button",
1183
+ {
1184
+ type: "button",
1185
+ className: styles14.handle,
1186
+ "aria-label": `${resizeLabel}, primary pane ${Math.round(primarySize)} percent`,
1187
+ onPointerDown: startDragging,
1188
+ onKeyDown: resizeWithKeyboard
1189
+ }
1190
+ ),
1191
+ /* @__PURE__ */ jsx43("section", { className: styles14.pane, "aria-label": secondaryLabel, children: secondary })
1192
+ ]
1193
+ }
1194
+ );
1195
+ }
1196
+
1197
+ // src/components/TagInput/TagInput.tsx
1198
+ import { useMemo, useState as useState3 } from "react";
1199
+ import styles15 from "./TagInput.module.css";
1200
+ import { jsx as jsx44, jsxs as jsxs30 } from "react/jsx-runtime";
1201
+ var DEFAULT_SEPARATORS = /[\n,]+/;
1202
+ var splitTags = (value, separators) => value.split(separators).map((tag) => tag.trim()).filter(Boolean);
1203
+ function TagInput({
1204
+ value,
1205
+ onChange,
1206
+ id,
1207
+ name,
1208
+ placeholder = "Add tag",
1209
+ disabled = false,
1210
+ required = false,
1211
+ error,
1212
+ hint,
1213
+ validateTag,
1214
+ separators = DEFAULT_SEPARATORS,
1215
+ className,
1216
+ "aria-describedby": ariaDescribedBy,
1217
+ "aria-invalid": ariaInvalid,
1218
+ "aria-label": ariaLabel
1219
+ }) {
1220
+ const [draft, setDraft] = useState3("");
1221
+ const [localError, setLocalError] = useState3();
1222
+ const message = error ?? localError ?? hint;
1223
+ const messageId = message && id ? `${id}-tag-message` : void 0;
1224
+ const describedBy = [ariaDescribedBy, messageId].filter(Boolean).join(" ") || void 0;
1225
+ const normalized = useMemo(() => new Set(value.map((tag) => tag.toLocaleLowerCase())), [value]);
1226
+ const addTags = (tags) => {
1227
+ const next = [...value];
1228
+ const nextNormalized = new Set(normalized);
1229
+ for (const tag of tags) {
1230
+ const normalizedTag = tag.toLocaleLowerCase();
1231
+ const duplicate = nextNormalized.has(normalizedTag);
1232
+ const validationMessage = duplicate ? "Tag already exists." : validateTag?.(tag, next) ?? void 0;
1233
+ if (validationMessage) {
1234
+ setLocalError(validationMessage);
1235
+ return;
1236
+ }
1237
+ next.push(tag);
1238
+ nextNormalized.add(normalizedTag);
1239
+ }
1240
+ setLocalError(void 0);
1241
+ setDraft("");
1242
+ onChange(next);
1243
+ };
1244
+ const commitDraft = () => {
1245
+ const tags = splitTags(draft, separators);
1246
+ if (tags.length === 0) {
1247
+ return;
1248
+ }
1249
+ addTags(tags);
1250
+ };
1251
+ const removeTag = (index) => {
1252
+ setLocalError(void 0);
1253
+ onChange(value.filter((_, tagIndex) => tagIndex !== index));
1254
+ };
1255
+ const handleKeyDown = (event) => {
1256
+ if (event.key === "Enter" || event.key === ",") {
1257
+ event.preventDefault();
1258
+ commitDraft();
1259
+ }
1260
+ if (event.key === "Backspace" && draft.length === 0 && value.length > 0) {
1261
+ event.preventDefault();
1262
+ removeTag(value.length - 1);
1263
+ }
1264
+ };
1265
+ const handlePaste = (event) => {
1266
+ const text = event.clipboardData.getData("text");
1267
+ const tags = splitTags(text, separators);
1268
+ if (tags.length <= 1) {
1269
+ return;
1270
+ }
1271
+ event.preventDefault();
1272
+ addTags(tags);
1273
+ };
1274
+ return /* @__PURE__ */ jsxs30("div", { className: [styles15.wrapper, className].filter(Boolean).join(" "), children: [
1275
+ /* @__PURE__ */ jsxs30(
1276
+ "div",
1277
+ {
1278
+ className: [
1279
+ styles15.control,
1280
+ disabled ? styles15.disabled : void 0,
1281
+ error || localError || ariaInvalid ? styles15.invalid : void 0
1282
+ ].filter(Boolean).join(" "),
1283
+ children: [
1284
+ value.map((tag, index) => /* @__PURE__ */ jsxs30("span", { className: styles15.tag, children: [
1285
+ /* @__PURE__ */ jsx44("span", { className: styles15.tagLabel, children: tag }),
1286
+ /* @__PURE__ */ jsx44(
1287
+ "button",
1288
+ {
1289
+ type: "button",
1290
+ className: styles15.removeButton,
1291
+ onClick: () => removeTag(index),
1292
+ disabled,
1293
+ "aria-label": `Remove ${tag}`,
1294
+ children: "\xD7"
1295
+ }
1296
+ )
1297
+ ] }, `${tag}-${index}`)),
1298
+ /* @__PURE__ */ jsx44(
1299
+ "input",
1300
+ {
1301
+ id,
1302
+ name,
1303
+ className: styles15.input,
1304
+ value: draft,
1305
+ placeholder: value.length === 0 ? placeholder : void 0,
1306
+ disabled,
1307
+ required: required && value.length === 0,
1308
+ "aria-describedby": describedBy,
1309
+ "aria-invalid": ariaInvalid ?? (Boolean(error || localError) || void 0),
1310
+ "aria-label": ariaLabel,
1311
+ onChange: (event) => setDraft(event.currentTarget.value),
1312
+ onKeyDown: handleKeyDown,
1313
+ onBlur: commitDraft,
1314
+ onPaste: handlePaste
1315
+ }
1316
+ )
1317
+ ]
1318
+ }
1319
+ ),
1320
+ message && /* @__PURE__ */ jsx44(
1321
+ "p",
1322
+ {
1323
+ id: messageId,
1324
+ className: error || localError ? styles15.error : styles15.hint,
1325
+ role: error || localError ? "alert" : void 0,
1326
+ children: message
1327
+ }
1328
+ )
1329
+ ] });
904
1330
  }
905
1331
 
906
1332
  // src/components/Dialog/Dialog.tsx
907
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
1333
+ import { createContext, useContext, useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
908
1334
 
909
1335
  // src/scroll-lock.ts
910
1336
  function lockBodyScroll() {
@@ -918,9 +1344,74 @@ function unlockBodyScroll() {
918
1344
  if (next === 0) document.body.style.overflowY = "";
919
1345
  }
920
1346
 
1347
+ // src/motion.ts
1348
+ import { useEffect as useEffect3, useState as useState4 } from "react";
1349
+ function useMountTransition(open, durationMs) {
1350
+ const [phase, setPhase] = useState4(open ? "entering" : "closed");
1351
+ useEffect3(() => {
1352
+ if (open) {
1353
+ setPhase("entering");
1354
+ const raf = requestAnimationFrame(() => setPhase("open"));
1355
+ return () => cancelAnimationFrame(raf);
1356
+ }
1357
+ setPhase((p) => p === "closed" ? "closed" : "closing");
1358
+ const timer = setTimeout(() => setPhase("closed"), durationMs);
1359
+ return () => clearTimeout(timer);
1360
+ }, [open, durationMs]);
1361
+ return { mounted: phase !== "closed", phase };
1362
+ }
1363
+ function usePrefersReducedMotion() {
1364
+ const [reduced, setReduced] = useState4(false);
1365
+ useEffect3(() => {
1366
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
1367
+ setReduced(mql.matches);
1368
+ function handleChange(e) {
1369
+ setReduced(e.matches);
1370
+ }
1371
+ mql.addEventListener("change", handleChange);
1372
+ return () => mql.removeEventListener("change", handleChange);
1373
+ }, []);
1374
+ return reduced;
1375
+ }
1376
+
1377
+ // src/components/OverlayHeader/OverlayHeader.tsx
1378
+ import styles16 from "./OverlayHeader.module.css";
1379
+ import { jsx as jsx45, jsxs as jsxs31 } from "react/jsx-runtime";
1380
+ function OverlayHeader({
1381
+ title,
1382
+ onClose,
1383
+ onBack,
1384
+ action,
1385
+ secondRow,
1386
+ className
1387
+ }) {
1388
+ return /* @__PURE__ */ jsxs31("div", { className: [styles16.header, className].filter(Boolean).join(" "), children: [
1389
+ /* @__PURE__ */ jsxs31("div", { className: styles16.row, children: [
1390
+ onBack && /* @__PURE__ */ jsx45("button", { type: "button", className: styles16.backButton, "aria-label": "Back", onClick: onBack, children: /* @__PURE__ */ jsx45(Icon, { name: "chevron-left", size: "md", "aria-hidden": true }) }),
1391
+ /* @__PURE__ */ jsx45("span", { className: styles16.title, children: title }),
1392
+ action && /* @__PURE__ */ jsx45("div", { className: styles16.action, children: action }),
1393
+ /* @__PURE__ */ jsx45("button", { type: "button", className: styles16.closeButton, "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx45(Icon, { name: "x", size: "sm", "aria-hidden": true }) })
1394
+ ] }),
1395
+ secondRow && /* @__PURE__ */ jsx45("div", { className: styles16.secondRow, children: secondRow })
1396
+ ] });
1397
+ }
1398
+
921
1399
  // src/components/Dialog/Dialog.tsx
922
- import styles11 from "./Dialog.module.css";
923
- import { jsx as jsx39, jsxs as jsxs26 } from "react/jsx-runtime";
1400
+ import styles17 from "./Dialog.module.css";
1401
+ import { jsx as jsx46, jsxs as jsxs32 } from "react/jsx-runtime";
1402
+ var MOTION_DURATION_MS = 250;
1403
+ var OverlaySecondRowContext = createContext(
1404
+ void 0
1405
+ );
1406
+ function useOverlaySecondRow(node) {
1407
+ const setSecondRow = useContext(OverlaySecondRowContext);
1408
+ useEffect4(() => {
1409
+ if (!setSecondRow) return;
1410
+ setSecondRow(node);
1411
+ return () => setSecondRow(null);
1412
+ }, [setSecondRow, node]);
1413
+ return setSecondRow !== void 0;
1414
+ }
924
1415
  var FOCUSABLE = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
925
1416
  function Dialog({
926
1417
  open,
@@ -930,14 +1421,17 @@ function Dialog({
930
1421
  title,
931
1422
  children
932
1423
  }) {
933
- const panelRef = useRef2(null);
934
- const previouslyFocused = useRef2(null);
935
- useEffect2(() => {
936
- if (!open) return;
1424
+ const panelRef = useRef3(null);
1425
+ const previouslyFocused = useRef3(null);
1426
+ const reducedMotion = usePrefersReducedMotion();
1427
+ const { mounted, phase } = useMountTransition(open, reducedMotion ? 0 : MOTION_DURATION_MS);
1428
+ const [secondRow, setSecondRow] = useState5(null);
1429
+ useEffect4(() => {
1430
+ if (!mounted) return;
937
1431
  lockBodyScroll();
938
1432
  return unlockBodyScroll;
939
- }, [open]);
940
- useEffect2(() => {
1433
+ }, [mounted]);
1434
+ useEffect4(() => {
941
1435
  if (!open) return;
942
1436
  previouslyFocused.current = document.activeElement;
943
1437
  const panel = panelRef.current;
@@ -945,7 +1439,7 @@ function Dialog({
945
1439
  (first ?? panel)?.focus();
946
1440
  return () => previouslyFocused.current?.focus();
947
1441
  }, [open]);
948
- useEffect2(() => {
1442
+ useEffect4(() => {
949
1443
  if (!open) return;
950
1444
  function handleKeyDown(e) {
951
1445
  if (e.key === "Escape") {
@@ -975,14 +1469,15 @@ function Dialog({
975
1469
  document.addEventListener("keydown", handleKeyDown);
976
1470
  return () => document.removeEventListener("keydown", handleKeyDown);
977
1471
  }, [open, onClose]);
978
- if (!open) return null;
1472
+ if (!mounted) return null;
1473
+ const isOpenPhase = phase === "open";
979
1474
  return (
980
1475
  // role="presentation" removes the scrim from the AT (it is purely visual).
981
1476
  // e.target check lets clicks inside the panel bubble without triggering dismiss.
982
- /* @__PURE__ */ jsx39(
1477
+ /* @__PURE__ */ jsx46(
983
1478
  "div",
984
1479
  {
985
- className: styles11.scrim,
1480
+ className: [styles17.scrim, isOpenPhase ? styles17.scrimOpen : ""].filter(Boolean).join(" "),
986
1481
  role: "presentation",
987
1482
  onClick: (e) => {
988
1483
  if (e.target === e.currentTarget) onClose();
@@ -990,7 +1485,7 @@ function Dialog({
990
1485
  onKeyDown: (e) => {
991
1486
  if (e.key === "Escape") onClose();
992
1487
  },
993
- children: /* @__PURE__ */ jsxs26(
1488
+ children: /* @__PURE__ */ jsxs32(
994
1489
  "div",
995
1490
  {
996
1491
  ref: panelRef,
@@ -998,23 +1493,19 @@ function Dialog({
998
1493
  "aria-modal": "true",
999
1494
  "aria-label": ariaLabel ?? title,
1000
1495
  tabIndex: -1,
1001
- className: [styles11.panel, styles11[size]].join(" "),
1496
+ className: [styles17.panel, styles17[size], isOpenPhase ? styles17.panelOpen : ""].filter(Boolean).join(" "),
1002
1497
  children: [
1003
- /* @__PURE__ */ jsxs26("div", { className: styles11.mobileBar, children: [
1004
- title && /* @__PURE__ */ jsx39("span", { className: styles11.mobileBarTitle, children: title }),
1005
- /* @__PURE__ */ jsx39(
1006
- "button",
1007
- {
1008
- type: "button",
1009
- className: styles11.mobileBarClose,
1010
- "aria-label": "Close",
1011
- onClick: onClose,
1012
- children: "\xD7"
1013
- }
1014
- )
1015
- ] }),
1016
- /* @__PURE__ */ jsx39("button", { type: "button", className: styles11.close, "aria-label": "Close", onClick: onClose, children: "\xD7" }),
1017
- /* @__PURE__ */ jsx39("div", { className: styles11.content, children })
1498
+ /* @__PURE__ */ jsx46(
1499
+ OverlayHeader,
1500
+ {
1501
+ title,
1502
+ onClose,
1503
+ secondRow,
1504
+ className: styles17.mobileHeader
1505
+ }
1506
+ ),
1507
+ /* @__PURE__ */ jsx46("button", { type: "button", className: styles17.close, "aria-label": "Close", onClick: onClose, children: "\xD7" }),
1508
+ /* @__PURE__ */ jsx46("div", { className: styles17.content, children: /* @__PURE__ */ jsx46(OverlaySecondRowContext.Provider, { value: setSecondRow, children }) })
1018
1509
  ]
1019
1510
  }
1020
1511
  )
@@ -1024,19 +1515,30 @@ function Dialog({
1024
1515
  }
1025
1516
 
1026
1517
  // src/components/Drawer/Drawer.tsx
1027
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
1028
- import styles12 from "./Drawer.module.css";
1029
- import { jsx as jsx40 } from "react/jsx-runtime";
1518
+ import { useEffect as useEffect5, useRef as useRef4 } from "react";
1519
+ import styles18 from "./Drawer.module.css";
1520
+ import { jsx as jsx47, jsxs as jsxs33 } from "react/jsx-runtime";
1521
+ var MOTION_DURATION_MS2 = 250;
1030
1522
  var FOCUSABLE2 = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
1031
- function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1032
- const panelRef = useRef3(null);
1033
- const previouslyFocused = useRef3(null);
1034
- useEffect3(() => {
1035
- if (!open) return;
1523
+ var SWIPE_DISMISS_THRESHOLD_PX = 100;
1524
+ function Drawer({
1525
+ open,
1526
+ onClose,
1527
+ "aria-label": ariaLabel,
1528
+ snapHeight = "content",
1529
+ children
1530
+ }) {
1531
+ const panelRef = useRef4(null);
1532
+ const previouslyFocused = useRef4(null);
1533
+ const reducedMotion = usePrefersReducedMotion();
1534
+ const { mounted, phase } = useMountTransition(open, reducedMotion ? 0 : MOTION_DURATION_MS2);
1535
+ const dragStartY = useRef4(null);
1536
+ useEffect5(() => {
1537
+ if (!mounted) return;
1036
1538
  lockBodyScroll();
1037
1539
  return unlockBodyScroll;
1038
- }, [open]);
1039
- useEffect3(() => {
1540
+ }, [mounted]);
1541
+ useEffect5(() => {
1040
1542
  if (!open) return;
1041
1543
  previouslyFocused.current = document.activeElement;
1042
1544
  const panel = panelRef.current;
@@ -1044,7 +1546,7 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1044
1546
  (first ?? panel)?.focus();
1045
1547
  return () => previouslyFocused.current?.focus();
1046
1548
  }, [open]);
1047
- useEffect3(() => {
1549
+ useEffect5(() => {
1048
1550
  if (!open) return;
1049
1551
  function handleKeyDown(e) {
1050
1552
  if (e.key === "Escape") {
@@ -1074,14 +1576,40 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1074
1576
  document.addEventListener("keydown", handleKeyDown);
1075
1577
  return () => document.removeEventListener("keydown", handleKeyDown);
1076
1578
  }, [open, onClose]);
1077
- if (!open) return null;
1579
+ function handleHandlePointerDown(e) {
1580
+ e.currentTarget.setPointerCapture(e.pointerId);
1581
+ dragStartY.current = e.clientY;
1582
+ }
1583
+ function handleHandlePointerMove(e) {
1584
+ const startY = dragStartY.current;
1585
+ if (startY === null) return;
1586
+ const dy = Math.max(0, e.clientY - startY);
1587
+ const panel = panelRef.current;
1588
+ if (!panel) return;
1589
+ panel.style.transition = "none";
1590
+ panel.style.transform = `translateY(${dy}px)`;
1591
+ }
1592
+ function releaseDrag(e, honorDistance) {
1593
+ const startY = dragStartY.current;
1594
+ dragStartY.current = null;
1595
+ if (startY === null) return;
1596
+ const dy = Math.max(0, e.clientY - startY);
1597
+ const panel = panelRef.current;
1598
+ if (panel) {
1599
+ panel.style.transform = "";
1600
+ panel.style.transition = "";
1601
+ }
1602
+ if (honorDistance && dy > SWIPE_DISMISS_THRESHOLD_PX) onClose();
1603
+ }
1604
+ if (!mounted) return null;
1605
+ const isOpenPhase = phase === "open";
1078
1606
  return (
1079
1607
  // role="presentation" removes the scrim from the AT (it is purely visual).
1080
1608
  // e.target check lets clicks inside the panel bubble without triggering dismiss.
1081
- /* @__PURE__ */ jsx40(
1609
+ /* @__PURE__ */ jsx47(
1082
1610
  "div",
1083
1611
  {
1084
- className: styles12.scrim,
1612
+ className: [styles18.scrim, isOpenPhase ? styles18.scrimOpen : ""].filter(Boolean).join(" "),
1085
1613
  role: "presentation",
1086
1614
  onClick: (e) => {
1087
1615
  if (e.target === e.currentTarget) onClose();
@@ -1089,7 +1617,7 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1089
1617
  onKeyDown: (e) => {
1090
1618
  if (e.key === "Escape") onClose();
1091
1619
  },
1092
- children: /* @__PURE__ */ jsx40(
1620
+ children: /* @__PURE__ */ jsxs33(
1093
1621
  "div",
1094
1622
  {
1095
1623
  ref: panelRef,
@@ -1097,8 +1625,25 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1097
1625
  "aria-modal": "true",
1098
1626
  "aria-label": ariaLabel,
1099
1627
  tabIndex: -1,
1100
- className: styles12.panel,
1101
- children
1628
+ className: [
1629
+ styles18.panel,
1630
+ snapHeight === "half" ? styles18.panelHalf : "",
1631
+ isOpenPhase ? styles18.panelOpen : ""
1632
+ ].filter(Boolean).join(" "),
1633
+ children: [
1634
+ /* @__PURE__ */ jsx47(
1635
+ "div",
1636
+ {
1637
+ className: styles18.handle,
1638
+ "aria-hidden": "true",
1639
+ onPointerDown: handleHandlePointerDown,
1640
+ onPointerMove: handleHandlePointerMove,
1641
+ onPointerUp: (e) => releaseDrag(e, true),
1642
+ onPointerCancel: (e) => releaseDrag(e, false)
1643
+ }
1644
+ ),
1645
+ children
1646
+ ]
1102
1647
  }
1103
1648
  )
1104
1649
  }
@@ -1106,37 +1651,391 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1106
1651
  );
1107
1652
  }
1108
1653
 
1654
+ // src/components/Sheet/Sheet.tsx
1655
+ import { useEffect as useEffect6, useRef as useRef5 } from "react";
1656
+ import styles19 from "./Sheet.module.css";
1657
+ import { jsx as jsx48, jsxs as jsxs34 } from "react/jsx-runtime";
1658
+ var FOCUSABLE3 = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
1659
+ var MOTION_DURATION_MS3 = 250;
1660
+ function Sheet({
1661
+ open,
1662
+ onClose,
1663
+ "aria-label": ariaLabel,
1664
+ title,
1665
+ onBack,
1666
+ headerAction,
1667
+ slideFrom = "bottom",
1668
+ children
1669
+ }) {
1670
+ const panelRef = useRef5(null);
1671
+ const previouslyFocused = useRef5(null);
1672
+ const reducedMotion = usePrefersReducedMotion();
1673
+ const { mounted, phase } = useMountTransition(open, reducedMotion ? 0 : MOTION_DURATION_MS3);
1674
+ useEffect6(() => {
1675
+ if (!mounted) return;
1676
+ lockBodyScroll();
1677
+ return unlockBodyScroll;
1678
+ }, [mounted]);
1679
+ useEffect6(() => {
1680
+ if (!mounted) return;
1681
+ previouslyFocused.current = document.activeElement;
1682
+ const panel = panelRef.current;
1683
+ const first = panel?.querySelector(FOCUSABLE3);
1684
+ (first ?? panel)?.focus();
1685
+ return () => previouslyFocused.current?.focus();
1686
+ }, [mounted]);
1687
+ useEffect6(() => {
1688
+ if (!open) return;
1689
+ function handleKeyDown(e) {
1690
+ if (e.key === "Escape") {
1691
+ e.stopPropagation();
1692
+ onClose();
1693
+ return;
1694
+ }
1695
+ if (e.key !== "Tab") return;
1696
+ const panel = panelRef.current;
1697
+ if (!panel) return;
1698
+ const focusable = [...panel.querySelectorAll(FOCUSABLE3)];
1699
+ const first = focusable[0];
1700
+ const last = focusable.at(-1);
1701
+ if (!first || !last) {
1702
+ e.preventDefault();
1703
+ return;
1704
+ }
1705
+ const active = document.activeElement;
1706
+ if (e.shiftKey && active === first) {
1707
+ e.preventDefault();
1708
+ last.focus();
1709
+ } else if (!e.shiftKey && active === last) {
1710
+ e.preventDefault();
1711
+ first.focus();
1712
+ }
1713
+ }
1714
+ document.addEventListener("keydown", handleKeyDown);
1715
+ return () => document.removeEventListener("keydown", handleKeyDown);
1716
+ }, [open, onClose]);
1717
+ if (!mounted) return null;
1718
+ return /* @__PURE__ */ jsxs34(
1719
+ "div",
1720
+ {
1721
+ ref: panelRef,
1722
+ role: "dialog",
1723
+ "aria-modal": "true",
1724
+ "aria-label": ariaLabel ?? title,
1725
+ tabIndex: -1,
1726
+ className: [
1727
+ styles19.panel,
1728
+ slideFrom === "top" ? styles19.panelFromTop : "",
1729
+ phase === "open" ? styles19.panelOpen : ""
1730
+ ].filter(Boolean).join(" "),
1731
+ children: [
1732
+ title && /* @__PURE__ */ jsx48(OverlayHeader, { title, onClose, onBack, action: headerAction }),
1733
+ /* @__PURE__ */ jsx48("div", { className: styles19.content, children })
1734
+ ]
1735
+ }
1736
+ );
1737
+ }
1738
+
1739
+ // src/components/ConfirmDialog/ConfirmDialog.tsx
1740
+ import { useEffect as useEffect7, useRef as useRef6 } from "react";
1741
+ import styles20 from "./ConfirmDialog.module.css";
1742
+ import { jsx as jsx49, jsxs as jsxs35 } from "react/jsx-runtime";
1743
+ function ConfirmDialog({
1744
+ open,
1745
+ onClose,
1746
+ title,
1747
+ message,
1748
+ onConfirm,
1749
+ confirmLabel = "Confirm",
1750
+ cancelLabel = "Cancel",
1751
+ destructive = false,
1752
+ pending = false,
1753
+ error
1754
+ }) {
1755
+ const dialogRef = useRef6(null);
1756
+ useEffect7(() => {
1757
+ const el = dialogRef.current;
1758
+ if (!el) return;
1759
+ if (open) el.showModal();
1760
+ else el.close();
1761
+ }, [open]);
1762
+ useEffect7(() => {
1763
+ const el = dialogRef.current;
1764
+ if (!el) return;
1765
+ const handleClose = () => onClose();
1766
+ el.addEventListener("close", handleClose);
1767
+ return () => el.removeEventListener("close", handleClose);
1768
+ }, [onClose]);
1769
+ return (
1770
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
1771
+ /* @__PURE__ */ jsx49(
1772
+ "dialog",
1773
+ {
1774
+ ref: dialogRef,
1775
+ className: styles20.dialog,
1776
+ "aria-label": title,
1777
+ onClick: (e) => {
1778
+ if (e.target === e.currentTarget) onClose();
1779
+ },
1780
+ children: /* @__PURE__ */ jsxs35("div", { className: styles20.body, children: [
1781
+ /* @__PURE__ */ jsx49("h2", { className: styles20.title, children: title }),
1782
+ /* @__PURE__ */ jsx49("div", { className: styles20.message, children: message }),
1783
+ error && /* @__PURE__ */ jsx49("p", { className: styles20.error, children: error }),
1784
+ /* @__PURE__ */ jsxs35("div", { className: styles20.actions, children: [
1785
+ /* @__PURE__ */ jsx49(Button, { type: "button", variant: "secondary", onClick: onClose, disabled: pending, children: cancelLabel }),
1786
+ destructive ? (
1787
+ // A solid red fill, not Button's own `destructive` variant (an
1788
+ // outlined/ghost style meant for lower-emphasis placements) — a
1789
+ // confirm action should read as strongly as `Button`'s primary
1790
+ // variant does. Uses the same --sv-color-error-solid /
1791
+ // -text-on-error tokens the codebase already introduced for this
1792
+ // exact "reads as a filled action button, not a tinted status
1793
+ // surface" case (see semantic.css).
1794
+ /* @__PURE__ */ jsx49(
1795
+ "button",
1796
+ {
1797
+ type: "button",
1798
+ className: styles20.destructiveConfirm,
1799
+ onClick: onConfirm,
1800
+ disabled: pending,
1801
+ children: confirmLabel
1802
+ }
1803
+ )
1804
+ ) : /* @__PURE__ */ jsx49(Button, { type: "button", variant: "primary", onClick: onConfirm, disabled: pending, children: confirmLabel })
1805
+ ] })
1806
+ ] })
1807
+ }
1808
+ )
1809
+ );
1810
+ }
1811
+
1812
+ // src/hooks/useLongPress.ts
1813
+ import { useCallback as useCallback2, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
1814
+ var DEFAULT_DELAY_MS = 500;
1815
+ var DEFAULT_MOVE_TOLERANCE_PX = 10;
1816
+ var DEFAULT_SUPPRESS_CLICK_MS = 700;
1817
+ function usePrefersCoarsePointer() {
1818
+ const [coarse, setCoarse] = useState6(false);
1819
+ useEffect8(() => {
1820
+ const mql = window.matchMedia("(pointer: coarse)");
1821
+ setCoarse(mql.matches);
1822
+ function handleChange(e) {
1823
+ setCoarse(e.matches);
1824
+ }
1825
+ mql.addEventListener("change", handleChange);
1826
+ return () => mql.removeEventListener("change", handleChange);
1827
+ }, []);
1828
+ return coarse;
1829
+ }
1830
+ function useLongPress({
1831
+ onLongPress,
1832
+ delay = DEFAULT_DELAY_MS,
1833
+ moveTolerance = DEFAULT_MOVE_TOLERANCE_PX,
1834
+ suppressClickMs = DEFAULT_SUPPRESS_CLICK_MS,
1835
+ vibrate = true,
1836
+ disabled = false
1837
+ }) {
1838
+ const timerRef = useRef7(null);
1839
+ const startRef = useRef7(null);
1840
+ const suppressUntilRef = useRef7(0);
1841
+ const clearTimer = useCallback2(() => {
1842
+ if (timerRef.current) {
1843
+ clearTimeout(timerRef.current);
1844
+ timerRef.current = null;
1845
+ }
1846
+ }, []);
1847
+ useEffect8(() => clearTimer, [clearTimer]);
1848
+ const onPointerDown = useCallback2(
1849
+ (e) => {
1850
+ if (disabled || e.pointerType !== "touch") return;
1851
+ startRef.current = { x: e.clientX, y: e.clientY };
1852
+ clearTimer();
1853
+ timerRef.current = setTimeout(() => {
1854
+ timerRef.current = null;
1855
+ suppressUntilRef.current = Date.now() + suppressClickMs;
1856
+ if (vibrate && typeof navigator !== "undefined" && navigator.vibrate) {
1857
+ navigator.vibrate(10);
1858
+ }
1859
+ onLongPress();
1860
+ }, delay);
1861
+ },
1862
+ [disabled, delay, suppressClickMs, vibrate, onLongPress, clearTimer]
1863
+ );
1864
+ const onPointerMove = useCallback2(
1865
+ (e) => {
1866
+ const start = startRef.current;
1867
+ if (!start || !timerRef.current) return;
1868
+ const dx = e.clientX - start.x;
1869
+ const dy = e.clientY - start.y;
1870
+ if (Math.hypot(dx, dy) > moveTolerance) clearTimer();
1871
+ },
1872
+ [moveTolerance, clearTimer]
1873
+ );
1874
+ const cancelPress = useCallback2(() => {
1875
+ clearTimer();
1876
+ startRef.current = null;
1877
+ }, [clearTimer]);
1878
+ const onContextMenu = useCallback2((e) => {
1879
+ if (timerRef.current || Date.now() < suppressUntilRef.current) e.preventDefault();
1880
+ }, []);
1881
+ const onClick = useCallback2((e) => {
1882
+ if (Date.now() < suppressUntilRef.current) {
1883
+ e.preventDefault();
1884
+ e.stopPropagation();
1885
+ }
1886
+ }, []);
1887
+ const isCoarsePointer = usePrefersCoarsePointer();
1888
+ const style = isCoarsePointer ? {
1889
+ WebkitTouchCallout: "none",
1890
+ WebkitUserSelect: "none",
1891
+ userSelect: "none",
1892
+ touchAction: "manipulation"
1893
+ } : {};
1894
+ return {
1895
+ onPointerDown,
1896
+ onPointerMove,
1897
+ onPointerUp: cancelPress,
1898
+ onPointerCancel: cancelPress,
1899
+ onPointerLeave: cancelPress,
1900
+ onContextMenu,
1901
+ onClick,
1902
+ style
1903
+ };
1904
+ }
1905
+
1906
+ // src/hooks/useDoubleTap.ts
1907
+ import { useRef as useRef8 } from "react";
1908
+ var DOUBLE_TAP_MS = 350;
1909
+ function useDoubleTapHandler(onDoubleTap) {
1910
+ const lastTime = useRef8(0);
1911
+ return (e) => {
1912
+ if (e.detail === 2) {
1913
+ onDoubleTap(e);
1914
+ return;
1915
+ }
1916
+ const now = Date.now();
1917
+ if (now - lastTime.current < DOUBLE_TAP_MS) {
1918
+ lastTime.current = 0;
1919
+ onDoubleTap(e);
1920
+ } else {
1921
+ lastTime.current = now;
1922
+ }
1923
+ };
1924
+ }
1925
+ function useSingleOrDoubleTap(onSingle, onDouble) {
1926
+ const timer = useRef8(null);
1927
+ return (e) => {
1928
+ if (e.detail === 2) {
1929
+ if (timer.current) {
1930
+ clearTimeout(timer.current);
1931
+ timer.current = null;
1932
+ }
1933
+ onDouble(e);
1934
+ return;
1935
+ }
1936
+ if (timer.current) {
1937
+ clearTimeout(timer.current);
1938
+ timer.current = null;
1939
+ onDouble(e);
1940
+ return;
1941
+ }
1942
+ timer.current = setTimeout(() => {
1943
+ timer.current = null;
1944
+ onSingle(e);
1945
+ }, DOUBLE_TAP_MS);
1946
+ };
1947
+ }
1948
+
1949
+ // src/hooks/useIsMobile.ts
1950
+ import { useEffect as useEffect9, useState as useState7 } from "react";
1951
+ var MOBILE_BREAKPOINT_PX = 768;
1952
+ function useIsMobile(breakpointPx = MOBILE_BREAKPOINT_PX) {
1953
+ const [isMobile, setIsMobile] = useState7(false);
1954
+ useEffect9(() => {
1955
+ const mql = window.matchMedia(`(max-width: ${breakpointPx}px)`);
1956
+ setIsMobile(mql.matches);
1957
+ function handleChange(e) {
1958
+ setIsMobile(e.matches);
1959
+ }
1960
+ mql.addEventListener("change", handleChange);
1961
+ return () => mql.removeEventListener("change", handleChange);
1962
+ }, [breakpointPx]);
1963
+ return isMobile;
1964
+ }
1965
+
1966
+ // src/components/Menu/Menu.tsx
1967
+ import styles21 from "./Menu.module.css";
1968
+ import { Fragment, jsx as jsx50, jsxs as jsxs36 } from "react/jsx-runtime";
1969
+ function Menu({ trigger, open, onClose, items, "aria-label": ariaLabel, align }) {
1970
+ const isMobile = useIsMobile();
1971
+ const list = /* @__PURE__ */ jsx50("ul", { className: styles21.list, role: "menu", children: items.map((entry, index) => {
1972
+ if (entry.type === "separator") {
1973
+ return /* @__PURE__ */ jsx50("li", { role: "separator", className: styles21.separator }, index);
1974
+ }
1975
+ if (entry.type === "label") {
1976
+ return /* @__PURE__ */ jsx50("li", { role: "presentation", className: styles21.label, children: entry.label }, index);
1977
+ }
1978
+ const isCheckable = entry.checked !== void 0;
1979
+ return /* @__PURE__ */ jsx50("li", { role: "none", children: /* @__PURE__ */ jsxs36(
1980
+ "button",
1981
+ {
1982
+ role: isCheckable ? "menuitemradio" : "menuitem",
1983
+ "aria-checked": isCheckable ? entry.checked : void 0,
1984
+ type: "button",
1985
+ className: [styles21.item, entry.destructive ? styles21.itemDestructive : ""].filter(Boolean).join(" "),
1986
+ disabled: entry.disabled,
1987
+ onClick: () => {
1988
+ onClose();
1989
+ entry.onSelect();
1990
+ },
1991
+ children: [
1992
+ isCheckable && /* @__PURE__ */ jsx50("span", { className: styles21.check, "aria-hidden": true, children: entry.checked && /* @__PURE__ */ jsx50(Icon, { name: "check", size: "sm", "aria-hidden": true }) }),
1993
+ entry.icon && /* @__PURE__ */ jsx50(Icon, { name: entry.icon, size: "sm", "aria-hidden": true }),
1994
+ entry.label
1995
+ ]
1996
+ }
1997
+ ) }, index);
1998
+ }) });
1999
+ if (isMobile) {
2000
+ return /* @__PURE__ */ jsxs36(Fragment, { children: [
2001
+ trigger,
2002
+ /* @__PURE__ */ jsx50(Drawer, { open, onClose, "aria-label": ariaLabel, children: list })
2003
+ ] });
2004
+ }
2005
+ return /* @__PURE__ */ jsx50(Popover, { trigger, open, onClose, "aria-label": ariaLabel, align, children: list });
2006
+ }
2007
+
1109
2008
  // src/components/Toast/Toast.tsx
1110
2009
  import {
1111
- createContext,
1112
- useCallback,
1113
- useContext,
1114
- useEffect as useEffect4,
2010
+ createContext as createContext2,
2011
+ useCallback as useCallback3,
2012
+ useContext as useContext2,
2013
+ useEffect as useEffect10,
1115
2014
  useId,
1116
- useMemo,
1117
- useRef as useRef4,
1118
- useState
2015
+ useMemo as useMemo2,
2016
+ useRef as useRef9,
2017
+ useState as useState8
1119
2018
  } from "react";
1120
- import styles13 from "./Toast.module.css";
1121
- import { jsx as jsx41, jsxs as jsxs27 } from "react/jsx-runtime";
1122
- var ToastContext = createContext(null);
2019
+ import styles22 from "./Toast.module.css";
2020
+ import { jsx as jsx51, jsxs as jsxs37 } from "react/jsx-runtime";
2021
+ var ToastContext = createContext2(null);
1123
2022
  function useToast() {
1124
- const ctx = useContext(ToastContext);
2023
+ const ctx = useContext2(ToastContext);
1125
2024
  if (!ctx) throw new Error("useToast() must be used inside <ToastProvider>");
1126
2025
  return ctx;
1127
2026
  }
1128
2027
  function ToastProvider({ children }) {
1129
- const [toasts, setToasts] = useState([]);
1130
- const timers = useRef4(/* @__PURE__ */ new Map());
2028
+ const [toasts, setToasts] = useState8([]);
2029
+ const timers = useRef9(/* @__PURE__ */ new Map());
1131
2030
  const seedId = useId();
1132
- const counter = useRef4(0);
1133
- const dismiss = useCallback((id) => {
2031
+ const counter = useRef9(0);
2032
+ const dismiss = useCallback3((id) => {
1134
2033
  setToasts((prev) => prev.map((t) => t.id === id ? { ...t, exiting: true } : t));
1135
2034
  setTimeout(() => {
1136
2035
  setToasts((prev) => prev.filter((t) => t.id !== id));
1137
2036
  }, 220);
1138
2037
  }, []);
1139
- const show = useCallback(
2038
+ const show = useCallback3(
1140
2039
  (item) => {
1141
2040
  const id = `${seedId}-${++counter.current}`;
1142
2041
  const duration = item.duration ?? 5e3;
@@ -1149,23 +2048,23 @@ function ToastProvider({ children }) {
1149
2048
  },
1150
2049
  [seedId, dismiss]
1151
2050
  );
1152
- useEffect4(() => {
2051
+ useEffect10(() => {
1153
2052
  const currentTimers = timers.current;
1154
2053
  return () => {
1155
2054
  currentTimers.forEach((t) => clearTimeout(t));
1156
2055
  };
1157
2056
  }, []);
1158
- const value = useMemo(() => ({ show, dismiss }), [show, dismiss]);
1159
- return /* @__PURE__ */ jsxs27(ToastContext.Provider, { value, children: [
2057
+ const value = useMemo2(() => ({ show, dismiss }), [show, dismiss]);
2058
+ return /* @__PURE__ */ jsxs37(ToastContext.Provider, { value, children: [
1160
2059
  children,
1161
- /* @__PURE__ */ jsx41(
2060
+ /* @__PURE__ */ jsx51(
1162
2061
  "ol",
1163
2062
  {
1164
- className: styles13.region,
2063
+ className: styles22.region,
1165
2064
  "aria-label": "Notifications",
1166
2065
  "aria-live": "polite",
1167
2066
  "aria-atomic": "false",
1168
- children: toasts.map((toast) => /* @__PURE__ */ jsx41(Toast, { ...toast, onDismiss: dismiss }, toast.id))
2067
+ children: toasts.map((toast) => /* @__PURE__ */ jsx51(Toast, { ...toast, onDismiss: dismiss }, toast.id))
1169
2068
  }
1170
2069
  )
1171
2070
  ] });
@@ -1186,24 +2085,24 @@ function Toast({
1186
2085
  exiting,
1187
2086
  onDismiss
1188
2087
  }) {
1189
- const categoryClass = styles13[category] ?? styles13.info;
2088
+ const categoryClass = styles22[category] ?? styles22.info;
1190
2089
  const iconName = CATEGORY_ICON[category] ?? "info";
1191
- return /* @__PURE__ */ jsxs27(
2090
+ return /* @__PURE__ */ jsxs37(
1192
2091
  "li",
1193
2092
  {
1194
2093
  role: "status",
1195
- className: `${styles13.toast} ${String(categoryClass)} ${exiting ? styles13.exiting : ""}`,
2094
+ className: `${styles22.toast} ${String(categoryClass)} ${exiting ? styles22.exiting : ""}`,
1196
2095
  children: [
1197
- /* @__PURE__ */ jsx41("span", { className: styles13.icon, "aria-hidden": "true", children: /* @__PURE__ */ jsx41(Icon, { name: iconName, size: "sm", "aria-hidden": true }) }),
1198
- /* @__PURE__ */ jsxs27("div", { className: styles13.body, children: [
1199
- /* @__PURE__ */ jsx41("div", { className: styles13.title, children: title }),
1200
- message && /* @__PURE__ */ jsx41("div", { className: styles13.message, children: message })
2096
+ /* @__PURE__ */ jsx51("span", { className: styles22.icon, "aria-hidden": "true", children: /* @__PURE__ */ jsx51(Icon, { name: iconName, size: "sm", "aria-hidden": true }) }),
2097
+ /* @__PURE__ */ jsxs37("div", { className: styles22.body, children: [
2098
+ /* @__PURE__ */ jsx51("div", { className: styles22.title, children: title }),
2099
+ message && /* @__PURE__ */ jsx51("div", { className: styles22.message, children: message })
1201
2100
  ] }),
1202
- /* @__PURE__ */ jsx41(
2101
+ /* @__PURE__ */ jsx51(
1203
2102
  "button",
1204
2103
  {
1205
2104
  type: "button",
1206
- className: styles13.close,
2105
+ className: styles22.close,
1207
2106
  "aria-label": "Dismiss notification",
1208
2107
  onClick: () => onDismiss(id),
1209
2108
  children: "\u2715"
@@ -1215,12 +2114,12 @@ function Toast({
1215
2114
  }
1216
2115
 
1217
2116
  // src/components/Card/Card.tsx
1218
- import styles14 from "./Card.module.css";
1219
- import { jsx as jsx42 } from "react/jsx-runtime";
2117
+ import styles23 from "./Card.module.css";
2118
+ import { jsx as jsx52 } from "react/jsx-runtime";
1220
2119
  var paddingClass = {
1221
- sm: styles14.paddingSm,
1222
- md: styles14.paddingMd,
1223
- lg: styles14.paddingLg
2120
+ sm: styles23.paddingSm,
2121
+ md: styles23.paddingMd,
2122
+ lg: styles23.paddingLg
1224
2123
  };
1225
2124
  function Card({
1226
2125
  as: Tag = "div",
@@ -1230,118 +2129,126 @@ function Card({
1230
2129
  children,
1231
2130
  ...rest
1232
2131
  }) {
1233
- const cls = [styles14.card, paddingClass[padding], interactive && styles14.interactive, className].filter(Boolean).join(" ");
1234
- return /* @__PURE__ */ jsx42(Tag, { className: cls, ...rest, children });
2132
+ const cls = [styles23.card, paddingClass[padding], interactive && styles23.interactive, className].filter(Boolean).join(" ");
2133
+ return /* @__PURE__ */ jsx52(Tag, { className: cls, ...rest, children });
1235
2134
  }
1236
2135
 
1237
2136
  // src/components/FormField/FormField.tsx
1238
2137
  import { useId as useId2 } from "react";
1239
- import styles15 from "./FormField.module.css";
1240
- import { jsx as jsx43, jsxs as jsxs28 } from "react/jsx-runtime";
2138
+ import styles24 from "./FormField.module.css";
2139
+ import { jsx as jsx53, jsxs as jsxs38 } from "react/jsx-runtime";
1241
2140
  function FormField({
1242
2141
  label,
1243
2142
  hint,
1244
2143
  error,
1245
- htmlFor,
2144
+ id,
1246
2145
  required = false,
1247
- children,
1248
- className
2146
+ className,
2147
+ children
1249
2148
  }) {
1250
- const hintId = useId2();
1251
- const errorId = useId2();
1252
- const describedBy = [hint && hintId, error && errorId].filter(Boolean).join(" ") || void 0;
1253
- return /* @__PURE__ */ jsxs28("div", { className: [styles15.field, className].filter(Boolean).join(" "), children: [
1254
- /* @__PURE__ */ jsxs28("label", { className: styles15.label, htmlFor, children: [
2149
+ const generatedId = useId2();
2150
+ const fieldId = id ?? generatedId;
2151
+ const hintId = `${fieldId}-hint`;
2152
+ const errorId = `${fieldId}-error`;
2153
+ const describedBy = [hint && !error && hintId, error && errorId].filter(Boolean).join(" ") || void 0;
2154
+ const field = {
2155
+ id: fieldId,
2156
+ "aria-describedby": describedBy,
2157
+ "aria-invalid": error ? true : void 0,
2158
+ required: required || void 0
2159
+ };
2160
+ return /* @__PURE__ */ jsxs38("div", { className: [styles24.field, className].filter(Boolean).join(" "), children: [
2161
+ /* @__PURE__ */ jsxs38("label", { className: styles24.label, htmlFor: fieldId, children: [
1255
2162
  label,
1256
- required && /* @__PURE__ */ jsx43("span", { className: styles15.required, "aria-hidden": "true", children: "*" })
2163
+ required && /* @__PURE__ */ jsx53("span", { className: styles24.required, "aria-hidden": "true", children: "*" })
1257
2164
  ] }),
1258
- /* @__PURE__ */ jsx43("div", { "aria-describedby": describedBy, children }),
1259
- hint && !error && /* @__PURE__ */ jsx43("p", { id: hintId, className: styles15.hint, children: hint }),
1260
- error && /* @__PURE__ */ jsx43("p", { id: errorId, className: styles15.error, role: "alert", children: error })
2165
+ children(field),
2166
+ hint && !error && /* @__PURE__ */ jsx53("p", { id: hintId, className: styles24.hint, children: hint }),
2167
+ error && /* @__PURE__ */ jsx53("p", { id: errorId, className: styles24.error, role: "alert", children: error })
1261
2168
  ] });
1262
2169
  }
1263
2170
 
1264
2171
  // src/components/PageHeader/PageHeader.tsx
1265
- import styles16 from "./PageHeader.module.css";
1266
- import { jsx as jsx44, jsxs as jsxs29 } from "react/jsx-runtime";
2172
+ import styles25 from "./PageHeader.module.css";
2173
+ import { jsx as jsx54, jsxs as jsxs39 } from "react/jsx-runtime";
1267
2174
  function PageHeader({ title, description, action, className }) {
1268
- return /* @__PURE__ */ jsxs29("header", { className: [styles16.header, className].filter(Boolean).join(" "), children: [
1269
- /* @__PURE__ */ jsxs29("div", { className: styles16.text, children: [
1270
- /* @__PURE__ */ jsx44("h1", { className: styles16.title, children: title }),
1271
- description && /* @__PURE__ */ jsx44("p", { className: styles16.description, children: description })
2175
+ return /* @__PURE__ */ jsxs39("header", { className: [styles25.header, className].filter(Boolean).join(" "), children: [
2176
+ /* @__PURE__ */ jsxs39("div", { className: styles25.text, children: [
2177
+ /* @__PURE__ */ jsx54("h1", { className: styles25.title, children: title }),
2178
+ description && /* @__PURE__ */ jsx54("p", { className: styles25.description, children: description })
1272
2179
  ] }),
1273
- action && /* @__PURE__ */ jsx44("div", { className: styles16.action, children: action })
2180
+ action && /* @__PURE__ */ jsx54("div", { className: styles25.action, children: action })
1274
2181
  ] });
1275
2182
  }
1276
2183
 
1277
2184
  // src/components/EmptyState/EmptyState.tsx
1278
- import styles17 from "./EmptyState.module.css";
1279
- import { jsx as jsx45, jsxs as jsxs30 } from "react/jsx-runtime";
2185
+ import styles26 from "./EmptyState.module.css";
2186
+ import { jsx as jsx55, jsxs as jsxs40 } from "react/jsx-runtime";
1280
2187
  function EmptyState({ icon, heading, description, action, className }) {
1281
- return /* @__PURE__ */ jsxs30("div", { className: [styles17.root, className].filter(Boolean).join(" "), children: [
1282
- icon && /* @__PURE__ */ jsx45("span", { className: styles17.icon, children: /* @__PURE__ */ jsx45(Icon, { name: icon, size: "lg", "aria-hidden": true }) }),
1283
- /* @__PURE__ */ jsx45("h2", { className: styles17.heading, children: heading }),
1284
- description && /* @__PURE__ */ jsx45("p", { className: styles17.description, children: description }),
2188
+ return /* @__PURE__ */ jsxs40("div", { className: [styles26.root, className].filter(Boolean).join(" "), children: [
2189
+ icon && /* @__PURE__ */ jsx55("span", { className: styles26.icon, children: /* @__PURE__ */ jsx55(Icon, { name: icon, size: "lg", "aria-hidden": true }) }),
2190
+ /* @__PURE__ */ jsx55("h2", { className: styles26.heading, children: heading }),
2191
+ description && /* @__PURE__ */ jsx55("p", { className: styles26.description, children: description }),
1285
2192
  action
1286
2193
  ] });
1287
2194
  }
1288
2195
 
1289
2196
  // src/components/Spinner/Spinner.tsx
1290
- import styles18 from "./Spinner.module.css";
1291
- import { jsx as jsx46 } from "react/jsx-runtime";
2197
+ import styles27 from "./Spinner.module.css";
2198
+ import { jsx as jsx56 } from "react/jsx-runtime";
1292
2199
  var sizeClass = {
1293
- sm: styles18.sm,
1294
- md: styles18.md,
1295
- lg: styles18.lg
2200
+ sm: styles27.sm,
2201
+ md: styles27.md,
2202
+ lg: styles27.lg
1296
2203
  };
1297
2204
  function Spinner({ size = "md", label = "Loading\u2026", className }) {
1298
- return /* @__PURE__ */ jsx46(
2205
+ return /* @__PURE__ */ jsx56(
1299
2206
  "span",
1300
2207
  {
1301
2208
  role: "status",
1302
2209
  "aria-label": label,
1303
- className: [styles18.spinner, sizeClass[size], className].filter(Boolean).join(" ")
2210
+ className: [styles27.spinner, sizeClass[size], className].filter(Boolean).join(" ")
1304
2211
  }
1305
2212
  );
1306
2213
  }
1307
2214
 
1308
2215
  // src/components/Avatar/Avatar.tsx
1309
- import { useState as useState2 } from "react";
1310
- import styles19 from "./Avatar.module.css";
1311
- import { jsx as jsx47 } from "react/jsx-runtime";
2216
+ import { useState as useState9 } from "react";
2217
+ import styles28 from "./Avatar.module.css";
2218
+ import { jsx as jsx57 } from "react/jsx-runtime";
1312
2219
  function initials(name) {
1313
2220
  const parts = name.trim().split(/\s+/);
1314
2221
  if (parts.length === 1) return (parts[0] ?? "").slice(0, 2).toUpperCase();
1315
2222
  return ((parts[0]?.[0] ?? "") + (parts[parts.length - 1]?.[0] ?? "")).toUpperCase();
1316
2223
  }
1317
2224
  var sizeClass2 = {
1318
- sm: styles19.sm,
1319
- md: styles19.md,
1320
- lg: styles19.lg
2225
+ sm: styles28.sm,
2226
+ md: styles28.md,
2227
+ lg: styles28.lg
1321
2228
  };
1322
2229
  function Avatar({ name, src, size = "md", className }) {
1323
- const [imgFailed, setImgFailed] = useState2(false);
1324
- const cls = [styles19.avatar, sizeClass2[size], className].filter(Boolean).join(" ");
2230
+ const [imgFailed, setImgFailed] = useState9(false);
2231
+ const cls = [styles28.avatar, sizeClass2[size], className].filter(Boolean).join(" ");
1325
2232
  if (src && !imgFailed) {
1326
- return /* @__PURE__ */ jsx47("span", { className: cls, "aria-label": name, children: /* @__PURE__ */ jsx47("img", { src, alt: name, className: styles19.img, onError: () => setImgFailed(true) }) });
2233
+ return /* @__PURE__ */ jsx57("span", { className: cls, "aria-label": name, children: /* @__PURE__ */ jsx57("img", { src, alt: name, className: styles28.img, onError: () => setImgFailed(true) }) });
1327
2234
  }
1328
- return /* @__PURE__ */ jsx47("span", { className: cls, "aria-label": name, children: initials(name) });
2235
+ return /* @__PURE__ */ jsx57("span", { className: cls, "aria-label": name, children: initials(name) });
1329
2236
  }
1330
2237
 
1331
2238
  // src/components/NavTabs/NavTabs.tsx
1332
- import styles20 from "./NavTabs.module.css";
1333
- import { jsx as jsx48 } from "react/jsx-runtime";
2239
+ import styles29 from "./NavTabs.module.css";
2240
+ import { jsx as jsx58 } from "react/jsx-runtime";
1334
2241
  function NavTabs({ items, className, "aria-label": ariaLabel }) {
1335
- return /* @__PURE__ */ jsx48(
2242
+ return /* @__PURE__ */ jsx58(
1336
2243
  "nav",
1337
2244
  {
1338
- className: [styles20.nav, className].filter(Boolean).join(" "),
2245
+ className: [styles29.nav, className].filter(Boolean).join(" "),
1339
2246
  "aria-label": ariaLabel ?? "Page navigation",
1340
- children: items.map((item) => /* @__PURE__ */ jsx48(
2247
+ children: items.map((item) => /* @__PURE__ */ jsx58(
1341
2248
  "a",
1342
2249
  {
1343
2250
  href: item.href,
1344
- className: [styles20.link, item.active && styles20.active].filter(Boolean).join(" "),
2251
+ className: [styles29.link, item.active && styles29.active].filter(Boolean).join(" "),
1345
2252
  "aria-current": item.active ? "page" : void 0,
1346
2253
  children: item.label
1347
2254
  },
@@ -1353,42 +2260,438 @@ function NavTabs({ items, className, "aria-label": ariaLabel }) {
1353
2260
 
1354
2261
  // src/components/Tooltip/Tooltip.tsx
1355
2262
  import { useId as useId3 } from "react";
1356
- import styles21 from "./Tooltip.module.css";
1357
- import { jsx as jsx49, jsxs as jsxs31 } from "react/jsx-runtime";
2263
+ import styles30 from "./Tooltip.module.css";
2264
+ import { jsx as jsx59, jsxs as jsxs41 } from "react/jsx-runtime";
1358
2265
  var sideClass = {
1359
- top: styles21.top,
1360
- bottom: styles21.bottom,
1361
- left: styles21.left,
1362
- right: styles21.right
2266
+ top: styles30.top,
2267
+ bottom: styles30.bottom,
2268
+ left: styles30.left,
2269
+ right: styles30.right
1363
2270
  };
1364
2271
  function Tooltip({ content, children, side = "top" }) {
1365
2272
  const tipId = useId3();
1366
- return /* @__PURE__ */ jsxs31("span", { className: styles21.wrapper, children: [
1367
- /* @__PURE__ */ jsx49("span", { "aria-describedby": tipId, children }),
1368
- /* @__PURE__ */ jsx49("span", { id: tipId, role: "tooltip", className: [styles21.tip, sideClass[side]].join(" "), children: content })
2273
+ return /* @__PURE__ */ jsxs41("span", { className: styles30.wrapper, children: [
2274
+ /* @__PURE__ */ jsx59("span", { "aria-describedby": tipId, children }),
2275
+ /* @__PURE__ */ jsx59("span", { id: tipId, role: "tooltip", className: [styles30.tip, sideClass[side]].join(" "), children: content })
1369
2276
  ] });
1370
2277
  }
2278
+
2279
+ // src/components/Checkbox/Checkbox.tsx
2280
+ import styles31 from "./Checkbox.module.css";
2281
+ import { jsx as jsx60, jsxs as jsxs42 } from "react/jsx-runtime";
2282
+ function Checkbox({
2283
+ checked,
2284
+ onChange,
2285
+ label,
2286
+ strikeThrough = false,
2287
+ disabled,
2288
+ className,
2289
+ id,
2290
+ ...rest
2291
+ }) {
2292
+ const inputId = id ?? `checkbox-${label.toLowerCase().replace(/\s+/g, "-")}`;
2293
+ return /* @__PURE__ */ jsxs42("span", { className: [styles31.root, className].filter(Boolean).join(" "), children: [
2294
+ /* @__PURE__ */ jsxs42("span", { className: [styles31.box, checked ? styles31.checked : ""].filter(Boolean).join(" "), children: [
2295
+ /* @__PURE__ */ jsx60(
2296
+ "input",
2297
+ {
2298
+ ...rest,
2299
+ id: inputId,
2300
+ type: "checkbox",
2301
+ checked,
2302
+ disabled,
2303
+ className: styles31.input,
2304
+ onChange: (e) => onChange(e.target.checked)
2305
+ }
2306
+ ),
2307
+ checked && /* @__PURE__ */ jsx60("svg", { className: styles31.tick, viewBox: "0 0 10 8", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx60(
2308
+ "path",
2309
+ {
2310
+ d: "M1 4l3 3 5-6",
2311
+ stroke: "currentColor",
2312
+ strokeWidth: "1.5",
2313
+ strokeLinecap: "round",
2314
+ strokeLinejoin: "round"
2315
+ }
2316
+ ) })
2317
+ ] }),
2318
+ label && /* @__PURE__ */ jsx60(
2319
+ "label",
2320
+ {
2321
+ htmlFor: inputId,
2322
+ className: [
2323
+ styles31.label,
2324
+ strikeThrough && checked ? styles31.struck : "",
2325
+ disabled ? styles31.disabled : ""
2326
+ ].filter(Boolean).join(" "),
2327
+ children: label
2328
+ }
2329
+ )
2330
+ ] });
2331
+ }
2332
+
2333
+ // src/components/DragHandleRow/DragHandleRow.tsx
2334
+ import styles32 from "./DragHandleRow.module.css";
2335
+ import { jsx as jsx61, jsxs as jsxs43 } from "react/jsx-runtime";
2336
+ function DragHandleRow({
2337
+ children,
2338
+ handleProps,
2339
+ isDragging,
2340
+ className,
2341
+ ...rest
2342
+ }) {
2343
+ return /* @__PURE__ */ jsxs43(
2344
+ "div",
2345
+ {
2346
+ ...rest,
2347
+ className: [styles32.row, isDragging ? styles32.dragging : "", className].filter(Boolean).join(" "),
2348
+ children: [
2349
+ /* @__PURE__ */ jsx61(
2350
+ "button",
2351
+ {
2352
+ type: "button",
2353
+ "aria-label": "Drag to reorder",
2354
+ className: styles32.handle,
2355
+ tabIndex: -1,
2356
+ ...handleProps,
2357
+ children: /* @__PURE__ */ jsx61(DragIcon, {})
2358
+ }
2359
+ ),
2360
+ /* @__PURE__ */ jsx61("div", { className: styles32.content, children })
2361
+ ]
2362
+ }
2363
+ );
2364
+ }
2365
+ function DragIcon() {
2366
+ return /* @__PURE__ */ jsx61(
2367
+ "svg",
2368
+ {
2369
+ width: "14",
2370
+ height: "14",
2371
+ viewBox: "0 0 14 14",
2372
+ fill: "none",
2373
+ "aria-hidden": "true",
2374
+ className: styles32.icon,
2375
+ children: [3, 7, 11].map(
2376
+ (cy) => [4, 10].map((cx) => /* @__PURE__ */ jsx61("circle", { cx, cy, r: 1.2, fill: "currentColor" }, `${cx}-${cy}`))
2377
+ )
2378
+ }
2379
+ );
2380
+ }
2381
+
2382
+ // src/components/Calendar/Calendar.tsx
2383
+ import { useRef as useRef10, useState as useState10 } from "react";
2384
+
2385
+ // src/components/Calendar/dateUtils.ts
2386
+ function startOfMonth(date) {
2387
+ return new Date(date.getFullYear(), date.getMonth(), 1);
2388
+ }
2389
+ function addMonths(date, count) {
2390
+ return new Date(date.getFullYear(), date.getMonth() + count, date.getDate());
2391
+ }
2392
+ function addDays(date, count) {
2393
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + count);
2394
+ }
2395
+ function isSameDay(a, b) {
2396
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2397
+ }
2398
+ function isSameMonth(a, b) {
2399
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
2400
+ }
2401
+ function isBeforeDay(a, b) {
2402
+ return a.getFullYear() < b.getFullYear() || a.getFullYear() === b.getFullYear() && (a.getMonth() < b.getMonth() || a.getMonth() === b.getMonth() && a.getDate() < b.getDate());
2403
+ }
2404
+ function isAfterDay(a, b) {
2405
+ return isBeforeDay(b, a);
2406
+ }
2407
+ function isWithinRange(date, min, max) {
2408
+ if (min && isBeforeDay(date, min)) return false;
2409
+ if (max && isAfterDay(date, max)) return false;
2410
+ return true;
2411
+ }
2412
+ function getMonthGrid(month) {
2413
+ const first = startOfMonth(month);
2414
+ const startOffset = first.getDay();
2415
+ const gridStart = addDays(first, -startOffset);
2416
+ const weeks = [];
2417
+ for (let week = 0; week < 6; week++) {
2418
+ const days = [];
2419
+ for (let day = 0; day < 7; day++) {
2420
+ days.push(addDays(gridStart, week * 7 + day));
2421
+ }
2422
+ weeks.push(days);
2423
+ }
2424
+ return weeks;
2425
+ }
2426
+ function formatMonthYear(date) {
2427
+ return new Intl.DateTimeFormat(void 0, { month: "long", year: "numeric" }).format(date);
2428
+ }
2429
+ function formatWeekdayShort(date) {
2430
+ return new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(date);
2431
+ }
2432
+ function formatDateShort(date) {
2433
+ return new Intl.DateTimeFormat(void 0, {
2434
+ month: "short",
2435
+ day: "numeric",
2436
+ year: "numeric"
2437
+ }).format(date);
2438
+ }
2439
+ function formatDateLabel(date) {
2440
+ return new Intl.DateTimeFormat(void 0, {
2441
+ weekday: "long",
2442
+ month: "long",
2443
+ day: "numeric",
2444
+ year: "numeric"
2445
+ }).format(date);
2446
+ }
2447
+
2448
+ // src/components/Calendar/Calendar.tsx
2449
+ import styles33 from "./Calendar.module.css";
2450
+ import { jsx as jsx62, jsxs as jsxs44 } from "react/jsx-runtime";
2451
+ var WEEKDAY_LABEL_WEEK = getMonthGrid(new Date(2026, 0, 1))[0] ?? [];
2452
+ function Calendar({
2453
+ value,
2454
+ onChange,
2455
+ minDate,
2456
+ maxDate,
2457
+ "aria-label": ariaLabel
2458
+ }) {
2459
+ const [displayedMonth, setDisplayedMonth] = useState10(() => startOfMonth(value ?? /* @__PURE__ */ new Date()));
2460
+ const [focusedDate, setFocusedDate] = useState10(() => value ?? /* @__PURE__ */ new Date());
2461
+ const gridRef = useRef10(null);
2462
+ const weeks = getMonthGrid(displayedMonth);
2463
+ function goToMonth(next) {
2464
+ setDisplayedMonth(startOfMonth(next));
2465
+ }
2466
+ function moveFocus(next) {
2467
+ setFocusedDate(next);
2468
+ if (!isSameMonth(next, displayedMonth)) goToMonth(next);
2469
+ requestAnimationFrame(() => {
2470
+ gridRef.current?.querySelector('[tabindex="0"]')?.focus();
2471
+ });
2472
+ }
2473
+ function selectDate(date) {
2474
+ if (!isWithinRange(date, minDate, maxDate)) return;
2475
+ onChange(date);
2476
+ setFocusedDate(date);
2477
+ }
2478
+ function handleKeyDown(e) {
2479
+ switch (e.key) {
2480
+ case "ArrowLeft":
2481
+ e.preventDefault();
2482
+ moveFocus(addDays(focusedDate, -1));
2483
+ return;
2484
+ case "ArrowRight":
2485
+ e.preventDefault();
2486
+ moveFocus(addDays(focusedDate, 1));
2487
+ return;
2488
+ case "ArrowUp":
2489
+ e.preventDefault();
2490
+ moveFocus(addDays(focusedDate, -7));
2491
+ return;
2492
+ case "ArrowDown":
2493
+ e.preventDefault();
2494
+ moveFocus(addDays(focusedDate, 7));
2495
+ return;
2496
+ case "Home":
2497
+ e.preventDefault();
2498
+ moveFocus(addDays(focusedDate, -focusedDate.getDay()));
2499
+ return;
2500
+ case "End":
2501
+ e.preventDefault();
2502
+ moveFocus(addDays(focusedDate, 6 - focusedDate.getDay()));
2503
+ return;
2504
+ case "PageUp":
2505
+ e.preventDefault();
2506
+ moveFocus(addMonths(focusedDate, -1));
2507
+ return;
2508
+ case "PageDown":
2509
+ e.preventDefault();
2510
+ moveFocus(addMonths(focusedDate, 1));
2511
+ return;
2512
+ case "Enter":
2513
+ case " ":
2514
+ e.preventDefault();
2515
+ selectDate(focusedDate);
2516
+ return;
2517
+ default:
2518
+ }
2519
+ }
2520
+ return /* @__PURE__ */ jsxs44("div", { className: styles33.calendar, "aria-label": ariaLabel, children: [
2521
+ /* @__PURE__ */ jsxs44("div", { className: styles33.header, children: [
2522
+ /* @__PURE__ */ jsx62(
2523
+ "button",
2524
+ {
2525
+ type: "button",
2526
+ className: styles33.navButton,
2527
+ "aria-label": "Previous month",
2528
+ onClick: () => goToMonth(addMonths(displayedMonth, -1)),
2529
+ children: /* @__PURE__ */ jsx62(Icon, { name: "chevron-left", size: "sm", "aria-hidden": true })
2530
+ }
2531
+ ),
2532
+ /* @__PURE__ */ jsx62("span", { className: styles33.monthLabel, children: formatMonthYear(displayedMonth) }),
2533
+ /* @__PURE__ */ jsx62(
2534
+ "button",
2535
+ {
2536
+ type: "button",
2537
+ className: styles33.navButton,
2538
+ "aria-label": "Next month",
2539
+ onClick: () => goToMonth(addMonths(displayedMonth, 1)),
2540
+ children: /* @__PURE__ */ jsx62(Icon, { name: "chevron-right", size: "sm", "aria-hidden": true })
2541
+ }
2542
+ )
2543
+ ] }),
2544
+ /* @__PURE__ */ jsxs44(
2545
+ "div",
2546
+ {
2547
+ ref: gridRef,
2548
+ role: "grid",
2549
+ "aria-label": formatMonthYear(displayedMonth),
2550
+ className: styles33.grid,
2551
+ onKeyDown: handleKeyDown,
2552
+ tabIndex: -1,
2553
+ children: [
2554
+ /* @__PURE__ */ jsx62("div", { role: "row", className: styles33.weekdayRow, children: WEEKDAY_LABEL_WEEK.map((d) => /* @__PURE__ */ jsx62("span", { role: "columnheader", className: styles33.weekday, "aria-hidden": true, children: formatWeekdayShort(d) }, d.getDay())) }),
2555
+ weeks.map((week) => {
2556
+ const firstDayOfWeek = week[0];
2557
+ if (!firstDayOfWeek) return null;
2558
+ return /* @__PURE__ */ jsx62("div", { role: "row", className: styles33.week, children: week.map((date) => {
2559
+ const isCurrentMonth = isSameMonth(date, displayedMonth);
2560
+ const isSelected = value !== null && isSameDay(date, value);
2561
+ const isToday = isSameDay(date, /* @__PURE__ */ new Date());
2562
+ const isFocusTarget = isSameDay(date, focusedDate);
2563
+ const disabled = !isWithinRange(date, minDate, maxDate);
2564
+ return /* @__PURE__ */ jsx62("div", { role: "gridcell", "aria-selected": isSelected, children: /* @__PURE__ */ jsx62(
2565
+ "button",
2566
+ {
2567
+ type: "button",
2568
+ tabIndex: isFocusTarget ? 0 : -1,
2569
+ disabled,
2570
+ "aria-current": isToday ? "date" : void 0,
2571
+ "aria-label": formatDateLabel(date),
2572
+ className: [
2573
+ styles33.day,
2574
+ !isCurrentMonth ? styles33.dayOutside : "",
2575
+ isSelected ? styles33.daySelected : "",
2576
+ isToday && !isSelected ? styles33.dayToday : ""
2577
+ ].filter(Boolean).join(" "),
2578
+ onClick: () => selectDate(date),
2579
+ onFocus: () => setFocusedDate(date),
2580
+ children: date.getDate()
2581
+ }
2582
+ ) }, date.toISOString());
2583
+ }) }, firstDayOfWeek.toISOString());
2584
+ })
2585
+ ]
2586
+ }
2587
+ )
2588
+ ] });
2589
+ }
2590
+
2591
+ // src/components/DatePicker/DatePicker.tsx
2592
+ import { useState as useState11 } from "react";
2593
+ import styles34 from "./DatePicker.module.css";
2594
+ import { Fragment as Fragment2, jsx as jsx63, jsxs as jsxs45 } from "react/jsx-runtime";
2595
+ var POPOVER_WIDTH = 320;
2596
+ function DatePicker({
2597
+ value,
2598
+ onChange,
2599
+ minDate,
2600
+ maxDate,
2601
+ placeholder = "Select date",
2602
+ "aria-label": ariaLabel,
2603
+ disabled = false
2604
+ }) {
2605
+ const [open, setOpen] = useState11(false);
2606
+ const isMobile = useIsMobile();
2607
+ function handleSelect(date) {
2608
+ onChange(date);
2609
+ setOpen(false);
2610
+ }
2611
+ const trigger = /* @__PURE__ */ jsxs45(
2612
+ "button",
2613
+ {
2614
+ type: "button",
2615
+ className: styles34.trigger,
2616
+ disabled,
2617
+ "aria-label": ariaLabel,
2618
+ "aria-haspopup": "dialog",
2619
+ "aria-expanded": open,
2620
+ onClick: () => setOpen((o) => !o),
2621
+ children: [
2622
+ /* @__PURE__ */ jsx63("span", { className: value ? styles34.triggerValue : styles34.triggerPlaceholder, children: value ? formatDateShort(value) : placeholder }),
2623
+ /* @__PURE__ */ jsx63(Icon, { name: "calendar", size: "sm", "aria-hidden": true })
2624
+ ]
2625
+ }
2626
+ );
2627
+ const calendar = /* @__PURE__ */ jsx63(
2628
+ Calendar,
2629
+ {
2630
+ value,
2631
+ onChange: handleSelect,
2632
+ minDate,
2633
+ maxDate,
2634
+ "aria-label": ariaLabel
2635
+ }
2636
+ );
2637
+ if (isMobile) {
2638
+ return /* @__PURE__ */ jsxs45(Fragment2, { children: [
2639
+ trigger,
2640
+ /* @__PURE__ */ jsx63(Drawer, { open, onClose: () => setOpen(false), "aria-label": ariaLabel, children: /* @__PURE__ */ jsx63("div", { className: styles34.drawerBody, children: calendar }) })
2641
+ ] });
2642
+ }
2643
+ return /* @__PURE__ */ jsx63(
2644
+ Popover,
2645
+ {
2646
+ trigger,
2647
+ open,
2648
+ onClose: () => setOpen(false),
2649
+ "aria-label": ariaLabel,
2650
+ width: POPOVER_WIDTH,
2651
+ children: /* @__PURE__ */ jsx63("div", { className: styles34.popoverBody, children: calendar })
2652
+ }
2653
+ );
2654
+ }
1371
2655
  export {
1372
2656
  Avatar,
1373
2657
  Badge,
1374
2658
  Button,
2659
+ Calendar,
1375
2660
  Card,
2661
+ Checkbox,
2662
+ CodeTextarea,
2663
+ ConfirmDialog,
2664
+ DatePicker,
1376
2665
  Dialog,
2666
+ DragHandleRow,
1377
2667
  Drawer,
1378
2668
  EmptyState,
1379
2669
  FormField,
1380
2670
  Icon,
1381
2671
  Input,
2672
+ MOBILE_BREAKPOINT_PX,
2673
+ Menu,
1382
2674
  NavTabs,
2675
+ OverlayHeader,
1383
2676
  PageHeader,
1384
2677
  Popover,
1385
2678
  SegmentedControl,
1386
2679
  Select,
2680
+ Sheet,
1387
2681
  Spinner,
2682
+ SplitPane,
2683
+ StatusBadge,
1388
2684
  SystemBanner,
1389
2685
  Tabs,
2686
+ TagInput,
2687
+ Textarea,
1390
2688
  ToastProvider,
1391
2689
  Toggle,
1392
2690
  Tooltip,
2691
+ useDoubleTapHandler,
2692
+ useIsMobile,
2693
+ useLongPress,
2694
+ useOverlaySecondRow,
2695
+ useSingleOrDoubleTap,
1393
2696
  useToast
1394
2697
  };