@bmsuisse/ui 0.8.0 → 0.12.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.d.ts +216 -4
- package/dist/index.js +930 -152
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,14 +641,19 @@ 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: {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
648
|
+
// Every edge border below is paired with `border-border` explicitly —
|
|
649
|
+
// Tailwind v4's bare `border-*` utilities default to `currentColor`,
|
|
650
|
+
// not the theme's border token, which otherwise renders this seam in
|
|
651
|
+
// the foreground text color (a harsh, high-contrast line in both
|
|
652
|
+
// themes) instead of the intended subtle divider.
|
|
653
|
+
top: "inset-x-0 top-0 border-b border-border data-[state=closed]:-translate-y-full data-[state=open]:translate-y-0",
|
|
654
|
+
bottom: "inset-x-0 bottom-0 border-t border-border data-[state=closed]:translate-y-full data-[state=open]:translate-y-0",
|
|
655
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r border-border data-[state=closed]:-translate-x-full data-[state=open]:translate-x-0 sm:max-w-sm",
|
|
656
|
+
right: "inset-y-0 right-0 h-full w-3/4 border-l border-border data-[state=closed]:translate-x-full data-[state=open]:translate-x-0 sm:max-w-sm"
|
|
652
657
|
}
|
|
653
658
|
},
|
|
654
659
|
defaultVariants: {
|
|
@@ -656,17 +661,84 @@ var sheetVariants = cva3(
|
|
|
656
661
|
}
|
|
657
662
|
}
|
|
658
663
|
);
|
|
664
|
+
var enterFromTransform = {
|
|
665
|
+
top: "translateY(-100%)",
|
|
666
|
+
bottom: "translateY(100%)",
|
|
667
|
+
left: "translateX(-100%)",
|
|
668
|
+
right: "translateX(100%)"
|
|
669
|
+
};
|
|
659
670
|
var SheetContent = forwardRef15(
|
|
660
|
-
({ side = "right", className, children, ...props }, ref) =>
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
671
|
+
({ side = "right", className, children, resizable = false, style: styleProp, ...props }, ref) => {
|
|
672
|
+
const [entered, setEntered] = useState(false);
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
const id = requestAnimationFrame(() => setEntered(true));
|
|
675
|
+
return () => cancelAnimationFrame(id);
|
|
676
|
+
}, []);
|
|
677
|
+
const [dragHeight, setDragHeight] = useState(null);
|
|
678
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
679
|
+
const dragStart = useRef(null);
|
|
680
|
+
const canResize = resizable && side === "bottom";
|
|
681
|
+
const onHandlePointerDown = (e) => {
|
|
682
|
+
const popup = e.currentTarget.closest('[data-slot="sheet-content"]');
|
|
683
|
+
if (!(popup instanceof HTMLElement)) return;
|
|
684
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
685
|
+
dragStart.current = { y: e.clientY, height: popup.getBoundingClientRect().height };
|
|
686
|
+
setIsDragging(true);
|
|
687
|
+
};
|
|
688
|
+
const onHandlePointerMove = (e) => {
|
|
689
|
+
if (!dragStart.current) return;
|
|
690
|
+
const delta = dragStart.current.y - e.clientY;
|
|
691
|
+
const next = dragStart.current.height + delta;
|
|
692
|
+
const min = window.innerHeight * 0.3;
|
|
693
|
+
const max = window.innerHeight * 0.95;
|
|
694
|
+
setDragHeight(Math.min(max, Math.max(min, next)));
|
|
695
|
+
};
|
|
696
|
+
const onHandlePointerUp = () => {
|
|
697
|
+
dragStart.current = null;
|
|
698
|
+
setIsDragging(false);
|
|
699
|
+
};
|
|
700
|
+
const style = entered ? { ...styleProp, ...canResize && dragHeight != null ? { height: dragHeight, maxHeight: dragHeight } : null } : { ...styleProp, transform: enterFromTransform[side ?? "right"] };
|
|
701
|
+
return /* @__PURE__ */ jsxs5(SheetPortal, { children: [
|
|
702
|
+
/* @__PURE__ */ jsx18(SheetOverlay, {}),
|
|
703
|
+
/* @__PURE__ */ jsxs5(
|
|
704
|
+
DialogPrimitive2.Content,
|
|
705
|
+
{
|
|
706
|
+
ref,
|
|
707
|
+
"data-slot": "sheet-content",
|
|
708
|
+
style,
|
|
709
|
+
className: cn(sheetVariants({ side }), isDragging && "shadow-2xl", className),
|
|
710
|
+
...props,
|
|
711
|
+
children: [
|
|
712
|
+
canResize && /* @__PURE__ */ jsx18(
|
|
713
|
+
"div",
|
|
714
|
+
{
|
|
715
|
+
"data-testid": "sheet-drag-handle",
|
|
716
|
+
className: "group -mb-2 flex shrink-0 cursor-grab touch-none justify-center py-2 active:cursor-grabbing",
|
|
717
|
+
onPointerDown: onHandlePointerDown,
|
|
718
|
+
onPointerMove: onHandlePointerMove,
|
|
719
|
+
onPointerUp: onHandlePointerUp,
|
|
720
|
+
onPointerCancel: onHandlePointerUp,
|
|
721
|
+
children: /* @__PURE__ */ jsx18(
|
|
722
|
+
"div",
|
|
723
|
+
{
|
|
724
|
+
className: cn(
|
|
725
|
+
"h-1 w-10 rounded-full bg-muted-foreground/30 transition-all duration-150 group-hover:w-14 group-hover:bg-muted-foreground/50",
|
|
726
|
+
isDragging && "w-14 bg-primary/60"
|
|
727
|
+
)
|
|
728
|
+
}
|
|
729
|
+
)
|
|
730
|
+
}
|
|
731
|
+
),
|
|
732
|
+
children,
|
|
733
|
+
/* @__PURE__ */ jsxs5(DialogPrimitive2.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100", children: [
|
|
734
|
+
/* @__PURE__ */ jsx18(X2, { className: "h-4 w-4" }),
|
|
735
|
+
/* @__PURE__ */ jsx18("span", { className: "sr-only", children: "Close" })
|
|
736
|
+
] })
|
|
737
|
+
]
|
|
738
|
+
}
|
|
739
|
+
)
|
|
740
|
+
] });
|
|
741
|
+
}
|
|
670
742
|
);
|
|
671
743
|
SheetContent.displayName = "SheetContent";
|
|
672
744
|
var SheetHeader = ({
|
|
@@ -730,7 +802,7 @@ var Modal = ({
|
|
|
730
802
|
] }) });
|
|
731
803
|
|
|
732
804
|
// src/patterns/modal/ConfirmDialog.tsx
|
|
733
|
-
import { useState } from "react";
|
|
805
|
+
import { useState as useState2 } from "react";
|
|
734
806
|
import { Fragment, jsx as jsx21, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
735
807
|
var ConfirmDialog = ({
|
|
736
808
|
open,
|
|
@@ -744,7 +816,7 @@ var ConfirmDialog = ({
|
|
|
744
816
|
confirmTestId,
|
|
745
817
|
cancelTestId
|
|
746
818
|
}) => {
|
|
747
|
-
const [pending, setPending] =
|
|
819
|
+
const [pending, setPending] = useState2(false);
|
|
748
820
|
const handleCancel = () => {
|
|
749
821
|
onOpenChange(false);
|
|
750
822
|
};
|
|
@@ -785,7 +857,7 @@ var ConfirmDialog = ({
|
|
|
785
857
|
};
|
|
786
858
|
|
|
787
859
|
// src/patterns/modal/FormModal.tsx
|
|
788
|
-
import { useState as
|
|
860
|
+
import { useState as useState3 } from "react";
|
|
789
861
|
import { jsx as jsx22, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
790
862
|
var FormModal = ({
|
|
791
863
|
open,
|
|
@@ -801,7 +873,7 @@ var FormModal = ({
|
|
|
801
873
|
submitTestId,
|
|
802
874
|
cancelTestId
|
|
803
875
|
}) => {
|
|
804
|
-
const [pending, setPending] =
|
|
876
|
+
const [pending, setPending] = useState3(false);
|
|
805
877
|
const handleSubmit = async (event) => {
|
|
806
878
|
event.preventDefault();
|
|
807
879
|
setPending(true);
|
|
@@ -837,9 +909,236 @@ var FormModal = ({
|
|
|
837
909
|
] }) }) });
|
|
838
910
|
};
|
|
839
911
|
|
|
912
|
+
// src/patterns/modal/ResponsivePanel.tsx
|
|
913
|
+
import { useRef as useRef2, useState as useState5 } from "react";
|
|
914
|
+
|
|
915
|
+
// src/lib/useMediaQuery.ts
|
|
916
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
917
|
+
function useMediaQuery(query) {
|
|
918
|
+
const [matches, setMatches] = useState4(
|
|
919
|
+
() => typeof window !== "undefined" ? window.matchMedia(query).matches : false
|
|
920
|
+
);
|
|
921
|
+
useEffect2(() => {
|
|
922
|
+
const mq = window.matchMedia(query);
|
|
923
|
+
setMatches(mq.matches);
|
|
924
|
+
const handler = (e) => setMatches(e.matches);
|
|
925
|
+
mq.addEventListener("change", handler);
|
|
926
|
+
return () => mq.removeEventListener("change", handler);
|
|
927
|
+
}, [query]);
|
|
928
|
+
return matches;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/patterns/modal/ResponsivePanel.tsx
|
|
932
|
+
import { jsx as jsx23, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
933
|
+
var desktopSizeClasses = {
|
|
934
|
+
sm: "max-w-md",
|
|
935
|
+
md: "max-w-xl",
|
|
936
|
+
lg: "max-w-2xl",
|
|
937
|
+
xl: "max-w-7xl"
|
|
938
|
+
};
|
|
939
|
+
var drawerSizeClasses = {
|
|
940
|
+
sm: "max-h-[50vh]",
|
|
941
|
+
md: "max-h-[70vh]",
|
|
942
|
+
lg: "max-h-[90vh]",
|
|
943
|
+
xl: "max-h-[96vh]"
|
|
944
|
+
};
|
|
945
|
+
var MIN_PANEL_WIDTH = 320;
|
|
946
|
+
var MIN_PANEL_HEIGHT = 240;
|
|
947
|
+
var VIEWPORT_MARGIN = 16;
|
|
948
|
+
var cornerCursor = {
|
|
949
|
+
"top-left": "cursor-nwse-resize",
|
|
950
|
+
"bottom-right": "cursor-nwse-resize",
|
|
951
|
+
"top-right": "cursor-nesw-resize",
|
|
952
|
+
"bottom-left": "cursor-nesw-resize"
|
|
953
|
+
};
|
|
954
|
+
var cornerPosition = {
|
|
955
|
+
"top-left": "left-0 top-0",
|
|
956
|
+
"top-right": "right-0 top-0",
|
|
957
|
+
"bottom-left": "left-0 bottom-0",
|
|
958
|
+
"bottom-right": "right-0 bottom-0"
|
|
959
|
+
};
|
|
960
|
+
var cornerGripPosition = {
|
|
961
|
+
"top-left": "left-1 top-1 border-l-2 border-t-2",
|
|
962
|
+
"top-right": "right-1 top-1 border-r-2 border-t-2",
|
|
963
|
+
"bottom-left": "left-1 bottom-1 border-l-2 border-b-2",
|
|
964
|
+
"bottom-right": "right-1 bottom-1 border-r-2 border-b-2"
|
|
965
|
+
};
|
|
966
|
+
function useDesktopPanelGeometry() {
|
|
967
|
+
const [style, setStyle] = useState5(void 0);
|
|
968
|
+
const [isDragging, setIsDragging] = useState5(false);
|
|
969
|
+
const drag = useRef2(null);
|
|
970
|
+
const lastRect = useRef2(null);
|
|
971
|
+
const beginDrag = (mode, corner) => (e) => {
|
|
972
|
+
const content = e.currentTarget.closest('[data-slot="dialog-content"]');
|
|
973
|
+
if (!(content instanceof HTMLElement)) return;
|
|
974
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
975
|
+
const domRect = content.getBoundingClientRect();
|
|
976
|
+
const rect = lastRect.current ?? {
|
|
977
|
+
left: domRect.left,
|
|
978
|
+
top: domRect.top,
|
|
979
|
+
width: domRect.width,
|
|
980
|
+
height: domRect.height
|
|
981
|
+
};
|
|
982
|
+
drag.current = { mode, corner, x: e.clientX, y: e.clientY, rect };
|
|
983
|
+
setIsDragging(true);
|
|
984
|
+
};
|
|
985
|
+
const onPointerMove = (e) => {
|
|
986
|
+
const state = drag.current;
|
|
987
|
+
if (!state) return;
|
|
988
|
+
const deltaX = e.clientX - state.x;
|
|
989
|
+
const deltaY = e.clientY - state.y;
|
|
990
|
+
if (state.mode === "move") {
|
|
991
|
+
const maxLeft = window.innerWidth - VIEWPORT_MARGIN - state.rect.width;
|
|
992
|
+
const maxTop = window.innerHeight - VIEWPORT_MARGIN - state.rect.height;
|
|
993
|
+
const next2 = {
|
|
994
|
+
left: Math.min(maxLeft, Math.max(VIEWPORT_MARGIN, state.rect.left + deltaX)),
|
|
995
|
+
top: Math.min(maxTop, Math.max(VIEWPORT_MARGIN, state.rect.top + deltaY)),
|
|
996
|
+
width: state.rect.width,
|
|
997
|
+
height: state.rect.height
|
|
998
|
+
};
|
|
999
|
+
lastRect.current = next2;
|
|
1000
|
+
setStyle({ position: "fixed", transform: "none", translate: "none", maxWidth: "none", maxHeight: "none", ...next2 });
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const corner = state.corner ?? "top-left";
|
|
1004
|
+
const growsLeft = corner === "top-left" || corner === "bottom-left";
|
|
1005
|
+
const growsUp = corner === "top-left" || corner === "top-right";
|
|
1006
|
+
const right = state.rect.left + state.rect.width;
|
|
1007
|
+
const bottom = state.rect.top + state.rect.height;
|
|
1008
|
+
const maxWidth = window.innerWidth - VIEWPORT_MARGIN * 2;
|
|
1009
|
+
const maxHeight = window.innerHeight - VIEWPORT_MARGIN * 2;
|
|
1010
|
+
let width = Math.max(
|
|
1011
|
+
MIN_PANEL_WIDTH,
|
|
1012
|
+
Math.min(maxWidth, growsLeft ? state.rect.width - deltaX : state.rect.width + deltaX)
|
|
1013
|
+
);
|
|
1014
|
+
let height = Math.max(
|
|
1015
|
+
MIN_PANEL_HEIGHT,
|
|
1016
|
+
Math.min(maxHeight, growsUp ? state.rect.height - deltaY : state.rect.height + deltaY)
|
|
1017
|
+
);
|
|
1018
|
+
const left = growsLeft ? right - Math.min(width, right - VIEWPORT_MARGIN) : state.rect.left;
|
|
1019
|
+
width = growsLeft ? right - left : Math.min(width, window.innerWidth - VIEWPORT_MARGIN - state.rect.left);
|
|
1020
|
+
const top = growsUp ? bottom - Math.min(height, bottom - VIEWPORT_MARGIN) : state.rect.top;
|
|
1021
|
+
height = growsUp ? bottom - top : Math.min(height, window.innerHeight - VIEWPORT_MARGIN - state.rect.top);
|
|
1022
|
+
const next = { left, top, width, height };
|
|
1023
|
+
lastRect.current = next;
|
|
1024
|
+
setStyle({ position: "fixed", transform: "none", translate: "none", maxWidth: "none", maxHeight: "none", ...next });
|
|
1025
|
+
};
|
|
1026
|
+
const onPointerUp = () => {
|
|
1027
|
+
drag.current = null;
|
|
1028
|
+
setIsDragging(false);
|
|
1029
|
+
};
|
|
1030
|
+
return {
|
|
1031
|
+
style,
|
|
1032
|
+
isDragging,
|
|
1033
|
+
startMove: beginDrag("move"),
|
|
1034
|
+
startResize: (corner) => beginDrag("resize", corner),
|
|
1035
|
+
onPointerMove,
|
|
1036
|
+
onPointerUp
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
var ResponsivePanel = ({
|
|
1040
|
+
open,
|
|
1041
|
+
onOpenChange,
|
|
1042
|
+
title,
|
|
1043
|
+
description,
|
|
1044
|
+
children,
|
|
1045
|
+
footer,
|
|
1046
|
+
className,
|
|
1047
|
+
breakpoint = "(min-width: 1024px)",
|
|
1048
|
+
size = "lg",
|
|
1049
|
+
resizable = false,
|
|
1050
|
+
draggable = false,
|
|
1051
|
+
closeOnOutsideClick = true
|
|
1052
|
+
}) => {
|
|
1053
|
+
const isDesktop = useMediaQuery(breakpoint);
|
|
1054
|
+
const geometry = useDesktopPanelGeometry();
|
|
1055
|
+
const onInteractOutside = closeOnOutsideClick ? void 0 : (e) => e.preventDefault();
|
|
1056
|
+
if (isDesktop) {
|
|
1057
|
+
const corners = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
1058
|
+
return /* @__PURE__ */ jsx23(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs9(
|
|
1059
|
+
DialogContent,
|
|
1060
|
+
{
|
|
1061
|
+
"data-slot": "dialog-content",
|
|
1062
|
+
onInteractOutside,
|
|
1063
|
+
style: resizable || draggable ? geometry.style : void 0,
|
|
1064
|
+
className: cn(
|
|
1065
|
+
"max-h-[85vh] overflow-y-auto transition-shadow duration-150 ease-out",
|
|
1066
|
+
geometry.isDragging && "shadow-2xl ring-2 ring-primary/30",
|
|
1067
|
+
desktopSizeClasses[size],
|
|
1068
|
+
className
|
|
1069
|
+
),
|
|
1070
|
+
children: [
|
|
1071
|
+
resizable && corners.map((corner) => /* @__PURE__ */ jsx23(
|
|
1072
|
+
"div",
|
|
1073
|
+
{
|
|
1074
|
+
"data-testid": `panel-resize-handle-${corner}`,
|
|
1075
|
+
className: cn(
|
|
1076
|
+
"group absolute h-4 w-4 touch-none",
|
|
1077
|
+
cornerPosition[corner],
|
|
1078
|
+
cornerCursor[corner]
|
|
1079
|
+
),
|
|
1080
|
+
onPointerDown: geometry.startResize(corner),
|
|
1081
|
+
onPointerMove: geometry.onPointerMove,
|
|
1082
|
+
onPointerUp: geometry.onPointerUp,
|
|
1083
|
+
onPointerCancel: geometry.onPointerUp,
|
|
1084
|
+
children: /* @__PURE__ */ jsx23(
|
|
1085
|
+
"div",
|
|
1086
|
+
{
|
|
1087
|
+
className: cn(
|
|
1088
|
+
"absolute h-2 w-2 scale-100 border-muted-foreground/40 transition-transform duration-150 group-hover:scale-125 group-hover:border-primary/60",
|
|
1089
|
+
cornerGripPosition[corner]
|
|
1090
|
+
)
|
|
1091
|
+
}
|
|
1092
|
+
)
|
|
1093
|
+
},
|
|
1094
|
+
corner
|
|
1095
|
+
)),
|
|
1096
|
+
/* @__PURE__ */ jsxs9(
|
|
1097
|
+
DialogHeader,
|
|
1098
|
+
{
|
|
1099
|
+
className: cn(draggable && "touch-none cursor-move select-none"),
|
|
1100
|
+
onPointerDown: draggable ? geometry.startMove : void 0,
|
|
1101
|
+
onPointerMove: draggable ? geometry.onPointerMove : void 0,
|
|
1102
|
+
onPointerUp: draggable ? geometry.onPointerUp : void 0,
|
|
1103
|
+
onPointerCancel: draggable ? geometry.onPointerUp : void 0,
|
|
1104
|
+
children: [
|
|
1105
|
+
/* @__PURE__ */ jsx23(DialogTitle, { children: title }),
|
|
1106
|
+
description ? /* @__PURE__ */ jsx23(DialogDescription, { children: description }) : null
|
|
1107
|
+
]
|
|
1108
|
+
}
|
|
1109
|
+
),
|
|
1110
|
+
children,
|
|
1111
|
+
footer ? /* @__PURE__ */ jsx23(DialogFooter, { children: footer }) : null
|
|
1112
|
+
]
|
|
1113
|
+
}
|
|
1114
|
+
) });
|
|
1115
|
+
}
|
|
1116
|
+
return /* @__PURE__ */ jsx23(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs9(
|
|
1117
|
+
SheetContent,
|
|
1118
|
+
{
|
|
1119
|
+
side: "bottom",
|
|
1120
|
+
resizable,
|
|
1121
|
+
onInteractOutside,
|
|
1122
|
+
className: cn(
|
|
1123
|
+
"flex flex-col overflow-y-auto rounded-t-2xl",
|
|
1124
|
+
drawerSizeClasses[size],
|
|
1125
|
+
className
|
|
1126
|
+
),
|
|
1127
|
+
children: [
|
|
1128
|
+
/* @__PURE__ */ jsxs9(SheetHeader, { children: [
|
|
1129
|
+
/* @__PURE__ */ jsx23(SheetTitle, { children: title }),
|
|
1130
|
+
description ? /* @__PURE__ */ jsx23(SheetDescription, { children: description }) : null
|
|
1131
|
+
] }),
|
|
1132
|
+
children,
|
|
1133
|
+
footer ? /* @__PURE__ */ jsx23(SheetFooter, { children: footer }) : null
|
|
1134
|
+
]
|
|
1135
|
+
}
|
|
1136
|
+
) });
|
|
1137
|
+
};
|
|
1138
|
+
|
|
840
1139
|
// src/patterns/form-field/FormField.tsx
|
|
841
1140
|
import { cloneElement, isValidElement, useId } from "react";
|
|
842
|
-
import { jsx as
|
|
1141
|
+
import { jsx as jsx24, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
843
1142
|
function FormField({
|
|
844
1143
|
label,
|
|
845
1144
|
htmlFor,
|
|
@@ -864,22 +1163,22 @@ function FormField({
|
|
|
864
1163
|
...messageId ? { "aria-describedby": messageId } : {}
|
|
865
1164
|
}
|
|
866
1165
|
) : children;
|
|
867
|
-
return /* @__PURE__ */
|
|
868
|
-
/* @__PURE__ */
|
|
1166
|
+
return /* @__PURE__ */ jsxs10("div", { className: cn("flex flex-col gap-1.5", className), children: [
|
|
1167
|
+
/* @__PURE__ */ jsxs10(Label, { htmlFor: effectiveId, children: [
|
|
869
1168
|
label,
|
|
870
|
-
required ? /* @__PURE__ */
|
|
1169
|
+
required ? /* @__PURE__ */ jsxs10("span", { className: "text-destructive", "aria-hidden": "true", children: [
|
|
871
1170
|
" ",
|
|
872
1171
|
"*"
|
|
873
1172
|
] }) : null
|
|
874
1173
|
] }),
|
|
875
1174
|
child,
|
|
876
|
-
error ? /* @__PURE__ */
|
|
1175
|
+
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
1176
|
] });
|
|
878
1177
|
}
|
|
879
1178
|
|
|
880
1179
|
// src/patterns/combobox/Combobox.tsx
|
|
881
1180
|
import { Check as Check4, ChevronsUpDown, X as X3 } from "lucide-react";
|
|
882
|
-
import { useEffect, useMemo, useRef, useState as
|
|
1181
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState6 } from "react";
|
|
883
1182
|
|
|
884
1183
|
// src/lib/optionGrouping.ts
|
|
885
1184
|
function groupMembers(options, group) {
|
|
@@ -907,8 +1206,78 @@ function buildRenderChunks(visibleOptions) {
|
|
|
907
1206
|
return chunks;
|
|
908
1207
|
}
|
|
909
1208
|
|
|
1209
|
+
// src/lib/optionGrid.tsx
|
|
1210
|
+
import { jsx as jsx25, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1211
|
+
function renderGridOptions({
|
|
1212
|
+
columns,
|
|
1213
|
+
options,
|
|
1214
|
+
activeIndex,
|
|
1215
|
+
multiple,
|
|
1216
|
+
isSelected,
|
|
1217
|
+
onSelect,
|
|
1218
|
+
onHover,
|
|
1219
|
+
testId
|
|
1220
|
+
}) {
|
|
1221
|
+
return /* @__PURE__ */ jsxs11(Table, { className: "text-sm", children: [
|
|
1222
|
+
/* @__PURE__ */ jsx25(TableHeader, { children: /* @__PURE__ */ jsxs11(TableRow, { className: "hover:bg-transparent", children: [
|
|
1223
|
+
multiple && /* @__PURE__ */ jsx25(TableHead, { className: "sticky top-0 z-10 w-6 bg-muted" }),
|
|
1224
|
+
columns.map((column) => /* @__PURE__ */ jsx25(
|
|
1225
|
+
TableHead,
|
|
1226
|
+
{
|
|
1227
|
+
className: cn("sticky top-0 z-10 whitespace-nowrap bg-muted text-xs", column.className),
|
|
1228
|
+
children: column.header
|
|
1229
|
+
},
|
|
1230
|
+
column.key
|
|
1231
|
+
))
|
|
1232
|
+
] }) }),
|
|
1233
|
+
/* @__PURE__ */ jsx25(TableBody, { children: options.map((option, index) => {
|
|
1234
|
+
const selected = isSelected(option);
|
|
1235
|
+
const active = index === activeIndex;
|
|
1236
|
+
return /* @__PURE__ */ jsxs11(
|
|
1237
|
+
TableRow,
|
|
1238
|
+
{
|
|
1239
|
+
role: "option",
|
|
1240
|
+
"aria-selected": selected,
|
|
1241
|
+
"aria-label": option.label,
|
|
1242
|
+
"aria-disabled": option.disabled,
|
|
1243
|
+
"data-testid": testId ? `${testId}-option` : void 0,
|
|
1244
|
+
"data-option-value": option.value,
|
|
1245
|
+
onMouseEnter: () => onHover(index),
|
|
1246
|
+
onClick: () => {
|
|
1247
|
+
if (!option.disabled) onSelect(option);
|
|
1248
|
+
},
|
|
1249
|
+
className: cn(
|
|
1250
|
+
"cursor-pointer outline-none",
|
|
1251
|
+
// `hover:bg-accent` repeats the active row's own background under
|
|
1252
|
+
// `:hover` -- otherwise TableRow's default `hover:bg-muted/50`
|
|
1253
|
+
// would win over the plain `bg-accent` below (later in the
|
|
1254
|
+
// stylesheet) and flash the active/keyboard-selected row to the
|
|
1255
|
+
// wrong color the instant the mouse sits over it.
|
|
1256
|
+
active && "bg-accent text-accent-foreground hover:bg-accent hover:text-accent-foreground",
|
|
1257
|
+
option.disabled && "pointer-events-none opacity-50"
|
|
1258
|
+
),
|
|
1259
|
+
children: [
|
|
1260
|
+
multiple && /* @__PURE__ */ jsx25(TableCell, { className: "w-6", children: /* @__PURE__ */ jsx25(
|
|
1261
|
+
"input",
|
|
1262
|
+
{
|
|
1263
|
+
type: "checkbox",
|
|
1264
|
+
checked: selected,
|
|
1265
|
+
readOnly: true,
|
|
1266
|
+
tabIndex: -1,
|
|
1267
|
+
className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
|
|
1268
|
+
}
|
|
1269
|
+
) }),
|
|
1270
|
+
columns.map((column) => /* @__PURE__ */ jsx25(TableCell, { className: cn("truncate", column.className), children: column.key === "label" ? option.label : option.data?.[column.key] }, column.key))
|
|
1271
|
+
]
|
|
1272
|
+
},
|
|
1273
|
+
option.value
|
|
1274
|
+
);
|
|
1275
|
+
}) })
|
|
1276
|
+
] });
|
|
1277
|
+
}
|
|
1278
|
+
|
|
910
1279
|
// src/patterns/combobox/Combobox.tsx
|
|
911
|
-
import { jsx as
|
|
1280
|
+
import { jsx as jsx26, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
912
1281
|
function resolveGroupTriggerLabel(options, selectedValues, groupLabel) {
|
|
913
1282
|
const selectedSet = new Set(selectedValues);
|
|
914
1283
|
const groupsPresent = Array.from(
|
|
@@ -943,13 +1312,14 @@ function Combobox(props) {
|
|
|
943
1312
|
groupLabels,
|
|
944
1313
|
container,
|
|
945
1314
|
onSearchChange,
|
|
946
|
-
selectedLabel
|
|
1315
|
+
selectedLabel,
|
|
1316
|
+
columns
|
|
947
1317
|
} = props;
|
|
948
1318
|
const testId = props["data-testid"];
|
|
949
|
-
const [open, setOpen] =
|
|
950
|
-
const [search, setSearch] =
|
|
951
|
-
const [activeIndex, setActiveIndex] =
|
|
952
|
-
const inputRef =
|
|
1319
|
+
const [open, setOpen] = useState6(false);
|
|
1320
|
+
const [search, setSearch] = useState6("");
|
|
1321
|
+
const [activeIndex, setActiveIndex] = useState6(0);
|
|
1322
|
+
const inputRef = useRef3(null);
|
|
953
1323
|
const selectedValues = useMemo(
|
|
954
1324
|
() => props.multiple ? props.value : props.value ? [props.value] : [],
|
|
955
1325
|
[props.multiple, props.value]
|
|
@@ -964,7 +1334,7 @@ function Combobox(props) {
|
|
|
964
1334
|
if (!term) return options;
|
|
965
1335
|
return options.filter((option) => option.label.toLowerCase().includes(term));
|
|
966
1336
|
}, [options, search, onSearchChange]);
|
|
967
|
-
|
|
1337
|
+
useEffect3(() => {
|
|
968
1338
|
if (!open) return;
|
|
969
1339
|
setSearch("");
|
|
970
1340
|
onSearchChange?.("");
|
|
@@ -973,7 +1343,7 @@ function Combobox(props) {
|
|
|
973
1343
|
setActiveIndex(selectedIdx >= 0 ? selectedIdx : 0);
|
|
974
1344
|
inputRef.current?.focus();
|
|
975
1345
|
}, [open]);
|
|
976
|
-
|
|
1346
|
+
useEffect3(() => {
|
|
977
1347
|
setActiveIndex((i) => Math.min(i, Math.max(visibleOptions.length - 1, 0)));
|
|
978
1348
|
}, [visibleOptions.length]);
|
|
979
1349
|
function selectOption(option) {
|
|
@@ -1007,11 +1377,11 @@ function Combobox(props) {
|
|
|
1007
1377
|
props.onChange(next);
|
|
1008
1378
|
}
|
|
1009
1379
|
const triggerLabel = loading ? loadingMessage : selectedOptions.length === 0 ? selectedValues.length > 0 ? selectedLabel ?? placeholder : placeholder : selectedOptions.length === 1 ? selectedOptions[0]?.label ?? placeholder : (props.multiple ? resolveGroupTriggerLabel(options, selectedValues, groupLabel) : null) ?? `${selectedOptions.length} selected`;
|
|
1010
|
-
const renderChunks = useMemo(() => buildRenderChunks(visibleOptions), [visibleOptions]);
|
|
1380
|
+
const renderChunks = useMemo(() => columns ? [] : buildRenderChunks(visibleOptions), [visibleOptions, columns]);
|
|
1011
1381
|
function renderOption({ option, index }) {
|
|
1012
1382
|
const isSelected = selectedValues.includes(option.value);
|
|
1013
1383
|
const isActive = index === activeIndex;
|
|
1014
|
-
return /* @__PURE__ */
|
|
1384
|
+
return /* @__PURE__ */ jsxs12(
|
|
1015
1385
|
"button",
|
|
1016
1386
|
{
|
|
1017
1387
|
type: "button",
|
|
@@ -1027,7 +1397,7 @@ function Combobox(props) {
|
|
|
1027
1397
|
isActive && "bg-accent text-accent-foreground"
|
|
1028
1398
|
),
|
|
1029
1399
|
children: [
|
|
1030
|
-
props.multiple ? /* @__PURE__ */
|
|
1400
|
+
props.multiple ? /* @__PURE__ */ jsx26(
|
|
1031
1401
|
"input",
|
|
1032
1402
|
{
|
|
1033
1403
|
type: "checkbox",
|
|
@@ -1036,15 +1406,15 @@ function Combobox(props) {
|
|
|
1036
1406
|
tabIndex: -1,
|
|
1037
1407
|
className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
|
|
1038
1408
|
}
|
|
1039
|
-
) : /* @__PURE__ */
|
|
1040
|
-
/* @__PURE__ */
|
|
1409
|
+
) : /* @__PURE__ */ jsx26(Check4, { className: cn("h-4 w-4 shrink-0", isSelected ? "opacity-100" : "opacity-0") }),
|
|
1410
|
+
/* @__PURE__ */ jsx26("span", { className: "truncate", children: option.label })
|
|
1041
1411
|
]
|
|
1042
1412
|
},
|
|
1043
1413
|
option.value
|
|
1044
1414
|
);
|
|
1045
1415
|
}
|
|
1046
|
-
return /* @__PURE__ */
|
|
1047
|
-
/* @__PURE__ */
|
|
1416
|
+
return /* @__PURE__ */ jsxs12(Popover, { open, onOpenChange: setOpen, children: [
|
|
1417
|
+
/* @__PURE__ */ jsx26(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs12(
|
|
1048
1418
|
Button,
|
|
1049
1419
|
{
|
|
1050
1420
|
id,
|
|
@@ -1060,9 +1430,9 @@ function Combobox(props) {
|
|
|
1060
1430
|
className
|
|
1061
1431
|
),
|
|
1062
1432
|
children: [
|
|
1063
|
-
/* @__PURE__ */
|
|
1064
|
-
/* @__PURE__ */
|
|
1065
|
-
clearable && !loading && selectedValues.length > 0 ? /* @__PURE__ */
|
|
1433
|
+
/* @__PURE__ */ jsx26("span", { className: "truncate", children: triggerLabel }),
|
|
1434
|
+
/* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-1", children: [
|
|
1435
|
+
clearable && !loading && selectedValues.length > 0 ? /* @__PURE__ */ jsx26(
|
|
1066
1436
|
"span",
|
|
1067
1437
|
{
|
|
1068
1438
|
role: "button",
|
|
@@ -1077,23 +1447,26 @@ function Combobox(props) {
|
|
|
1077
1447
|
props.onChange(null);
|
|
1078
1448
|
}
|
|
1079
1449
|
},
|
|
1080
|
-
children: /* @__PURE__ */
|
|
1450
|
+
children: /* @__PURE__ */ jsx26(X3, { className: "h-3.5 w-3.5" })
|
|
1081
1451
|
}
|
|
1082
1452
|
) : null,
|
|
1083
|
-
/* @__PURE__ */
|
|
1453
|
+
/* @__PURE__ */ jsx26(ChevronsUpDown, { className: "h-4 w-4 shrink-0 opacity-50", "aria-hidden": "true" })
|
|
1084
1454
|
] })
|
|
1085
1455
|
]
|
|
1086
1456
|
}
|
|
1087
1457
|
) }),
|
|
1088
|
-
/* @__PURE__ */
|
|
1458
|
+
/* @__PURE__ */ jsx26(
|
|
1089
1459
|
PopoverContent,
|
|
1090
1460
|
{
|
|
1091
1461
|
align: "start",
|
|
1092
1462
|
container,
|
|
1093
|
-
className:
|
|
1463
|
+
className: cn(
|
|
1464
|
+
"w-[var(--radix-popover-trigger-width)] min-w-[10rem] p-2",
|
|
1465
|
+
columns && "w-max min-w-[var(--radix-popover-trigger-width)] max-w-[min(40rem,90vw)]"
|
|
1466
|
+
),
|
|
1094
1467
|
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
1095
|
-
children: /* @__PURE__ */
|
|
1096
|
-
/* @__PURE__ */
|
|
1468
|
+
children: /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-2", children: [
|
|
1469
|
+
/* @__PURE__ */ jsx26(
|
|
1097
1470
|
Input,
|
|
1098
1471
|
{
|
|
1099
1472
|
ref: inputRef,
|
|
@@ -1107,7 +1480,16 @@ function Combobox(props) {
|
|
|
1107
1480
|
"aria-label": searchPlaceholder
|
|
1108
1481
|
}
|
|
1109
1482
|
),
|
|
1110
|
-
/* @__PURE__ */
|
|
1483
|
+
/* @__PURE__ */ jsx26("div", { role: "listbox", className: cn("max-h-60 overflow-y-auto", !columns && "flex flex-col gap-0.5"), children: visibleOptions.length === 0 ? /* @__PURE__ */ jsx26("p", { className: "py-2 text-center text-sm text-muted-foreground", children: emptyMessage }) : columns ? renderGridOptions({
|
|
1484
|
+
columns,
|
|
1485
|
+
options: visibleOptions,
|
|
1486
|
+
activeIndex,
|
|
1487
|
+
multiple: Boolean(props.multiple),
|
|
1488
|
+
isSelected: (option) => selectedValues.includes(option.value),
|
|
1489
|
+
onSelect: selectOption,
|
|
1490
|
+
onHover: setActiveIndex,
|
|
1491
|
+
testId
|
|
1492
|
+
}) : renderChunks.map((chunk) => {
|
|
1111
1493
|
if (chunk.kind === "single") return renderOption(chunk.row);
|
|
1112
1494
|
const headerState = groupCheckState(options, selectedValues, chunk.group);
|
|
1113
1495
|
return (
|
|
@@ -1118,14 +1500,14 @@ function Combobox(props) {
|
|
|
1118
1500
|
// lives on this wrapper's `pt-1`, not on the header itself -- a margin on
|
|
1119
1501
|
// the sticky element would travel with it once stuck, leaving a gap at the
|
|
1120
1502
|
// scrollport's top that the row behind it shows through.
|
|
1121
|
-
/* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */
|
|
1503
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-0.5 pt-1 first:pt-0", children: [
|
|
1504
|
+
/* @__PURE__ */ jsxs12(
|
|
1123
1505
|
"div",
|
|
1124
1506
|
{
|
|
1125
1507
|
"data-group-header": true,
|
|
1126
1508
|
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",
|
|
1127
1509
|
children: [
|
|
1128
|
-
props.multiple ? /* @__PURE__ */
|
|
1510
|
+
props.multiple ? /* @__PURE__ */ jsx26(
|
|
1129
1511
|
"input",
|
|
1130
1512
|
{
|
|
1131
1513
|
type: "checkbox",
|
|
@@ -1139,7 +1521,7 @@ function Combobox(props) {
|
|
|
1139
1521
|
"aria-label": `Select all of ${groupLabel(chunk.group)}`
|
|
1140
1522
|
}
|
|
1141
1523
|
) : null,
|
|
1142
|
-
/* @__PURE__ */
|
|
1524
|
+
/* @__PURE__ */ jsx26("span", { className: "truncate", children: groupLabel(chunk.group) })
|
|
1143
1525
|
]
|
|
1144
1526
|
}
|
|
1145
1527
|
),
|
|
@@ -1155,8 +1537,8 @@ function Combobox(props) {
|
|
|
1155
1537
|
|
|
1156
1538
|
// src/patterns/tag-combobox/TagCombobox.tsx
|
|
1157
1539
|
import { X as X4 } from "lucide-react";
|
|
1158
|
-
import { useEffect as
|
|
1159
|
-
import { jsx as
|
|
1540
|
+
import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState7 } from "react";
|
|
1541
|
+
import { jsx as jsx27, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1160
1542
|
function TagCombobox({
|
|
1161
1543
|
options,
|
|
1162
1544
|
value,
|
|
@@ -1171,14 +1553,15 @@ function TagCombobox({
|
|
|
1171
1553
|
onSearchChange,
|
|
1172
1554
|
container,
|
|
1173
1555
|
groupLabels,
|
|
1556
|
+
columns,
|
|
1174
1557
|
...rest
|
|
1175
1558
|
}) {
|
|
1176
1559
|
const testId = rest["data-testid"];
|
|
1177
|
-
const [open, setOpen] =
|
|
1178
|
-
const [search, setSearch] =
|
|
1179
|
-
const [activeIndex, setActiveIndex] =
|
|
1180
|
-
const inputRef =
|
|
1181
|
-
const fieldRef =
|
|
1560
|
+
const [open, setOpen] = useState7(false);
|
|
1561
|
+
const [search, setSearch] = useState7("");
|
|
1562
|
+
const [activeIndex, setActiveIndex] = useState7(0);
|
|
1563
|
+
const inputRef = useRef4(null);
|
|
1564
|
+
const fieldRef = useRef4(null);
|
|
1182
1565
|
const selectedOptions = useMemo2(
|
|
1183
1566
|
() => value.map((v) => options.find((o) => o.value === v) ?? { value: v, label: v }),
|
|
1184
1567
|
[options, value]
|
|
@@ -1189,11 +1572,11 @@ function TagCombobox({
|
|
|
1189
1572
|
if (!term) return options;
|
|
1190
1573
|
return options.filter((option) => option.label.toLowerCase().includes(term));
|
|
1191
1574
|
}, [options, search, onSearchChange]);
|
|
1192
|
-
|
|
1575
|
+
useEffect4(() => {
|
|
1193
1576
|
setActiveIndex((i) => Math.min(Math.max(i, 0), Math.max(visibleOptions.length - 1, 0)));
|
|
1194
1577
|
}, [visibleOptions.length]);
|
|
1195
|
-
const renderChunks = useMemo2(() => buildRenderChunks(visibleOptions), [visibleOptions]);
|
|
1196
|
-
|
|
1578
|
+
const renderChunks = useMemo2(() => columns ? [] : buildRenderChunks(visibleOptions), [visibleOptions, columns]);
|
|
1579
|
+
useEffect4(() => {
|
|
1197
1580
|
if (!open) return;
|
|
1198
1581
|
setSearch("");
|
|
1199
1582
|
onSearchChange?.("");
|
|
@@ -1246,7 +1629,7 @@ function TagCombobox({
|
|
|
1246
1629
|
function renderOption({ option, index }) {
|
|
1247
1630
|
const isSelected = value.includes(option.value);
|
|
1248
1631
|
const isActive = index === activeIndex;
|
|
1249
|
-
return /* @__PURE__ */
|
|
1632
|
+
return /* @__PURE__ */ jsxs13(
|
|
1250
1633
|
"button",
|
|
1251
1634
|
{
|
|
1252
1635
|
type: "button",
|
|
@@ -1262,7 +1645,7 @@ function TagCombobox({
|
|
|
1262
1645
|
isActive && "bg-accent text-accent-foreground"
|
|
1263
1646
|
),
|
|
1264
1647
|
children: [
|
|
1265
|
-
/* @__PURE__ */
|
|
1648
|
+
/* @__PURE__ */ jsx27(
|
|
1266
1649
|
"input",
|
|
1267
1650
|
{
|
|
1268
1651
|
type: "checkbox",
|
|
@@ -1272,14 +1655,14 @@ function TagCombobox({
|
|
|
1272
1655
|
className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
|
|
1273
1656
|
}
|
|
1274
1657
|
),
|
|
1275
|
-
/* @__PURE__ */
|
|
1658
|
+
/* @__PURE__ */ jsx27("span", { className: "truncate", children: option.label })
|
|
1276
1659
|
]
|
|
1277
1660
|
},
|
|
1278
1661
|
option.value
|
|
1279
1662
|
);
|
|
1280
1663
|
}
|
|
1281
|
-
return /* @__PURE__ */
|
|
1282
|
-
/* @__PURE__ */
|
|
1664
|
+
return /* @__PURE__ */ jsxs13(Popover, { open, onOpenChange: setOpen, children: [
|
|
1665
|
+
/* @__PURE__ */ jsx27(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs13(
|
|
1283
1666
|
"div",
|
|
1284
1667
|
{
|
|
1285
1668
|
ref: fieldRef,
|
|
@@ -1297,13 +1680,13 @@ function TagCombobox({
|
|
|
1297
1680
|
if (!disabled) inputRef.current?.focus();
|
|
1298
1681
|
},
|
|
1299
1682
|
children: [
|
|
1300
|
-
selectedOptions.map((option) => /* @__PURE__ */
|
|
1683
|
+
selectedOptions.map((option) => /* @__PURE__ */ jsxs13(
|
|
1301
1684
|
"span",
|
|
1302
1685
|
{
|
|
1303
1686
|
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",
|
|
1304
1687
|
children: [
|
|
1305
|
-
/* @__PURE__ */
|
|
1306
|
-
!disabled && /* @__PURE__ */
|
|
1688
|
+
/* @__PURE__ */ jsx27("span", { className: "max-w-48 truncate", children: option.label }),
|
|
1689
|
+
!disabled && /* @__PURE__ */ jsx27(
|
|
1307
1690
|
"button",
|
|
1308
1691
|
{
|
|
1309
1692
|
type: "button",
|
|
@@ -1313,14 +1696,14 @@ function TagCombobox({
|
|
|
1313
1696
|
event.stopPropagation();
|
|
1314
1697
|
removeValue(option.value);
|
|
1315
1698
|
},
|
|
1316
|
-
children: /* @__PURE__ */
|
|
1699
|
+
children: /* @__PURE__ */ jsx27(X4, { className: "h-3 w-3" })
|
|
1317
1700
|
}
|
|
1318
1701
|
)
|
|
1319
1702
|
]
|
|
1320
1703
|
},
|
|
1321
1704
|
option.value
|
|
1322
1705
|
)),
|
|
1323
|
-
/* @__PURE__ */
|
|
1706
|
+
/* @__PURE__ */ jsx27(
|
|
1324
1707
|
"input",
|
|
1325
1708
|
{
|
|
1326
1709
|
ref: inputRef,
|
|
@@ -1338,18 +1721,30 @@ function TagCombobox({
|
|
|
1338
1721
|
]
|
|
1339
1722
|
}
|
|
1340
1723
|
) }),
|
|
1341
|
-
/* @__PURE__ */
|
|
1724
|
+
/* @__PURE__ */ jsx27(
|
|
1342
1725
|
PopoverContent,
|
|
1343
1726
|
{
|
|
1344
1727
|
align: "start",
|
|
1345
1728
|
container,
|
|
1346
|
-
className:
|
|
1729
|
+
className: cn(
|
|
1730
|
+
"w-[var(--radix-popover-trigger-width)] min-w-[10rem] p-2",
|
|
1731
|
+
columns && "w-max min-w-[var(--radix-popover-trigger-width)] max-w-[min(40rem,90vw)]"
|
|
1732
|
+
),
|
|
1347
1733
|
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
1348
1734
|
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
1349
1735
|
onInteractOutside: (event) => {
|
|
1350
1736
|
if (fieldRef.current?.contains(event.target)) event.preventDefault();
|
|
1351
1737
|
},
|
|
1352
|
-
children: /* @__PURE__ */
|
|
1738
|
+
children: /* @__PURE__ */ jsx27("div", { role: "listbox", className: cn("max-h-60 overflow-y-auto", !columns && "flex flex-col gap-0.5"), children: loading ? /* @__PURE__ */ jsx27("p", { className: "py-2 text-center text-sm text-muted-foreground", children: loadingMessage }) : visibleOptions.length === 0 ? /* @__PURE__ */ jsx27("p", { className: "py-2 text-center text-sm text-muted-foreground", children: emptyMessage }) : columns ? renderGridOptions({
|
|
1739
|
+
columns,
|
|
1740
|
+
options: visibleOptions,
|
|
1741
|
+
activeIndex,
|
|
1742
|
+
multiple: true,
|
|
1743
|
+
isSelected: (option) => value.includes(option.value),
|
|
1744
|
+
onSelect: toggleOption,
|
|
1745
|
+
onHover: setActiveIndex,
|
|
1746
|
+
testId
|
|
1747
|
+
}) : renderChunks.map((chunk) => {
|
|
1353
1748
|
if (chunk.kind === "single") return renderOption(chunk.row);
|
|
1354
1749
|
const headerState = groupCheckState(options, value, chunk.group);
|
|
1355
1750
|
return (
|
|
@@ -1359,14 +1754,14 @@ function TagCombobox({
|
|
|
1359
1754
|
// past its first row. Spacing above a group lives on this wrapper's
|
|
1360
1755
|
// `pt-1`/`first:pt-0`, not on the header itself -- see `Combobox`'s own
|
|
1361
1756
|
// identical structure for why both of these matter.
|
|
1362
|
-
/* @__PURE__ */
|
|
1363
|
-
/* @__PURE__ */
|
|
1757
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-0.5 pt-1 first:pt-0", children: [
|
|
1758
|
+
/* @__PURE__ */ jsxs13(
|
|
1364
1759
|
"div",
|
|
1365
1760
|
{
|
|
1366
1761
|
"data-group-header": true,
|
|
1367
1762
|
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",
|
|
1368
1763
|
children: [
|
|
1369
|
-
/* @__PURE__ */
|
|
1764
|
+
/* @__PURE__ */ jsx27(
|
|
1370
1765
|
"input",
|
|
1371
1766
|
{
|
|
1372
1767
|
type: "checkbox",
|
|
@@ -1380,7 +1775,7 @@ function TagCombobox({
|
|
|
1380
1775
|
"aria-label": `Select all of ${groupLabel(chunk.group)}`
|
|
1381
1776
|
}
|
|
1382
1777
|
),
|
|
1383
|
-
/* @__PURE__ */
|
|
1778
|
+
/* @__PURE__ */ jsx27("span", { className: "truncate", children: groupLabel(chunk.group) })
|
|
1384
1779
|
]
|
|
1385
1780
|
}
|
|
1386
1781
|
),
|
|
@@ -1395,37 +1790,37 @@ function TagCombobox({
|
|
|
1395
1790
|
|
|
1396
1791
|
// src/patterns/alert-box/AlertBox.tsx
|
|
1397
1792
|
import { AlertCircle, AlertTriangle, CheckCircle2, Info } from "lucide-react";
|
|
1398
|
-
import { jsx as
|
|
1793
|
+
import { jsx as jsx28, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1399
1794
|
var variantStyles = {
|
|
1400
|
-
error: "border-destructive bg-destructive/10 text-
|
|
1401
|
-
warning: "border-amber-500 bg-amber-500/10 text-amber-
|
|
1795
|
+
error: "border-destructive bg-destructive/10 text-red-800 dark:text-red-300",
|
|
1796
|
+
warning: "border-amber-500 bg-amber-500/10 text-amber-900 dark:text-amber-200",
|
|
1402
1797
|
info: "border-sky-500 bg-sky-500/10 text-sky-700 dark:text-sky-300",
|
|
1403
1798
|
success: "border-emerald-500 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"
|
|
1404
1799
|
};
|
|
1405
1800
|
var defaultIcons = {
|
|
1406
|
-
error: /* @__PURE__ */
|
|
1407
|
-
warning: /* @__PURE__ */
|
|
1408
|
-
info: /* @__PURE__ */
|
|
1409
|
-
success: /* @__PURE__ */
|
|
1801
|
+
error: /* @__PURE__ */ jsx28(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
1802
|
+
warning: /* @__PURE__ */ jsx28(AlertTriangle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
1803
|
+
info: /* @__PURE__ */ jsx28(Info, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
1804
|
+
success: /* @__PURE__ */ jsx28(CheckCircle2, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" })
|
|
1410
1805
|
};
|
|
1411
1806
|
var AlertBox = ({ variant, title, children, className, icon }) => {
|
|
1412
1807
|
const resolvedIcon = icon === void 0 ? defaultIcons[variant] : icon;
|
|
1413
|
-
return /* @__PURE__ */
|
|
1414
|
-
resolvedIcon != null && /* @__PURE__ */
|
|
1415
|
-
/* @__PURE__ */
|
|
1416
|
-
title && /* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
1808
|
+
return /* @__PURE__ */ jsx28("div", { className: cn("rounded-md border p-3 text-sm", variantStyles[variant], className), children: /* @__PURE__ */ jsxs14("div", { className: "flex flex-row items-start gap-2", children: [
|
|
1809
|
+
resolvedIcon != null && /* @__PURE__ */ jsx28("span", { className: "mt-0.5 shrink-0", children: resolvedIcon }),
|
|
1810
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-1", children: [
|
|
1811
|
+
title && /* @__PURE__ */ jsx28("p", { className: "font-bold", children: title }),
|
|
1812
|
+
/* @__PURE__ */ jsx28("div", { children })
|
|
1418
1813
|
] })
|
|
1419
1814
|
] }) });
|
|
1420
1815
|
};
|
|
1421
1816
|
|
|
1422
1817
|
// src/patterns/status-badge/StatusBadge.tsx
|
|
1423
|
-
import { jsx as
|
|
1818
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
1424
1819
|
var TONE_CLASSES = {
|
|
1425
1820
|
success: "border-transparent bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300",
|
|
1426
|
-
warning: "border-transparent bg-amber-500/15 text-amber-
|
|
1821
|
+
warning: "border-transparent bg-amber-500/15 text-amber-800 dark:bg-amber-500/20 dark:text-amber-300",
|
|
1427
1822
|
info: "border-transparent bg-sky-500/15 text-sky-700 dark:bg-sky-500/20 dark:text-sky-300",
|
|
1428
|
-
error: "border-transparent bg-destructive/15 text-
|
|
1823
|
+
error: "border-transparent bg-destructive/15 text-red-800 dark:text-red-300",
|
|
1429
1824
|
neutral: "border-transparent bg-muted text-muted-foreground"
|
|
1430
1825
|
};
|
|
1431
1826
|
var DEFAULT_TONE_MAP = {
|
|
@@ -1467,11 +1862,11 @@ function resolveTone(status, tone, toneMap) {
|
|
|
1467
1862
|
var StatusBadge = ({ status, label, tone, toneMap, className, ...props }) => {
|
|
1468
1863
|
const resolvedTone = resolveTone(status, tone, toneMap);
|
|
1469
1864
|
const displayLabel = label ?? deriveLabel(status);
|
|
1470
|
-
return /* @__PURE__ */
|
|
1865
|
+
return /* @__PURE__ */ jsx29(Badge, { variant: "outline", className: cn(TONE_CLASSES[resolvedTone], className), ...props, children: displayLabel });
|
|
1471
1866
|
};
|
|
1472
1867
|
|
|
1473
1868
|
// src/patterns/button-group/ButtonGroup.tsx
|
|
1474
|
-
import { jsx as
|
|
1869
|
+
import { jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1475
1870
|
function ButtonGroup({
|
|
1476
1871
|
options,
|
|
1477
1872
|
value,
|
|
@@ -1481,10 +1876,10 @@ function ButtonGroup({
|
|
|
1481
1876
|
className,
|
|
1482
1877
|
...props
|
|
1483
1878
|
}) {
|
|
1484
|
-
return /* @__PURE__ */
|
|
1879
|
+
return /* @__PURE__ */ jsx30("div", { role: "group", "aria-label": props["aria-label"], className: cn("inline-flex", className), "data-testid": props["data-testid"], children: options.map((option, index) => {
|
|
1485
1880
|
const selected = option.value === value;
|
|
1486
1881
|
const Icon2 = option.icon;
|
|
1487
|
-
return /* @__PURE__ */
|
|
1882
|
+
return /* @__PURE__ */ jsxs15(
|
|
1488
1883
|
Button,
|
|
1489
1884
|
{
|
|
1490
1885
|
type: "button",
|
|
@@ -1502,7 +1897,7 @@ function ButtonGroup({
|
|
|
1502
1897
|
index > 0 && "-ml-px"
|
|
1503
1898
|
),
|
|
1504
1899
|
children: [
|
|
1505
|
-
Icon2 && /* @__PURE__ */
|
|
1900
|
+
Icon2 && /* @__PURE__ */ jsx30(Icon2, { className: "h-4 w-4" }),
|
|
1506
1901
|
option.label
|
|
1507
1902
|
]
|
|
1508
1903
|
},
|
|
@@ -1514,7 +1909,7 @@ function ButtonGroup({
|
|
|
1514
1909
|
// src/patterns/loading-spinner/LoadingSpinner.tsx
|
|
1515
1910
|
import { cva as cva4 } from "class-variance-authority";
|
|
1516
1911
|
import { Loader2 } from "lucide-react";
|
|
1517
|
-
import { jsx as
|
|
1912
|
+
import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1518
1913
|
var loadingSpinnerIconVariants = cva4("animate-spin text-muted-foreground", {
|
|
1519
1914
|
variants: {
|
|
1520
1915
|
size: {
|
|
@@ -1532,27 +1927,27 @@ var LoadingSpinner = ({
|
|
|
1532
1927
|
label,
|
|
1533
1928
|
className,
|
|
1534
1929
|
...props
|
|
1535
|
-
}) => /* @__PURE__ */
|
|
1536
|
-
/* @__PURE__ */
|
|
1537
|
-
label ? /* @__PURE__ */
|
|
1930
|
+
}) => /* @__PURE__ */ jsxs16("span", { role: "status", className: cn("inline-flex items-center gap-2", className), ...props, children: [
|
|
1931
|
+
/* @__PURE__ */ jsx31(Loader2, { className: cn(loadingSpinnerIconVariants({ size })), "aria-hidden": "true" }),
|
|
1932
|
+
label ? /* @__PURE__ */ jsx31("span", { children: label }) : null
|
|
1538
1933
|
] });
|
|
1539
1934
|
var LoadingOverlay = ({
|
|
1540
1935
|
size = "lg",
|
|
1541
1936
|
label,
|
|
1542
1937
|
className,
|
|
1543
1938
|
...props
|
|
1544
|
-
}) => /* @__PURE__ */
|
|
1939
|
+
}) => /* @__PURE__ */ jsx31(
|
|
1545
1940
|
"div",
|
|
1546
1941
|
{
|
|
1547
1942
|
className: cn("flex h-full min-h-32 w-full items-center justify-center", className),
|
|
1548
1943
|
...props,
|
|
1549
|
-
children: /* @__PURE__ */
|
|
1944
|
+
children: /* @__PURE__ */ jsx31(LoadingSpinner, { size, label })
|
|
1550
1945
|
}
|
|
1551
1946
|
);
|
|
1552
1947
|
|
|
1553
1948
|
// src/patterns/search-bar/SearchBar.tsx
|
|
1554
1949
|
import { Search, X as X5 } from "lucide-react";
|
|
1555
|
-
import { jsx as
|
|
1950
|
+
import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1556
1951
|
function SearchBar({
|
|
1557
1952
|
value,
|
|
1558
1953
|
onChange,
|
|
@@ -1564,9 +1959,9 @@ function SearchBar({
|
|
|
1564
1959
|
...props
|
|
1565
1960
|
}) {
|
|
1566
1961
|
const showClear = onClear !== false && value.length > 0;
|
|
1567
|
-
return /* @__PURE__ */
|
|
1568
|
-
/* @__PURE__ */
|
|
1569
|
-
/* @__PURE__ */
|
|
1962
|
+
return /* @__PURE__ */ jsxs17("div", { className: cn("relative", className), children: [
|
|
1963
|
+
/* @__PURE__ */ jsx32("span", { className: "pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-muted-foreground", children: isLoading ? /* @__PURE__ */ jsx32(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx32(Search, { className: "h-4 w-4", "aria-hidden": "true" }) }),
|
|
1964
|
+
/* @__PURE__ */ jsx32(
|
|
1570
1965
|
"input",
|
|
1571
1966
|
{
|
|
1572
1967
|
ref: inputRef,
|
|
@@ -1585,19 +1980,370 @@ function SearchBar({
|
|
|
1585
1980
|
...props
|
|
1586
1981
|
}
|
|
1587
1982
|
),
|
|
1588
|
-
showClear && /* @__PURE__ */
|
|
1983
|
+
showClear && /* @__PURE__ */ jsx32(
|
|
1589
1984
|
"button",
|
|
1590
1985
|
{
|
|
1591
1986
|
type: "button",
|
|
1592
1987
|
onClick: () => onClear ? onClear() : onChange(""),
|
|
1593
1988
|
"aria-label": clearLabel,
|
|
1594
1989
|
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",
|
|
1595
|
-
children: /* @__PURE__ */
|
|
1990
|
+
children: /* @__PURE__ */ jsx32(X5, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
1596
1991
|
}
|
|
1597
1992
|
)
|
|
1598
1993
|
] });
|
|
1599
1994
|
}
|
|
1600
1995
|
|
|
1996
|
+
// src/patterns/kpi-card/KpiCard.tsx
|
|
1997
|
+
import { memo } from "react";
|
|
1998
|
+
import { Fragment as Fragment2, jsx as jsx33, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1999
|
+
var DONUT_COLORS = [
|
|
2000
|
+
"var(--chart-1, #3b82f6)",
|
|
2001
|
+
"var(--chart-2, #22c55e)",
|
|
2002
|
+
"var(--chart-3, #f59e0b)",
|
|
2003
|
+
"var(--chart-4, #8b5cf6)",
|
|
2004
|
+
"var(--chart-5, #ef4444)",
|
|
2005
|
+
"var(--chart-6, #06b6d4)"
|
|
2006
|
+
];
|
|
2007
|
+
function DonutChart({
|
|
2008
|
+
data,
|
|
2009
|
+
size = 96,
|
|
2010
|
+
thickness = 14,
|
|
2011
|
+
centerValue,
|
|
2012
|
+
centerLabel
|
|
2013
|
+
}) {
|
|
2014
|
+
const total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2015
|
+
if (total <= 0) return null;
|
|
2016
|
+
const radius = (size - thickness) / 2;
|
|
2017
|
+
const circumference = 2 * Math.PI * radius;
|
|
2018
|
+
let offset = 0;
|
|
2019
|
+
return /* @__PURE__ */ jsxs18("div", { className: "relative inline-flex shrink-0 items-center justify-center", style: { width: size, height: size }, children: [
|
|
2020
|
+
/* @__PURE__ */ jsxs18("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "-rotate-90", children: [
|
|
2021
|
+
/* @__PURE__ */ jsx33("circle", { cx: size / 2, cy: size / 2, r: radius, fill: "none", stroke: "currentColor", className: "text-muted/40", strokeWidth: thickness }),
|
|
2022
|
+
data.map((segment, i) => {
|
|
2023
|
+
const fraction = segment.value / total;
|
|
2024
|
+
const dash = fraction * circumference;
|
|
2025
|
+
const el = /* @__PURE__ */ jsx33(
|
|
2026
|
+
"circle",
|
|
2027
|
+
{
|
|
2028
|
+
cx: size / 2,
|
|
2029
|
+
cy: size / 2,
|
|
2030
|
+
r: radius,
|
|
2031
|
+
fill: "none",
|
|
2032
|
+
stroke: segment.color ?? DONUT_COLORS[i % DONUT_COLORS.length],
|
|
2033
|
+
strokeWidth: thickness,
|
|
2034
|
+
strokeDasharray: `${dash} ${circumference - dash}`,
|
|
2035
|
+
strokeDashoffset: -offset,
|
|
2036
|
+
strokeLinecap: data.length > 1 ? "butt" : "round"
|
|
2037
|
+
},
|
|
2038
|
+
segment.label
|
|
2039
|
+
);
|
|
2040
|
+
offset += dash;
|
|
2041
|
+
return el;
|
|
2042
|
+
})
|
|
2043
|
+
] }),
|
|
2044
|
+
(centerValue || centerLabel) && /* @__PURE__ */ jsxs18("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: [
|
|
2045
|
+
centerValue && /* @__PURE__ */ jsx33("span", { className: "text-base font-black tracking-tight text-foreground tabular-nums", children: centerValue }),
|
|
2046
|
+
centerLabel && /* @__PURE__ */ jsx33("span", { className: "text-[9px] font-medium text-muted-foreground uppercase", children: centerLabel })
|
|
2047
|
+
] })
|
|
2048
|
+
] });
|
|
2049
|
+
}
|
|
2050
|
+
function Sparkline({ data, color }) {
|
|
2051
|
+
if (data.length < 2) return null;
|
|
2052
|
+
const min = Math.min(...data);
|
|
2053
|
+
const max = Math.max(...data);
|
|
2054
|
+
const range = max - min || 1;
|
|
2055
|
+
const W = 72;
|
|
2056
|
+
const H = 28;
|
|
2057
|
+
const pts = data.map((v, i) => `${i / (data.length - 1) * W},${H - (v - min) / range * (H - 2) - 1}`).join(" ");
|
|
2058
|
+
return /* @__PURE__ */ jsx33("svg", { width: W, height: H, viewBox: `0 0 ${W} ${H}`, className: "overflow-visible opacity-70", children: /* @__PURE__ */ jsx33("polyline", { points: pts, fill: "none", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
2059
|
+
}
|
|
2060
|
+
function AreaSparkline({ data, color }) {
|
|
2061
|
+
if (data.length < 2) return null;
|
|
2062
|
+
const min = Math.min(...data);
|
|
2063
|
+
const max = Math.max(...data);
|
|
2064
|
+
const range = max - min || 1;
|
|
2065
|
+
const W = 200;
|
|
2066
|
+
const H = 28;
|
|
2067
|
+
const P = 2;
|
|
2068
|
+
const xs = data.map((_, i) => i / (data.length - 1) * W);
|
|
2069
|
+
const ys = data.map((v) => H - (v - min) / range * (H - 2 * P) - P);
|
|
2070
|
+
const linePts = xs.map((x, i) => `${x},${ys[i]}`).join(" ");
|
|
2071
|
+
const areaPath = `M${xs[0]},${H} ` + xs.map((x, i) => `L${x},${ys[i]}`).join(" ") + ` L${xs[xs.length - 1]},${H} Z`;
|
|
2072
|
+
return /* @__PURE__ */ jsxs18("svg", { width: W, height: H, viewBox: `0 0 ${W} ${H}`, className: "overflow-visible", preserveAspectRatio: "none", children: [
|
|
2073
|
+
/* @__PURE__ */ jsx33("defs", { children: /* @__PURE__ */ jsxs18("linearGradient", { id: "kpi-card-spark-fill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
2074
|
+
/* @__PURE__ */ jsx33("stop", { offset: "0%", stopColor: color, stopOpacity: "0.22" }),
|
|
2075
|
+
/* @__PURE__ */ jsx33("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
|
|
2076
|
+
] }) }),
|
|
2077
|
+
/* @__PURE__ */ jsx33("path", { d: areaPath, fill: "url(#kpi-card-spark-fill)" }),
|
|
2078
|
+
/* @__PURE__ */ jsx33("polyline", { points: linePts, fill: "none", stroke: color, strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
2079
|
+
/* @__PURE__ */ jsx33("circle", { cx: xs[xs.length - 1], cy: ys[ys.length - 1], r: "2.5", fill: color })
|
|
2080
|
+
] });
|
|
2081
|
+
}
|
|
2082
|
+
var rowBetween = "flex items-center justify-between";
|
|
2083
|
+
var KpiCard = memo(function KpiCard2({
|
|
2084
|
+
label,
|
|
2085
|
+
value,
|
|
2086
|
+
icon: Icon2,
|
|
2087
|
+
badge,
|
|
2088
|
+
badgeLabel,
|
|
2089
|
+
variant = "default",
|
|
2090
|
+
loading = false,
|
|
2091
|
+
sub,
|
|
2092
|
+
subTone = "default",
|
|
2093
|
+
href,
|
|
2094
|
+
sparkline,
|
|
2095
|
+
sparklineColor = "var(--color-primary, #3b82f6)",
|
|
2096
|
+
progress,
|
|
2097
|
+
progressLabel,
|
|
2098
|
+
segments,
|
|
2099
|
+
centerValue,
|
|
2100
|
+
testId
|
|
2101
|
+
}) {
|
|
2102
|
+
if (variant === "hero") {
|
|
2103
|
+
return /* @__PURE__ */ jsxs18(
|
|
2104
|
+
"div",
|
|
2105
|
+
{
|
|
2106
|
+
"data-testid": testId,
|
|
2107
|
+
className: "relative col-span-2 flex min-w-0 flex-1 flex-col gap-1.5 overflow-hidden rounded-2xl p-4 md:p-3 lg:col-span-1 lg:max-w-[340px] lg:min-w-[260px]",
|
|
2108
|
+
style: {
|
|
2109
|
+
background: "var(--color-primary)",
|
|
2110
|
+
boxShadow: "0 2px 10px color-mix(in oklch, var(--color-primary) 18%, transparent)"
|
|
2111
|
+
},
|
|
2112
|
+
children: [
|
|
2113
|
+
/* @__PURE__ */ jsxs18("div", { className: "relative flex items-start justify-between", children: [
|
|
2114
|
+
/* @__PURE__ */ jsx33("p", { className: "text-[11px] font-bold tracking-[0.14em] text-white/70 uppercase", children: label }),
|
|
2115
|
+
Icon2 && /* @__PURE__ */ jsx33("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-white/15 ring-1 ring-white/20", children: /* @__PURE__ */ jsx33(Icon2, { className: "h-3 w-3 text-white/90", "aria-hidden": "true" }) })
|
|
2116
|
+
] }),
|
|
2117
|
+
/* @__PURE__ */ jsx33("div", { className: "relative", children: loading ? /* @__PURE__ */ jsx33("div", { className: "h-6 w-28 animate-pulse rounded-lg bg-white/15" }) : /* @__PURE__ */ jsx33("p", { className: "text-2xl leading-none font-black tracking-tight text-white tabular-nums md:text-[22px]", children: value ?? "\u2014" }) }),
|
|
2118
|
+
(badge || badgeLabel) && !loading && /* @__PURE__ */ jsxs18("div", { className: "relative flex items-center gap-2", children: [
|
|
2119
|
+
badge && /* @__PURE__ */ jsx33(
|
|
2120
|
+
"span",
|
|
2121
|
+
{
|
|
2122
|
+
className: cn(
|
|
2123
|
+
"inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-[11px] font-bold tabular-nums ring-1",
|
|
2124
|
+
badge.positive ? "bg-white/15 text-white ring-white/25" : "bg-black/25 text-white ring-white/15"
|
|
2125
|
+
),
|
|
2126
|
+
children: badge.text
|
|
2127
|
+
}
|
|
2128
|
+
),
|
|
2129
|
+
badgeLabel && /* @__PURE__ */ jsx33("span", { className: "text-[11px] text-white/70", children: badgeLabel })
|
|
2130
|
+
] }),
|
|
2131
|
+
sub && !loading && /* @__PURE__ */ jsx33("p", { className: "relative -mt-1 text-[11px] font-medium text-white/75 tabular-nums", children: sub }),
|
|
2132
|
+
progress != null && !loading && /* @__PURE__ */ jsxs18("div", { className: "relative flex items-center gap-1.5", children: [
|
|
2133
|
+
/* @__PURE__ */ jsx33("div", { className: "h-1 flex-1 overflow-hidden rounded-full bg-white/20", children: /* @__PURE__ */ jsx33("div", { className: "h-1 rounded-full bg-white/85", style: { width: `${Math.min(100, Math.max(0, progress))}%` } }) }),
|
|
2134
|
+
progressLabel && /* @__PURE__ */ jsx33("span", { className: "shrink-0 text-[11px] font-semibold text-white/80 tabular-nums", children: progressLabel })
|
|
2135
|
+
] }),
|
|
2136
|
+
sparkline && sparkline.length > 1 && !loading && /* @__PURE__ */ jsx33("div", { className: "relative -mx-4 mt-auto -mb-4 md:-mx-3 md:-mb-3", children: /* @__PURE__ */ jsx33(AreaSparkline, { data: sparkline, color: "#ffffff" }) })
|
|
2137
|
+
]
|
|
2138
|
+
}
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
if (variant === "mini") {
|
|
2142
|
+
const iconSlot = Icon2 ? href ? /* @__PURE__ */ jsx33(
|
|
2143
|
+
"a",
|
|
2144
|
+
{
|
|
2145
|
+
href,
|
|
2146
|
+
className: "tap-target flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary transition-[background-color,scale] hover:bg-primary/20 active:scale-90 motion-reduce:transition-none md:h-6 md:w-6",
|
|
2147
|
+
children: /* @__PURE__ */ jsx33(Icon2, { className: "h-4 w-4 md:h-3.5 md:w-3.5", "aria-hidden": "true" })
|
|
2148
|
+
}
|
|
2149
|
+
) : /* @__PURE__ */ jsx33("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary md:h-6 md:w-6", children: /* @__PURE__ */ jsx33(Icon2, { className: "h-4 w-4 md:h-3.5 md:w-3.5", "aria-hidden": "true" }) }) : null;
|
|
2150
|
+
return /* @__PURE__ */ jsxs18(
|
|
2151
|
+
"div",
|
|
2152
|
+
{
|
|
2153
|
+
"data-testid": testId,
|
|
2154
|
+
className: cn(
|
|
2155
|
+
"flex h-full min-w-0 flex-1 flex-col gap-1.5 overflow-hidden rounded-2xl border border-border bg-card p-3 transition-shadow md:p-2.5",
|
|
2156
|
+
href && "hover:border-primary/20 hover:shadow-md"
|
|
2157
|
+
),
|
|
2158
|
+
children: [
|
|
2159
|
+
/* @__PURE__ */ jsxs18("div", { className: rowBetween, children: [
|
|
2160
|
+
/* @__PURE__ */ jsx33("p", { className: "text-[11px] font-bold tracking-[0.12em] text-muted-foreground uppercase", children: label }),
|
|
2161
|
+
iconSlot
|
|
2162
|
+
] }),
|
|
2163
|
+
/* @__PURE__ */ jsx33("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: loading ? /* @__PURE__ */ jsx33(Skeleton, { className: "h-6 w-16" }) : /* @__PURE__ */ jsxs18(Fragment2, { children: [
|
|
2164
|
+
/* @__PURE__ */ jsx33("p", { className: "min-w-0 animate-in truncate text-[20px] leading-none font-black tracking-tight text-foreground duration-200 fade-in-0 tabular-nums motion-reduce:animate-none md:text-[16px]", children: value ?? "\u2014" }),
|
|
2165
|
+
badge && /* @__PURE__ */ jsx33(
|
|
2166
|
+
"span",
|
|
2167
|
+
{
|
|
2168
|
+
className: cn(
|
|
2169
|
+
"shrink-0 animate-in rounded-md px-1.5 py-0.5 text-[11px] font-bold duration-200 fade-in-0 tabular-nums motion-reduce:animate-none",
|
|
2170
|
+
badge.positive ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400" : "bg-muted text-muted-foreground"
|
|
2171
|
+
),
|
|
2172
|
+
children: badge.text
|
|
2173
|
+
}
|
|
2174
|
+
)
|
|
2175
|
+
] }) }),
|
|
2176
|
+
(sub || sparkline) && !loading && /* @__PURE__ */ jsxs18("div", { className: "flex items-end justify-between gap-2", children: [
|
|
2177
|
+
sub && /* @__PURE__ */ jsx33(
|
|
2178
|
+
"p",
|
|
2179
|
+
{
|
|
2180
|
+
className: cn(
|
|
2181
|
+
"text-[11px]",
|
|
2182
|
+
subTone === "danger" ? "font-semibold text-red-600 dark:text-red-400" : subTone === "warn" ? "font-semibold text-amber-700 dark:text-amber-300" : "text-muted-foreground"
|
|
2183
|
+
),
|
|
2184
|
+
children: sub
|
|
2185
|
+
}
|
|
2186
|
+
),
|
|
2187
|
+
sparkline && /* @__PURE__ */ jsx33(Sparkline, { data: sparkline, color: sparklineColor })
|
|
2188
|
+
] })
|
|
2189
|
+
]
|
|
2190
|
+
}
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
if (variant === "donut") {
|
|
2194
|
+
const total = segments?.reduce((sum, s) => sum + s.value, 0) ?? 0;
|
|
2195
|
+
return /* @__PURE__ */ jsxs18("div", { "data-testid": testId, className: "rounded-xl border border-border bg-card p-5", children: [
|
|
2196
|
+
/* @__PURE__ */ jsxs18("div", { className: rowBetween, children: [
|
|
2197
|
+
/* @__PURE__ */ jsx33("p", { className: "text-[11px] font-bold tracking-[0.12em] text-muted-foreground uppercase", children: label }),
|
|
2198
|
+
Icon2 && /* @__PURE__ */ jsx33("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-primary/8 text-primary", children: /* @__PURE__ */ jsx33(Icon2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) })
|
|
2199
|
+
] }),
|
|
2200
|
+
loading ? /* @__PURE__ */ jsxs18("div", { className: "mt-3 flex items-center gap-4", children: [
|
|
2201
|
+
/* @__PURE__ */ jsx33(Skeleton, { className: "h-24 w-24 shrink-0 rounded-full" }),
|
|
2202
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex flex-1 flex-col gap-2", children: [
|
|
2203
|
+
/* @__PURE__ */ jsx33(Skeleton, { className: "h-3 w-full" }),
|
|
2204
|
+
/* @__PURE__ */ jsx33(Skeleton, { className: "h-3 w-3/4" }),
|
|
2205
|
+
/* @__PURE__ */ jsx33(Skeleton, { className: "h-3 w-1/2" })
|
|
2206
|
+
] })
|
|
2207
|
+
] }) : segments && segments.length > 0 ? /* @__PURE__ */ jsxs18("div", { className: "mt-3 flex items-center gap-4", children: [
|
|
2208
|
+
/* @__PURE__ */ jsx33(DonutChart, { data: segments, centerValue: centerValue ?? String(total) }),
|
|
2209
|
+
/* @__PURE__ */ jsx33("ul", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: segments.map((segment, i) => /* @__PURE__ */ jsxs18("li", { className: "flex items-center gap-2 text-[12px]", children: [
|
|
2210
|
+
/* @__PURE__ */ jsx33(
|
|
2211
|
+
"span",
|
|
2212
|
+
{
|
|
2213
|
+
className: "h-2 w-2 shrink-0 rounded-full",
|
|
2214
|
+
style: { backgroundColor: segment.color ?? DONUT_COLORS[i % DONUT_COLORS.length] },
|
|
2215
|
+
"aria-hidden": "true"
|
|
2216
|
+
}
|
|
2217
|
+
),
|
|
2218
|
+
/* @__PURE__ */ jsx33("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: segment.label }),
|
|
2219
|
+
/* @__PURE__ */ jsx33("span", { className: "shrink-0 font-semibold text-foreground tabular-nums", children: total > 0 ? `${Math.round(segment.value / total * 100)}%` : segment.value })
|
|
2220
|
+
] }, segment.label)) })
|
|
2221
|
+
] }) : /* @__PURE__ */ jsx33("span", { className: "mt-3 block text-2xl font-black text-muted-foreground/40", children: "\u2014" }),
|
|
2222
|
+
sub && !loading && /* @__PURE__ */ jsx33("p", { className: "mt-2.5 text-[11px] text-muted-foreground tabular-nums", children: sub })
|
|
2223
|
+
] });
|
|
2224
|
+
}
|
|
2225
|
+
const strValue = value != null ? String(value) : null;
|
|
2226
|
+
const parts = strValue ? strValue.split(/\s(.+)/) : null;
|
|
2227
|
+
const plain = !parts || parts.length === 1;
|
|
2228
|
+
const isSuffix = !plain && /^\d/.test(parts[0]) && parts[1].length <= 3;
|
|
2229
|
+
const prefix = isSuffix ? null : parts?.[0];
|
|
2230
|
+
const number = isSuffix ? parts[0] : parts?.[1] ?? parts?.[0];
|
|
2231
|
+
const suffix = isSuffix ? parts[1] : null;
|
|
2232
|
+
return /* @__PURE__ */ jsxs18("div", { "data-testid": testId, className: "rounded-xl border border-border bg-card p-5", children: [
|
|
2233
|
+
/* @__PURE__ */ jsxs18("div", { className: rowBetween, children: [
|
|
2234
|
+
/* @__PURE__ */ jsx33("p", { className: "text-[11px] font-bold tracking-[0.12em] text-muted-foreground uppercase", children: label }),
|
|
2235
|
+
Icon2 && /* @__PURE__ */ jsx33("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-primary/8 text-primary", children: /* @__PURE__ */ jsx33(Icon2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) })
|
|
2236
|
+
] }),
|
|
2237
|
+
/* @__PURE__ */ jsxs18("div", { className: "mt-3", children: [
|
|
2238
|
+
loading ? /* @__PURE__ */ jsx33(Skeleton, { className: "h-7 w-20" }) : value == null ? /* @__PURE__ */ jsx33("span", { className: "text-2xl font-black text-muted-foreground/40", children: "\u2014" }) : plain ? /* @__PURE__ */ jsx33("span", { className: "animate-in text-2xl font-black tracking-tight text-foreground duration-200 fade-in-0 tabular-nums motion-reduce:animate-none", children: strValue }) : /* @__PURE__ */ jsxs18("span", { className: "animate-in font-black tracking-tight text-foreground duration-200 fade-in-0 tabular-nums motion-reduce:animate-none", children: [
|
|
2239
|
+
prefix && /* @__PURE__ */ jsxs18("span", { className: "text-sm text-muted-foreground", children: [
|
|
2240
|
+
prefix,
|
|
2241
|
+
" "
|
|
2242
|
+
] }),
|
|
2243
|
+
/* @__PURE__ */ jsx33("span", { className: "text-2xl", children: number }),
|
|
2244
|
+
suffix && /* @__PURE__ */ jsxs18("span", { className: "text-sm text-muted-foreground", children: [
|
|
2245
|
+
" ",
|
|
2246
|
+
suffix
|
|
2247
|
+
] })
|
|
2248
|
+
] }),
|
|
2249
|
+
sub && !loading && /* @__PURE__ */ jsx33("p", { className: "mt-0.5 text-[11px] text-muted-foreground tabular-nums", children: sub })
|
|
2250
|
+
] }),
|
|
2251
|
+
(badge || badgeLabel || sparkline) && !loading && /* @__PURE__ */ jsxs18("div", { className: "mt-2.5 flex items-end justify-between gap-2", children: [
|
|
2252
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2253
|
+
badge && /* @__PURE__ */ jsx33(
|
|
2254
|
+
"span",
|
|
2255
|
+
{
|
|
2256
|
+
className: cn(
|
|
2257
|
+
"rounded-md px-1.5 py-0.5 text-[11px] font-semibold tabular-nums",
|
|
2258
|
+
badge.positive ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400" : "bg-muted text-muted-foreground"
|
|
2259
|
+
),
|
|
2260
|
+
children: badge.text
|
|
2261
|
+
}
|
|
2262
|
+
),
|
|
2263
|
+
badgeLabel && /* @__PURE__ */ jsx33("span", { className: "text-[11px] text-muted-foreground", children: badgeLabel })
|
|
2264
|
+
] }),
|
|
2265
|
+
sparkline && /* @__PURE__ */ jsx33(Sparkline, { data: sparkline, color: sparklineColor })
|
|
2266
|
+
] })
|
|
2267
|
+
] });
|
|
2268
|
+
});
|
|
2269
|
+
|
|
2270
|
+
// src/patterns/search-panel/SearchPanel.tsx
|
|
2271
|
+
import { Search as Search2 } from "lucide-react";
|
|
2272
|
+
import { jsx as jsx34, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2273
|
+
function SearchPanel({
|
|
2274
|
+
value,
|
|
2275
|
+
onChange,
|
|
2276
|
+
isLoading,
|
|
2277
|
+
trailingSlot,
|
|
2278
|
+
shortcutHint,
|
|
2279
|
+
modes,
|
|
2280
|
+
activeMode,
|
|
2281
|
+
onModeChange,
|
|
2282
|
+
expanded = false,
|
|
2283
|
+
inputRef,
|
|
2284
|
+
className,
|
|
2285
|
+
...props
|
|
2286
|
+
}) {
|
|
2287
|
+
return /* @__PURE__ */ jsxs19(
|
|
2288
|
+
"div",
|
|
2289
|
+
{
|
|
2290
|
+
className: cn(
|
|
2291
|
+
"flex flex-col border border-border bg-card shadow-sm",
|
|
2292
|
+
expanded ? "rounded-t-2xl border-b-transparent" : "rounded-2xl",
|
|
2293
|
+
className
|
|
2294
|
+
),
|
|
2295
|
+
children: [
|
|
2296
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex h-14 items-center gap-3 px-4", children: [
|
|
2297
|
+
isLoading ? /* @__PURE__ */ jsx34(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx34(Search2, { className: "h-5 w-5 shrink-0 text-muted-foreground", "aria-hidden": "true" }),
|
|
2298
|
+
/* @__PURE__ */ jsx34(
|
|
2299
|
+
"input",
|
|
2300
|
+
{
|
|
2301
|
+
ref: inputRef,
|
|
2302
|
+
type: "text",
|
|
2303
|
+
value,
|
|
2304
|
+
onChange: (event) => onChange(event.target.value),
|
|
2305
|
+
className: "min-w-0 flex-1 bg-transparent text-[15px] text-foreground outline-none placeholder:text-muted-foreground",
|
|
2306
|
+
...props
|
|
2307
|
+
}
|
|
2308
|
+
),
|
|
2309
|
+
trailingSlot,
|
|
2310
|
+
shortcutHint && /* @__PURE__ */ jsx34(
|
|
2311
|
+
"kbd",
|
|
2312
|
+
{
|
|
2313
|
+
"aria-hidden": "true",
|
|
2314
|
+
className: "hidden shrink-0 items-center rounded border border-border bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground select-none sm:inline-flex",
|
|
2315
|
+
children: shortcutHint
|
|
2316
|
+
}
|
|
2317
|
+
)
|
|
2318
|
+
] }),
|
|
2319
|
+
modes && modes.length > 0 && /* @__PURE__ */ jsx34("div", { className: "flex items-center gap-2 px-4 pb-3", children: modes.map(({ key, label, icon: Icon2 }) => /* @__PURE__ */ jsxs19(
|
|
2320
|
+
"button",
|
|
2321
|
+
{
|
|
2322
|
+
type: "button",
|
|
2323
|
+
onClick: () => onModeChange?.(key),
|
|
2324
|
+
className: cn(
|
|
2325
|
+
"flex items-center gap-1.5 rounded-full px-3 py-1.5 text-[13px] font-medium transition-colors",
|
|
2326
|
+
activeMode === key ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
|
|
2327
|
+
),
|
|
2328
|
+
children: [
|
|
2329
|
+
Icon2 && /* @__PURE__ */ jsx34(Icon2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2330
|
+
label
|
|
2331
|
+
]
|
|
2332
|
+
},
|
|
2333
|
+
key
|
|
2334
|
+
)) })
|
|
2335
|
+
]
|
|
2336
|
+
}
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// src/patterns/search-panel/SearchTrigger.tsx
|
|
2341
|
+
import { Search as Search3 } from "lucide-react";
|
|
2342
|
+
import { jsx as jsx35 } from "react/jsx-runtime";
|
|
2343
|
+
function SearchTrigger({ label = "Search", ...props }) {
|
|
2344
|
+
return /* @__PURE__ */ jsx35(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": label, ...props, children: /* @__PURE__ */ jsx35(Search3, { className: "h-[18px] w-[18px]", "aria-hidden": "true" }) });
|
|
2345
|
+
}
|
|
2346
|
+
|
|
1601
2347
|
// src/patterns/sidebar/context.ts
|
|
1602
2348
|
import { createContext, useContext } from "react";
|
|
1603
2349
|
var SidebarContext = createContext({ collapsed: false });
|
|
@@ -1607,7 +2353,7 @@ function useSidebarCollapsed() {
|
|
|
1607
2353
|
}
|
|
1608
2354
|
|
|
1609
2355
|
// src/patterns/sidebar/NavItem.tsx
|
|
1610
|
-
import { jsx as
|
|
2356
|
+
import { jsx as jsx36, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1611
2357
|
function NavItem({
|
|
1612
2358
|
as,
|
|
1613
2359
|
icon: Icon2,
|
|
@@ -1619,7 +2365,7 @@ function NavItem({
|
|
|
1619
2365
|
}) {
|
|
1620
2366
|
const collapsed = useSidebarCollapsed();
|
|
1621
2367
|
const Comp = as ?? "a";
|
|
1622
|
-
const row = /* @__PURE__ */
|
|
2368
|
+
const row = /* @__PURE__ */ jsxs20(
|
|
1623
2369
|
Comp,
|
|
1624
2370
|
{
|
|
1625
2371
|
className: cn(
|
|
@@ -1631,7 +2377,7 @@ function NavItem({
|
|
|
1631
2377
|
),
|
|
1632
2378
|
...rest,
|
|
1633
2379
|
children: [
|
|
1634
|
-
Icon2 && /* @__PURE__ */
|
|
2380
|
+
Icon2 && /* @__PURE__ */ jsx36(
|
|
1635
2381
|
Icon2,
|
|
1636
2382
|
{
|
|
1637
2383
|
className: cn("h-[18px] w-[18px] shrink-0", active ? "text-nav-primary" : "text-muted-foreground"),
|
|
@@ -1639,7 +2385,7 @@ function NavItem({
|
|
|
1639
2385
|
"aria-hidden": true
|
|
1640
2386
|
}
|
|
1641
2387
|
),
|
|
1642
|
-
/* @__PURE__ */
|
|
2388
|
+
/* @__PURE__ */ jsx36(
|
|
1643
2389
|
"span",
|
|
1644
2390
|
{
|
|
1645
2391
|
className: cn(
|
|
@@ -1654,9 +2400,9 @@ function NavItem({
|
|
|
1654
2400
|
}
|
|
1655
2401
|
);
|
|
1656
2402
|
if (!collapsed) return row;
|
|
1657
|
-
return /* @__PURE__ */
|
|
1658
|
-
/* @__PURE__ */
|
|
1659
|
-
/* @__PURE__ */
|
|
2403
|
+
return /* @__PURE__ */ jsxs20(Tooltip, { children: [
|
|
2404
|
+
/* @__PURE__ */ jsx36(TooltipTrigger, { asChild: true, children: row }),
|
|
2405
|
+
/* @__PURE__ */ jsxs20(TooltipContent, { side: "right", className: "flex items-center gap-1.5", children: [
|
|
1660
2406
|
label,
|
|
1661
2407
|
badge
|
|
1662
2408
|
] })
|
|
@@ -1665,8 +2411,8 @@ function NavItem({
|
|
|
1665
2411
|
|
|
1666
2412
|
// src/patterns/sidebar/NavGroup.tsx
|
|
1667
2413
|
import { ChevronDown as ChevronDown2 } from "lucide-react";
|
|
1668
|
-
import { useState as
|
|
1669
|
-
import { jsx as
|
|
2414
|
+
import { useState as useState8 } from "react";
|
|
2415
|
+
import { jsx as jsx37, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1670
2416
|
function NavGroup({
|
|
1671
2417
|
label,
|
|
1672
2418
|
defaultCollapsed = false,
|
|
@@ -1675,15 +2421,15 @@ function NavGroup({
|
|
|
1675
2421
|
children,
|
|
1676
2422
|
className
|
|
1677
2423
|
}) {
|
|
1678
|
-
const [internalCollapsed, setInternalCollapsed] =
|
|
2424
|
+
const [internalCollapsed, setInternalCollapsed] = useState8(defaultCollapsed);
|
|
1679
2425
|
const groupCollapsed = collapsed ?? internalCollapsed;
|
|
1680
2426
|
const railCollapsed = useSidebarCollapsed();
|
|
1681
2427
|
const setGroupCollapsed = (next) => {
|
|
1682
2428
|
onCollapsedChange?.(next);
|
|
1683
2429
|
if (collapsed === void 0) setInternalCollapsed(next);
|
|
1684
2430
|
};
|
|
1685
|
-
return /* @__PURE__ */
|
|
1686
|
-
label && (railCollapsed ? /* @__PURE__ */
|
|
2431
|
+
return /* @__PURE__ */ jsxs21("div", { className: cn("flex flex-col gap-0.5", className), children: [
|
|
2432
|
+
label && (railCollapsed ? /* @__PURE__ */ jsx37("div", { className: "mx-2 my-1 h-px bg-border/50", "aria-hidden": true }) : /* @__PURE__ */ jsxs21(
|
|
1687
2433
|
"button",
|
|
1688
2434
|
{
|
|
1689
2435
|
type: "button",
|
|
@@ -1691,8 +2437,8 @@ function NavGroup({
|
|
|
1691
2437
|
"aria-expanded": !groupCollapsed,
|
|
1692
2438
|
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",
|
|
1693
2439
|
children: [
|
|
1694
|
-
/* @__PURE__ */
|
|
1695
|
-
/* @__PURE__ */
|
|
2440
|
+
/* @__PURE__ */ jsx37("span", { children: label }),
|
|
2441
|
+
/* @__PURE__ */ jsx37(
|
|
1696
2442
|
ChevronDown2,
|
|
1697
2443
|
{
|
|
1698
2444
|
className: cn(
|
|
@@ -1705,7 +2451,7 @@ function NavGroup({
|
|
|
1705
2451
|
]
|
|
1706
2452
|
}
|
|
1707
2453
|
)),
|
|
1708
|
-
!groupCollapsed && /* @__PURE__ */
|
|
2454
|
+
!groupCollapsed && /* @__PURE__ */ jsx37("div", { className: "flex flex-col gap-0.5", children })
|
|
1709
2455
|
] });
|
|
1710
2456
|
}
|
|
1711
2457
|
|
|
@@ -1713,11 +2459,11 @@ function NavGroup({
|
|
|
1713
2459
|
import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
|
|
1714
2460
|
import {
|
|
1715
2461
|
useCallback,
|
|
1716
|
-
useEffect as
|
|
1717
|
-
useRef as
|
|
1718
|
-
useState as
|
|
2462
|
+
useEffect as useEffect5,
|
|
2463
|
+
useRef as useRef5,
|
|
2464
|
+
useState as useState9
|
|
1719
2465
|
} from "react";
|
|
1720
|
-
import { jsx as
|
|
2466
|
+
import { Fragment as Fragment3, jsx as jsx38, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1721
2467
|
function resolve(value, collapsed) {
|
|
1722
2468
|
return typeof value === "function" ? value(collapsed) : value;
|
|
1723
2469
|
}
|
|
@@ -1736,9 +2482,9 @@ function Sidebar({
|
|
|
1736
2482
|
children,
|
|
1737
2483
|
className
|
|
1738
2484
|
}) {
|
|
1739
|
-
const [internalWidth, setInternalWidth] =
|
|
2485
|
+
const [internalWidth, setInternalWidth] = useState9(defaultWidth);
|
|
1740
2486
|
const currentWidth = width ?? internalWidth;
|
|
1741
|
-
const [isDragging, setIsDragging] =
|
|
2487
|
+
const [isDragging, setIsDragging] = useState9(false);
|
|
1742
2488
|
const setWidth = useCallback(
|
|
1743
2489
|
(next) => {
|
|
1744
2490
|
const clamped = Math.min(maxWidth, Math.max(minWidth, next));
|
|
@@ -1747,7 +2493,7 @@ function Sidebar({
|
|
|
1747
2493
|
},
|
|
1748
2494
|
[maxWidth, minWidth, onWidthChange, width]
|
|
1749
2495
|
);
|
|
1750
|
-
const dragStartRef =
|
|
2496
|
+
const dragStartRef = useRef5(null);
|
|
1751
2497
|
const handlePointerDown = (event) => {
|
|
1752
2498
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
1753
2499
|
dragStartRef.current = { x: event.clientX, width: currentWidth };
|
|
@@ -1764,7 +2510,7 @@ function Sidebar({
|
|
|
1764
2510
|
dragStartRef.current = null;
|
|
1765
2511
|
setIsDragging(false);
|
|
1766
2512
|
};
|
|
1767
|
-
return /* @__PURE__ */
|
|
2513
|
+
return /* @__PURE__ */ jsx38(SidebarContextProvider, { value: { collapsed }, children: /* @__PURE__ */ jsx38(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs22(
|
|
1768
2514
|
"aside",
|
|
1769
2515
|
{
|
|
1770
2516
|
style: { width: collapsed ? railWidth : currentWidth },
|
|
@@ -1774,22 +2520,47 @@ function Sidebar({
|
|
|
1774
2520
|
className
|
|
1775
2521
|
),
|
|
1776
2522
|
children: [
|
|
1777
|
-
(header || onCollapsedChange) && /* @__PURE__ */
|
|
1778
|
-
|
|
1779
|
-
|
|
2523
|
+
(header || onCollapsedChange) && /* @__PURE__ */ jsx38("div", { className: "flex h-16 items-center gap-2 border-b border-border px-3", children: collapsed && onCollapsedChange ? (
|
|
2524
|
+
// Collapsed rail: the header slot (e.g. a compact logo mark) and the
|
|
2525
|
+
// expand toggle would otherwise compete for the narrow rail's width
|
|
2526
|
+
// and clip each other, so they're stacked instead — the header shows
|
|
2527
|
+
// by default and cross-fades to the toggle icon on hover, matching
|
|
2528
|
+
// the same affordance OneSales' own sidebar rail uses.
|
|
2529
|
+
/* @__PURE__ */ jsxs22(
|
|
1780
2530
|
"button",
|
|
1781
2531
|
{
|
|
1782
2532
|
type: "button",
|
|
1783
2533
|
onClick: () => onCollapsedChange(!collapsed),
|
|
1784
|
-
"aria-label":
|
|
2534
|
+
"aria-label": "Expand sidebar",
|
|
2535
|
+
className: "group relative flex h-9 w-9 shrink-0 items-center justify-center",
|
|
2536
|
+
children: [
|
|
2537
|
+
header && /* @__PURE__ */ jsx38("span", { className: "transition-opacity group-hover:opacity-0", children: resolve(header, collapsed) }),
|
|
2538
|
+
/* @__PURE__ */ jsx38(
|
|
2539
|
+
PanelLeftOpen,
|
|
2540
|
+
{
|
|
2541
|
+
className: "absolute h-4 w-4 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
|
|
2542
|
+
"aria-hidden": true
|
|
2543
|
+
}
|
|
2544
|
+
)
|
|
2545
|
+
]
|
|
2546
|
+
}
|
|
2547
|
+
)
|
|
2548
|
+
) : /* @__PURE__ */ jsxs22(Fragment3, { children: [
|
|
2549
|
+
header && /* @__PURE__ */ jsx38("div", { className: "min-w-0 flex-1", children: resolve(header, collapsed) }),
|
|
2550
|
+
onCollapsedChange && /* @__PURE__ */ jsx38(
|
|
2551
|
+
"button",
|
|
2552
|
+
{
|
|
2553
|
+
type: "button",
|
|
2554
|
+
onClick: () => onCollapsedChange(!collapsed),
|
|
2555
|
+
"aria-label": "Collapse sidebar",
|
|
1785
2556
|
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1786
|
-
children:
|
|
2557
|
+
children: /* @__PURE__ */ jsx38(PanelLeftClose, { className: "h-4 w-4", "aria-hidden": true })
|
|
1787
2558
|
}
|
|
1788
2559
|
)
|
|
1789
|
-
] }),
|
|
1790
|
-
/* @__PURE__ */
|
|
1791
|
-
footer && /* @__PURE__ */
|
|
1792
|
-
resizable && !collapsed && /* @__PURE__ */
|
|
2560
|
+
] }) }),
|
|
2561
|
+
/* @__PURE__ */ jsx38(SidebarNav, { children }),
|
|
2562
|
+
footer && /* @__PURE__ */ jsx38("div", { className: "border-t border-border/50 px-2 py-2", children: resolve(footer, collapsed) }),
|
|
2563
|
+
resizable && !collapsed && /* @__PURE__ */ jsx38(
|
|
1793
2564
|
"div",
|
|
1794
2565
|
{
|
|
1795
2566
|
onPointerDown: handlePointerDown,
|
|
@@ -1804,9 +2575,9 @@ function Sidebar({
|
|
|
1804
2575
|
) }) });
|
|
1805
2576
|
}
|
|
1806
2577
|
function SidebarNav({ children, className, ...rest }) {
|
|
1807
|
-
const ref =
|
|
1808
|
-
const [showFade, setShowFade] =
|
|
1809
|
-
|
|
2578
|
+
const ref = useRef5(null);
|
|
2579
|
+
const [showFade, setShowFade] = useState9(false);
|
|
2580
|
+
useEffect5(() => {
|
|
1810
2581
|
const el = ref.current;
|
|
1811
2582
|
if (!el) return;
|
|
1812
2583
|
const update = () => setShowFade(el.scrollHeight - el.scrollTop - el.clientHeight > 1);
|
|
@@ -1819,8 +2590,8 @@ function SidebarNav({ children, className, ...rest }) {
|
|
|
1819
2590
|
observer.disconnect();
|
|
1820
2591
|
};
|
|
1821
2592
|
}, []);
|
|
1822
|
-
return /* @__PURE__ */
|
|
1823
|
-
/* @__PURE__ */
|
|
2593
|
+
return /* @__PURE__ */ jsxs22("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
|
|
2594
|
+
/* @__PURE__ */ jsx38(
|
|
1824
2595
|
"div",
|
|
1825
2596
|
{
|
|
1826
2597
|
ref,
|
|
@@ -1829,7 +2600,7 @@ function SidebarNav({ children, className, ...rest }) {
|
|
|
1829
2600
|
children
|
|
1830
2601
|
}
|
|
1831
2602
|
),
|
|
1832
|
-
showFade && /* @__PURE__ */
|
|
2603
|
+
showFade && /* @__PURE__ */ jsx38(
|
|
1833
2604
|
"div",
|
|
1834
2605
|
{
|
|
1835
2606
|
className: "pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-sidebar to-transparent",
|
|
@@ -1859,6 +2630,7 @@ export {
|
|
|
1859
2630
|
DialogHeader,
|
|
1860
2631
|
DialogTitle,
|
|
1861
2632
|
DialogTrigger,
|
|
2633
|
+
DonutChart,
|
|
1862
2634
|
DropdownMenu,
|
|
1863
2635
|
DropdownMenuCheckboxItem,
|
|
1864
2636
|
DropdownMenuContent,
|
|
@@ -1877,6 +2649,7 @@ export {
|
|
|
1877
2649
|
FormField,
|
|
1878
2650
|
FormModal,
|
|
1879
2651
|
Input,
|
|
2652
|
+
KpiCard,
|
|
1880
2653
|
Label,
|
|
1881
2654
|
LoadingOverlay,
|
|
1882
2655
|
LoadingSpinner,
|
|
@@ -1887,9 +2660,12 @@ export {
|
|
|
1887
2660
|
PopoverAnchor,
|
|
1888
2661
|
PopoverContent,
|
|
1889
2662
|
PopoverTrigger,
|
|
2663
|
+
ResponsivePanel,
|
|
1890
2664
|
ScrollArea,
|
|
1891
2665
|
ScrollBar,
|
|
1892
2666
|
SearchBar,
|
|
2667
|
+
SearchPanel,
|
|
2668
|
+
SearchTrigger,
|
|
1893
2669
|
Select,
|
|
1894
2670
|
SelectContent,
|
|
1895
2671
|
SelectGroup,
|
|
@@ -1909,6 +2685,7 @@ export {
|
|
|
1909
2685
|
Sidebar,
|
|
1910
2686
|
SidebarNav,
|
|
1911
2687
|
Skeleton,
|
|
2688
|
+
Sparkline,
|
|
1912
2689
|
StatusBadge,
|
|
1913
2690
|
Switch,
|
|
1914
2691
|
Table,
|
|
@@ -1934,6 +2711,7 @@ export {
|
|
|
1934
2711
|
cn,
|
|
1935
2712
|
loadingSpinnerIconVariants,
|
|
1936
2713
|
sheetVariants,
|
|
2714
|
+
useMediaQuery,
|
|
1937
2715
|
useSidebarCollapsed
|
|
1938
2716
|
};
|
|
1939
2717
|
//# sourceMappingURL=index.js.map
|