@bmsuisse/ui 0.7.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -625,7 +625,7 @@ DropdownMenuSubContent.displayName = "DropdownMenuSubContent";
625
625
  import { cva as cva3 } from "class-variance-authority";
626
626
  import * as DialogPrimitive2 from "@radix-ui/react-dialog";
627
627
  import { X as X2 } from "lucide-react";
628
- import { forwardRef as forwardRef15 } from "react";
628
+ import { forwardRef as forwardRef15, useEffect, useRef, useState } from "react";
629
629
  import { jsx as jsx18, jsxs as jsxs5 } from "react/jsx-runtime";
630
630
  var Sheet = DialogPrimitive2.Root;
631
631
  var SheetTrigger = DialogPrimitive2.Trigger;
@@ -641,7 +641,7 @@ var SheetOverlay = forwardRef15(({ className, ...props }, ref) => /* @__PURE__ *
641
641
  ));
642
642
  SheetOverlay.displayName = "SheetOverlay";
643
643
  var sheetVariants = cva3(
644
- "fixed z-50 gap-4 bg-background p-6 shadow-lg transition-transform duration-300 ease-in-out",
644
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition-[transform,box-shadow] duration-300 ease-in-out",
645
645
  {
646
646
  variants: {
647
647
  side: {
@@ -656,17 +656,84 @@ var sheetVariants = cva3(
656
656
  }
657
657
  }
658
658
  );
659
+ var enterFromTransform = {
660
+ top: "translateY(-100%)",
661
+ bottom: "translateY(100%)",
662
+ left: "translateX(-100%)",
663
+ right: "translateX(100%)"
664
+ };
659
665
  var SheetContent = forwardRef15(
660
- ({ side = "right", className, children, ...props }, ref) => /* @__PURE__ */ jsxs5(SheetPortal, { children: [
661
- /* @__PURE__ */ jsx18(SheetOverlay, {}),
662
- /* @__PURE__ */ jsxs5(DialogPrimitive2.Content, { ref, className: cn(sheetVariants({ side }), className), ...props, children: [
663
- children,
664
- /* @__PURE__ */ jsxs5(DialogPrimitive2.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100", children: [
665
- /* @__PURE__ */ jsx18(X2, { className: "h-4 w-4" }),
666
- /* @__PURE__ */ jsx18("span", { className: "sr-only", children: "Close" })
667
- ] })
668
- ] })
669
- ] })
666
+ ({ side = "right", className, children, resizable = false, style: styleProp, ...props }, ref) => {
667
+ const [entered, setEntered] = useState(false);
668
+ useEffect(() => {
669
+ const id = requestAnimationFrame(() => setEntered(true));
670
+ return () => cancelAnimationFrame(id);
671
+ }, []);
672
+ const [dragHeight, setDragHeight] = useState(null);
673
+ const [isDragging, setIsDragging] = useState(false);
674
+ const dragStart = useRef(null);
675
+ const canResize = resizable && side === "bottom";
676
+ const onHandlePointerDown = (e) => {
677
+ const popup = e.currentTarget.closest('[data-slot="sheet-content"]');
678
+ if (!(popup instanceof HTMLElement)) return;
679
+ e.currentTarget.setPointerCapture(e.pointerId);
680
+ dragStart.current = { y: e.clientY, height: popup.getBoundingClientRect().height };
681
+ setIsDragging(true);
682
+ };
683
+ const onHandlePointerMove = (e) => {
684
+ if (!dragStart.current) return;
685
+ const delta = dragStart.current.y - e.clientY;
686
+ const next = dragStart.current.height + delta;
687
+ const min = window.innerHeight * 0.3;
688
+ const max = window.innerHeight * 0.95;
689
+ setDragHeight(Math.min(max, Math.max(min, next)));
690
+ };
691
+ const onHandlePointerUp = () => {
692
+ dragStart.current = null;
693
+ setIsDragging(false);
694
+ };
695
+ const style = entered ? { ...styleProp, ...canResize && dragHeight != null ? { height: dragHeight, maxHeight: dragHeight } : null } : { ...styleProp, transform: enterFromTransform[side ?? "right"] };
696
+ return /* @__PURE__ */ jsxs5(SheetPortal, { children: [
697
+ /* @__PURE__ */ jsx18(SheetOverlay, {}),
698
+ /* @__PURE__ */ jsxs5(
699
+ DialogPrimitive2.Content,
700
+ {
701
+ ref,
702
+ "data-slot": "sheet-content",
703
+ style,
704
+ className: cn(sheetVariants({ side }), isDragging && "shadow-2xl", className),
705
+ ...props,
706
+ children: [
707
+ canResize && /* @__PURE__ */ jsx18(
708
+ "div",
709
+ {
710
+ "data-testid": "sheet-drag-handle",
711
+ className: "group -mb-2 flex shrink-0 cursor-grab touch-none justify-center py-2 active:cursor-grabbing",
712
+ onPointerDown: onHandlePointerDown,
713
+ onPointerMove: onHandlePointerMove,
714
+ onPointerUp: onHandlePointerUp,
715
+ onPointerCancel: onHandlePointerUp,
716
+ children: /* @__PURE__ */ jsx18(
717
+ "div",
718
+ {
719
+ className: cn(
720
+ "h-1 w-10 rounded-full bg-muted-foreground/30 transition-all duration-150 group-hover:w-14 group-hover:bg-muted-foreground/50",
721
+ isDragging && "w-14 bg-primary/60"
722
+ )
723
+ }
724
+ )
725
+ }
726
+ ),
727
+ children,
728
+ /* @__PURE__ */ jsxs5(DialogPrimitive2.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100", children: [
729
+ /* @__PURE__ */ jsx18(X2, { className: "h-4 w-4" }),
730
+ /* @__PURE__ */ jsx18("span", { className: "sr-only", children: "Close" })
731
+ ] })
732
+ ]
733
+ }
734
+ )
735
+ ] });
736
+ }
670
737
  );
671
738
  SheetContent.displayName = "SheetContent";
672
739
  var SheetHeader = ({
@@ -730,7 +797,7 @@ var Modal = ({
730
797
  ] }) });
731
798
 
732
799
  // src/patterns/modal/ConfirmDialog.tsx
733
- import { useState } from "react";
800
+ import { useState as useState2 } from "react";
734
801
  import { Fragment, jsx as jsx21, jsxs as jsxs7 } from "react/jsx-runtime";
735
802
  var ConfirmDialog = ({
736
803
  open,
@@ -744,7 +811,7 @@ var ConfirmDialog = ({
744
811
  confirmTestId,
745
812
  cancelTestId
746
813
  }) => {
747
- const [pending, setPending] = useState(false);
814
+ const [pending, setPending] = useState2(false);
748
815
  const handleCancel = () => {
749
816
  onOpenChange(false);
750
817
  };
@@ -785,7 +852,7 @@ var ConfirmDialog = ({
785
852
  };
786
853
 
787
854
  // src/patterns/modal/FormModal.tsx
788
- import { useState as useState2 } from "react";
855
+ import { useState as useState3 } from "react";
789
856
  import { jsx as jsx22, jsxs as jsxs8 } from "react/jsx-runtime";
790
857
  var FormModal = ({
791
858
  open,
@@ -801,7 +868,7 @@ var FormModal = ({
801
868
  submitTestId,
802
869
  cancelTestId
803
870
  }) => {
804
- const [pending, setPending] = useState2(false);
871
+ const [pending, setPending] = useState3(false);
805
872
  const handleSubmit = async (event) => {
806
873
  event.preventDefault();
807
874
  setPending(true);
@@ -837,9 +904,236 @@ var FormModal = ({
837
904
  ] }) }) });
838
905
  };
839
906
 
907
+ // src/patterns/modal/ResponsivePanel.tsx
908
+ import { useRef as useRef2, useState as useState5 } from "react";
909
+
910
+ // src/lib/useMediaQuery.ts
911
+ import { useEffect as useEffect2, useState as useState4 } from "react";
912
+ function useMediaQuery(query) {
913
+ const [matches, setMatches] = useState4(
914
+ () => typeof window !== "undefined" ? window.matchMedia(query).matches : false
915
+ );
916
+ useEffect2(() => {
917
+ const mq = window.matchMedia(query);
918
+ setMatches(mq.matches);
919
+ const handler = (e) => setMatches(e.matches);
920
+ mq.addEventListener("change", handler);
921
+ return () => mq.removeEventListener("change", handler);
922
+ }, [query]);
923
+ return matches;
924
+ }
925
+
926
+ // src/patterns/modal/ResponsivePanel.tsx
927
+ import { jsx as jsx23, jsxs as jsxs9 } from "react/jsx-runtime";
928
+ var desktopSizeClasses = {
929
+ sm: "max-w-md",
930
+ md: "max-w-xl",
931
+ lg: "max-w-2xl",
932
+ xl: "max-w-4xl"
933
+ };
934
+ var drawerSizeClasses = {
935
+ sm: "max-h-[50vh]",
936
+ md: "max-h-[70vh]",
937
+ lg: "max-h-[90vh]",
938
+ xl: "max-h-[96vh]"
939
+ };
940
+ var MIN_PANEL_WIDTH = 320;
941
+ var MIN_PANEL_HEIGHT = 240;
942
+ var VIEWPORT_MARGIN = 16;
943
+ var cornerCursor = {
944
+ "top-left": "cursor-nwse-resize",
945
+ "bottom-right": "cursor-nwse-resize",
946
+ "top-right": "cursor-nesw-resize",
947
+ "bottom-left": "cursor-nesw-resize"
948
+ };
949
+ var cornerPosition = {
950
+ "top-left": "left-0 top-0",
951
+ "top-right": "right-0 top-0",
952
+ "bottom-left": "left-0 bottom-0",
953
+ "bottom-right": "right-0 bottom-0"
954
+ };
955
+ var cornerGripPosition = {
956
+ "top-left": "left-1 top-1 border-l-2 border-t-2",
957
+ "top-right": "right-1 top-1 border-r-2 border-t-2",
958
+ "bottom-left": "left-1 bottom-1 border-l-2 border-b-2",
959
+ "bottom-right": "right-1 bottom-1 border-r-2 border-b-2"
960
+ };
961
+ function useDesktopPanelGeometry() {
962
+ const [style, setStyle] = useState5(void 0);
963
+ const [isDragging, setIsDragging] = useState5(false);
964
+ const drag = useRef2(null);
965
+ const lastRect = useRef2(null);
966
+ const beginDrag = (mode, corner) => (e) => {
967
+ const content = e.currentTarget.closest('[data-slot="dialog-content"]');
968
+ if (!(content instanceof HTMLElement)) return;
969
+ e.currentTarget.setPointerCapture(e.pointerId);
970
+ const domRect = content.getBoundingClientRect();
971
+ const rect = lastRect.current ?? {
972
+ left: domRect.left,
973
+ top: domRect.top,
974
+ width: domRect.width,
975
+ height: domRect.height
976
+ };
977
+ drag.current = { mode, corner, x: e.clientX, y: e.clientY, rect };
978
+ setIsDragging(true);
979
+ };
980
+ const onPointerMove = (e) => {
981
+ const state = drag.current;
982
+ if (!state) return;
983
+ const deltaX = e.clientX - state.x;
984
+ const deltaY = e.clientY - state.y;
985
+ if (state.mode === "move") {
986
+ const maxLeft = window.innerWidth - VIEWPORT_MARGIN - state.rect.width;
987
+ const maxTop = window.innerHeight - VIEWPORT_MARGIN - state.rect.height;
988
+ const next2 = {
989
+ left: Math.min(maxLeft, Math.max(VIEWPORT_MARGIN, state.rect.left + deltaX)),
990
+ top: Math.min(maxTop, Math.max(VIEWPORT_MARGIN, state.rect.top + deltaY)),
991
+ width: state.rect.width,
992
+ height: state.rect.height
993
+ };
994
+ lastRect.current = next2;
995
+ setStyle({ position: "fixed", transform: "none", translate: "none", maxWidth: "none", maxHeight: "none", ...next2 });
996
+ return;
997
+ }
998
+ const corner = state.corner ?? "top-left";
999
+ const growsLeft = corner === "top-left" || corner === "bottom-left";
1000
+ const growsUp = corner === "top-left" || corner === "top-right";
1001
+ const right = state.rect.left + state.rect.width;
1002
+ const bottom = state.rect.top + state.rect.height;
1003
+ const maxWidth = window.innerWidth - VIEWPORT_MARGIN * 2;
1004
+ const maxHeight = window.innerHeight - VIEWPORT_MARGIN * 2;
1005
+ let width = Math.max(
1006
+ MIN_PANEL_WIDTH,
1007
+ Math.min(maxWidth, growsLeft ? state.rect.width - deltaX : state.rect.width + deltaX)
1008
+ );
1009
+ let height = Math.max(
1010
+ MIN_PANEL_HEIGHT,
1011
+ Math.min(maxHeight, growsUp ? state.rect.height - deltaY : state.rect.height + deltaY)
1012
+ );
1013
+ const left = growsLeft ? right - Math.min(width, right - VIEWPORT_MARGIN) : state.rect.left;
1014
+ width = growsLeft ? right - left : Math.min(width, window.innerWidth - VIEWPORT_MARGIN - state.rect.left);
1015
+ const top = growsUp ? bottom - Math.min(height, bottom - VIEWPORT_MARGIN) : state.rect.top;
1016
+ height = growsUp ? bottom - top : Math.min(height, window.innerHeight - VIEWPORT_MARGIN - state.rect.top);
1017
+ const next = { left, top, width, height };
1018
+ lastRect.current = next;
1019
+ setStyle({ position: "fixed", transform: "none", translate: "none", maxWidth: "none", maxHeight: "none", ...next });
1020
+ };
1021
+ const onPointerUp = () => {
1022
+ drag.current = null;
1023
+ setIsDragging(false);
1024
+ };
1025
+ return {
1026
+ style,
1027
+ isDragging,
1028
+ startMove: beginDrag("move"),
1029
+ startResize: (corner) => beginDrag("resize", corner),
1030
+ onPointerMove,
1031
+ onPointerUp
1032
+ };
1033
+ }
1034
+ var ResponsivePanel = ({
1035
+ open,
1036
+ onOpenChange,
1037
+ title,
1038
+ description,
1039
+ children,
1040
+ footer,
1041
+ className,
1042
+ breakpoint = "(min-width: 1024px)",
1043
+ size = "lg",
1044
+ resizable = false,
1045
+ draggable = false,
1046
+ closeOnOutsideClick = true
1047
+ }) => {
1048
+ const isDesktop = useMediaQuery(breakpoint);
1049
+ const geometry = useDesktopPanelGeometry();
1050
+ const onInteractOutside = closeOnOutsideClick ? void 0 : (e) => e.preventDefault();
1051
+ if (isDesktop) {
1052
+ const corners = ["top-left", "top-right", "bottom-left", "bottom-right"];
1053
+ return /* @__PURE__ */ jsx23(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs9(
1054
+ DialogContent,
1055
+ {
1056
+ "data-slot": "dialog-content",
1057
+ onInteractOutside,
1058
+ style: resizable || draggable ? geometry.style : void 0,
1059
+ className: cn(
1060
+ "max-h-[85vh] overflow-y-auto transition-shadow duration-150 ease-out",
1061
+ geometry.isDragging && "shadow-2xl ring-2 ring-primary/30",
1062
+ desktopSizeClasses[size],
1063
+ className
1064
+ ),
1065
+ children: [
1066
+ resizable && corners.map((corner) => /* @__PURE__ */ jsx23(
1067
+ "div",
1068
+ {
1069
+ "data-testid": `panel-resize-handle-${corner}`,
1070
+ className: cn(
1071
+ "group absolute h-4 w-4 touch-none",
1072
+ cornerPosition[corner],
1073
+ cornerCursor[corner]
1074
+ ),
1075
+ onPointerDown: geometry.startResize(corner),
1076
+ onPointerMove: geometry.onPointerMove,
1077
+ onPointerUp: geometry.onPointerUp,
1078
+ onPointerCancel: geometry.onPointerUp,
1079
+ children: /* @__PURE__ */ jsx23(
1080
+ "div",
1081
+ {
1082
+ className: cn(
1083
+ "absolute h-2 w-2 scale-100 border-muted-foreground/40 transition-transform duration-150 group-hover:scale-125 group-hover:border-primary/60",
1084
+ cornerGripPosition[corner]
1085
+ )
1086
+ }
1087
+ )
1088
+ },
1089
+ corner
1090
+ )),
1091
+ /* @__PURE__ */ jsxs9(
1092
+ DialogHeader,
1093
+ {
1094
+ className: cn(draggable && "touch-none cursor-move select-none"),
1095
+ onPointerDown: draggable ? geometry.startMove : void 0,
1096
+ onPointerMove: draggable ? geometry.onPointerMove : void 0,
1097
+ onPointerUp: draggable ? geometry.onPointerUp : void 0,
1098
+ onPointerCancel: draggable ? geometry.onPointerUp : void 0,
1099
+ children: [
1100
+ /* @__PURE__ */ jsx23(DialogTitle, { children: title }),
1101
+ description ? /* @__PURE__ */ jsx23(DialogDescription, { children: description }) : null
1102
+ ]
1103
+ }
1104
+ ),
1105
+ children,
1106
+ footer ? /* @__PURE__ */ jsx23(DialogFooter, { children: footer }) : null
1107
+ ]
1108
+ }
1109
+ ) });
1110
+ }
1111
+ return /* @__PURE__ */ jsx23(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs9(
1112
+ SheetContent,
1113
+ {
1114
+ side: "bottom",
1115
+ resizable,
1116
+ onInteractOutside,
1117
+ className: cn(
1118
+ "flex flex-col overflow-y-auto rounded-t-2xl",
1119
+ drawerSizeClasses[size],
1120
+ className
1121
+ ),
1122
+ children: [
1123
+ /* @__PURE__ */ jsxs9(SheetHeader, { children: [
1124
+ /* @__PURE__ */ jsx23(SheetTitle, { children: title }),
1125
+ description ? /* @__PURE__ */ jsx23(SheetDescription, { children: description }) : null
1126
+ ] }),
1127
+ children,
1128
+ footer ? /* @__PURE__ */ jsx23(SheetFooter, { children: footer }) : null
1129
+ ]
1130
+ }
1131
+ ) });
1132
+ };
1133
+
840
1134
  // src/patterns/form-field/FormField.tsx
841
1135
  import { cloneElement, isValidElement, useId } from "react";
842
- import { jsx as jsx23, jsxs as jsxs9 } from "react/jsx-runtime";
1136
+ import { jsx as jsx24, jsxs as jsxs10 } from "react/jsx-runtime";
843
1137
  function FormField({
844
1138
  label,
845
1139
  htmlFor,
@@ -864,23 +1158,24 @@ function FormField({
864
1158
  ...messageId ? { "aria-describedby": messageId } : {}
865
1159
  }
866
1160
  ) : children;
867
- return /* @__PURE__ */ jsxs9("div", { className: cn("flex flex-col gap-1.5", className), children: [
868
- /* @__PURE__ */ jsxs9(Label, { htmlFor: effectiveId, children: [
1161
+ return /* @__PURE__ */ jsxs10("div", { className: cn("flex flex-col gap-1.5", className), children: [
1162
+ /* @__PURE__ */ jsxs10(Label, { htmlFor: effectiveId, children: [
869
1163
  label,
870
- required ? /* @__PURE__ */ jsxs9("span", { className: "text-destructive", "aria-hidden": "true", children: [
1164
+ required ? /* @__PURE__ */ jsxs10("span", { className: "text-destructive", "aria-hidden": "true", children: [
871
1165
  " ",
872
1166
  "*"
873
1167
  ] }) : null
874
1168
  ] }),
875
1169
  child,
876
- error ? /* @__PURE__ */ jsx23("p", { id: messageId, className: "text-sm text-destructive", children: error }) : description ? /* @__PURE__ */ jsx23("p", { id: messageId, className: "text-sm text-muted-foreground", children: description }) : null
1170
+ error ? /* @__PURE__ */ jsx24("p", { id: messageId, className: "text-sm text-destructive", children: error }) : description ? /* @__PURE__ */ jsx24("p", { id: messageId, className: "text-sm text-muted-foreground", children: description }) : null
877
1171
  ] });
878
1172
  }
879
1173
 
880
1174
  // src/patterns/combobox/Combobox.tsx
881
1175
  import { Check as Check4, ChevronsUpDown, X as X3 } from "lucide-react";
882
- import { useEffect, useMemo, useRef, useState as useState3 } from "react";
883
- import { jsx as jsx24, jsxs as jsxs10 } from "react/jsx-runtime";
1176
+ import { useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState6 } from "react";
1177
+
1178
+ // src/lib/optionGrouping.ts
884
1179
  function groupMembers(options, group) {
885
1180
  return options.filter((o) => o.group === group && !o.disabled).map((o) => o.value);
886
1181
  }
@@ -890,6 +1185,24 @@ function groupCheckState(options, selectedValues, group) {
890
1185
  if (selectedCount === 0) return "unchecked";
891
1186
  return selectedCount === members.length ? "checked" : "indeterminate";
892
1187
  }
1188
+ function buildRenderChunks(visibleOptions) {
1189
+ const chunks = [];
1190
+ visibleOptions.forEach((option, index) => {
1191
+ const row = { option, index };
1192
+ const last = chunks[chunks.length - 1];
1193
+ if (option.group && last?.kind === "group" && last.group === option.group) {
1194
+ last.rows.push(row);
1195
+ } else if (option.group) {
1196
+ chunks.push({ kind: "group", group: option.group, rows: [row] });
1197
+ } else {
1198
+ chunks.push({ kind: "single", row });
1199
+ }
1200
+ });
1201
+ return chunks;
1202
+ }
1203
+
1204
+ // src/patterns/combobox/Combobox.tsx
1205
+ import { jsx as jsx25, jsxs as jsxs11 } from "react/jsx-runtime";
893
1206
  function resolveGroupTriggerLabel(options, selectedValues, groupLabel) {
894
1207
  const selectedSet = new Set(selectedValues);
895
1208
  const groupsPresent = Array.from(
@@ -909,21 +1222,6 @@ function resolveGroupTriggerLabel(options, selectedValues, groupLabel) {
909
1222
  if (fullyCoveredGroups.length === 0 || hasLeftover) return null;
910
1223
  return fullyCoveredGroups.map(groupLabel).join(", ");
911
1224
  }
912
- function buildRenderChunks(visibleOptions) {
913
- const chunks = [];
914
- visibleOptions.forEach((option, index) => {
915
- const row = { option, index };
916
- const last = chunks[chunks.length - 1];
917
- if (option.group && last?.kind === "group" && last.group === option.group) {
918
- last.rows.push(row);
919
- } else if (option.group) {
920
- chunks.push({ kind: "group", group: option.group, rows: [row] });
921
- } else {
922
- chunks.push({ kind: "single", row });
923
- }
924
- });
925
- return chunks;
926
- }
927
1225
  function Combobox(props) {
928
1226
  const {
929
1227
  options,
@@ -942,10 +1240,10 @@ function Combobox(props) {
942
1240
  selectedLabel
943
1241
  } = props;
944
1242
  const testId = props["data-testid"];
945
- const [open, setOpen] = useState3(false);
946
- const [search, setSearch] = useState3("");
947
- const [activeIndex, setActiveIndex] = useState3(0);
948
- const inputRef = useRef(null);
1243
+ const [open, setOpen] = useState6(false);
1244
+ const [search, setSearch] = useState6("");
1245
+ const [activeIndex, setActiveIndex] = useState6(0);
1246
+ const inputRef = useRef3(null);
949
1247
  const selectedValues = useMemo(
950
1248
  () => props.multiple ? props.value : props.value ? [props.value] : [],
951
1249
  [props.multiple, props.value]
@@ -960,7 +1258,7 @@ function Combobox(props) {
960
1258
  if (!term) return options;
961
1259
  return options.filter((option) => option.label.toLowerCase().includes(term));
962
1260
  }, [options, search, onSearchChange]);
963
- useEffect(() => {
1261
+ useEffect3(() => {
964
1262
  if (!open) return;
965
1263
  setSearch("");
966
1264
  onSearchChange?.("");
@@ -969,7 +1267,7 @@ function Combobox(props) {
969
1267
  setActiveIndex(selectedIdx >= 0 ? selectedIdx : 0);
970
1268
  inputRef.current?.focus();
971
1269
  }, [open]);
972
- useEffect(() => {
1270
+ useEffect3(() => {
973
1271
  setActiveIndex((i) => Math.min(i, Math.max(visibleOptions.length - 1, 0)));
974
1272
  }, [visibleOptions.length]);
975
1273
  function selectOption(option) {
@@ -1007,7 +1305,7 @@ function Combobox(props) {
1007
1305
  function renderOption({ option, index }) {
1008
1306
  const isSelected = selectedValues.includes(option.value);
1009
1307
  const isActive = index === activeIndex;
1010
- return /* @__PURE__ */ jsxs10(
1308
+ return /* @__PURE__ */ jsxs11(
1011
1309
  "button",
1012
1310
  {
1013
1311
  type: "button",
@@ -1023,7 +1321,7 @@ function Combobox(props) {
1023
1321
  isActive && "bg-accent text-accent-foreground"
1024
1322
  ),
1025
1323
  children: [
1026
- props.multiple ? /* @__PURE__ */ jsx24(
1324
+ props.multiple ? /* @__PURE__ */ jsx25(
1027
1325
  "input",
1028
1326
  {
1029
1327
  type: "checkbox",
@@ -1032,15 +1330,15 @@ function Combobox(props) {
1032
1330
  tabIndex: -1,
1033
1331
  className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
1034
1332
  }
1035
- ) : /* @__PURE__ */ jsx24(Check4, { className: cn("h-4 w-4 shrink-0", isSelected ? "opacity-100" : "opacity-0") }),
1036
- /* @__PURE__ */ jsx24("span", { className: "truncate", children: option.label })
1333
+ ) : /* @__PURE__ */ jsx25(Check4, { className: cn("h-4 w-4 shrink-0", isSelected ? "opacity-100" : "opacity-0") }),
1334
+ /* @__PURE__ */ jsx25("span", { className: "truncate", children: option.label })
1037
1335
  ]
1038
1336
  },
1039
1337
  option.value
1040
1338
  );
1041
1339
  }
1042
- return /* @__PURE__ */ jsxs10(Popover, { open, onOpenChange: setOpen, children: [
1043
- /* @__PURE__ */ jsx24(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs10(
1340
+ return /* @__PURE__ */ jsxs11(Popover, { open, onOpenChange: setOpen, children: [
1341
+ /* @__PURE__ */ jsx25(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs11(
1044
1342
  Button,
1045
1343
  {
1046
1344
  id,
@@ -1056,9 +1354,9 @@ function Combobox(props) {
1056
1354
  className
1057
1355
  ),
1058
1356
  children: [
1059
- /* @__PURE__ */ jsx24("span", { className: "truncate", children: triggerLabel }),
1060
- /* @__PURE__ */ jsxs10("span", { className: "flex items-center gap-1", children: [
1061
- clearable && !loading && selectedValues.length > 0 ? /* @__PURE__ */ jsx24(
1357
+ /* @__PURE__ */ jsx25("span", { className: "truncate", children: triggerLabel }),
1358
+ /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1", children: [
1359
+ clearable && !loading && selectedValues.length > 0 ? /* @__PURE__ */ jsx25(
1062
1360
  "span",
1063
1361
  {
1064
1362
  role: "button",
@@ -1073,23 +1371,23 @@ function Combobox(props) {
1073
1371
  props.onChange(null);
1074
1372
  }
1075
1373
  },
1076
- children: /* @__PURE__ */ jsx24(X3, { className: "h-3.5 w-3.5" })
1374
+ children: /* @__PURE__ */ jsx25(X3, { className: "h-3.5 w-3.5" })
1077
1375
  }
1078
1376
  ) : null,
1079
- /* @__PURE__ */ jsx24(ChevronsUpDown, { className: "h-4 w-4 shrink-0 opacity-50", "aria-hidden": "true" })
1377
+ /* @__PURE__ */ jsx25(ChevronsUpDown, { className: "h-4 w-4 shrink-0 opacity-50", "aria-hidden": "true" })
1080
1378
  ] })
1081
1379
  ]
1082
1380
  }
1083
1381
  ) }),
1084
- /* @__PURE__ */ jsx24(
1382
+ /* @__PURE__ */ jsx25(
1085
1383
  PopoverContent,
1086
1384
  {
1087
1385
  align: "start",
1088
1386
  container,
1089
1387
  className: "w-[var(--radix-popover-trigger-width)] min-w-[10rem] p-2",
1090
1388
  onOpenAutoFocus: (event) => event.preventDefault(),
1091
- children: /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2", children: [
1092
- /* @__PURE__ */ jsx24(
1389
+ children: /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-2", children: [
1390
+ /* @__PURE__ */ jsx25(
1093
1391
  Input,
1094
1392
  {
1095
1393
  ref: inputRef,
@@ -1103,7 +1401,7 @@ function Combobox(props) {
1103
1401
  "aria-label": searchPlaceholder
1104
1402
  }
1105
1403
  ),
1106
- /* @__PURE__ */ jsx24("div", { role: "listbox", className: "flex max-h-60 flex-col gap-0.5 overflow-y-auto", children: renderChunks.length === 0 ? /* @__PURE__ */ jsx24("p", { className: "py-2 text-center text-sm text-muted-foreground", children: emptyMessage }) : renderChunks.map((chunk) => {
1404
+ /* @__PURE__ */ jsx25("div", { role: "listbox", className: "flex max-h-60 flex-col gap-0.5 overflow-y-auto", children: renderChunks.length === 0 ? /* @__PURE__ */ jsx25("p", { className: "py-2 text-center text-sm text-muted-foreground", children: emptyMessage }) : renderChunks.map((chunk) => {
1107
1405
  if (chunk.kind === "single") return renderOption(chunk.row);
1108
1406
  const headerState = groupCheckState(options, selectedValues, chunk.group);
1109
1407
  return (
@@ -1114,14 +1412,14 @@ function Combobox(props) {
1114
1412
  // lives on this wrapper's `pt-1`, not on the header itself -- a margin on
1115
1413
  // the sticky element would travel with it once stuck, leaving a gap at the
1116
1414
  // scrollport's top that the row behind it shows through.
1117
- /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-0.5 pt-1 first:pt-0", children: [
1118
- /* @__PURE__ */ jsxs10(
1415
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-0.5 pt-1 first:pt-0", children: [
1416
+ /* @__PURE__ */ jsxs11(
1119
1417
  "div",
1120
1418
  {
1121
1419
  "data-group-header": true,
1122
1420
  className: "sticky top-0 z-10 flex items-center gap-2 rounded-sm bg-muted px-2 py-1.5 text-sm font-semibold text-foreground",
1123
1421
  children: [
1124
- props.multiple ? /* @__PURE__ */ jsx24(
1422
+ props.multiple ? /* @__PURE__ */ jsx25(
1125
1423
  "input",
1126
1424
  {
1127
1425
  type: "checkbox",
@@ -1135,7 +1433,7 @@ function Combobox(props) {
1135
1433
  "aria-label": `Select all of ${groupLabel(chunk.group)}`
1136
1434
  }
1137
1435
  ) : null,
1138
- /* @__PURE__ */ jsx24("span", { className: "truncate", children: groupLabel(chunk.group) })
1436
+ /* @__PURE__ */ jsx25("span", { className: "truncate", children: groupLabel(chunk.group) })
1139
1437
  ]
1140
1438
  }
1141
1439
  ),
@@ -1149,9 +1447,249 @@ function Combobox(props) {
1149
1447
  ] });
1150
1448
  }
1151
1449
 
1450
+ // src/patterns/tag-combobox/TagCombobox.tsx
1451
+ import { X as X4 } from "lucide-react";
1452
+ import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState7 } from "react";
1453
+ import { jsx as jsx26, jsxs as jsxs12 } from "react/jsx-runtime";
1454
+ function TagCombobox({
1455
+ options,
1456
+ value,
1457
+ onChange,
1458
+ placeholder = "Select\u2026",
1459
+ emptyMessage = "No matches.",
1460
+ disabled,
1461
+ loading = false,
1462
+ loadingMessage = "Loading\u2026",
1463
+ className,
1464
+ id,
1465
+ onSearchChange,
1466
+ container,
1467
+ groupLabels,
1468
+ ...rest
1469
+ }) {
1470
+ const testId = rest["data-testid"];
1471
+ const [open, setOpen] = useState7(false);
1472
+ const [search, setSearch] = useState7("");
1473
+ const [activeIndex, setActiveIndex] = useState7(0);
1474
+ const inputRef = useRef4(null);
1475
+ const fieldRef = useRef4(null);
1476
+ const selectedOptions = useMemo2(
1477
+ () => value.map((v) => options.find((o) => o.value === v) ?? { value: v, label: v }),
1478
+ [options, value]
1479
+ );
1480
+ const visibleOptions = useMemo2(() => {
1481
+ if (onSearchChange) return options;
1482
+ const term = search.trim().toLowerCase();
1483
+ if (!term) return options;
1484
+ return options.filter((option) => option.label.toLowerCase().includes(term));
1485
+ }, [options, search, onSearchChange]);
1486
+ useEffect4(() => {
1487
+ setActiveIndex((i) => Math.min(Math.max(i, 0), Math.max(visibleOptions.length - 1, 0)));
1488
+ }, [visibleOptions.length]);
1489
+ const renderChunks = useMemo2(() => buildRenderChunks(visibleOptions), [visibleOptions]);
1490
+ useEffect4(() => {
1491
+ if (!open) return;
1492
+ setSearch("");
1493
+ onSearchChange?.("");
1494
+ const firstSelected = value[0];
1495
+ const selectedIdx = firstSelected ? options.findIndex((o) => o.value === firstSelected) : -1;
1496
+ setActiveIndex(selectedIdx >= 0 ? selectedIdx : 0);
1497
+ }, [open]);
1498
+ function updateSearch(next) {
1499
+ setSearch(next);
1500
+ onSearchChange?.(next);
1501
+ if (!open) setOpen(true);
1502
+ }
1503
+ function toggleOption(option) {
1504
+ if (option.disabled) return;
1505
+ const next = value.includes(option.value) ? value.filter((v) => v !== option.value) : [...value, option.value];
1506
+ onChange(next);
1507
+ updateSearch("");
1508
+ inputRef.current?.focus();
1509
+ }
1510
+ function removeValue(v) {
1511
+ if (options.find((o) => o.value === v)?.disabled) return;
1512
+ onChange(value.filter((existing) => existing !== v));
1513
+ }
1514
+ const groupLabel = (group) => groupLabels?.[group] ?? group;
1515
+ function toggleGroup(group) {
1516
+ const members = groupMembers(options, group);
1517
+ const next = groupCheckState(options, value, group) === "checked" ? value.filter((v) => !members.includes(v)) : Array.from(/* @__PURE__ */ new Set([...value, ...members]));
1518
+ onChange(next);
1519
+ inputRef.current?.focus();
1520
+ }
1521
+ function handleKeyDown(event) {
1522
+ if (event.key === "ArrowDown") {
1523
+ event.preventDefault();
1524
+ setOpen(true);
1525
+ setActiveIndex((i) => Math.min(i + 1, Math.max(visibleOptions.length - 1, 0)));
1526
+ } else if (event.key === "ArrowUp") {
1527
+ event.preventDefault();
1528
+ setActiveIndex((i) => Math.max(i - 1, 0));
1529
+ } else if (event.key === "Enter") {
1530
+ event.preventDefault();
1531
+ const option = visibleOptions[activeIndex];
1532
+ if (option) toggleOption(option);
1533
+ } else if (event.key === "Escape") {
1534
+ setOpen(false);
1535
+ } else if (event.key === "Backspace" && search === "") {
1536
+ const last = value[value.length - 1];
1537
+ if (last !== void 0) removeValue(last);
1538
+ }
1539
+ }
1540
+ function renderOption({ option, index }) {
1541
+ const isSelected = value.includes(option.value);
1542
+ const isActive = index === activeIndex;
1543
+ return /* @__PURE__ */ jsxs12(
1544
+ "button",
1545
+ {
1546
+ type: "button",
1547
+ role: "option",
1548
+ "aria-selected": isSelected,
1549
+ disabled: option.disabled,
1550
+ "data-testid": testId ? `${testId}-option` : void 0,
1551
+ "data-option-value": option.value,
1552
+ onMouseEnter: () => setActiveIndex(index),
1553
+ onClick: () => toggleOption(option),
1554
+ className: cn(
1555
+ "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none disabled:pointer-events-none disabled:opacity-50",
1556
+ isActive && "bg-accent text-accent-foreground"
1557
+ ),
1558
+ children: [
1559
+ /* @__PURE__ */ jsx26(
1560
+ "input",
1561
+ {
1562
+ type: "checkbox",
1563
+ checked: isSelected,
1564
+ readOnly: true,
1565
+ tabIndex: -1,
1566
+ className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
1567
+ }
1568
+ ),
1569
+ /* @__PURE__ */ jsx26("span", { className: "truncate", children: option.label })
1570
+ ]
1571
+ },
1572
+ option.value
1573
+ );
1574
+ }
1575
+ return /* @__PURE__ */ jsxs12(Popover, { open, onOpenChange: setOpen, children: [
1576
+ /* @__PURE__ */ jsx26(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs12(
1577
+ "div",
1578
+ {
1579
+ ref: fieldRef,
1580
+ "data-testid": testId,
1581
+ role: "combobox",
1582
+ "aria-expanded": open,
1583
+ "aria-disabled": disabled,
1584
+ className: cn(
1585
+ "flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent px-2 py-1.5 text-sm",
1586
+ "focus-within:outline-none focus-within:ring-2 focus-within:ring-ring",
1587
+ disabled && "cursor-not-allowed opacity-50",
1588
+ className
1589
+ ),
1590
+ onClick: () => {
1591
+ if (!disabled) inputRef.current?.focus();
1592
+ },
1593
+ children: [
1594
+ selectedOptions.map((option) => /* @__PURE__ */ jsxs12(
1595
+ "span",
1596
+ {
1597
+ className: "inline-flex items-center gap-1 rounded-full bg-muted py-0.5 pr-1 pl-2.5 text-xs font-medium text-muted-foreground",
1598
+ children: [
1599
+ /* @__PURE__ */ jsx26("span", { className: "max-w-48 truncate", children: option.label }),
1600
+ !disabled && /* @__PURE__ */ jsx26(
1601
+ "button",
1602
+ {
1603
+ type: "button",
1604
+ "aria-label": `Remove ${option.label}`,
1605
+ className: "rounded-full p-0.5 hover:bg-accent hover:text-accent-foreground",
1606
+ onClick: (event) => {
1607
+ event.stopPropagation();
1608
+ removeValue(option.value);
1609
+ },
1610
+ children: /* @__PURE__ */ jsx26(X4, { className: "h-3 w-3" })
1611
+ }
1612
+ )
1613
+ ]
1614
+ },
1615
+ option.value
1616
+ )),
1617
+ /* @__PURE__ */ jsx26(
1618
+ "input",
1619
+ {
1620
+ ref: inputRef,
1621
+ id,
1622
+ value: search,
1623
+ disabled,
1624
+ placeholder: selectedOptions.length === 0 ? placeholder : void 0,
1625
+ "aria-label": placeholder,
1626
+ className: "min-w-24 flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed",
1627
+ onFocus: () => setOpen(true),
1628
+ onChange: (event) => updateSearch(event.target.value),
1629
+ onKeyDown: handleKeyDown
1630
+ }
1631
+ )
1632
+ ]
1633
+ }
1634
+ ) }),
1635
+ /* @__PURE__ */ jsx26(
1636
+ PopoverContent,
1637
+ {
1638
+ align: "start",
1639
+ container,
1640
+ className: "w-[var(--radix-popover-trigger-width)] min-w-[10rem] p-2",
1641
+ onOpenAutoFocus: (event) => event.preventDefault(),
1642
+ onCloseAutoFocus: (event) => event.preventDefault(),
1643
+ onInteractOutside: (event) => {
1644
+ if (fieldRef.current?.contains(event.target)) event.preventDefault();
1645
+ },
1646
+ children: /* @__PURE__ */ jsx26("div", { role: "listbox", className: "flex max-h-60 flex-col gap-0.5 overflow-y-auto", children: loading ? /* @__PURE__ */ jsx26("p", { className: "py-2 text-center text-sm text-muted-foreground", children: loadingMessage }) : renderChunks.length === 0 ? /* @__PURE__ */ jsx26("p", { className: "py-2 text-center text-sm text-muted-foreground", children: emptyMessage }) : renderChunks.map((chunk) => {
1647
+ if (chunk.kind === "single") return renderOption(chunk.row);
1648
+ const headerState = groupCheckState(options, value, chunk.group);
1649
+ return (
1650
+ // The header and every one of its group's rows share this wrapper -- its
1651
+ // bounds are the sticky header's containing block, so `sticky top-0` keeps
1652
+ // the header pinned for the group's whole scroll extent instead of just
1653
+ // past its first row. Spacing above a group lives on this wrapper's
1654
+ // `pt-1`/`first:pt-0`, not on the header itself -- see `Combobox`'s own
1655
+ // identical structure for why both of these matter.
1656
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-0.5 pt-1 first:pt-0", children: [
1657
+ /* @__PURE__ */ jsxs12(
1658
+ "div",
1659
+ {
1660
+ "data-group-header": true,
1661
+ className: "sticky top-0 z-10 flex items-center gap-2 rounded-sm bg-muted px-2 py-1.5 text-sm font-semibold text-foreground",
1662
+ children: [
1663
+ /* @__PURE__ */ jsx26(
1664
+ "input",
1665
+ {
1666
+ type: "checkbox",
1667
+ checked: headerState === "checked",
1668
+ ref: (el) => {
1669
+ if (el) el.indeterminate = headerState === "indeterminate";
1670
+ },
1671
+ onChange: () => toggleGroup(chunk.group),
1672
+ onClick: (event) => event.stopPropagation(),
1673
+ className: "h-3.5 w-3.5 shrink-0 accent-primary",
1674
+ "aria-label": `Select all of ${groupLabel(chunk.group)}`
1675
+ }
1676
+ ),
1677
+ /* @__PURE__ */ jsx26("span", { className: "truncate", children: groupLabel(chunk.group) })
1678
+ ]
1679
+ }
1680
+ ),
1681
+ chunk.rows.map(renderOption)
1682
+ ] }, `group-${chunk.group}`)
1683
+ );
1684
+ }) })
1685
+ }
1686
+ )
1687
+ ] });
1688
+ }
1689
+
1152
1690
  // src/patterns/alert-box/AlertBox.tsx
1153
1691
  import { AlertCircle, AlertTriangle, CheckCircle2, Info } from "lucide-react";
1154
- import { jsx as jsx25, jsxs as jsxs11 } from "react/jsx-runtime";
1692
+ import { jsx as jsx27, jsxs as jsxs13 } from "react/jsx-runtime";
1155
1693
  var variantStyles = {
1156
1694
  error: "border-destructive bg-destructive/10 text-destructive",
1157
1695
  warning: "border-amber-500 bg-amber-500/10 text-amber-700 dark:text-amber-300",
@@ -1159,24 +1697,24 @@ var variantStyles = {
1159
1697
  success: "border-emerald-500 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"
1160
1698
  };
1161
1699
  var defaultIcons = {
1162
- error: /* @__PURE__ */ jsx25(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1163
- warning: /* @__PURE__ */ jsx25(AlertTriangle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1164
- info: /* @__PURE__ */ jsx25(Info, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1165
- success: /* @__PURE__ */ jsx25(CheckCircle2, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" })
1700
+ error: /* @__PURE__ */ jsx27(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1701
+ warning: /* @__PURE__ */ jsx27(AlertTriangle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1702
+ info: /* @__PURE__ */ jsx27(Info, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1703
+ success: /* @__PURE__ */ jsx27(CheckCircle2, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" })
1166
1704
  };
1167
1705
  var AlertBox = ({ variant, title, children, className, icon }) => {
1168
1706
  const resolvedIcon = icon === void 0 ? defaultIcons[variant] : icon;
1169
- return /* @__PURE__ */ jsx25("div", { className: cn("rounded-md border p-3 text-sm", variantStyles[variant], className), children: /* @__PURE__ */ jsxs11("div", { className: "flex flex-row items-start gap-2", children: [
1170
- resolvedIcon != null && /* @__PURE__ */ jsx25("span", { className: "mt-0.5 shrink-0", children: resolvedIcon }),
1171
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1", children: [
1172
- title && /* @__PURE__ */ jsx25("p", { className: "font-bold", children: title }),
1173
- /* @__PURE__ */ jsx25("div", { children })
1707
+ return /* @__PURE__ */ jsx27("div", { className: cn("rounded-md border p-3 text-sm", variantStyles[variant], className), children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-row items-start gap-2", children: [
1708
+ resolvedIcon != null && /* @__PURE__ */ jsx27("span", { className: "mt-0.5 shrink-0", children: resolvedIcon }),
1709
+ /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-1", children: [
1710
+ title && /* @__PURE__ */ jsx27("p", { className: "font-bold", children: title }),
1711
+ /* @__PURE__ */ jsx27("div", { children })
1174
1712
  ] })
1175
1713
  ] }) });
1176
1714
  };
1177
1715
 
1178
1716
  // src/patterns/status-badge/StatusBadge.tsx
1179
- import { jsx as jsx26 } from "react/jsx-runtime";
1717
+ import { jsx as jsx28 } from "react/jsx-runtime";
1180
1718
  var TONE_CLASSES = {
1181
1719
  success: "border-transparent bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300",
1182
1720
  warning: "border-transparent bg-amber-500/15 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300",
@@ -1223,11 +1761,11 @@ function resolveTone(status, tone, toneMap) {
1223
1761
  var StatusBadge = ({ status, label, tone, toneMap, className, ...props }) => {
1224
1762
  const resolvedTone = resolveTone(status, tone, toneMap);
1225
1763
  const displayLabel = label ?? deriveLabel(status);
1226
- return /* @__PURE__ */ jsx26(Badge, { variant: "outline", className: cn(TONE_CLASSES[resolvedTone], className), ...props, children: displayLabel });
1764
+ return /* @__PURE__ */ jsx28(Badge, { variant: "outline", className: cn(TONE_CLASSES[resolvedTone], className), ...props, children: displayLabel });
1227
1765
  };
1228
1766
 
1229
1767
  // src/patterns/button-group/ButtonGroup.tsx
1230
- import { jsx as jsx27, jsxs as jsxs12 } from "react/jsx-runtime";
1768
+ import { jsx as jsx29, jsxs as jsxs14 } from "react/jsx-runtime";
1231
1769
  function ButtonGroup({
1232
1770
  options,
1233
1771
  value,
@@ -1237,10 +1775,10 @@ function ButtonGroup({
1237
1775
  className,
1238
1776
  ...props
1239
1777
  }) {
1240
- return /* @__PURE__ */ jsx27("div", { role: "group", "aria-label": props["aria-label"], className: cn("inline-flex", className), "data-testid": props["data-testid"], children: options.map((option, index) => {
1778
+ return /* @__PURE__ */ jsx29("div", { role: "group", "aria-label": props["aria-label"], className: cn("inline-flex", className), "data-testid": props["data-testid"], children: options.map((option, index) => {
1241
1779
  const selected = option.value === value;
1242
1780
  const Icon2 = option.icon;
1243
- return /* @__PURE__ */ jsxs12(
1781
+ return /* @__PURE__ */ jsxs14(
1244
1782
  Button,
1245
1783
  {
1246
1784
  type: "button",
@@ -1258,7 +1796,7 @@ function ButtonGroup({
1258
1796
  index > 0 && "-ml-px"
1259
1797
  ),
1260
1798
  children: [
1261
- Icon2 && /* @__PURE__ */ jsx27(Icon2, { className: "h-4 w-4" }),
1799
+ Icon2 && /* @__PURE__ */ jsx29(Icon2, { className: "h-4 w-4" }),
1262
1800
  option.label
1263
1801
  ]
1264
1802
  },
@@ -1270,7 +1808,7 @@ function ButtonGroup({
1270
1808
  // src/patterns/loading-spinner/LoadingSpinner.tsx
1271
1809
  import { cva as cva4 } from "class-variance-authority";
1272
1810
  import { Loader2 } from "lucide-react";
1273
- import { jsx as jsx28, jsxs as jsxs13 } from "react/jsx-runtime";
1811
+ import { jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
1274
1812
  var loadingSpinnerIconVariants = cva4("animate-spin text-muted-foreground", {
1275
1813
  variants: {
1276
1814
  size: {
@@ -1288,27 +1826,27 @@ var LoadingSpinner = ({
1288
1826
  label,
1289
1827
  className,
1290
1828
  ...props
1291
- }) => /* @__PURE__ */ jsxs13("span", { role: "status", className: cn("inline-flex items-center gap-2", className), ...props, children: [
1292
- /* @__PURE__ */ jsx28(Loader2, { className: cn(loadingSpinnerIconVariants({ size })), "aria-hidden": "true" }),
1293
- label ? /* @__PURE__ */ jsx28("span", { children: label }) : null
1829
+ }) => /* @__PURE__ */ jsxs15("span", { role: "status", className: cn("inline-flex items-center gap-2", className), ...props, children: [
1830
+ /* @__PURE__ */ jsx30(Loader2, { className: cn(loadingSpinnerIconVariants({ size })), "aria-hidden": "true" }),
1831
+ label ? /* @__PURE__ */ jsx30("span", { children: label }) : null
1294
1832
  ] });
1295
1833
  var LoadingOverlay = ({
1296
1834
  size = "lg",
1297
1835
  label,
1298
1836
  className,
1299
1837
  ...props
1300
- }) => /* @__PURE__ */ jsx28(
1838
+ }) => /* @__PURE__ */ jsx30(
1301
1839
  "div",
1302
1840
  {
1303
1841
  className: cn("flex h-full min-h-32 w-full items-center justify-center", className),
1304
1842
  ...props,
1305
- children: /* @__PURE__ */ jsx28(LoadingSpinner, { size, label })
1843
+ children: /* @__PURE__ */ jsx30(LoadingSpinner, { size, label })
1306
1844
  }
1307
1845
  );
1308
1846
 
1309
1847
  // src/patterns/search-bar/SearchBar.tsx
1310
- import { Search, X as X4 } from "lucide-react";
1311
- import { jsx as jsx29, jsxs as jsxs14 } from "react/jsx-runtime";
1848
+ import { Search, X as X5 } from "lucide-react";
1849
+ import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
1312
1850
  function SearchBar({
1313
1851
  value,
1314
1852
  onChange,
@@ -1320,9 +1858,9 @@ function SearchBar({
1320
1858
  ...props
1321
1859
  }) {
1322
1860
  const showClear = onClear !== false && value.length > 0;
1323
- return /* @__PURE__ */ jsxs14("div", { className: cn("relative", className), children: [
1324
- /* @__PURE__ */ jsx29("span", { className: "pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-muted-foreground", children: isLoading ? /* @__PURE__ */ jsx29(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx29(Search, { className: "h-4 w-4", "aria-hidden": "true" }) }),
1325
- /* @__PURE__ */ jsx29(
1861
+ return /* @__PURE__ */ jsxs16("div", { className: cn("relative", className), children: [
1862
+ /* @__PURE__ */ jsx31("span", { className: "pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-muted-foreground", children: isLoading ? /* @__PURE__ */ jsx31(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx31(Search, { className: "h-4 w-4", "aria-hidden": "true" }) }),
1863
+ /* @__PURE__ */ jsx31(
1326
1864
  "input",
1327
1865
  {
1328
1866
  ref: inputRef,
@@ -1341,14 +1879,14 @@ function SearchBar({
1341
1879
  ...props
1342
1880
  }
1343
1881
  ),
1344
- showClear && /* @__PURE__ */ jsx29(
1882
+ showClear && /* @__PURE__ */ jsx31(
1345
1883
  "button",
1346
1884
  {
1347
1885
  type: "button",
1348
1886
  onClick: () => onClear ? onClear() : onChange(""),
1349
1887
  "aria-label": clearLabel,
1350
1888
  className: "absolute top-1/2 right-2 flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-full text-muted-foreground hover:bg-accent hover:text-foreground",
1351
- children: /* @__PURE__ */ jsx29(X4, { className: "h-4 w-4", "aria-hidden": "true" })
1889
+ children: /* @__PURE__ */ jsx31(X5, { className: "h-4 w-4", "aria-hidden": "true" })
1352
1890
  }
1353
1891
  )
1354
1892
  ] });
@@ -1363,7 +1901,7 @@ function useSidebarCollapsed() {
1363
1901
  }
1364
1902
 
1365
1903
  // src/patterns/sidebar/NavItem.tsx
1366
- import { jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
1904
+ import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
1367
1905
  function NavItem({
1368
1906
  as,
1369
1907
  icon: Icon2,
@@ -1375,7 +1913,7 @@ function NavItem({
1375
1913
  }) {
1376
1914
  const collapsed = useSidebarCollapsed();
1377
1915
  const Comp = as ?? "a";
1378
- const row = /* @__PURE__ */ jsxs15(
1916
+ const row = /* @__PURE__ */ jsxs17(
1379
1917
  Comp,
1380
1918
  {
1381
1919
  className: cn(
@@ -1387,7 +1925,7 @@ function NavItem({
1387
1925
  ),
1388
1926
  ...rest,
1389
1927
  children: [
1390
- Icon2 && /* @__PURE__ */ jsx30(
1928
+ Icon2 && /* @__PURE__ */ jsx32(
1391
1929
  Icon2,
1392
1930
  {
1393
1931
  className: cn("h-[18px] w-[18px] shrink-0", active ? "text-nav-primary" : "text-muted-foreground"),
@@ -1395,7 +1933,7 @@ function NavItem({
1395
1933
  "aria-hidden": true
1396
1934
  }
1397
1935
  ),
1398
- /* @__PURE__ */ jsx30(
1936
+ /* @__PURE__ */ jsx32(
1399
1937
  "span",
1400
1938
  {
1401
1939
  className: cn(
@@ -1410,9 +1948,9 @@ function NavItem({
1410
1948
  }
1411
1949
  );
1412
1950
  if (!collapsed) return row;
1413
- return /* @__PURE__ */ jsxs15(Tooltip, { children: [
1414
- /* @__PURE__ */ jsx30(TooltipTrigger, { asChild: true, children: row }),
1415
- /* @__PURE__ */ jsxs15(TooltipContent, { side: "right", className: "flex items-center gap-1.5", children: [
1951
+ return /* @__PURE__ */ jsxs17(Tooltip, { children: [
1952
+ /* @__PURE__ */ jsx32(TooltipTrigger, { asChild: true, children: row }),
1953
+ /* @__PURE__ */ jsxs17(TooltipContent, { side: "right", className: "flex items-center gap-1.5", children: [
1416
1954
  label,
1417
1955
  badge
1418
1956
  ] })
@@ -1421,8 +1959,8 @@ function NavItem({
1421
1959
 
1422
1960
  // src/patterns/sidebar/NavGroup.tsx
1423
1961
  import { ChevronDown as ChevronDown2 } from "lucide-react";
1424
- import { useState as useState4 } from "react";
1425
- import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
1962
+ import { useState as useState8 } from "react";
1963
+ import { jsx as jsx33, jsxs as jsxs18 } from "react/jsx-runtime";
1426
1964
  function NavGroup({
1427
1965
  label,
1428
1966
  defaultCollapsed = false,
@@ -1431,15 +1969,15 @@ function NavGroup({
1431
1969
  children,
1432
1970
  className
1433
1971
  }) {
1434
- const [internalCollapsed, setInternalCollapsed] = useState4(defaultCollapsed);
1972
+ const [internalCollapsed, setInternalCollapsed] = useState8(defaultCollapsed);
1435
1973
  const groupCollapsed = collapsed ?? internalCollapsed;
1436
1974
  const railCollapsed = useSidebarCollapsed();
1437
1975
  const setGroupCollapsed = (next) => {
1438
1976
  onCollapsedChange?.(next);
1439
1977
  if (collapsed === void 0) setInternalCollapsed(next);
1440
1978
  };
1441
- return /* @__PURE__ */ jsxs16("div", { className: cn("flex flex-col gap-0.5", className), children: [
1442
- label && (railCollapsed ? /* @__PURE__ */ jsx31("div", { className: "mx-2 my-1 h-px bg-border/50", "aria-hidden": true }) : /* @__PURE__ */ jsxs16(
1979
+ return /* @__PURE__ */ jsxs18("div", { className: cn("flex flex-col gap-0.5", className), children: [
1980
+ label && (railCollapsed ? /* @__PURE__ */ jsx33("div", { className: "mx-2 my-1 h-px bg-border/50", "aria-hidden": true }) : /* @__PURE__ */ jsxs18(
1443
1981
  "button",
1444
1982
  {
1445
1983
  type: "button",
@@ -1447,8 +1985,8 @@ function NavGroup({
1447
1985
  "aria-expanded": !groupCollapsed,
1448
1986
  className: "group flex w-full items-center justify-between px-2.5 py-0.5 text-[11px] font-medium text-muted-foreground/80 hover:text-foreground",
1449
1987
  children: [
1450
- /* @__PURE__ */ jsx31("span", { children: label }),
1451
- /* @__PURE__ */ jsx31(
1988
+ /* @__PURE__ */ jsx33("span", { children: label }),
1989
+ /* @__PURE__ */ jsx33(
1452
1990
  ChevronDown2,
1453
1991
  {
1454
1992
  className: cn(
@@ -1461,7 +1999,7 @@ function NavGroup({
1461
1999
  ]
1462
2000
  }
1463
2001
  )),
1464
- !groupCollapsed && /* @__PURE__ */ jsx31("div", { className: "flex flex-col gap-0.5", children })
2002
+ !groupCollapsed && /* @__PURE__ */ jsx33("div", { className: "flex flex-col gap-0.5", children })
1465
2003
  ] });
1466
2004
  }
1467
2005
 
@@ -1469,11 +2007,11 @@ function NavGroup({
1469
2007
  import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
1470
2008
  import {
1471
2009
  useCallback,
1472
- useEffect as useEffect2,
1473
- useRef as useRef2,
1474
- useState as useState5
2010
+ useEffect as useEffect5,
2011
+ useRef as useRef5,
2012
+ useState as useState9
1475
2013
  } from "react";
1476
- import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
2014
+ import { jsx as jsx34, jsxs as jsxs19 } from "react/jsx-runtime";
1477
2015
  function resolve(value, collapsed) {
1478
2016
  return typeof value === "function" ? value(collapsed) : value;
1479
2017
  }
@@ -1492,9 +2030,9 @@ function Sidebar({
1492
2030
  children,
1493
2031
  className
1494
2032
  }) {
1495
- const [internalWidth, setInternalWidth] = useState5(defaultWidth);
2033
+ const [internalWidth, setInternalWidth] = useState9(defaultWidth);
1496
2034
  const currentWidth = width ?? internalWidth;
1497
- const [isDragging, setIsDragging] = useState5(false);
2035
+ const [isDragging, setIsDragging] = useState9(false);
1498
2036
  const setWidth = useCallback(
1499
2037
  (next) => {
1500
2038
  const clamped = Math.min(maxWidth, Math.max(minWidth, next));
@@ -1503,7 +2041,7 @@ function Sidebar({
1503
2041
  },
1504
2042
  [maxWidth, minWidth, onWidthChange, width]
1505
2043
  );
1506
- const dragStartRef = useRef2(null);
2044
+ const dragStartRef = useRef5(null);
1507
2045
  const handlePointerDown = (event) => {
1508
2046
  event.currentTarget.setPointerCapture(event.pointerId);
1509
2047
  dragStartRef.current = { x: event.clientX, width: currentWidth };
@@ -1520,7 +2058,7 @@ function Sidebar({
1520
2058
  dragStartRef.current = null;
1521
2059
  setIsDragging(false);
1522
2060
  };
1523
- return /* @__PURE__ */ jsx32(SidebarContextProvider, { value: { collapsed }, children: /* @__PURE__ */ jsx32(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs17(
2061
+ return /* @__PURE__ */ jsx34(SidebarContextProvider, { value: { collapsed }, children: /* @__PURE__ */ jsx34(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs19(
1524
2062
  "aside",
1525
2063
  {
1526
2064
  style: { width: collapsed ? railWidth : currentWidth },
@@ -1530,22 +2068,22 @@ function Sidebar({
1530
2068
  className
1531
2069
  ),
1532
2070
  children: [
1533
- (header || onCollapsedChange) && /* @__PURE__ */ jsxs17("div", { className: "flex h-16 items-center gap-2 border-b border-border px-3", children: [
1534
- header && /* @__PURE__ */ jsx32("div", { className: "min-w-0 flex-1", children: resolve(header, collapsed) }),
1535
- onCollapsedChange && /* @__PURE__ */ jsx32(
2071
+ (header || onCollapsedChange) && /* @__PURE__ */ jsxs19("div", { className: "flex h-16 items-center gap-2 border-b border-border px-3", children: [
2072
+ header && /* @__PURE__ */ jsx34("div", { className: "min-w-0 flex-1", children: resolve(header, collapsed) }),
2073
+ onCollapsedChange && /* @__PURE__ */ jsx34(
1536
2074
  "button",
1537
2075
  {
1538
2076
  type: "button",
1539
2077
  onClick: () => onCollapsedChange(!collapsed),
1540
2078
  "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
1541
2079
  className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
1542
- children: collapsed ? /* @__PURE__ */ jsx32(PanelLeftOpen, { className: "h-4 w-4", "aria-hidden": true }) : /* @__PURE__ */ jsx32(PanelLeftClose, { className: "h-4 w-4", "aria-hidden": true })
2080
+ children: collapsed ? /* @__PURE__ */ jsx34(PanelLeftOpen, { className: "h-4 w-4", "aria-hidden": true }) : /* @__PURE__ */ jsx34(PanelLeftClose, { className: "h-4 w-4", "aria-hidden": true })
1543
2081
  }
1544
2082
  )
1545
2083
  ] }),
1546
- /* @__PURE__ */ jsx32(SidebarNav, { children }),
1547
- footer && /* @__PURE__ */ jsx32("div", { className: "border-t border-border/50 px-2 py-2", children: resolve(footer, collapsed) }),
1548
- resizable && !collapsed && /* @__PURE__ */ jsx32(
2084
+ /* @__PURE__ */ jsx34(SidebarNav, { children }),
2085
+ footer && /* @__PURE__ */ jsx34("div", { className: "border-t border-border/50 px-2 py-2", children: resolve(footer, collapsed) }),
2086
+ resizable && !collapsed && /* @__PURE__ */ jsx34(
1549
2087
  "div",
1550
2088
  {
1551
2089
  onPointerDown: handlePointerDown,
@@ -1560,9 +2098,9 @@ function Sidebar({
1560
2098
  ) }) });
1561
2099
  }
1562
2100
  function SidebarNav({ children, className, ...rest }) {
1563
- const ref = useRef2(null);
1564
- const [showFade, setShowFade] = useState5(false);
1565
- useEffect2(() => {
2101
+ const ref = useRef5(null);
2102
+ const [showFade, setShowFade] = useState9(false);
2103
+ useEffect5(() => {
1566
2104
  const el = ref.current;
1567
2105
  if (!el) return;
1568
2106
  const update = () => setShowFade(el.scrollHeight - el.scrollTop - el.clientHeight > 1);
@@ -1575,8 +2113,8 @@ function SidebarNav({ children, className, ...rest }) {
1575
2113
  observer.disconnect();
1576
2114
  };
1577
2115
  }, []);
1578
- return /* @__PURE__ */ jsxs17("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
1579
- /* @__PURE__ */ jsx32(
2116
+ return /* @__PURE__ */ jsxs19("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
2117
+ /* @__PURE__ */ jsx34(
1580
2118
  "div",
1581
2119
  {
1582
2120
  ref,
@@ -1585,7 +2123,7 @@ function SidebarNav({ children, className, ...rest }) {
1585
2123
  children
1586
2124
  }
1587
2125
  ),
1588
- showFade && /* @__PURE__ */ jsx32(
2126
+ showFade && /* @__PURE__ */ jsx34(
1589
2127
  "div",
1590
2128
  {
1591
2129
  className: "pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-sidebar to-transparent",
@@ -1643,6 +2181,7 @@ export {
1643
2181
  PopoverAnchor,
1644
2182
  PopoverContent,
1645
2183
  PopoverTrigger,
2184
+ ResponsivePanel,
1646
2185
  ScrollArea,
1647
2186
  ScrollBar,
1648
2187
  SearchBar,
@@ -1679,6 +2218,7 @@ export {
1679
2218
  TabsContent,
1680
2219
  TabsList,
1681
2220
  TabsTrigger,
2221
+ TagCombobox,
1682
2222
  Textarea,
1683
2223
  Tooltip,
1684
2224
  TooltipContent,
@@ -1689,6 +2229,7 @@ export {
1689
2229
  cn,
1690
2230
  loadingSpinnerIconVariants,
1691
2231
  sheetVariants,
2232
+ useMediaQuery,
1692
2233
  useSidebarCollapsed
1693
2234
  };
1694
2235
  //# sourceMappingURL=index.js.map