@sovereignfs/ui 0.23.0 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) 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 +14 -2
  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 +77 -50
  9. package/dist/DragHandleRow.module.css +9 -3
  10. package/dist/Drawer.module.css +61 -0
  11. package/dist/Icon.module.css +5 -0
  12. package/dist/Menu.module.css +82 -0
  13. package/dist/NavTabs.module.css +4 -2
  14. package/dist/OverlayHeader.module.css +98 -0
  15. package/dist/Popover.module.css +31 -1
  16. package/dist/SegmentedControl.module.css +4 -2
  17. package/dist/Sheet.module.css +55 -0
  18. package/dist/SplitPane.module.css +85 -0
  19. package/dist/StatusBadge.module.css +59 -0
  20. package/dist/SystemBanner.module.css +4 -2
  21. package/dist/Tabs.module.css +4 -2
  22. package/dist/TagInput.module.css +141 -0
  23. package/dist/Toast.module.css +4 -2
  24. package/dist/Tooltip.module.css +6 -1
  25. package/dist/index.d.ts +493 -17
  26. package/dist/index.js +1390 -209
  27. package/dist/tokens/primitives.css +19 -0
  28. package/dist/tokens/semantic.css +13 -0
  29. package/package.json +1 -1
  30. package/src/__tests__/motion.test.tsx +105 -0
  31. package/src/components/Button/Button.module.css +75 -8
  32. package/src/components/Button/Button.stories.tsx +6 -0
  33. package/src/components/Button/Button.tsx +17 -1
  34. package/src/components/Button/__tests__/Button.test.tsx +19 -0
  35. package/src/components/Calendar/Calendar.module.css +126 -0
  36. package/src/components/Calendar/Calendar.tsx +213 -0
  37. package/src/components/Calendar/__tests__/Calendar.test.tsx +98 -0
  38. package/src/components/Calendar/dateUtils.ts +97 -0
  39. package/src/components/Card/Card.module.css +4 -2
  40. package/src/components/Checkbox/Checkbox.module.css +14 -2
  41. package/src/components/Checkbox/Checkbox.tsx +22 -12
  42. package/src/components/CodeTextarea/CodeTextarea.module.css +53 -0
  43. package/src/components/CodeTextarea/CodeTextarea.stories.tsx +77 -0
  44. package/src/components/CodeTextarea/CodeTextarea.tsx +36 -0
  45. package/src/components/ConfirmDialog/ConfirmDialog.module.css +109 -0
  46. package/src/components/ConfirmDialog/ConfirmDialog.tsx +124 -0
  47. package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +146 -0
  48. package/src/components/DatePicker/DatePicker.module.css +57 -0
  49. package/src/components/DatePicker/DatePicker.tsx +103 -0
  50. package/src/components/DatePicker/__tests__/DatePicker.test.tsx +89 -0
  51. package/src/components/Dialog/Dialog.module.css +77 -50
  52. package/src/components/Dialog/Dialog.stories.tsx +51 -1
  53. package/src/components/Dialog/Dialog.tsx +86 -22
  54. package/src/components/Dialog/__tests__/Dialog.test.tsx +67 -3
  55. package/src/components/DragHandleRow/DragHandleRow.module.css +9 -3
  56. package/src/components/Drawer/Drawer.module.css +61 -0
  57. package/src/components/Drawer/Drawer.tsx +117 -13
  58. package/src/components/Drawer/__tests__/Drawer.test.tsx +123 -2
  59. package/src/components/Icon/Icon.module.css +5 -0
  60. package/src/components/Icon/Icon.stories.tsx +4 -3
  61. package/src/components/Icon/Icon.tsx +1 -1
  62. package/src/components/Icon/icons/calendar.tsx +23 -0
  63. package/src/components/Icon/icons/ellipsis-vertical.tsx +22 -0
  64. package/src/components/Icon/icons/index.ts +8 -4
  65. package/src/components/Icon/icons/sliders-horizontal.tsx +9 -9
  66. package/src/components/Menu/Menu.module.css +82 -0
  67. package/src/components/Menu/Menu.tsx +137 -0
  68. package/src/components/Menu/__tests__/Menu.test.tsx +197 -0
  69. package/src/components/NavTabs/NavTabs.module.css +4 -2
  70. package/src/components/OverlayHeader/OverlayHeader.module.css +98 -0
  71. package/src/components/OverlayHeader/OverlayHeader.tsx +62 -0
  72. package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +51 -0
  73. package/src/components/Popover/Popover.module.css +31 -1
  74. package/src/components/Popover/Popover.stories.tsx +126 -1
  75. package/src/components/Popover/Popover.tsx +140 -7
  76. package/src/components/SegmentedControl/SegmentedControl.module.css +4 -2
  77. package/src/components/Sheet/Sheet.module.css +55 -0
  78. package/src/components/Sheet/Sheet.tsx +151 -0
  79. package/src/components/Sheet/__tests__/Sheet.test.tsx +99 -0
  80. package/src/components/SplitPane/SplitPane.module.css +85 -0
  81. package/src/components/SplitPane/SplitPane.stories.tsx +160 -0
  82. package/src/components/SplitPane/SplitPane.tsx +149 -0
  83. package/src/components/SplitPane/__tests__/SplitPane.test.tsx +51 -0
  84. package/src/components/StatusBadge/StatusBadge.module.css +59 -0
  85. package/src/components/StatusBadge/StatusBadge.stories.tsx +74 -0
  86. package/src/components/StatusBadge/StatusBadge.tsx +65 -0
  87. package/src/components/SystemBanner/SystemBanner.module.css +4 -2
  88. package/src/components/Tabs/Tabs.module.css +4 -2
  89. package/src/components/TagInput/TagInput.module.css +141 -0
  90. package/src/components/TagInput/TagInput.stories.tsx +109 -0
  91. package/src/components/TagInput/TagInput.tsx +174 -0
  92. package/src/components/TagInput/__tests__/TagInput.test.tsx +69 -0
  93. package/src/components/Toast/Toast.module.css +4 -2
  94. package/src/components/Tooltip/Tooltip.module.css +6 -1
  95. package/src/hooks/__tests__/useDoubleTap.test.tsx +117 -0
  96. package/src/hooks/__tests__/useIsMobile.test.tsx +80 -0
  97. package/src/hooks/__tests__/useLongPress.test.tsx +158 -0
  98. package/src/hooks/index.ts +4 -0
  99. package/src/hooks/useDoubleTap.ts +91 -0
  100. package/src/hooks/useIsMobile.ts +33 -0
  101. package/src/hooks/useLongPress.ts +185 -0
  102. package/src/index.ts +30 -1
  103. package/src/motion.ts +78 -0
  104. package/src/stories/DesignSystemOverview.stories.tsx +292 -4
  105. package/src/stories/InteractionHooks.stories.tsx +388 -0
  106. package/src/stories/TokenGallery.stories.tsx +85 -1
  107. package/src/tokens/primitives.css +19 -0
  108. package/src/tokens/semantic.css +13 -0
  109. 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
799
  className: [styles6.wrapper, size === "sm" ? styles6.sm : void 0, className].filter(Boolean).join(" "),
774
800
  children: [
775
- /* @__PURE__ */ jsx34("select", { className: styles6.select, ...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,37 +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 });
904
1005
  }
905
1006
 
906
1007
  // src/components/Textarea/Textarea.tsx
907
1008
  import styles11 from "./Textarea.module.css";
908
- import { jsx as jsx39 } from "react/jsx-runtime";
1009
+ import { jsx as jsx40 } from "react/jsx-runtime";
909
1010
  function Textarea({ className, rows = 4, ...rest }) {
910
1011
  const classes = [styles11.textarea, className].filter(Boolean).join(" ");
911
- return /* @__PURE__ */ jsx39("textarea", { rows, className: classes, ...rest });
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
+ ] });
912
1330
  }
913
1331
 
914
1332
  // src/components/Dialog/Dialog.tsx
915
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
1333
+ import { createContext, useContext, useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
916
1334
 
917
1335
  // src/scroll-lock.ts
918
1336
  function lockBodyScroll() {
@@ -926,9 +1344,74 @@ function unlockBodyScroll() {
926
1344
  if (next === 0) document.body.style.overflowY = "";
927
1345
  }
928
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
+
929
1399
  // src/components/Dialog/Dialog.tsx
930
- import styles12 from "./Dialog.module.css";
931
- import { jsx as jsx40, 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
+ }
932
1415
  var FOCUSABLE = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
933
1416
  function Dialog({
934
1417
  open,
@@ -938,14 +1421,17 @@ function Dialog({
938
1421
  title,
939
1422
  children
940
1423
  }) {
941
- const panelRef = useRef2(null);
942
- const previouslyFocused = useRef2(null);
943
- useEffect2(() => {
944
- 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;
945
1431
  lockBodyScroll();
946
1432
  return unlockBodyScroll;
947
- }, [open]);
948
- useEffect2(() => {
1433
+ }, [mounted]);
1434
+ useEffect4(() => {
949
1435
  if (!open) return;
950
1436
  previouslyFocused.current = document.activeElement;
951
1437
  const panel = panelRef.current;
@@ -953,7 +1439,7 @@ function Dialog({
953
1439
  (first ?? panel)?.focus();
954
1440
  return () => previouslyFocused.current?.focus();
955
1441
  }, [open]);
956
- useEffect2(() => {
1442
+ useEffect4(() => {
957
1443
  if (!open) return;
958
1444
  function handleKeyDown(e) {
959
1445
  if (e.key === "Escape") {
@@ -983,14 +1469,15 @@ function Dialog({
983
1469
  document.addEventListener("keydown", handleKeyDown);
984
1470
  return () => document.removeEventListener("keydown", handleKeyDown);
985
1471
  }, [open, onClose]);
986
- if (!open) return null;
1472
+ if (!mounted) return null;
1473
+ const isOpenPhase = phase === "open";
987
1474
  return (
988
1475
  // role="presentation" removes the scrim from the AT (it is purely visual).
989
1476
  // e.target check lets clicks inside the panel bubble without triggering dismiss.
990
- /* @__PURE__ */ jsx40(
1477
+ /* @__PURE__ */ jsx46(
991
1478
  "div",
992
1479
  {
993
- className: styles12.scrim,
1480
+ className: [styles17.scrim, isOpenPhase ? styles17.scrimOpen : ""].filter(Boolean).join(" "),
994
1481
  role: "presentation",
995
1482
  onClick: (e) => {
996
1483
  if (e.target === e.currentTarget) onClose();
@@ -998,7 +1485,7 @@ function Dialog({
998
1485
  onKeyDown: (e) => {
999
1486
  if (e.key === "Escape") onClose();
1000
1487
  },
1001
- children: /* @__PURE__ */ jsxs26(
1488
+ children: /* @__PURE__ */ jsxs32(
1002
1489
  "div",
1003
1490
  {
1004
1491
  ref: panelRef,
@@ -1006,23 +1493,19 @@ function Dialog({
1006
1493
  "aria-modal": "true",
1007
1494
  "aria-label": ariaLabel ?? title,
1008
1495
  tabIndex: -1,
1009
- className: [styles12.panel, styles12[size]].join(" "),
1496
+ className: [styles17.panel, styles17[size], isOpenPhase ? styles17.panelOpen : ""].filter(Boolean).join(" "),
1010
1497
  children: [
1011
- /* @__PURE__ */ jsxs26("div", { className: styles12.mobileBar, children: [
1012
- title && /* @__PURE__ */ jsx40("span", { className: styles12.mobileBarTitle, children: title }),
1013
- /* @__PURE__ */ jsx40(
1014
- "button",
1015
- {
1016
- type: "button",
1017
- className: styles12.mobileBarClose,
1018
- "aria-label": "Close",
1019
- onClick: onClose,
1020
- children: "\xD7"
1021
- }
1022
- )
1023
- ] }),
1024
- /* @__PURE__ */ jsx40("button", { type: "button", className: styles12.close, "aria-label": "Close", onClick: onClose, children: "\xD7" }),
1025
- /* @__PURE__ */ jsx40("div", { className: styles12.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 }) })
1026
1509
  ]
1027
1510
  }
1028
1511
  )
@@ -1032,19 +1515,30 @@ function Dialog({
1032
1515
  }
1033
1516
 
1034
1517
  // src/components/Drawer/Drawer.tsx
1035
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
1036
- import styles13 from "./Drawer.module.css";
1037
- import { jsx as jsx41 } 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;
1038
1522
  var FOCUSABLE2 = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
1039
- function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1040
- const panelRef = useRef3(null);
1041
- const previouslyFocused = useRef3(null);
1042
- useEffect3(() => {
1043
- 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;
1044
1538
  lockBodyScroll();
1045
1539
  return unlockBodyScroll;
1046
- }, [open]);
1047
- useEffect3(() => {
1540
+ }, [mounted]);
1541
+ useEffect5(() => {
1048
1542
  if (!open) return;
1049
1543
  previouslyFocused.current = document.activeElement;
1050
1544
  const panel = panelRef.current;
@@ -1052,7 +1546,7 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1052
1546
  (first ?? panel)?.focus();
1053
1547
  return () => previouslyFocused.current?.focus();
1054
1548
  }, [open]);
1055
- useEffect3(() => {
1549
+ useEffect5(() => {
1056
1550
  if (!open) return;
1057
1551
  function handleKeyDown(e) {
1058
1552
  if (e.key === "Escape") {
@@ -1082,14 +1576,40 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1082
1576
  document.addEventListener("keydown", handleKeyDown);
1083
1577
  return () => document.removeEventListener("keydown", handleKeyDown);
1084
1578
  }, [open, onClose]);
1085
- 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";
1086
1606
  return (
1087
1607
  // role="presentation" removes the scrim from the AT (it is purely visual).
1088
1608
  // e.target check lets clicks inside the panel bubble without triggering dismiss.
1089
- /* @__PURE__ */ jsx41(
1609
+ /* @__PURE__ */ jsx47(
1090
1610
  "div",
1091
1611
  {
1092
- className: styles13.scrim,
1612
+ className: [styles18.scrim, isOpenPhase ? styles18.scrimOpen : ""].filter(Boolean).join(" "),
1093
1613
  role: "presentation",
1094
1614
  onClick: (e) => {
1095
1615
  if (e.target === e.currentTarget) onClose();
@@ -1097,7 +1617,7 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1097
1617
  onKeyDown: (e) => {
1098
1618
  if (e.key === "Escape") onClose();
1099
1619
  },
1100
- children: /* @__PURE__ */ jsx41(
1620
+ children: /* @__PURE__ */ jsxs33(
1101
1621
  "div",
1102
1622
  {
1103
1623
  ref: panelRef,
@@ -1105,8 +1625,25 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1105
1625
  "aria-modal": "true",
1106
1626
  "aria-label": ariaLabel,
1107
1627
  tabIndex: -1,
1108
- className: styles13.panel,
1109
- 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
+ ]
1110
1647
  }
1111
1648
  )
1112
1649
  }
@@ -1114,37 +1651,391 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1114
1651
  );
1115
1652
  }
1116
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
+
1117
2008
  // src/components/Toast/Toast.tsx
1118
2009
  import {
1119
- createContext,
1120
- useCallback,
1121
- useContext,
1122
- useEffect as useEffect4,
2010
+ createContext as createContext2,
2011
+ useCallback as useCallback3,
2012
+ useContext as useContext2,
2013
+ useEffect as useEffect10,
1123
2014
  useId,
1124
- useMemo,
1125
- useRef as useRef4,
1126
- useState
2015
+ useMemo as useMemo2,
2016
+ useRef as useRef9,
2017
+ useState as useState8
1127
2018
  } from "react";
1128
- import styles14 from "./Toast.module.css";
1129
- import { jsx as jsx42, jsxs as jsxs27 } from "react/jsx-runtime";
1130
- 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);
1131
2022
  function useToast() {
1132
- const ctx = useContext(ToastContext);
2023
+ const ctx = useContext2(ToastContext);
1133
2024
  if (!ctx) throw new Error("useToast() must be used inside <ToastProvider>");
1134
2025
  return ctx;
1135
2026
  }
1136
2027
  function ToastProvider({ children }) {
1137
- const [toasts, setToasts] = useState([]);
1138
- const timers = useRef4(/* @__PURE__ */ new Map());
2028
+ const [toasts, setToasts] = useState8([]);
2029
+ const timers = useRef9(/* @__PURE__ */ new Map());
1139
2030
  const seedId = useId();
1140
- const counter = useRef4(0);
1141
- const dismiss = useCallback((id) => {
2031
+ const counter = useRef9(0);
2032
+ const dismiss = useCallback3((id) => {
1142
2033
  setToasts((prev) => prev.map((t) => t.id === id ? { ...t, exiting: true } : t));
1143
2034
  setTimeout(() => {
1144
2035
  setToasts((prev) => prev.filter((t) => t.id !== id));
1145
2036
  }, 220);
1146
2037
  }, []);
1147
- const show = useCallback(
2038
+ const show = useCallback3(
1148
2039
  (item) => {
1149
2040
  const id = `${seedId}-${++counter.current}`;
1150
2041
  const duration = item.duration ?? 5e3;
@@ -1157,23 +2048,23 @@ function ToastProvider({ children }) {
1157
2048
  },
1158
2049
  [seedId, dismiss]
1159
2050
  );
1160
- useEffect4(() => {
2051
+ useEffect10(() => {
1161
2052
  const currentTimers = timers.current;
1162
2053
  return () => {
1163
2054
  currentTimers.forEach((t) => clearTimeout(t));
1164
2055
  };
1165
2056
  }, []);
1166
- const value = useMemo(() => ({ show, dismiss }), [show, dismiss]);
1167
- return /* @__PURE__ */ jsxs27(ToastContext.Provider, { value, children: [
2057
+ const value = useMemo2(() => ({ show, dismiss }), [show, dismiss]);
2058
+ return /* @__PURE__ */ jsxs37(ToastContext.Provider, { value, children: [
1168
2059
  children,
1169
- /* @__PURE__ */ jsx42(
2060
+ /* @__PURE__ */ jsx51(
1170
2061
  "ol",
1171
2062
  {
1172
- className: styles14.region,
2063
+ className: styles22.region,
1173
2064
  "aria-label": "Notifications",
1174
2065
  "aria-live": "polite",
1175
2066
  "aria-atomic": "false",
1176
- children: toasts.map((toast) => /* @__PURE__ */ jsx42(Toast, { ...toast, onDismiss: dismiss }, toast.id))
2067
+ children: toasts.map((toast) => /* @__PURE__ */ jsx51(Toast, { ...toast, onDismiss: dismiss }, toast.id))
1177
2068
  }
1178
2069
  )
1179
2070
  ] });
@@ -1194,24 +2085,24 @@ function Toast({
1194
2085
  exiting,
1195
2086
  onDismiss
1196
2087
  }) {
1197
- const categoryClass = styles14[category] ?? styles14.info;
2088
+ const categoryClass = styles22[category] ?? styles22.info;
1198
2089
  const iconName = CATEGORY_ICON[category] ?? "info";
1199
- return /* @__PURE__ */ jsxs27(
2090
+ return /* @__PURE__ */ jsxs37(
1200
2091
  "li",
1201
2092
  {
1202
2093
  role: "status",
1203
- className: `${styles14.toast} ${String(categoryClass)} ${exiting ? styles14.exiting : ""}`,
2094
+ className: `${styles22.toast} ${String(categoryClass)} ${exiting ? styles22.exiting : ""}`,
1204
2095
  children: [
1205
- /* @__PURE__ */ jsx42("span", { className: styles14.icon, "aria-hidden": "true", children: /* @__PURE__ */ jsx42(Icon, { name: iconName, size: "sm", "aria-hidden": true }) }),
1206
- /* @__PURE__ */ jsxs27("div", { className: styles14.body, children: [
1207
- /* @__PURE__ */ jsx42("div", { className: styles14.title, children: title }),
1208
- message && /* @__PURE__ */ jsx42("div", { className: styles14.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 })
1209
2100
  ] }),
1210
- /* @__PURE__ */ jsx42(
2101
+ /* @__PURE__ */ jsx51(
1211
2102
  "button",
1212
2103
  {
1213
2104
  type: "button",
1214
- className: styles14.close,
2105
+ className: styles22.close,
1215
2106
  "aria-label": "Dismiss notification",
1216
2107
  onClick: () => onDismiss(id),
1217
2108
  children: "\u2715"
@@ -1223,12 +2114,12 @@ function Toast({
1223
2114
  }
1224
2115
 
1225
2116
  // src/components/Card/Card.tsx
1226
- import styles15 from "./Card.module.css";
1227
- import { jsx as jsx43 } from "react/jsx-runtime";
2117
+ import styles23 from "./Card.module.css";
2118
+ import { jsx as jsx52 } from "react/jsx-runtime";
1228
2119
  var paddingClass = {
1229
- sm: styles15.paddingSm,
1230
- md: styles15.paddingMd,
1231
- lg: styles15.paddingLg
2120
+ sm: styles23.paddingSm,
2121
+ md: styles23.paddingMd,
2122
+ lg: styles23.paddingLg
1232
2123
  };
1233
2124
  function Card({
1234
2125
  as: Tag = "div",
@@ -1238,14 +2129,14 @@ function Card({
1238
2129
  children,
1239
2130
  ...rest
1240
2131
  }) {
1241
- const cls = [styles15.card, paddingClass[padding], interactive && styles15.interactive, className].filter(Boolean).join(" ");
1242
- return /* @__PURE__ */ jsx43(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 });
1243
2134
  }
1244
2135
 
1245
2136
  // src/components/FormField/FormField.tsx
1246
2137
  import { useId as useId2 } from "react";
1247
- import styles16 from "./FormField.module.css";
1248
- import { jsx as jsx44, 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";
1249
2140
  function FormField({
1250
2141
  label,
1251
2142
  hint,
@@ -1266,98 +2157,98 @@ function FormField({
1266
2157
  "aria-invalid": error ? true : void 0,
1267
2158
  required: required || void 0
1268
2159
  };
1269
- return /* @__PURE__ */ jsxs28("div", { className: [styles16.field, className].filter(Boolean).join(" "), children: [
1270
- /* @__PURE__ */ jsxs28("label", { className: styles16.label, htmlFor: fieldId, children: [
2160
+ return /* @__PURE__ */ jsxs38("div", { className: [styles24.field, className].filter(Boolean).join(" "), children: [
2161
+ /* @__PURE__ */ jsxs38("label", { className: styles24.label, htmlFor: fieldId, children: [
1271
2162
  label,
1272
- required && /* @__PURE__ */ jsx44("span", { className: styles16.required, "aria-hidden": "true", children: "*" })
2163
+ required && /* @__PURE__ */ jsx53("span", { className: styles24.required, "aria-hidden": "true", children: "*" })
1273
2164
  ] }),
1274
2165
  children(field),
1275
- hint && !error && /* @__PURE__ */ jsx44("p", { id: hintId, className: styles16.hint, children: hint }),
1276
- error && /* @__PURE__ */ jsx44("p", { id: errorId, className: styles16.error, role: "alert", children: error })
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 })
1277
2168
  ] });
1278
2169
  }
1279
2170
 
1280
2171
  // src/components/PageHeader/PageHeader.tsx
1281
- import styles17 from "./PageHeader.module.css";
1282
- import { jsx as jsx45, 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";
1283
2174
  function PageHeader({ title, description, action, className }) {
1284
- return /* @__PURE__ */ jsxs29("header", { className: [styles17.header, className].filter(Boolean).join(" "), children: [
1285
- /* @__PURE__ */ jsxs29("div", { className: styles17.text, children: [
1286
- /* @__PURE__ */ jsx45("h1", { className: styles17.title, children: title }),
1287
- description && /* @__PURE__ */ jsx45("p", { className: styles17.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 })
1288
2179
  ] }),
1289
- action && /* @__PURE__ */ jsx45("div", { className: styles17.action, children: action })
2180
+ action && /* @__PURE__ */ jsx54("div", { className: styles25.action, children: action })
1290
2181
  ] });
1291
2182
  }
1292
2183
 
1293
2184
  // src/components/EmptyState/EmptyState.tsx
1294
- import styles18 from "./EmptyState.module.css";
1295
- import { jsx as jsx46, 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";
1296
2187
  function EmptyState({ icon, heading, description, action, className }) {
1297
- return /* @__PURE__ */ jsxs30("div", { className: [styles18.root, className].filter(Boolean).join(" "), children: [
1298
- icon && /* @__PURE__ */ jsx46("span", { className: styles18.icon, children: /* @__PURE__ */ jsx46(Icon, { name: icon, size: "lg", "aria-hidden": true }) }),
1299
- /* @__PURE__ */ jsx46("h2", { className: styles18.heading, children: heading }),
1300
- description && /* @__PURE__ */ jsx46("p", { className: styles18.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 }),
1301
2192
  action
1302
2193
  ] });
1303
2194
  }
1304
2195
 
1305
2196
  // src/components/Spinner/Spinner.tsx
1306
- import styles19 from "./Spinner.module.css";
1307
- import { jsx as jsx47 } from "react/jsx-runtime";
2197
+ import styles27 from "./Spinner.module.css";
2198
+ import { jsx as jsx56 } from "react/jsx-runtime";
1308
2199
  var sizeClass = {
1309
- sm: styles19.sm,
1310
- md: styles19.md,
1311
- lg: styles19.lg
2200
+ sm: styles27.sm,
2201
+ md: styles27.md,
2202
+ lg: styles27.lg
1312
2203
  };
1313
2204
  function Spinner({ size = "md", label = "Loading\u2026", className }) {
1314
- return /* @__PURE__ */ jsx47(
2205
+ return /* @__PURE__ */ jsx56(
1315
2206
  "span",
1316
2207
  {
1317
2208
  role: "status",
1318
2209
  "aria-label": label,
1319
- className: [styles19.spinner, sizeClass[size], className].filter(Boolean).join(" ")
2210
+ className: [styles27.spinner, sizeClass[size], className].filter(Boolean).join(" ")
1320
2211
  }
1321
2212
  );
1322
2213
  }
1323
2214
 
1324
2215
  // src/components/Avatar/Avatar.tsx
1325
- import { useState as useState2 } from "react";
1326
- import styles20 from "./Avatar.module.css";
1327
- import { jsx as jsx48 } 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";
1328
2219
  function initials(name) {
1329
2220
  const parts = name.trim().split(/\s+/);
1330
2221
  if (parts.length === 1) return (parts[0] ?? "").slice(0, 2).toUpperCase();
1331
2222
  return ((parts[0]?.[0] ?? "") + (parts[parts.length - 1]?.[0] ?? "")).toUpperCase();
1332
2223
  }
1333
2224
  var sizeClass2 = {
1334
- sm: styles20.sm,
1335
- md: styles20.md,
1336
- lg: styles20.lg
2225
+ sm: styles28.sm,
2226
+ md: styles28.md,
2227
+ lg: styles28.lg
1337
2228
  };
1338
2229
  function Avatar({ name, src, size = "md", className }) {
1339
- const [imgFailed, setImgFailed] = useState2(false);
1340
- const cls = [styles20.avatar, sizeClass2[size], className].filter(Boolean).join(" ");
2230
+ const [imgFailed, setImgFailed] = useState9(false);
2231
+ const cls = [styles28.avatar, sizeClass2[size], className].filter(Boolean).join(" ");
1341
2232
  if (src && !imgFailed) {
1342
- return /* @__PURE__ */ jsx48("span", { className: cls, "aria-label": name, children: /* @__PURE__ */ jsx48("img", { src, alt: name, className: styles20.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) }) });
1343
2234
  }
1344
- return /* @__PURE__ */ jsx48("span", { className: cls, "aria-label": name, children: initials(name) });
2235
+ return /* @__PURE__ */ jsx57("span", { className: cls, "aria-label": name, children: initials(name) });
1345
2236
  }
1346
2237
 
1347
2238
  // src/components/NavTabs/NavTabs.tsx
1348
- import styles21 from "./NavTabs.module.css";
1349
- import { jsx as jsx49 } from "react/jsx-runtime";
2239
+ import styles29 from "./NavTabs.module.css";
2240
+ import { jsx as jsx58 } from "react/jsx-runtime";
1350
2241
  function NavTabs({ items, className, "aria-label": ariaLabel }) {
1351
- return /* @__PURE__ */ jsx49(
2242
+ return /* @__PURE__ */ jsx58(
1352
2243
  "nav",
1353
2244
  {
1354
- className: [styles21.nav, className].filter(Boolean).join(" "),
2245
+ className: [styles29.nav, className].filter(Boolean).join(" "),
1355
2246
  "aria-label": ariaLabel ?? "Page navigation",
1356
- children: items.map((item) => /* @__PURE__ */ jsx49(
2247
+ children: items.map((item) => /* @__PURE__ */ jsx58(
1357
2248
  "a",
1358
2249
  {
1359
2250
  href: item.href,
1360
- className: [styles21.link, item.active && styles21.active].filter(Boolean).join(" "),
2251
+ className: [styles29.link, item.active && styles29.active].filter(Boolean).join(" "),
1361
2252
  "aria-current": item.active ? "page" : void 0,
1362
2253
  children: item.label
1363
2254
  },
@@ -1369,25 +2260,25 @@ function NavTabs({ items, className, "aria-label": ariaLabel }) {
1369
2260
 
1370
2261
  // src/components/Tooltip/Tooltip.tsx
1371
2262
  import { useId as useId3 } from "react";
1372
- import styles22 from "./Tooltip.module.css";
1373
- import { jsx as jsx50, 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";
1374
2265
  var sideClass = {
1375
- top: styles22.top,
1376
- bottom: styles22.bottom,
1377
- left: styles22.left,
1378
- right: styles22.right
2266
+ top: styles30.top,
2267
+ bottom: styles30.bottom,
2268
+ left: styles30.left,
2269
+ right: styles30.right
1379
2270
  };
1380
2271
  function Tooltip({ content, children, side = "top" }) {
1381
2272
  const tipId = useId3();
1382
- return /* @__PURE__ */ jsxs31("span", { className: styles22.wrapper, children: [
1383
- /* @__PURE__ */ jsx50("span", { "aria-describedby": tipId, children }),
1384
- /* @__PURE__ */ jsx50("span", { id: tipId, role: "tooltip", className: [styles22.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 })
1385
2276
  ] });
1386
2277
  }
1387
2278
 
1388
2279
  // src/components/Checkbox/Checkbox.tsx
1389
- import styles23 from "./Checkbox.module.css";
1390
- import { jsx as jsx51, jsxs as jsxs32 } from "react/jsx-runtime";
2280
+ import styles31 from "./Checkbox.module.css";
2281
+ import { jsx as jsx60, jsxs as jsxs42 } from "react/jsx-runtime";
1391
2282
  function Checkbox({
1392
2283
  checked,
1393
2284
  onChange,
@@ -1399,9 +2290,9 @@ function Checkbox({
1399
2290
  ...rest
1400
2291
  }) {
1401
2292
  const inputId = id ?? `checkbox-${label.toLowerCase().replace(/\s+/g, "-")}`;
1402
- return /* @__PURE__ */ jsxs32("span", { className: [styles23.root, className].filter(Boolean).join(" "), children: [
1403
- /* @__PURE__ */ jsxs32("span", { className: [styles23.box, checked ? styles23.checked : ""].filter(Boolean).join(" "), children: [
1404
- /* @__PURE__ */ jsx51(
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(
1405
2296
  "input",
1406
2297
  {
1407
2298
  ...rest,
@@ -1409,11 +2300,11 @@ function Checkbox({
1409
2300
  type: "checkbox",
1410
2301
  checked,
1411
2302
  disabled,
1412
- className: styles23.input,
2303
+ className: styles31.input,
1413
2304
  onChange: (e) => onChange(e.target.checked)
1414
2305
  }
1415
2306
  ),
1416
- checked && /* @__PURE__ */ jsx51("svg", { className: styles23.tick, viewBox: "0 0 10 8", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx51(
2307
+ checked && /* @__PURE__ */ jsx60("svg", { className: styles31.tick, viewBox: "0 0 10 8", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx60(
1417
2308
  "path",
1418
2309
  {
1419
2310
  d: "M1 4l3 3 5-6",
@@ -1424,14 +2315,14 @@ function Checkbox({
1424
2315
  }
1425
2316
  ) })
1426
2317
  ] }),
1427
- /* @__PURE__ */ jsx51(
2318
+ label && /* @__PURE__ */ jsx60(
1428
2319
  "label",
1429
2320
  {
1430
2321
  htmlFor: inputId,
1431
2322
  className: [
1432
- styles23.label,
1433
- strikeThrough && checked ? styles23.struck : "",
1434
- disabled ? styles23.disabled : ""
2323
+ styles31.label,
2324
+ strikeThrough && checked ? styles31.struck : "",
2325
+ disabled ? styles31.disabled : ""
1435
2326
  ].filter(Boolean).join(" "),
1436
2327
  children: label
1437
2328
  }
@@ -1440,8 +2331,8 @@ function Checkbox({
1440
2331
  }
1441
2332
 
1442
2333
  // src/components/DragHandleRow/DragHandleRow.tsx
1443
- import styles24 from "./DragHandleRow.module.css";
1444
- import { jsx as jsx52, jsxs as jsxs33 } from "react/jsx-runtime";
2334
+ import styles32 from "./DragHandleRow.module.css";
2335
+ import { jsx as jsx61, jsxs as jsxs43 } from "react/jsx-runtime";
1445
2336
  function DragHandleRow({
1446
2337
  children,
1447
2338
  handleProps,
@@ -1449,30 +2340,30 @@ function DragHandleRow({
1449
2340
  className,
1450
2341
  ...rest
1451
2342
  }) {
1452
- return /* @__PURE__ */ jsxs33(
2343
+ return /* @__PURE__ */ jsxs43(
1453
2344
  "div",
1454
2345
  {
1455
2346
  ...rest,
1456
- className: [styles24.row, isDragging ? styles24.dragging : "", className].filter(Boolean).join(" "),
2347
+ className: [styles32.row, isDragging ? styles32.dragging : "", className].filter(Boolean).join(" "),
1457
2348
  children: [
1458
- /* @__PURE__ */ jsx52(
2349
+ /* @__PURE__ */ jsx61(
1459
2350
  "button",
1460
2351
  {
1461
2352
  type: "button",
1462
2353
  "aria-label": "Drag to reorder",
1463
- className: styles24.handle,
2354
+ className: styles32.handle,
1464
2355
  tabIndex: -1,
1465
2356
  ...handleProps,
1466
- children: /* @__PURE__ */ jsx52(DragIcon, {})
2357
+ children: /* @__PURE__ */ jsx61(DragIcon, {})
1467
2358
  }
1468
2359
  ),
1469
- /* @__PURE__ */ jsx52("div", { className: styles24.content, children })
2360
+ /* @__PURE__ */ jsx61("div", { className: styles32.content, children })
1470
2361
  ]
1471
2362
  }
1472
2363
  );
1473
2364
  }
1474
2365
  function DragIcon() {
1475
- return /* @__PURE__ */ jsx52(
2366
+ return /* @__PURE__ */ jsx61(
1476
2367
  "svg",
1477
2368
  {
1478
2369
  width: "14",
@@ -1480,19 +2371,297 @@ function DragIcon() {
1480
2371
  viewBox: "0 0 14 14",
1481
2372
  fill: "none",
1482
2373
  "aria-hidden": "true",
1483
- className: styles24.icon,
2374
+ className: styles32.icon,
1484
2375
  children: [3, 7, 11].map(
1485
- (cy) => [4, 10].map((cx) => /* @__PURE__ */ jsx52("circle", { cx, cy, r: 1.2, fill: "currentColor" }, `${cx}-${cy}`))
2376
+ (cy) => [4, 10].map((cx) => /* @__PURE__ */ jsx61("circle", { cx, cy, r: 1.2, fill: "currentColor" }, `${cx}-${cy}`))
1486
2377
  )
1487
2378
  }
1488
2379
  );
1489
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
+ }
1490
2655
  export {
1491
2656
  Avatar,
1492
2657
  Badge,
1493
2658
  Button,
2659
+ Calendar,
1494
2660
  Card,
1495
2661
  Checkbox,
2662
+ CodeTextarea,
2663
+ ConfirmDialog,
2664
+ DatePicker,
1496
2665
  Dialog,
1497
2666
  DragHandleRow,
1498
2667
  Drawer,
@@ -1500,17 +2669,29 @@ export {
1500
2669
  FormField,
1501
2670
  Icon,
1502
2671
  Input,
2672
+ MOBILE_BREAKPOINT_PX,
2673
+ Menu,
1503
2674
  NavTabs,
2675
+ OverlayHeader,
1504
2676
  PageHeader,
1505
2677
  Popover,
1506
2678
  SegmentedControl,
1507
2679
  Select,
2680
+ Sheet,
1508
2681
  Spinner,
2682
+ SplitPane,
2683
+ StatusBadge,
1509
2684
  SystemBanner,
1510
2685
  Tabs,
2686
+ TagInput,
1511
2687
  Textarea,
1512
2688
  ToastProvider,
1513
2689
  Toggle,
1514
2690
  Tooltip,
2691
+ useDoubleTapHandler,
2692
+ useIsMobile,
2693
+ useLongPress,
2694
+ useOverlaySecondRow,
2695
+ useSingleOrDoubleTap,
1515
2696
  useToast
1516
2697
  };