@bmsuisse/ui 0.13.0 → 0.16.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 +305 -2
- package/dist/index.js +647 -219
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29,7 +29,16 @@ var buttonVariants = cva(
|
|
|
29
29
|
// Tailwind theme (this package doesn't define them) — same pattern as AlertBox's
|
|
30
30
|
// warning/info/success colors, which assume tokens the shared theme doesn't have.
|
|
31
31
|
"swiss-primary": "bg-swiss-primary text-swiss-primary-foreground font-semibold shadow-sm hover:bg-swiss-primary-hover hover:-translate-y-px active:translate-y-px",
|
|
32
|
-
"swiss-secondary": "border border-input bg-background font-semibold text-foreground shadow-sm hover:bg-accent hover:-translate-y-px active:translate-y-px"
|
|
32
|
+
"swiss-secondary": "border border-input bg-background font-semibold text-foreground shadow-sm hover:bg-accent hover:-translate-y-px active:translate-y-px",
|
|
33
|
+
// Violet "AI action" accent, in the same three weights as default/outline/ghost.
|
|
34
|
+
// Fixed Tailwind palette rather than a token — same choice as Badge's `warning`
|
|
35
|
+
// and AlertBox/StatusBadge's warning/info/success tones: the shared theme has no
|
|
36
|
+
// `ai` color, and unlike `swiss-primary` these should work in a consuming app with
|
|
37
|
+
// zero setup. Tints go through `violet-500/N` (not per-mode `violet-50`/`-950`
|
|
38
|
+
// shades) so one class list reads correctly on both light and dark surfaces.
|
|
39
|
+
ai: "bg-violet-600 text-white shadow-sm hover:bg-violet-600/90",
|
|
40
|
+
"ai-subtle": "border border-violet-500/30 bg-violet-500/10 text-violet-700 hover:bg-violet-500/20 dark:text-violet-300",
|
|
41
|
+
"ai-ghost": "text-violet-700 hover:bg-violet-500/10 dark:text-violet-300"
|
|
33
42
|
},
|
|
34
43
|
size: {
|
|
35
44
|
default: "h-9 px-3",
|
|
@@ -219,7 +228,7 @@ DialogDescription.displayName = "DialogDescription";
|
|
|
219
228
|
var DialogFooter = ({
|
|
220
229
|
className,
|
|
221
230
|
...props
|
|
222
|
-
}) => /* @__PURE__ */ jsx7("div", { className: cn("flex justify-end gap-2", className), ...props });
|
|
231
|
+
}) => /* @__PURE__ */ jsx7("div", { className: cn("mt-4 flex justify-end gap-2", className), ...props });
|
|
223
232
|
|
|
224
233
|
// src/primitives/popover.tsx
|
|
225
234
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
@@ -802,8 +811,42 @@ var Modal = ({
|
|
|
802
811
|
] }) });
|
|
803
812
|
|
|
804
813
|
// src/patterns/modal/ConfirmDialog.tsx
|
|
805
|
-
import { useState as
|
|
806
|
-
|
|
814
|
+
import { useState as useState3 } from "react";
|
|
815
|
+
|
|
816
|
+
// src/patterns/modal/DialogActions.tsx
|
|
817
|
+
import { Fragment, useState as useState2 } from "react";
|
|
818
|
+
|
|
819
|
+
// src/lib/platform.ts
|
|
820
|
+
function isWindowsPlatform() {
|
|
821
|
+
if (typeof navigator === "undefined") return false;
|
|
822
|
+
return /Windows/i.test(navigator.userAgent);
|
|
823
|
+
}
|
|
824
|
+
function getConfirmButtonPlacement() {
|
|
825
|
+
return isWindowsPlatform() ? "leading" : "trailing";
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/patterns/modal/DialogActions.tsx
|
|
829
|
+
import { Fragment as Fragment2, jsx as jsx21, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
830
|
+
function DialogActions({ actions, cancel }) {
|
|
831
|
+
const [placement] = useState2(getConfirmButtonPlacement);
|
|
832
|
+
const cancelLeading = placement === "trailing";
|
|
833
|
+
const ordered = cancelLeading ? actions : [...actions].reverse();
|
|
834
|
+
const renderedActions = ordered.map((action, index) => /* @__PURE__ */ jsx21(Fragment, { children: action }, actionKey(action, index)));
|
|
835
|
+
return cancelLeading ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
836
|
+
cancel,
|
|
837
|
+
renderedActions
|
|
838
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
839
|
+
renderedActions,
|
|
840
|
+
cancel
|
|
841
|
+
] });
|
|
842
|
+
}
|
|
843
|
+
function actionKey(action, index) {
|
|
844
|
+
if (action && typeof action === "object" && "key" in action && action.key !== null) return action.key;
|
|
845
|
+
return index;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/patterns/modal/ConfirmDialog.tsx
|
|
849
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
807
850
|
var ConfirmDialog = ({
|
|
808
851
|
open,
|
|
809
852
|
onOpenChange,
|
|
@@ -816,7 +859,7 @@ var ConfirmDialog = ({
|
|
|
816
859
|
confirmTestId,
|
|
817
860
|
cancelTestId
|
|
818
861
|
}) => {
|
|
819
|
-
const [pending, setPending] =
|
|
862
|
+
const [pending, setPending] = useState3(false);
|
|
820
863
|
const handleCancel = () => {
|
|
821
864
|
onOpenChange(false);
|
|
822
865
|
};
|
|
@@ -831,34 +874,33 @@ var ConfirmDialog = ({
|
|
|
831
874
|
setPending(false);
|
|
832
875
|
}
|
|
833
876
|
};
|
|
834
|
-
|
|
877
|
+
const cancelButton = /* @__PURE__ */ jsx22(Button, { variant: "outline", onClick: handleCancel, disabled: pending, "data-testid": cancelTestId, children: cancelLabel });
|
|
878
|
+
const confirmButton = /* @__PURE__ */ jsx22(
|
|
879
|
+
Button,
|
|
880
|
+
{
|
|
881
|
+
variant: variant === "destructive" ? "destructive" : "default",
|
|
882
|
+
onClick: handleConfirm,
|
|
883
|
+
disabled: pending,
|
|
884
|
+
"data-testid": confirmTestId,
|
|
885
|
+
children: pending ? `${confirmLabel}\u2026` : confirmLabel
|
|
886
|
+
}
|
|
887
|
+
);
|
|
888
|
+
return /* @__PURE__ */ jsx22(
|
|
835
889
|
Modal,
|
|
836
890
|
{
|
|
837
891
|
open,
|
|
838
892
|
onOpenChange,
|
|
839
893
|
title,
|
|
840
894
|
description,
|
|
841
|
-
footer: /* @__PURE__ */
|
|
842
|
-
/* @__PURE__ */ jsx21(Button, { variant: "outline", onClick: handleCancel, disabled: pending, "data-testid": cancelTestId, children: cancelLabel }),
|
|
843
|
-
/* @__PURE__ */ jsx21(
|
|
844
|
-
Button,
|
|
845
|
-
{
|
|
846
|
-
variant: variant === "destructive" ? "destructive" : "default",
|
|
847
|
-
onClick: handleConfirm,
|
|
848
|
-
disabled: pending,
|
|
849
|
-
"data-testid": confirmTestId,
|
|
850
|
-
children: pending ? `${confirmLabel}\u2026` : confirmLabel
|
|
851
|
-
}
|
|
852
|
-
)
|
|
853
|
-
] }),
|
|
895
|
+
footer: /* @__PURE__ */ jsx22(DialogActions, { actions: [confirmButton], cancel: cancelButton }),
|
|
854
896
|
children: null
|
|
855
897
|
}
|
|
856
898
|
);
|
|
857
899
|
};
|
|
858
900
|
|
|
859
901
|
// src/patterns/modal/FormModal.tsx
|
|
860
|
-
import { useState as
|
|
861
|
-
import { jsx as
|
|
902
|
+
import { useState as useState4 } from "react";
|
|
903
|
+
import { jsx as jsx23, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
862
904
|
var FormModal = ({
|
|
863
905
|
open,
|
|
864
906
|
onOpenChange,
|
|
@@ -873,7 +915,7 @@ var FormModal = ({
|
|
|
873
915
|
submitTestId,
|
|
874
916
|
cancelTestId
|
|
875
917
|
}) => {
|
|
876
|
-
const [pending, setPending] =
|
|
918
|
+
const [pending, setPending] = useState4(false);
|
|
877
919
|
const handleSubmit = async (event) => {
|
|
878
920
|
event.preventDefault();
|
|
879
921
|
setPending(true);
|
|
@@ -886,14 +928,14 @@ var FormModal = ({
|
|
|
886
928
|
const handleCancel = () => {
|
|
887
929
|
onOpenChange(false);
|
|
888
930
|
};
|
|
889
|
-
return /* @__PURE__ */
|
|
931
|
+
return /* @__PURE__ */ jsx23(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsx23(DialogContent, { className, children: /* @__PURE__ */ jsxs8("form", { onSubmit: handleSubmit, children: [
|
|
890
932
|
/* @__PURE__ */ jsxs8(DialogHeader, { children: [
|
|
891
|
-
/* @__PURE__ */
|
|
892
|
-
description ? /* @__PURE__ */
|
|
933
|
+
/* @__PURE__ */ jsx23(DialogTitle, { children: title }),
|
|
934
|
+
description ? /* @__PURE__ */ jsx23(DialogDescription, { children: description }) : null
|
|
893
935
|
] }),
|
|
894
936
|
children,
|
|
895
937
|
/* @__PURE__ */ jsxs8(DialogFooter, { children: [
|
|
896
|
-
/* @__PURE__ */
|
|
938
|
+
/* @__PURE__ */ jsx23(
|
|
897
939
|
Button,
|
|
898
940
|
{
|
|
899
941
|
type: "button",
|
|
@@ -904,18 +946,18 @@ var FormModal = ({
|
|
|
904
946
|
children: cancelLabel
|
|
905
947
|
}
|
|
906
948
|
),
|
|
907
|
-
/* @__PURE__ */
|
|
949
|
+
/* @__PURE__ */ jsx23(Button, { type: "submit", disabled: pending || submitDisabled, "data-testid": submitTestId, children: pending ? `${submitLabel}\u2026` : submitLabel })
|
|
908
950
|
] })
|
|
909
951
|
] }) }) });
|
|
910
952
|
};
|
|
911
953
|
|
|
912
954
|
// src/patterns/modal/ResponsivePanel.tsx
|
|
913
|
-
import { useRef as useRef2, useState as
|
|
955
|
+
import { useRef as useRef2, useState as useState6 } from "react";
|
|
914
956
|
|
|
915
957
|
// src/lib/useMediaQuery.ts
|
|
916
|
-
import { useEffect as useEffect2, useState as
|
|
958
|
+
import { useEffect as useEffect2, useState as useState5 } from "react";
|
|
917
959
|
function useMediaQuery(query) {
|
|
918
|
-
const [matches, setMatches] =
|
|
960
|
+
const [matches, setMatches] = useState5(
|
|
919
961
|
() => typeof window !== "undefined" ? window.matchMedia(query).matches : false
|
|
920
962
|
);
|
|
921
963
|
useEffect2(() => {
|
|
@@ -929,7 +971,7 @@ function useMediaQuery(query) {
|
|
|
929
971
|
}
|
|
930
972
|
|
|
931
973
|
// src/patterns/modal/ResponsivePanel.tsx
|
|
932
|
-
import { jsx as
|
|
974
|
+
import { jsx as jsx24, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
933
975
|
var desktopSizeClasses = {
|
|
934
976
|
sm: "max-w-md",
|
|
935
977
|
md: "max-w-xl",
|
|
@@ -964,8 +1006,8 @@ var cornerGripPosition = {
|
|
|
964
1006
|
"bottom-right": "right-1 bottom-1 border-r-2 border-b-2"
|
|
965
1007
|
};
|
|
966
1008
|
function useDesktopPanelGeometry() {
|
|
967
|
-
const [style, setStyle] =
|
|
968
|
-
const [isDragging, setIsDragging] =
|
|
1009
|
+
const [style, setStyle] = useState6(void 0);
|
|
1010
|
+
const [isDragging, setIsDragging] = useState6(false);
|
|
969
1011
|
const drag = useRef2(null);
|
|
970
1012
|
const lastRect = useRef2(null);
|
|
971
1013
|
const beginDrag = (mode, corner) => (e) => {
|
|
@@ -1055,7 +1097,7 @@ var ResponsivePanel = ({
|
|
|
1055
1097
|
const onInteractOutside = closeOnOutsideClick ? void 0 : (e) => e.preventDefault();
|
|
1056
1098
|
if (isDesktop) {
|
|
1057
1099
|
const corners = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
1058
|
-
return /* @__PURE__ */
|
|
1100
|
+
return /* @__PURE__ */ jsx24(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs9(
|
|
1059
1101
|
DialogContent,
|
|
1060
1102
|
{
|
|
1061
1103
|
"data-slot": "dialog-content",
|
|
@@ -1068,7 +1110,7 @@ var ResponsivePanel = ({
|
|
|
1068
1110
|
className
|
|
1069
1111
|
),
|
|
1070
1112
|
children: [
|
|
1071
|
-
resizable && corners.map((corner) => /* @__PURE__ */
|
|
1113
|
+
resizable && corners.map((corner) => /* @__PURE__ */ jsx24(
|
|
1072
1114
|
"div",
|
|
1073
1115
|
{
|
|
1074
1116
|
"data-testid": `panel-resize-handle-${corner}`,
|
|
@@ -1081,7 +1123,7 @@ var ResponsivePanel = ({
|
|
|
1081
1123
|
onPointerMove: geometry.onPointerMove,
|
|
1082
1124
|
onPointerUp: geometry.onPointerUp,
|
|
1083
1125
|
onPointerCancel: geometry.onPointerUp,
|
|
1084
|
-
children: /* @__PURE__ */
|
|
1126
|
+
children: /* @__PURE__ */ jsx24(
|
|
1085
1127
|
"div",
|
|
1086
1128
|
{
|
|
1087
1129
|
className: cn(
|
|
@@ -1102,18 +1144,18 @@ var ResponsivePanel = ({
|
|
|
1102
1144
|
onPointerUp: draggable ? geometry.onPointerUp : void 0,
|
|
1103
1145
|
onPointerCancel: draggable ? geometry.onPointerUp : void 0,
|
|
1104
1146
|
children: [
|
|
1105
|
-
/* @__PURE__ */
|
|
1106
|
-
description ? /* @__PURE__ */
|
|
1147
|
+
/* @__PURE__ */ jsx24(DialogTitle, { children: title }),
|
|
1148
|
+
description ? /* @__PURE__ */ jsx24(DialogDescription, { children: description }) : null
|
|
1107
1149
|
]
|
|
1108
1150
|
}
|
|
1109
1151
|
),
|
|
1110
1152
|
children,
|
|
1111
|
-
footer ? /* @__PURE__ */
|
|
1153
|
+
footer ? /* @__PURE__ */ jsx24(DialogFooter, { children: footer }) : null
|
|
1112
1154
|
]
|
|
1113
1155
|
}
|
|
1114
1156
|
) });
|
|
1115
1157
|
}
|
|
1116
|
-
return /* @__PURE__ */
|
|
1158
|
+
return /* @__PURE__ */ jsx24(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs9(
|
|
1117
1159
|
SheetContent,
|
|
1118
1160
|
{
|
|
1119
1161
|
side: "bottom",
|
|
@@ -1126,19 +1168,76 @@ var ResponsivePanel = ({
|
|
|
1126
1168
|
),
|
|
1127
1169
|
children: [
|
|
1128
1170
|
/* @__PURE__ */ jsxs9(SheetHeader, { children: [
|
|
1129
|
-
/* @__PURE__ */
|
|
1130
|
-
description ? /* @__PURE__ */
|
|
1171
|
+
/* @__PURE__ */ jsx24(SheetTitle, { children: title }),
|
|
1172
|
+
description ? /* @__PURE__ */ jsx24(SheetDescription, { children: description }) : null
|
|
1131
1173
|
] }),
|
|
1132
1174
|
children,
|
|
1133
|
-
footer ? /* @__PURE__ */
|
|
1175
|
+
footer ? /* @__PURE__ */ jsx24(SheetFooter, { children: footer }) : null
|
|
1134
1176
|
]
|
|
1135
1177
|
}
|
|
1136
1178
|
) });
|
|
1137
1179
|
};
|
|
1138
1180
|
|
|
1181
|
+
// src/patterns/modal/QuestionDialog.tsx
|
|
1182
|
+
import { useState as useState7 } from "react";
|
|
1183
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
1184
|
+
var QuestionDialog = ({
|
|
1185
|
+
open,
|
|
1186
|
+
onOpenChange,
|
|
1187
|
+
title,
|
|
1188
|
+
description,
|
|
1189
|
+
actions,
|
|
1190
|
+
cancelLabel = "Cancel",
|
|
1191
|
+
cancelTestId
|
|
1192
|
+
}) => {
|
|
1193
|
+
const [pendingIndex, setPendingIndex] = useState7(null);
|
|
1194
|
+
const pending = pendingIndex !== null;
|
|
1195
|
+
const handleCancel = () => {
|
|
1196
|
+
onOpenChange(false);
|
|
1197
|
+
};
|
|
1198
|
+
const handleActionClick = async (action, index) => {
|
|
1199
|
+
setPendingIndex(index);
|
|
1200
|
+
try {
|
|
1201
|
+
await action.onClick();
|
|
1202
|
+
onOpenChange(false);
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
console.error(error);
|
|
1205
|
+
} finally {
|
|
1206
|
+
setPendingIndex(null);
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
return /* @__PURE__ */ jsx25(
|
|
1210
|
+
Modal,
|
|
1211
|
+
{
|
|
1212
|
+
open,
|
|
1213
|
+
onOpenChange,
|
|
1214
|
+
title,
|
|
1215
|
+
description,
|
|
1216
|
+
footer: /* @__PURE__ */ jsx25(
|
|
1217
|
+
DialogActions,
|
|
1218
|
+
{
|
|
1219
|
+
actions: actions.map((action, index) => /* @__PURE__ */ jsx25(
|
|
1220
|
+
Button,
|
|
1221
|
+
{
|
|
1222
|
+
variant: action.variant ?? "default",
|
|
1223
|
+
onClick: () => handleActionClick(action, index),
|
|
1224
|
+
disabled: pending,
|
|
1225
|
+
"data-testid": action.testId,
|
|
1226
|
+
children: pendingIndex === index ? `${action.label}\u2026` : action.label
|
|
1227
|
+
},
|
|
1228
|
+
action.label
|
|
1229
|
+
)),
|
|
1230
|
+
cancel: /* @__PURE__ */ jsx25(Button, { variant: "outline", onClick: handleCancel, disabled: pending, "data-testid": cancelTestId, children: cancelLabel })
|
|
1231
|
+
}
|
|
1232
|
+
),
|
|
1233
|
+
children: null
|
|
1234
|
+
}
|
|
1235
|
+
);
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1139
1238
|
// src/patterns/form-field/FormField.tsx
|
|
1140
1239
|
import { cloneElement, isValidElement, useId } from "react";
|
|
1141
|
-
import { jsx as
|
|
1240
|
+
import { jsx as jsx26, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1142
1241
|
function FormField({
|
|
1143
1242
|
label,
|
|
1144
1243
|
htmlFor,
|
|
@@ -1172,13 +1271,13 @@ function FormField({
|
|
|
1172
1271
|
] }) : null
|
|
1173
1272
|
] }),
|
|
1174
1273
|
child,
|
|
1175
|
-
error ? /* @__PURE__ */
|
|
1274
|
+
error ? /* @__PURE__ */ jsx26("p", { id: messageId, className: "text-sm text-destructive", children: error }) : description ? /* @__PURE__ */ jsx26("p", { id: messageId, className: "text-sm text-muted-foreground", children: description }) : null
|
|
1176
1275
|
] });
|
|
1177
1276
|
}
|
|
1178
1277
|
|
|
1179
1278
|
// src/patterns/combobox/Combobox.tsx
|
|
1180
1279
|
import { Check as Check4, ChevronsUpDown, X as X3 } from "lucide-react";
|
|
1181
|
-
import { useEffect as useEffect3, useMemo, useRef as useRef3, useState as
|
|
1280
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState8 } from "react";
|
|
1182
1281
|
|
|
1183
1282
|
// src/lib/optionGrouping.ts
|
|
1184
1283
|
function groupMembers(options, group) {
|
|
@@ -1207,7 +1306,7 @@ function buildRenderChunks(visibleOptions) {
|
|
|
1207
1306
|
}
|
|
1208
1307
|
|
|
1209
1308
|
// src/lib/optionGrid.tsx
|
|
1210
|
-
import { jsx as
|
|
1309
|
+
import { jsx as jsx27, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1211
1310
|
function renderGridOptions({
|
|
1212
1311
|
columns,
|
|
1213
1312
|
options,
|
|
@@ -1219,9 +1318,9 @@ function renderGridOptions({
|
|
|
1219
1318
|
testId
|
|
1220
1319
|
}) {
|
|
1221
1320
|
return /* @__PURE__ */ jsxs11(Table, { className: "text-sm", children: [
|
|
1222
|
-
/* @__PURE__ */
|
|
1223
|
-
multiple && /* @__PURE__ */
|
|
1224
|
-
columns.map((column) => /* @__PURE__ */
|
|
1321
|
+
/* @__PURE__ */ jsx27(TableHeader, { children: /* @__PURE__ */ jsxs11(TableRow, { className: "hover:bg-transparent", children: [
|
|
1322
|
+
multiple && /* @__PURE__ */ jsx27(TableHead, { className: "sticky top-0 z-10 w-6 bg-muted" }),
|
|
1323
|
+
columns.map((column) => /* @__PURE__ */ jsx27(
|
|
1225
1324
|
TableHead,
|
|
1226
1325
|
{
|
|
1227
1326
|
className: cn("sticky top-0 z-10 whitespace-nowrap bg-muted text-xs", column.className),
|
|
@@ -1230,7 +1329,7 @@ function renderGridOptions({
|
|
|
1230
1329
|
column.key
|
|
1231
1330
|
))
|
|
1232
1331
|
] }) }),
|
|
1233
|
-
/* @__PURE__ */
|
|
1332
|
+
/* @__PURE__ */ jsx27(TableBody, { children: options.map((option, index) => {
|
|
1234
1333
|
const selected = isSelected(option);
|
|
1235
1334
|
const active = index === activeIndex;
|
|
1236
1335
|
return /* @__PURE__ */ jsxs11(
|
|
@@ -1257,7 +1356,7 @@ function renderGridOptions({
|
|
|
1257
1356
|
option.disabled && "pointer-events-none opacity-50"
|
|
1258
1357
|
),
|
|
1259
1358
|
children: [
|
|
1260
|
-
multiple && /* @__PURE__ */
|
|
1359
|
+
multiple && /* @__PURE__ */ jsx27(TableCell, { className: "w-6", children: /* @__PURE__ */ jsx27(
|
|
1261
1360
|
"input",
|
|
1262
1361
|
{
|
|
1263
1362
|
type: "checkbox",
|
|
@@ -1267,7 +1366,7 @@ function renderGridOptions({
|
|
|
1267
1366
|
className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
|
|
1268
1367
|
}
|
|
1269
1368
|
) }),
|
|
1270
|
-
columns.map((column) => /* @__PURE__ */
|
|
1369
|
+
columns.map((column) => /* @__PURE__ */ jsx27(TableCell, { className: cn("truncate", column.className), children: column.key === "label" ? option.label : option.data?.[column.key] }, column.key))
|
|
1271
1370
|
]
|
|
1272
1371
|
},
|
|
1273
1372
|
option.value
|
|
@@ -1277,7 +1376,7 @@ function renderGridOptions({
|
|
|
1277
1376
|
}
|
|
1278
1377
|
|
|
1279
1378
|
// src/patterns/combobox/Combobox.tsx
|
|
1280
|
-
import { jsx as
|
|
1379
|
+
import { jsx as jsx28, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1281
1380
|
function resolveGroupTriggerLabel(options, selectedValues, groupLabel) {
|
|
1282
1381
|
const selectedSet = new Set(selectedValues);
|
|
1283
1382
|
const groupsPresent = Array.from(
|
|
@@ -1316,9 +1415,9 @@ function Combobox(props) {
|
|
|
1316
1415
|
columns
|
|
1317
1416
|
} = props;
|
|
1318
1417
|
const testId = props["data-testid"];
|
|
1319
|
-
const [open, setOpen] =
|
|
1320
|
-
const [search, setSearch] =
|
|
1321
|
-
const [activeIndex, setActiveIndex] =
|
|
1418
|
+
const [open, setOpen] = useState8(false);
|
|
1419
|
+
const [search, setSearch] = useState8("");
|
|
1420
|
+
const [activeIndex, setActiveIndex] = useState8(0);
|
|
1322
1421
|
const inputRef = useRef3(null);
|
|
1323
1422
|
const selectedValues = useMemo(
|
|
1324
1423
|
() => props.multiple ? props.value : props.value ? [props.value] : [],
|
|
@@ -1397,7 +1496,7 @@ function Combobox(props) {
|
|
|
1397
1496
|
isActive && "bg-accent text-accent-foreground"
|
|
1398
1497
|
),
|
|
1399
1498
|
children: [
|
|
1400
|
-
props.multiple ? /* @__PURE__ */
|
|
1499
|
+
props.multiple ? /* @__PURE__ */ jsx28(
|
|
1401
1500
|
"input",
|
|
1402
1501
|
{
|
|
1403
1502
|
type: "checkbox",
|
|
@@ -1406,15 +1505,15 @@ function Combobox(props) {
|
|
|
1406
1505
|
tabIndex: -1,
|
|
1407
1506
|
className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
|
|
1408
1507
|
}
|
|
1409
|
-
) : /* @__PURE__ */
|
|
1410
|
-
/* @__PURE__ */
|
|
1508
|
+
) : /* @__PURE__ */ jsx28(Check4, { className: cn("h-4 w-4 shrink-0", isSelected ? "opacity-100" : "opacity-0") }),
|
|
1509
|
+
/* @__PURE__ */ jsx28("span", { className: "truncate", children: option.label })
|
|
1411
1510
|
]
|
|
1412
1511
|
},
|
|
1413
1512
|
option.value
|
|
1414
1513
|
);
|
|
1415
1514
|
}
|
|
1416
1515
|
return /* @__PURE__ */ jsxs12(Popover, { open, onOpenChange: setOpen, children: [
|
|
1417
|
-
/* @__PURE__ */
|
|
1516
|
+
/* @__PURE__ */ jsx28(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs12(
|
|
1418
1517
|
Button,
|
|
1419
1518
|
{
|
|
1420
1519
|
id,
|
|
@@ -1430,9 +1529,9 @@ function Combobox(props) {
|
|
|
1430
1529
|
className
|
|
1431
1530
|
),
|
|
1432
1531
|
children: [
|
|
1433
|
-
/* @__PURE__ */
|
|
1532
|
+
/* @__PURE__ */ jsx28("span", { className: "truncate", children: triggerLabel }),
|
|
1434
1533
|
/* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-1", children: [
|
|
1435
|
-
clearable && !loading && selectedValues.length > 0 ? /* @__PURE__ */
|
|
1534
|
+
clearable && !loading && selectedValues.length > 0 ? /* @__PURE__ */ jsx28(
|
|
1436
1535
|
"span",
|
|
1437
1536
|
{
|
|
1438
1537
|
role: "button",
|
|
@@ -1447,15 +1546,15 @@ function Combobox(props) {
|
|
|
1447
1546
|
props.onChange(null);
|
|
1448
1547
|
}
|
|
1449
1548
|
},
|
|
1450
|
-
children: /* @__PURE__ */
|
|
1549
|
+
children: /* @__PURE__ */ jsx28(X3, { className: "h-3.5 w-3.5" })
|
|
1451
1550
|
}
|
|
1452
1551
|
) : null,
|
|
1453
|
-
/* @__PURE__ */
|
|
1552
|
+
/* @__PURE__ */ jsx28(ChevronsUpDown, { className: "h-4 w-4 shrink-0 opacity-50", "aria-hidden": "true" })
|
|
1454
1553
|
] })
|
|
1455
1554
|
]
|
|
1456
1555
|
}
|
|
1457
1556
|
) }),
|
|
1458
|
-
/* @__PURE__ */
|
|
1557
|
+
/* @__PURE__ */ jsx28(
|
|
1459
1558
|
PopoverContent,
|
|
1460
1559
|
{
|
|
1461
1560
|
align: "start",
|
|
@@ -1466,7 +1565,7 @@ function Combobox(props) {
|
|
|
1466
1565
|
),
|
|
1467
1566
|
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
1468
1567
|
children: /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-2", children: [
|
|
1469
|
-
/* @__PURE__ */
|
|
1568
|
+
/* @__PURE__ */ jsx28(
|
|
1470
1569
|
Input,
|
|
1471
1570
|
{
|
|
1472
1571
|
ref: inputRef,
|
|
@@ -1480,7 +1579,7 @@ function Combobox(props) {
|
|
|
1480
1579
|
"aria-label": searchPlaceholder
|
|
1481
1580
|
}
|
|
1482
1581
|
),
|
|
1483
|
-
/* @__PURE__ */
|
|
1582
|
+
/* @__PURE__ */ jsx28("div", { role: "listbox", className: cn("max-h-60 overflow-y-auto", !columns && "flex flex-col gap-0.5"), children: visibleOptions.length === 0 ? /* @__PURE__ */ jsx28("p", { className: "py-2 text-center text-sm text-muted-foreground", children: emptyMessage }) : columns ? renderGridOptions({
|
|
1484
1583
|
columns,
|
|
1485
1584
|
options: visibleOptions,
|
|
1486
1585
|
activeIndex,
|
|
@@ -1507,7 +1606,7 @@ function Combobox(props) {
|
|
|
1507
1606
|
"data-group-header": true,
|
|
1508
1607
|
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",
|
|
1509
1608
|
children: [
|
|
1510
|
-
props.multiple ? /* @__PURE__ */
|
|
1609
|
+
props.multiple ? /* @__PURE__ */ jsx28(
|
|
1511
1610
|
"input",
|
|
1512
1611
|
{
|
|
1513
1612
|
type: "checkbox",
|
|
@@ -1521,7 +1620,7 @@ function Combobox(props) {
|
|
|
1521
1620
|
"aria-label": `Select all of ${groupLabel(chunk.group)}`
|
|
1522
1621
|
}
|
|
1523
1622
|
) : null,
|
|
1524
|
-
/* @__PURE__ */
|
|
1623
|
+
/* @__PURE__ */ jsx28("span", { className: "truncate", children: groupLabel(chunk.group) })
|
|
1525
1624
|
]
|
|
1526
1625
|
}
|
|
1527
1626
|
),
|
|
@@ -1537,8 +1636,8 @@ function Combobox(props) {
|
|
|
1537
1636
|
|
|
1538
1637
|
// src/patterns/tag-combobox/TagCombobox.tsx
|
|
1539
1638
|
import { X as X4 } from "lucide-react";
|
|
1540
|
-
import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as
|
|
1541
|
-
import { jsx as
|
|
1639
|
+
import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState9 } from "react";
|
|
1640
|
+
import { jsx as jsx29, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1542
1641
|
function TagCombobox({
|
|
1543
1642
|
options,
|
|
1544
1643
|
value,
|
|
@@ -1557,9 +1656,9 @@ function TagCombobox({
|
|
|
1557
1656
|
...rest
|
|
1558
1657
|
}) {
|
|
1559
1658
|
const testId = rest["data-testid"];
|
|
1560
|
-
const [open, setOpen] =
|
|
1561
|
-
const [search, setSearch] =
|
|
1562
|
-
const [activeIndex, setActiveIndex] =
|
|
1659
|
+
const [open, setOpen] = useState9(false);
|
|
1660
|
+
const [search, setSearch] = useState9("");
|
|
1661
|
+
const [activeIndex, setActiveIndex] = useState9(0);
|
|
1563
1662
|
const inputRef = useRef4(null);
|
|
1564
1663
|
const fieldRef = useRef4(null);
|
|
1565
1664
|
const selectedOptions = useMemo2(
|
|
@@ -1645,7 +1744,7 @@ function TagCombobox({
|
|
|
1645
1744
|
isActive && "bg-accent text-accent-foreground"
|
|
1646
1745
|
),
|
|
1647
1746
|
children: [
|
|
1648
|
-
/* @__PURE__ */
|
|
1747
|
+
/* @__PURE__ */ jsx29(
|
|
1649
1748
|
"input",
|
|
1650
1749
|
{
|
|
1651
1750
|
type: "checkbox",
|
|
@@ -1655,14 +1754,14 @@ function TagCombobox({
|
|
|
1655
1754
|
className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
|
|
1656
1755
|
}
|
|
1657
1756
|
),
|
|
1658
|
-
/* @__PURE__ */
|
|
1757
|
+
/* @__PURE__ */ jsx29("span", { className: "truncate", children: option.label })
|
|
1659
1758
|
]
|
|
1660
1759
|
},
|
|
1661
1760
|
option.value
|
|
1662
1761
|
);
|
|
1663
1762
|
}
|
|
1664
1763
|
return /* @__PURE__ */ jsxs13(Popover, { open, onOpenChange: setOpen, children: [
|
|
1665
|
-
/* @__PURE__ */
|
|
1764
|
+
/* @__PURE__ */ jsx29(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs13(
|
|
1666
1765
|
"div",
|
|
1667
1766
|
{
|
|
1668
1767
|
ref: fieldRef,
|
|
@@ -1685,8 +1784,8 @@ function TagCombobox({
|
|
|
1685
1784
|
{
|
|
1686
1785
|
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",
|
|
1687
1786
|
children: [
|
|
1688
|
-
/* @__PURE__ */
|
|
1689
|
-
!disabled && /* @__PURE__ */
|
|
1787
|
+
/* @__PURE__ */ jsx29("span", { className: "max-w-48 truncate", children: option.label }),
|
|
1788
|
+
!disabled && /* @__PURE__ */ jsx29(
|
|
1690
1789
|
"button",
|
|
1691
1790
|
{
|
|
1692
1791
|
type: "button",
|
|
@@ -1696,14 +1795,14 @@ function TagCombobox({
|
|
|
1696
1795
|
event.stopPropagation();
|
|
1697
1796
|
removeValue(option.value);
|
|
1698
1797
|
},
|
|
1699
|
-
children: /* @__PURE__ */
|
|
1798
|
+
children: /* @__PURE__ */ jsx29(X4, { className: "h-3 w-3" })
|
|
1700
1799
|
}
|
|
1701
1800
|
)
|
|
1702
1801
|
]
|
|
1703
1802
|
},
|
|
1704
1803
|
option.value
|
|
1705
1804
|
)),
|
|
1706
|
-
/* @__PURE__ */
|
|
1805
|
+
/* @__PURE__ */ jsx29(
|
|
1707
1806
|
"input",
|
|
1708
1807
|
{
|
|
1709
1808
|
ref: inputRef,
|
|
@@ -1721,7 +1820,7 @@ function TagCombobox({
|
|
|
1721
1820
|
]
|
|
1722
1821
|
}
|
|
1723
1822
|
) }),
|
|
1724
|
-
/* @__PURE__ */
|
|
1823
|
+
/* @__PURE__ */ jsx29(
|
|
1725
1824
|
PopoverContent,
|
|
1726
1825
|
{
|
|
1727
1826
|
align: "start",
|
|
@@ -1735,7 +1834,7 @@ function TagCombobox({
|
|
|
1735
1834
|
onInteractOutside: (event) => {
|
|
1736
1835
|
if (fieldRef.current?.contains(event.target)) event.preventDefault();
|
|
1737
1836
|
},
|
|
1738
|
-
children: /* @__PURE__ */
|
|
1837
|
+
children: /* @__PURE__ */ jsx29("div", { role: "listbox", className: cn("max-h-60 overflow-y-auto", !columns && "flex flex-col gap-0.5"), children: loading ? /* @__PURE__ */ jsx29("p", { className: "py-2 text-center text-sm text-muted-foreground", children: loadingMessage }) : visibleOptions.length === 0 ? /* @__PURE__ */ jsx29("p", { className: "py-2 text-center text-sm text-muted-foreground", children: emptyMessage }) : columns ? renderGridOptions({
|
|
1739
1838
|
columns,
|
|
1740
1839
|
options: visibleOptions,
|
|
1741
1840
|
activeIndex,
|
|
@@ -1761,7 +1860,7 @@ function TagCombobox({
|
|
|
1761
1860
|
"data-group-header": true,
|
|
1762
1861
|
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",
|
|
1763
1862
|
children: [
|
|
1764
|
-
/* @__PURE__ */
|
|
1863
|
+
/* @__PURE__ */ jsx29(
|
|
1765
1864
|
"input",
|
|
1766
1865
|
{
|
|
1767
1866
|
type: "checkbox",
|
|
@@ -1775,7 +1874,7 @@ function TagCombobox({
|
|
|
1775
1874
|
"aria-label": `Select all of ${groupLabel(chunk.group)}`
|
|
1776
1875
|
}
|
|
1777
1876
|
),
|
|
1778
|
-
/* @__PURE__ */
|
|
1877
|
+
/* @__PURE__ */ jsx29("span", { className: "truncate", children: groupLabel(chunk.group) })
|
|
1779
1878
|
]
|
|
1780
1879
|
}
|
|
1781
1880
|
),
|
|
@@ -1790,7 +1889,7 @@ function TagCombobox({
|
|
|
1790
1889
|
|
|
1791
1890
|
// src/patterns/alert-box/AlertBox.tsx
|
|
1792
1891
|
import { AlertCircle, AlertTriangle, CheckCircle2, Info } from "lucide-react";
|
|
1793
|
-
import { jsx as
|
|
1892
|
+
import { jsx as jsx30, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1794
1893
|
var variantStyles = {
|
|
1795
1894
|
error: "border-destructive bg-destructive/10 text-red-800 dark:text-red-300",
|
|
1796
1895
|
warning: "border-amber-500 bg-amber-500/10 text-amber-900 dark:text-amber-200",
|
|
@@ -1798,24 +1897,24 @@ var variantStyles = {
|
|
|
1798
1897
|
success: "border-emerald-500 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"
|
|
1799
1898
|
};
|
|
1800
1899
|
var defaultIcons = {
|
|
1801
|
-
error: /* @__PURE__ */
|
|
1802
|
-
warning: /* @__PURE__ */
|
|
1803
|
-
info: /* @__PURE__ */
|
|
1804
|
-
success: /* @__PURE__ */
|
|
1900
|
+
error: /* @__PURE__ */ jsx30(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
1901
|
+
warning: /* @__PURE__ */ jsx30(AlertTriangle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
1902
|
+
info: /* @__PURE__ */ jsx30(Info, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
1903
|
+
success: /* @__PURE__ */ jsx30(CheckCircle2, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" })
|
|
1805
1904
|
};
|
|
1806
1905
|
var AlertBox = ({ variant, title, children, className, icon }) => {
|
|
1807
1906
|
const resolvedIcon = icon === void 0 ? defaultIcons[variant] : icon;
|
|
1808
|
-
return /* @__PURE__ */
|
|
1809
|
-
resolvedIcon != null && /* @__PURE__ */
|
|
1907
|
+
return /* @__PURE__ */ jsx30("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: [
|
|
1908
|
+
resolvedIcon != null && /* @__PURE__ */ jsx30("span", { className: "mt-0.5 shrink-0", children: resolvedIcon }),
|
|
1810
1909
|
/* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-1", children: [
|
|
1811
|
-
title && /* @__PURE__ */
|
|
1812
|
-
/* @__PURE__ */
|
|
1910
|
+
title && /* @__PURE__ */ jsx30("p", { className: "font-bold", children: title }),
|
|
1911
|
+
/* @__PURE__ */ jsx30("div", { children })
|
|
1813
1912
|
] })
|
|
1814
1913
|
] }) });
|
|
1815
1914
|
};
|
|
1816
1915
|
|
|
1817
1916
|
// src/patterns/status-badge/StatusBadge.tsx
|
|
1818
|
-
import { jsx as
|
|
1917
|
+
import { jsx as jsx31 } from "react/jsx-runtime";
|
|
1819
1918
|
var TONE_CLASSES = {
|
|
1820
1919
|
success: "border-transparent bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300",
|
|
1821
1920
|
warning: "border-transparent bg-amber-500/15 text-amber-800 dark:bg-amber-500/20 dark:text-amber-300",
|
|
@@ -1862,11 +1961,11 @@ function resolveTone(status, tone, toneMap) {
|
|
|
1862
1961
|
var StatusBadge = ({ status, label, tone, toneMap, className, ...props }) => {
|
|
1863
1962
|
const resolvedTone = resolveTone(status, tone, toneMap);
|
|
1864
1963
|
const displayLabel = label ?? deriveLabel(status);
|
|
1865
|
-
return /* @__PURE__ */
|
|
1964
|
+
return /* @__PURE__ */ jsx31(Badge, { variant: "outline", className: cn(TONE_CLASSES[resolvedTone], className), ...props, children: displayLabel });
|
|
1866
1965
|
};
|
|
1867
1966
|
|
|
1868
1967
|
// src/patterns/button-group/ButtonGroup.tsx
|
|
1869
|
-
import { jsx as
|
|
1968
|
+
import { jsx as jsx32, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1870
1969
|
function ButtonGroup({
|
|
1871
1970
|
options,
|
|
1872
1971
|
value,
|
|
@@ -1876,7 +1975,7 @@ function ButtonGroup({
|
|
|
1876
1975
|
className,
|
|
1877
1976
|
...props
|
|
1878
1977
|
}) {
|
|
1879
|
-
return /* @__PURE__ */
|
|
1978
|
+
return /* @__PURE__ */ jsx32("div", { role: "group", "aria-label": props["aria-label"], className: cn("inline-flex", className), "data-testid": props["data-testid"], children: options.map((option, index) => {
|
|
1880
1979
|
const selected = option.value === value;
|
|
1881
1980
|
const Icon2 = option.icon;
|
|
1882
1981
|
return /* @__PURE__ */ jsxs15(
|
|
@@ -1897,7 +1996,7 @@ function ButtonGroup({
|
|
|
1897
1996
|
index > 0 && "-ml-px"
|
|
1898
1997
|
),
|
|
1899
1998
|
children: [
|
|
1900
|
-
Icon2 && /* @__PURE__ */
|
|
1999
|
+
Icon2 && /* @__PURE__ */ jsx32(Icon2, { className: "h-4 w-4" }),
|
|
1901
2000
|
option.label
|
|
1902
2001
|
]
|
|
1903
2002
|
},
|
|
@@ -1909,7 +2008,7 @@ function ButtonGroup({
|
|
|
1909
2008
|
// src/patterns/loading-spinner/LoadingSpinner.tsx
|
|
1910
2009
|
import { cva as cva4 } from "class-variance-authority";
|
|
1911
2010
|
import { Loader2 } from "lucide-react";
|
|
1912
|
-
import { jsx as
|
|
2011
|
+
import { jsx as jsx33, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1913
2012
|
var loadingSpinnerIconVariants = cva4("animate-spin text-muted-foreground", {
|
|
1914
2013
|
variants: {
|
|
1915
2014
|
size: {
|
|
@@ -1928,26 +2027,26 @@ var LoadingSpinner = ({
|
|
|
1928
2027
|
className,
|
|
1929
2028
|
...props
|
|
1930
2029
|
}) => /* @__PURE__ */ jsxs16("span", { role: "status", className: cn("inline-flex items-center gap-2", className), ...props, children: [
|
|
1931
|
-
/* @__PURE__ */
|
|
1932
|
-
label ? /* @__PURE__ */
|
|
2030
|
+
/* @__PURE__ */ jsx33(Loader2, { className: cn(loadingSpinnerIconVariants({ size })), "aria-hidden": "true" }),
|
|
2031
|
+
label ? /* @__PURE__ */ jsx33("span", { children: label }) : null
|
|
1933
2032
|
] });
|
|
1934
2033
|
var LoadingOverlay = ({
|
|
1935
2034
|
size = "lg",
|
|
1936
2035
|
label,
|
|
1937
2036
|
className,
|
|
1938
2037
|
...props
|
|
1939
|
-
}) => /* @__PURE__ */
|
|
2038
|
+
}) => /* @__PURE__ */ jsx33(
|
|
1940
2039
|
"div",
|
|
1941
2040
|
{
|
|
1942
2041
|
className: cn("flex h-full min-h-32 w-full items-center justify-center", className),
|
|
1943
2042
|
...props,
|
|
1944
|
-
children: /* @__PURE__ */
|
|
2043
|
+
children: /* @__PURE__ */ jsx33(LoadingSpinner, { size, label })
|
|
1945
2044
|
}
|
|
1946
2045
|
);
|
|
1947
2046
|
|
|
1948
2047
|
// src/patterns/search-bar/SearchBar.tsx
|
|
1949
2048
|
import { Search, X as X5 } from "lucide-react";
|
|
1950
|
-
import { jsx as
|
|
2049
|
+
import { jsx as jsx34, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1951
2050
|
function SearchBar({
|
|
1952
2051
|
value,
|
|
1953
2052
|
onChange,
|
|
@@ -1960,8 +2059,8 @@ function SearchBar({
|
|
|
1960
2059
|
}) {
|
|
1961
2060
|
const showClear = onClear !== false && value.length > 0;
|
|
1962
2061
|
return /* @__PURE__ */ jsxs17("div", { className: cn("relative", className), children: [
|
|
1963
|
-
/* @__PURE__ */
|
|
1964
|
-
/* @__PURE__ */
|
|
2062
|
+
/* @__PURE__ */ jsx34("span", { className: "pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-muted-foreground", children: isLoading ? /* @__PURE__ */ jsx34(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx34(Search, { className: "h-4 w-4", "aria-hidden": "true" }) }),
|
|
2063
|
+
/* @__PURE__ */ jsx34(
|
|
1965
2064
|
"input",
|
|
1966
2065
|
{
|
|
1967
2066
|
ref: inputRef,
|
|
@@ -1980,14 +2079,14 @@ function SearchBar({
|
|
|
1980
2079
|
...props
|
|
1981
2080
|
}
|
|
1982
2081
|
),
|
|
1983
|
-
showClear && /* @__PURE__ */
|
|
2082
|
+
showClear && /* @__PURE__ */ jsx34(
|
|
1984
2083
|
"button",
|
|
1985
2084
|
{
|
|
1986
2085
|
type: "button",
|
|
1987
2086
|
onClick: () => onClear ? onClear() : onChange(""),
|
|
1988
2087
|
"aria-label": clearLabel,
|
|
1989
2088
|
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",
|
|
1990
|
-
children: /* @__PURE__ */
|
|
2089
|
+
children: /* @__PURE__ */ jsx34(X5, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
1991
2090
|
}
|
|
1992
2091
|
)
|
|
1993
2092
|
] });
|
|
@@ -1995,7 +2094,7 @@ function SearchBar({
|
|
|
1995
2094
|
|
|
1996
2095
|
// src/patterns/kpi-card/KpiCard.tsx
|
|
1997
2096
|
import { memo } from "react";
|
|
1998
|
-
import { Fragment as
|
|
2097
|
+
import { Fragment as Fragment3, jsx as jsx35, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1999
2098
|
var DONUT_COLORS = [
|
|
2000
2099
|
"var(--chart-1, #3b82f6)",
|
|
2001
2100
|
"var(--chart-2, #22c55e)",
|
|
@@ -2018,11 +2117,11 @@ function DonutChart({
|
|
|
2018
2117
|
let offset = 0;
|
|
2019
2118
|
return /* @__PURE__ */ jsxs18("div", { className: "relative inline-flex shrink-0 items-center justify-center", style: { width: size, height: size }, children: [
|
|
2020
2119
|
/* @__PURE__ */ jsxs18("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "-rotate-90", children: [
|
|
2021
|
-
/* @__PURE__ */
|
|
2120
|
+
/* @__PURE__ */ jsx35("circle", { cx: size / 2, cy: size / 2, r: radius, fill: "none", stroke: "currentColor", className: "text-muted/40", strokeWidth: thickness }),
|
|
2022
2121
|
data.map((segment, i) => {
|
|
2023
2122
|
const fraction = segment.value / total;
|
|
2024
2123
|
const dash = fraction * circumference;
|
|
2025
|
-
const el = /* @__PURE__ */
|
|
2124
|
+
const el = /* @__PURE__ */ jsx35(
|
|
2026
2125
|
"circle",
|
|
2027
2126
|
{
|
|
2028
2127
|
cx: size / 2,
|
|
@@ -2042,8 +2141,8 @@ function DonutChart({
|
|
|
2042
2141
|
})
|
|
2043
2142
|
] }),
|
|
2044
2143
|
(centerValue || centerLabel) && /* @__PURE__ */ jsxs18("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: [
|
|
2045
|
-
centerValue && /* @__PURE__ */
|
|
2046
|
-
centerLabel && /* @__PURE__ */
|
|
2144
|
+
centerValue && /* @__PURE__ */ jsx35("span", { className: "text-base font-black tracking-tight text-foreground tabular-nums", children: centerValue }),
|
|
2145
|
+
centerLabel && /* @__PURE__ */ jsx35("span", { className: "text-[9px] font-medium text-muted-foreground uppercase", children: centerLabel })
|
|
2047
2146
|
] })
|
|
2048
2147
|
] });
|
|
2049
2148
|
}
|
|
@@ -2055,7 +2154,7 @@ function Sparkline({ data, color }) {
|
|
|
2055
2154
|
const W = 72;
|
|
2056
2155
|
const H = 28;
|
|
2057
2156
|
const pts = data.map((v, i) => `${i / (data.length - 1) * W},${H - (v - min) / range * (H - 2) - 1}`).join(" ");
|
|
2058
|
-
return /* @__PURE__ */
|
|
2157
|
+
return /* @__PURE__ */ jsx35("svg", { width: W, height: H, viewBox: `0 0 ${W} ${H}`, className: "overflow-visible opacity-70", children: /* @__PURE__ */ jsx35("polyline", { points: pts, fill: "none", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
2059
2158
|
}
|
|
2060
2159
|
function AreaSparkline({ data, color }) {
|
|
2061
2160
|
if (data.length < 2) return null;
|
|
@@ -2070,13 +2169,13 @@ function AreaSparkline({ data, color }) {
|
|
|
2070
2169
|
const linePts = xs.map((x, i) => `${x},${ys[i]}`).join(" ");
|
|
2071
2170
|
const areaPath = `M${xs[0]},${H} ` + xs.map((x, i) => `L${x},${ys[i]}`).join(" ") + ` L${xs[xs.length - 1]},${H} Z`;
|
|
2072
2171
|
return /* @__PURE__ */ jsxs18("svg", { width: W, height: H, viewBox: `0 0 ${W} ${H}`, className: "overflow-visible", preserveAspectRatio: "none", children: [
|
|
2073
|
-
/* @__PURE__ */
|
|
2074
|
-
/* @__PURE__ */
|
|
2075
|
-
/* @__PURE__ */
|
|
2172
|
+
/* @__PURE__ */ jsx35("defs", { children: /* @__PURE__ */ jsxs18("linearGradient", { id: "kpi-card-spark-fill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
2173
|
+
/* @__PURE__ */ jsx35("stop", { offset: "0%", stopColor: color, stopOpacity: "0.22" }),
|
|
2174
|
+
/* @__PURE__ */ jsx35("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
|
|
2076
2175
|
] }) }),
|
|
2077
|
-
/* @__PURE__ */
|
|
2078
|
-
/* @__PURE__ */
|
|
2079
|
-
/* @__PURE__ */
|
|
2176
|
+
/* @__PURE__ */ jsx35("path", { d: areaPath, fill: "url(#kpi-card-spark-fill)" }),
|
|
2177
|
+
/* @__PURE__ */ jsx35("polyline", { points: linePts, fill: "none", stroke: color, strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
2178
|
+
/* @__PURE__ */ jsx35("circle", { cx: xs[xs.length - 1], cy: ys[ys.length - 1], r: "2.5", fill: color })
|
|
2080
2179
|
] });
|
|
2081
2180
|
}
|
|
2082
2181
|
var rowBetween = "flex items-center justify-between";
|
|
@@ -2097,8 +2196,10 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2097
2196
|
progressLabel,
|
|
2098
2197
|
segments,
|
|
2099
2198
|
centerValue,
|
|
2199
|
+
background,
|
|
2100
2200
|
testId
|
|
2101
2201
|
}) {
|
|
2202
|
+
const bgStyle = background ? { background } : void 0;
|
|
2102
2203
|
if (variant === "hero") {
|
|
2103
2204
|
return /* @__PURE__ */ jsxs18(
|
|
2104
2205
|
"div",
|
|
@@ -2106,17 +2207,17 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2106
2207
|
"data-testid": testId,
|
|
2107
2208
|
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
2209
|
style: {
|
|
2109
|
-
background: "var(--color-primary)",
|
|
2110
|
-
boxShadow:
|
|
2210
|
+
background: background ?? "var(--color-primary)",
|
|
2211
|
+
boxShadow: `0 2px 10px color-mix(in oklch, ${background ?? "var(--color-primary)"} 18%, transparent)`
|
|
2111
2212
|
},
|
|
2112
2213
|
children: [
|
|
2113
2214
|
/* @__PURE__ */ jsxs18("div", { className: "relative flex items-start justify-between", children: [
|
|
2114
|
-
/* @__PURE__ */
|
|
2115
|
-
Icon2 && /* @__PURE__ */
|
|
2215
|
+
/* @__PURE__ */ jsx35("p", { className: "text-[11px] font-bold tracking-[0.14em] text-white/70 uppercase", children: label }),
|
|
2216
|
+
Icon2 && /* @__PURE__ */ jsx35("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-white/15 ring-1 ring-white/20", children: /* @__PURE__ */ jsx35(Icon2, { className: "h-3 w-3 text-white/90", "aria-hidden": "true" }) })
|
|
2116
2217
|
] }),
|
|
2117
|
-
/* @__PURE__ */
|
|
2218
|
+
/* @__PURE__ */ jsx35("div", { className: "relative", children: loading ? /* @__PURE__ */ jsx35("div", { className: "h-6 w-28 animate-pulse rounded-lg bg-white/15" }) : /* @__PURE__ */ jsx35("p", { className: "text-2xl leading-none font-black tracking-tight text-white tabular-nums md:text-[22px]", children: value ?? "\u2014" }) }),
|
|
2118
2219
|
(badge || badgeLabel) && !loading && /* @__PURE__ */ jsxs18("div", { className: "relative flex items-center gap-2", children: [
|
|
2119
|
-
badge && /* @__PURE__ */
|
|
2220
|
+
badge && /* @__PURE__ */ jsx35(
|
|
2120
2221
|
"span",
|
|
2121
2222
|
{
|
|
2122
2223
|
className: cn(
|
|
@@ -2126,43 +2227,44 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2126
2227
|
children: badge.text
|
|
2127
2228
|
}
|
|
2128
2229
|
),
|
|
2129
|
-
badgeLabel && /* @__PURE__ */
|
|
2230
|
+
badgeLabel && /* @__PURE__ */ jsx35("span", { className: "text-[11px] text-white/70", children: badgeLabel })
|
|
2130
2231
|
] }),
|
|
2131
|
-
sub && !loading && /* @__PURE__ */
|
|
2232
|
+
sub && !loading && /* @__PURE__ */ jsx35("p", { className: "relative -mt-1 text-[11px] font-medium text-white/75 tabular-nums", children: sub }),
|
|
2132
2233
|
progress != null && !loading && /* @__PURE__ */ jsxs18("div", { className: "relative flex items-center gap-1.5", children: [
|
|
2133
|
-
/* @__PURE__ */
|
|
2134
|
-
progressLabel && /* @__PURE__ */
|
|
2234
|
+
/* @__PURE__ */ jsx35("div", { className: "h-1 flex-1 overflow-hidden rounded-full bg-white/20", children: /* @__PURE__ */ jsx35("div", { className: "h-1 rounded-full bg-white/85", style: { width: `${Math.min(100, Math.max(0, progress))}%` } }) }),
|
|
2235
|
+
progressLabel && /* @__PURE__ */ jsx35("span", { className: "shrink-0 text-[11px] font-semibold text-white/80 tabular-nums", children: progressLabel })
|
|
2135
2236
|
] }),
|
|
2136
|
-
sparkline && sparkline.length > 1 && !loading && /* @__PURE__ */
|
|
2237
|
+
sparkline && sparkline.length > 1 && !loading && /* @__PURE__ */ jsx35("div", { className: "relative -mx-4 mt-auto -mb-4 md:-mx-3 md:-mb-3", children: /* @__PURE__ */ jsx35(AreaSparkline, { data: sparkline, color: "#ffffff" }) })
|
|
2137
2238
|
]
|
|
2138
2239
|
}
|
|
2139
2240
|
);
|
|
2140
2241
|
}
|
|
2141
2242
|
if (variant === "mini") {
|
|
2142
|
-
const iconSlot = Icon2 ? href ? /* @__PURE__ */
|
|
2243
|
+
const iconSlot = Icon2 ? href ? /* @__PURE__ */ jsx35(
|
|
2143
2244
|
"a",
|
|
2144
2245
|
{
|
|
2145
2246
|
href,
|
|
2146
2247
|
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__ */
|
|
2248
|
+
children: /* @__PURE__ */ jsx35(Icon2, { className: "h-4 w-4 md:h-3.5 md:w-3.5", "aria-hidden": "true" })
|
|
2148
2249
|
}
|
|
2149
|
-
) : /* @__PURE__ */
|
|
2250
|
+
) : /* @__PURE__ */ jsx35("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__ */ jsx35(Icon2, { className: "h-4 w-4 md:h-3.5 md:w-3.5", "aria-hidden": "true" }) }) : null;
|
|
2150
2251
|
return /* @__PURE__ */ jsxs18(
|
|
2151
2252
|
"div",
|
|
2152
2253
|
{
|
|
2153
2254
|
"data-testid": testId,
|
|
2255
|
+
style: bgStyle,
|
|
2154
2256
|
className: cn(
|
|
2155
2257
|
"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
2258
|
href && "hover:border-primary/20 hover:shadow-md"
|
|
2157
2259
|
),
|
|
2158
2260
|
children: [
|
|
2159
2261
|
/* @__PURE__ */ jsxs18("div", { className: rowBetween, children: [
|
|
2160
|
-
/* @__PURE__ */
|
|
2262
|
+
/* @__PURE__ */ jsx35("p", { className: "text-[11px] font-bold tracking-[0.12em] text-muted-foreground uppercase", children: label }),
|
|
2161
2263
|
iconSlot
|
|
2162
2264
|
] }),
|
|
2163
|
-
/* @__PURE__ */
|
|
2164
|
-
/* @__PURE__ */
|
|
2165
|
-
badge && /* @__PURE__ */
|
|
2265
|
+
/* @__PURE__ */ jsx35("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: loading ? /* @__PURE__ */ jsx35(Skeleton, { className: "h-6 w-16" }) : /* @__PURE__ */ jsxs18(Fragment3, { children: [
|
|
2266
|
+
/* @__PURE__ */ jsx35("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" }),
|
|
2267
|
+
badge && /* @__PURE__ */ jsx35(
|
|
2166
2268
|
"span",
|
|
2167
2269
|
{
|
|
2168
2270
|
className: cn(
|
|
@@ -2174,7 +2276,7 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2174
2276
|
)
|
|
2175
2277
|
] }) }),
|
|
2176
2278
|
(sub || sparkline) && !loading && /* @__PURE__ */ jsxs18("div", { className: "flex items-end justify-between gap-2", children: [
|
|
2177
|
-
sub && /* @__PURE__ */
|
|
2279
|
+
sub && /* @__PURE__ */ jsx35(
|
|
2178
2280
|
"p",
|
|
2179
2281
|
{
|
|
2180
2282
|
className: cn(
|
|
@@ -2184,7 +2286,7 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2184
2286
|
children: sub
|
|
2185
2287
|
}
|
|
2186
2288
|
),
|
|
2187
|
-
sparkline && /* @__PURE__ */
|
|
2289
|
+
sparkline && /* @__PURE__ */ jsx35(Sparkline, { data: sparkline, color: sparklineColor })
|
|
2188
2290
|
] })
|
|
2189
2291
|
]
|
|
2190
2292
|
}
|
|
@@ -2192,22 +2294,22 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2192
2294
|
}
|
|
2193
2295
|
if (variant === "donut") {
|
|
2194
2296
|
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: [
|
|
2297
|
+
return /* @__PURE__ */ jsxs18("div", { "data-testid": testId, style: bgStyle, className: "rounded-xl border border-border bg-card p-5", children: [
|
|
2196
2298
|
/* @__PURE__ */ jsxs18("div", { className: rowBetween, children: [
|
|
2197
|
-
/* @__PURE__ */
|
|
2198
|
-
Icon2 && /* @__PURE__ */
|
|
2299
|
+
/* @__PURE__ */ jsx35("p", { className: "text-[11px] font-bold tracking-[0.12em] text-muted-foreground uppercase", children: label }),
|
|
2300
|
+
Icon2 && /* @__PURE__ */ jsx35("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-primary/8 text-primary", children: /* @__PURE__ */ jsx35(Icon2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) })
|
|
2199
2301
|
] }),
|
|
2200
2302
|
loading ? /* @__PURE__ */ jsxs18("div", { className: "mt-3 flex items-center gap-4", children: [
|
|
2201
|
-
/* @__PURE__ */
|
|
2303
|
+
/* @__PURE__ */ jsx35(Skeleton, { className: "h-24 w-24 shrink-0 rounded-full" }),
|
|
2202
2304
|
/* @__PURE__ */ jsxs18("div", { className: "flex flex-1 flex-col gap-2", children: [
|
|
2203
|
-
/* @__PURE__ */
|
|
2204
|
-
/* @__PURE__ */
|
|
2205
|
-
/* @__PURE__ */
|
|
2305
|
+
/* @__PURE__ */ jsx35(Skeleton, { className: "h-3 w-full" }),
|
|
2306
|
+
/* @__PURE__ */ jsx35(Skeleton, { className: "h-3 w-3/4" }),
|
|
2307
|
+
/* @__PURE__ */ jsx35(Skeleton, { className: "h-3 w-1/2" })
|
|
2206
2308
|
] })
|
|
2207
2309
|
] }) : segments && segments.length > 0 ? /* @__PURE__ */ jsxs18("div", { className: "mt-3 flex items-center gap-4", children: [
|
|
2208
|
-
/* @__PURE__ */
|
|
2209
|
-
/* @__PURE__ */
|
|
2210
|
-
/* @__PURE__ */
|
|
2310
|
+
/* @__PURE__ */ jsx35(DonutChart, { data: segments, centerValue: centerValue ?? String(total) }),
|
|
2311
|
+
/* @__PURE__ */ jsx35("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: [
|
|
2312
|
+
/* @__PURE__ */ jsx35(
|
|
2211
2313
|
"span",
|
|
2212
2314
|
{
|
|
2213
2315
|
className: "h-2 w-2 shrink-0 rounded-full",
|
|
@@ -2215,11 +2317,11 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2215
2317
|
"aria-hidden": "true"
|
|
2216
2318
|
}
|
|
2217
2319
|
),
|
|
2218
|
-
/* @__PURE__ */
|
|
2219
|
-
/* @__PURE__ */
|
|
2320
|
+
/* @__PURE__ */ jsx35("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: segment.label }),
|
|
2321
|
+
/* @__PURE__ */ jsx35("span", { className: "shrink-0 font-semibold text-foreground tabular-nums", children: total > 0 ? `${Math.round(segment.value / total * 100)}%` : segment.value })
|
|
2220
2322
|
] }, segment.label)) })
|
|
2221
|
-
] }) : /* @__PURE__ */
|
|
2222
|
-
sub && !loading && /* @__PURE__ */
|
|
2323
|
+
] }) : /* @__PURE__ */ jsx35("span", { className: "mt-3 block text-2xl font-black text-muted-foreground/40", children: "\u2014" }),
|
|
2324
|
+
sub && !loading && /* @__PURE__ */ jsx35("p", { className: "mt-2.5 text-[11px] text-muted-foreground tabular-nums", children: sub })
|
|
2223
2325
|
] });
|
|
2224
2326
|
}
|
|
2225
2327
|
const strValue = value != null ? String(value) : null;
|
|
@@ -2229,28 +2331,28 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2229
2331
|
const prefix = isSuffix ? null : parts?.[0];
|
|
2230
2332
|
const number = isSuffix ? parts[0] : parts?.[1] ?? parts?.[0];
|
|
2231
2333
|
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: [
|
|
2334
|
+
return /* @__PURE__ */ jsxs18("div", { "data-testid": testId, style: bgStyle, className: "rounded-xl border border-border bg-card p-5", children: [
|
|
2233
2335
|
/* @__PURE__ */ jsxs18("div", { className: rowBetween, children: [
|
|
2234
|
-
/* @__PURE__ */
|
|
2235
|
-
Icon2 && /* @__PURE__ */
|
|
2336
|
+
/* @__PURE__ */ jsx35("p", { className: "text-[11px] font-bold tracking-[0.12em] text-muted-foreground uppercase", children: label }),
|
|
2337
|
+
Icon2 && /* @__PURE__ */ jsx35("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-primary/8 text-primary", children: /* @__PURE__ */ jsx35(Icon2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) })
|
|
2236
2338
|
] }),
|
|
2237
2339
|
/* @__PURE__ */ jsxs18("div", { className: "mt-3", children: [
|
|
2238
|
-
loading ? /* @__PURE__ */
|
|
2340
|
+
loading ? /* @__PURE__ */ jsx35(Skeleton, { className: "h-7 w-20" }) : value == null ? /* @__PURE__ */ jsx35("span", { className: "text-2xl font-black text-muted-foreground/40", children: "\u2014" }) : plain ? /* @__PURE__ */ jsx35("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
2341
|
prefix && /* @__PURE__ */ jsxs18("span", { className: "text-sm text-muted-foreground", children: [
|
|
2240
2342
|
prefix,
|
|
2241
2343
|
" "
|
|
2242
2344
|
] }),
|
|
2243
|
-
/* @__PURE__ */
|
|
2345
|
+
/* @__PURE__ */ jsx35("span", { className: "text-2xl", children: number }),
|
|
2244
2346
|
suffix && /* @__PURE__ */ jsxs18("span", { className: "text-sm text-muted-foreground", children: [
|
|
2245
2347
|
" ",
|
|
2246
2348
|
suffix
|
|
2247
2349
|
] })
|
|
2248
2350
|
] }),
|
|
2249
|
-
sub && !loading && /* @__PURE__ */
|
|
2351
|
+
sub && !loading && /* @__PURE__ */ jsx35("p", { className: "mt-0.5 text-[11px] text-muted-foreground tabular-nums", children: sub })
|
|
2250
2352
|
] }),
|
|
2251
2353
|
(badge || badgeLabel || sparkline) && !loading && /* @__PURE__ */ jsxs18("div", { className: "mt-2.5 flex items-end justify-between gap-2", children: [
|
|
2252
2354
|
/* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2253
|
-
badge && /* @__PURE__ */
|
|
2355
|
+
badge && /* @__PURE__ */ jsx35(
|
|
2254
2356
|
"span",
|
|
2255
2357
|
{
|
|
2256
2358
|
className: cn(
|
|
@@ -2260,16 +2362,16 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2260
2362
|
children: badge.text
|
|
2261
2363
|
}
|
|
2262
2364
|
),
|
|
2263
|
-
badgeLabel && /* @__PURE__ */
|
|
2365
|
+
badgeLabel && /* @__PURE__ */ jsx35("span", { className: "text-[11px] text-muted-foreground", children: badgeLabel })
|
|
2264
2366
|
] }),
|
|
2265
|
-
sparkline && /* @__PURE__ */
|
|
2367
|
+
sparkline && /* @__PURE__ */ jsx35(Sparkline, { data: sparkline, color: sparklineColor })
|
|
2266
2368
|
] })
|
|
2267
2369
|
] });
|
|
2268
2370
|
});
|
|
2269
2371
|
|
|
2270
2372
|
// src/patterns/search-panel/SearchPanel.tsx
|
|
2271
2373
|
import { Search as Search2 } from "lucide-react";
|
|
2272
|
-
import { jsx as
|
|
2374
|
+
import { jsx as jsx36, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2273
2375
|
function SearchPanel({
|
|
2274
2376
|
value,
|
|
2275
2377
|
onChange,
|
|
@@ -2294,8 +2396,8 @@ function SearchPanel({
|
|
|
2294
2396
|
),
|
|
2295
2397
|
children: [
|
|
2296
2398
|
/* @__PURE__ */ jsxs19("div", { className: "flex h-14 items-center gap-3 px-4", children: [
|
|
2297
|
-
isLoading ? /* @__PURE__ */
|
|
2298
|
-
/* @__PURE__ */
|
|
2399
|
+
isLoading ? /* @__PURE__ */ jsx36(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx36(Search2, { className: "h-5 w-5 shrink-0 text-muted-foreground", "aria-hidden": "true" }),
|
|
2400
|
+
/* @__PURE__ */ jsx36(
|
|
2299
2401
|
"input",
|
|
2300
2402
|
{
|
|
2301
2403
|
ref: inputRef,
|
|
@@ -2307,7 +2409,7 @@ function SearchPanel({
|
|
|
2307
2409
|
}
|
|
2308
2410
|
),
|
|
2309
2411
|
trailingSlot,
|
|
2310
|
-
shortcutHint && /* @__PURE__ */
|
|
2412
|
+
shortcutHint && /* @__PURE__ */ jsx36(
|
|
2311
2413
|
"kbd",
|
|
2312
2414
|
{
|
|
2313
2415
|
"aria-hidden": "true",
|
|
@@ -2316,7 +2418,7 @@ function SearchPanel({
|
|
|
2316
2418
|
}
|
|
2317
2419
|
)
|
|
2318
2420
|
] }),
|
|
2319
|
-
modes && modes.length > 0 && /* @__PURE__ */
|
|
2421
|
+
modes && modes.length > 0 && /* @__PURE__ */ jsx36("div", { className: "flex items-center gap-2 px-4 pb-3", children: modes.map(({ key, label, icon: Icon2 }) => /* @__PURE__ */ jsxs19(
|
|
2320
2422
|
"button",
|
|
2321
2423
|
{
|
|
2322
2424
|
type: "button",
|
|
@@ -2326,7 +2428,7 @@ function SearchPanel({
|
|
|
2326
2428
|
activeMode === key ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
|
|
2327
2429
|
),
|
|
2328
2430
|
children: [
|
|
2329
|
-
Icon2 && /* @__PURE__ */
|
|
2431
|
+
Icon2 && /* @__PURE__ */ jsx36(Icon2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2330
2432
|
label
|
|
2331
2433
|
]
|
|
2332
2434
|
},
|
|
@@ -2339,9 +2441,9 @@ function SearchPanel({
|
|
|
2339
2441
|
|
|
2340
2442
|
// src/patterns/search-panel/SearchTrigger.tsx
|
|
2341
2443
|
import { Search as Search3 } from "lucide-react";
|
|
2342
|
-
import { jsx as
|
|
2444
|
+
import { jsx as jsx37 } from "react/jsx-runtime";
|
|
2343
2445
|
function SearchTrigger({ label = "Search", ...props }) {
|
|
2344
|
-
return /* @__PURE__ */
|
|
2446
|
+
return /* @__PURE__ */ jsx37(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": label, ...props, children: /* @__PURE__ */ jsx37(Search3, { className: "h-[18px] w-[18px]", "aria-hidden": "true" }) });
|
|
2345
2447
|
}
|
|
2346
2448
|
|
|
2347
2449
|
// src/patterns/sidebar/context.ts
|
|
@@ -2353,7 +2455,7 @@ function useSidebarCollapsed() {
|
|
|
2353
2455
|
}
|
|
2354
2456
|
|
|
2355
2457
|
// src/patterns/sidebar/NavItem.tsx
|
|
2356
|
-
import { jsx as
|
|
2458
|
+
import { jsx as jsx38, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2357
2459
|
function NavItem({
|
|
2358
2460
|
as,
|
|
2359
2461
|
icon: Icon2,
|
|
@@ -2377,7 +2479,7 @@ function NavItem({
|
|
|
2377
2479
|
),
|
|
2378
2480
|
...rest,
|
|
2379
2481
|
children: [
|
|
2380
|
-
Icon2 && /* @__PURE__ */
|
|
2482
|
+
Icon2 && /* @__PURE__ */ jsx38(
|
|
2381
2483
|
Icon2,
|
|
2382
2484
|
{
|
|
2383
2485
|
className: cn("h-[18px] w-[18px] shrink-0", active ? "text-nav-primary" : "text-muted-foreground"),
|
|
@@ -2385,7 +2487,7 @@ function NavItem({
|
|
|
2385
2487
|
"aria-hidden": true
|
|
2386
2488
|
}
|
|
2387
2489
|
),
|
|
2388
|
-
/* @__PURE__ */
|
|
2490
|
+
/* @__PURE__ */ jsx38(
|
|
2389
2491
|
"span",
|
|
2390
2492
|
{
|
|
2391
2493
|
className: cn(
|
|
@@ -2401,7 +2503,7 @@ function NavItem({
|
|
|
2401
2503
|
);
|
|
2402
2504
|
if (!collapsed) return row;
|
|
2403
2505
|
return /* @__PURE__ */ jsxs20(Tooltip, { children: [
|
|
2404
|
-
/* @__PURE__ */
|
|
2506
|
+
/* @__PURE__ */ jsx38(TooltipTrigger, { asChild: true, children: row }),
|
|
2405
2507
|
/* @__PURE__ */ jsxs20(TooltipContent, { side: "right", className: "flex items-center gap-1.5", children: [
|
|
2406
2508
|
label,
|
|
2407
2509
|
badge
|
|
@@ -2411,8 +2513,8 @@ function NavItem({
|
|
|
2411
2513
|
|
|
2412
2514
|
// src/patterns/sidebar/NavGroup.tsx
|
|
2413
2515
|
import { ChevronDown as ChevronDown2 } from "lucide-react";
|
|
2414
|
-
import { useState as
|
|
2415
|
-
import { jsx as
|
|
2516
|
+
import { useState as useState10 } from "react";
|
|
2517
|
+
import { jsx as jsx39, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2416
2518
|
function NavGroup({
|
|
2417
2519
|
label,
|
|
2418
2520
|
defaultCollapsed = false,
|
|
@@ -2421,7 +2523,7 @@ function NavGroup({
|
|
|
2421
2523
|
children,
|
|
2422
2524
|
className
|
|
2423
2525
|
}) {
|
|
2424
|
-
const [internalCollapsed, setInternalCollapsed] =
|
|
2526
|
+
const [internalCollapsed, setInternalCollapsed] = useState10(defaultCollapsed);
|
|
2425
2527
|
const groupCollapsed = collapsed ?? internalCollapsed;
|
|
2426
2528
|
const railCollapsed = useSidebarCollapsed();
|
|
2427
2529
|
const setGroupCollapsed = (next) => {
|
|
@@ -2429,7 +2531,7 @@ function NavGroup({
|
|
|
2429
2531
|
if (collapsed === void 0) setInternalCollapsed(next);
|
|
2430
2532
|
};
|
|
2431
2533
|
return /* @__PURE__ */ jsxs21("div", { className: cn("flex flex-col gap-0.5", className), children: [
|
|
2432
|
-
label && (railCollapsed ? /* @__PURE__ */
|
|
2534
|
+
label && (railCollapsed ? /* @__PURE__ */ jsx39("div", { className: "mx-2 my-1 h-px bg-border/50", "aria-hidden": true }) : /* @__PURE__ */ jsxs21(
|
|
2433
2535
|
"button",
|
|
2434
2536
|
{
|
|
2435
2537
|
type: "button",
|
|
@@ -2437,8 +2539,8 @@ function NavGroup({
|
|
|
2437
2539
|
"aria-expanded": !groupCollapsed,
|
|
2438
2540
|
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",
|
|
2439
2541
|
children: [
|
|
2440
|
-
/* @__PURE__ */
|
|
2441
|
-
/* @__PURE__ */
|
|
2542
|
+
/* @__PURE__ */ jsx39("span", { children: label }),
|
|
2543
|
+
/* @__PURE__ */ jsx39(
|
|
2442
2544
|
ChevronDown2,
|
|
2443
2545
|
{
|
|
2444
2546
|
className: cn(
|
|
@@ -2451,7 +2553,7 @@ function NavGroup({
|
|
|
2451
2553
|
]
|
|
2452
2554
|
}
|
|
2453
2555
|
)),
|
|
2454
|
-
!groupCollapsed && /* @__PURE__ */
|
|
2556
|
+
!groupCollapsed && /* @__PURE__ */ jsx39("div", { className: "flex flex-col gap-0.5", children })
|
|
2455
2557
|
] });
|
|
2456
2558
|
}
|
|
2457
2559
|
|
|
@@ -2461,9 +2563,9 @@ import {
|
|
|
2461
2563
|
useCallback,
|
|
2462
2564
|
useEffect as useEffect5,
|
|
2463
2565
|
useRef as useRef5,
|
|
2464
|
-
useState as
|
|
2566
|
+
useState as useState11
|
|
2465
2567
|
} from "react";
|
|
2466
|
-
import { Fragment as
|
|
2568
|
+
import { Fragment as Fragment4, jsx as jsx40, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2467
2569
|
function resolve(value, collapsed) {
|
|
2468
2570
|
return typeof value === "function" ? value(collapsed) : value;
|
|
2469
2571
|
}
|
|
@@ -2482,9 +2584,9 @@ function Sidebar({
|
|
|
2482
2584
|
children,
|
|
2483
2585
|
className
|
|
2484
2586
|
}) {
|
|
2485
|
-
const [internalWidth, setInternalWidth] =
|
|
2587
|
+
const [internalWidth, setInternalWidth] = useState11(defaultWidth);
|
|
2486
2588
|
const currentWidth = width ?? internalWidth;
|
|
2487
|
-
const [isDragging, setIsDragging] =
|
|
2589
|
+
const [isDragging, setIsDragging] = useState11(false);
|
|
2488
2590
|
const setWidth = useCallback(
|
|
2489
2591
|
(next) => {
|
|
2490
2592
|
const clamped = Math.min(maxWidth, Math.max(minWidth, next));
|
|
@@ -2510,7 +2612,7 @@ function Sidebar({
|
|
|
2510
2612
|
dragStartRef.current = null;
|
|
2511
2613
|
setIsDragging(false);
|
|
2512
2614
|
};
|
|
2513
|
-
return /* @__PURE__ */
|
|
2615
|
+
return /* @__PURE__ */ jsx40(SidebarContextProvider, { value: { collapsed }, children: /* @__PURE__ */ jsx40(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs22(
|
|
2514
2616
|
"aside",
|
|
2515
2617
|
{
|
|
2516
2618
|
style: { width: collapsed ? railWidth : currentWidth },
|
|
@@ -2520,7 +2622,7 @@ function Sidebar({
|
|
|
2520
2622
|
className
|
|
2521
2623
|
),
|
|
2522
2624
|
children: [
|
|
2523
|
-
(header || onCollapsedChange) && /* @__PURE__ */
|
|
2625
|
+
(header || onCollapsedChange) && /* @__PURE__ */ jsx40("div", { className: "flex h-16 items-center gap-2 border-b border-border px-3", children: collapsed && onCollapsedChange ? (
|
|
2524
2626
|
// Collapsed rail: the header slot (e.g. a compact logo mark) and the
|
|
2525
2627
|
// expand toggle would otherwise compete for the narrow rail's width
|
|
2526
2628
|
// and clip each other, so they're stacked instead — the header shows
|
|
@@ -2534,8 +2636,8 @@ function Sidebar({
|
|
|
2534
2636
|
"aria-label": "Expand sidebar",
|
|
2535
2637
|
className: "group relative flex h-9 w-9 shrink-0 items-center justify-center",
|
|
2536
2638
|
children: [
|
|
2537
|
-
header && /* @__PURE__ */
|
|
2538
|
-
/* @__PURE__ */
|
|
2639
|
+
header && /* @__PURE__ */ jsx40("span", { className: "transition-opacity group-hover:opacity-0", children: resolve(header, collapsed) }),
|
|
2640
|
+
/* @__PURE__ */ jsx40(
|
|
2539
2641
|
PanelLeftOpen,
|
|
2540
2642
|
{
|
|
2541
2643
|
className: "absolute h-4 w-4 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
|
|
@@ -2545,22 +2647,22 @@ function Sidebar({
|
|
|
2545
2647
|
]
|
|
2546
2648
|
}
|
|
2547
2649
|
)
|
|
2548
|
-
) : /* @__PURE__ */ jsxs22(
|
|
2549
|
-
header && /* @__PURE__ */
|
|
2550
|
-
onCollapsedChange && /* @__PURE__ */
|
|
2650
|
+
) : /* @__PURE__ */ jsxs22(Fragment4, { children: [
|
|
2651
|
+
header && /* @__PURE__ */ jsx40("div", { className: "min-w-0 flex-1", children: resolve(header, collapsed) }),
|
|
2652
|
+
onCollapsedChange && /* @__PURE__ */ jsx40(
|
|
2551
2653
|
"button",
|
|
2552
2654
|
{
|
|
2553
2655
|
type: "button",
|
|
2554
2656
|
onClick: () => onCollapsedChange(!collapsed),
|
|
2555
2657
|
"aria-label": "Collapse sidebar",
|
|
2556
2658
|
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
2557
|
-
children: /* @__PURE__ */
|
|
2659
|
+
children: /* @__PURE__ */ jsx40(PanelLeftClose, { className: "h-4 w-4", "aria-hidden": true })
|
|
2558
2660
|
}
|
|
2559
2661
|
)
|
|
2560
2662
|
] }) }),
|
|
2561
|
-
/* @__PURE__ */
|
|
2562
|
-
footer && /* @__PURE__ */
|
|
2563
|
-
resizable && !collapsed && /* @__PURE__ */
|
|
2663
|
+
/* @__PURE__ */ jsx40(SidebarNav, { children }),
|
|
2664
|
+
footer && /* @__PURE__ */ jsx40("div", { className: "border-t border-border/50 px-2 py-2", children: resolve(footer, collapsed) }),
|
|
2665
|
+
resizable && !collapsed && /* @__PURE__ */ jsx40(
|
|
2564
2666
|
"div",
|
|
2565
2667
|
{
|
|
2566
2668
|
onPointerDown: handlePointerDown,
|
|
@@ -2576,7 +2678,7 @@ function Sidebar({
|
|
|
2576
2678
|
}
|
|
2577
2679
|
function SidebarNav({ children, className, ...rest }) {
|
|
2578
2680
|
const ref = useRef5(null);
|
|
2579
|
-
const [showFade, setShowFade] =
|
|
2681
|
+
const [showFade, setShowFade] = useState11(false);
|
|
2580
2682
|
useEffect5(() => {
|
|
2581
2683
|
const el = ref.current;
|
|
2582
2684
|
if (!el) return;
|
|
@@ -2591,7 +2693,7 @@ function SidebarNav({ children, className, ...rest }) {
|
|
|
2591
2693
|
};
|
|
2592
2694
|
}, []);
|
|
2593
2695
|
return /* @__PURE__ */ jsxs22("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
|
|
2594
|
-
/* @__PURE__ */
|
|
2696
|
+
/* @__PURE__ */ jsx40(
|
|
2595
2697
|
"div",
|
|
2596
2698
|
{
|
|
2597
2699
|
ref,
|
|
@@ -2600,7 +2702,7 @@ function SidebarNav({ children, className, ...rest }) {
|
|
|
2600
2702
|
children
|
|
2601
2703
|
}
|
|
2602
2704
|
),
|
|
2603
|
-
showFade && /* @__PURE__ */
|
|
2705
|
+
showFade && /* @__PURE__ */ jsx40(
|
|
2604
2706
|
"div",
|
|
2605
2707
|
{
|
|
2606
2708
|
className: "pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-sidebar to-transparent",
|
|
@@ -2610,10 +2712,326 @@ function SidebarNav({ children, className, ...rest }) {
|
|
|
2610
2712
|
] });
|
|
2611
2713
|
}
|
|
2612
2714
|
|
|
2715
|
+
// src/patterns/ai/AiButton.tsx
|
|
2716
|
+
import { Sparkles } from "lucide-react";
|
|
2717
|
+
import { forwardRef as forwardRef17 } from "react";
|
|
2718
|
+
import { jsx as jsx41, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2719
|
+
var AiButton = forwardRef17(
|
|
2720
|
+
({ variant = "ai-subtle", loading = false, icon: Icon2 = Sparkles, disabled, children, ...props }, ref) => /* @__PURE__ */ jsxs23(
|
|
2721
|
+
Button,
|
|
2722
|
+
{
|
|
2723
|
+
ref,
|
|
2724
|
+
variant,
|
|
2725
|
+
disabled: disabled || loading,
|
|
2726
|
+
"aria-busy": loading || void 0,
|
|
2727
|
+
...props,
|
|
2728
|
+
children: [
|
|
2729
|
+
loading ? /* @__PURE__ */ jsx41(LoadingSpinner, { size: "sm", className: "[&_svg]:text-current" }) : /* @__PURE__ */ jsx41(Icon2, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
2730
|
+
children
|
|
2731
|
+
]
|
|
2732
|
+
}
|
|
2733
|
+
)
|
|
2734
|
+
);
|
|
2735
|
+
AiButton.displayName = "AiButton";
|
|
2736
|
+
|
|
2737
|
+
// src/patterns/ai/AiExplainButton.tsx
|
|
2738
|
+
import { RefreshCw, Sparkles as Sparkles2 } from "lucide-react";
|
|
2739
|
+
import { useState as useState12 } from "react";
|
|
2740
|
+
import { jsx as jsx42, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2741
|
+
function AiExplainButton({
|
|
2742
|
+
onExplain,
|
|
2743
|
+
label = "Explain",
|
|
2744
|
+
title,
|
|
2745
|
+
align = "start",
|
|
2746
|
+
contentClassName = "w-80",
|
|
2747
|
+
size = "sm",
|
|
2748
|
+
...props
|
|
2749
|
+
}) {
|
|
2750
|
+
const [open, setOpen] = useState12(false);
|
|
2751
|
+
const [loading, setLoading] = useState12(false);
|
|
2752
|
+
const [content, setContent] = useState12(null);
|
|
2753
|
+
const [error, setError] = useState12(null);
|
|
2754
|
+
const load = async () => {
|
|
2755
|
+
setLoading(true);
|
|
2756
|
+
setError(null);
|
|
2757
|
+
try {
|
|
2758
|
+
setContent(await onExplain());
|
|
2759
|
+
} catch (err) {
|
|
2760
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2761
|
+
} finally {
|
|
2762
|
+
setLoading(false);
|
|
2763
|
+
}
|
|
2764
|
+
};
|
|
2765
|
+
const handleOpenChange = (next) => {
|
|
2766
|
+
setOpen(next);
|
|
2767
|
+
if (next && content === null && error === null && !loading) void load();
|
|
2768
|
+
};
|
|
2769
|
+
return /* @__PURE__ */ jsxs24(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2770
|
+
/* @__PURE__ */ jsx42(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(AiButton, { size, ...props, children: label }) }),
|
|
2771
|
+
/* @__PURE__ */ jsxs24(PopoverContent, { align, className: contentClassName, children: [
|
|
2772
|
+
/* @__PURE__ */ jsxs24("p", { className: "mb-2 flex items-center gap-1.5 text-sm font-semibold", children: [
|
|
2773
|
+
/* @__PURE__ */ jsx42(Sparkles2, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }),
|
|
2774
|
+
title ?? label
|
|
2775
|
+
] }),
|
|
2776
|
+
loading ? /* @__PURE__ */ jsx42(LoadingSpinner, { size: "sm", label: "Thinking\u2026", className: "text-sm text-muted-foreground" }) : error ? /* @__PURE__ */ jsxs24("div", { className: "flex flex-col items-start gap-2", children: [
|
|
2777
|
+
/* @__PURE__ */ jsx42("div", { role: "alert", className: "w-full", children: /* @__PURE__ */ jsx42(AlertBox, { variant: "error", children: error }) }),
|
|
2778
|
+
/* @__PURE__ */ jsxs24(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void load(), children: [
|
|
2779
|
+
/* @__PURE__ */ jsx42(RefreshCw, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
2780
|
+
"Try again"
|
|
2781
|
+
] })
|
|
2782
|
+
] }) : /* @__PURE__ */ jsx42("div", { className: "max-h-80 overflow-y-auto text-sm leading-relaxed", children: content })
|
|
2783
|
+
] })
|
|
2784
|
+
] });
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// src/patterns/ai/VoiceInputButton.tsx
|
|
2788
|
+
import { Mic, MicOff } from "lucide-react";
|
|
2789
|
+
import { forwardRef as forwardRef18 } from "react";
|
|
2790
|
+
|
|
2791
|
+
// src/patterns/ai/useSpeechRecognition.ts
|
|
2792
|
+
import { useCallback as useCallback2, useEffect as useEffect6, useRef as useRef6, useState as useState13 } from "react";
|
|
2793
|
+
function getCtor() {
|
|
2794
|
+
if (typeof window === "undefined") return void 0;
|
|
2795
|
+
const w = window;
|
|
2796
|
+
return w.SpeechRecognition ?? w.webkitSpeechRecognition;
|
|
2797
|
+
}
|
|
2798
|
+
function browserEngine() {
|
|
2799
|
+
const Ctor = getCtor();
|
|
2800
|
+
return Ctor ? new Ctor() : null;
|
|
2801
|
+
}
|
|
2802
|
+
function describeSpeechError(code) {
|
|
2803
|
+
switch (code) {
|
|
2804
|
+
case "aborted":
|
|
2805
|
+
return null;
|
|
2806
|
+
case "not-allowed":
|
|
2807
|
+
case "service-not-allowed":
|
|
2808
|
+
return "Microphone access is blocked. Allow it in the browser's site settings and try again.";
|
|
2809
|
+
case "audio-capture":
|
|
2810
|
+
return "No microphone was found.";
|
|
2811
|
+
case "no-speech":
|
|
2812
|
+
return "No speech was detected.";
|
|
2813
|
+
case "network":
|
|
2814
|
+
return "Speech recognition needs a network connection.";
|
|
2815
|
+
default:
|
|
2816
|
+
return `Speech recognition failed (${code}).`;
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
function useSpeechRecognition({
|
|
2820
|
+
lang,
|
|
2821
|
+
onResult,
|
|
2822
|
+
onError,
|
|
2823
|
+
engine
|
|
2824
|
+
} = {}) {
|
|
2825
|
+
const [listening, setListening] = useState13(false);
|
|
2826
|
+
const [interim, setInterim] = useState13("");
|
|
2827
|
+
const [error, setError] = useState13(null);
|
|
2828
|
+
const recognitionRef = useRef6(null);
|
|
2829
|
+
const onResultRef = useRef6(onResult);
|
|
2830
|
+
const onErrorRef = useRef6(onError);
|
|
2831
|
+
const engineRef = useRef6(engine);
|
|
2832
|
+
onResultRef.current = onResult;
|
|
2833
|
+
onErrorRef.current = onError;
|
|
2834
|
+
engineRef.current = engine;
|
|
2835
|
+
const supported = engine !== void 0 || getCtor() !== void 0;
|
|
2836
|
+
const stop = useCallback2(() => {
|
|
2837
|
+
recognitionRef.current?.stop();
|
|
2838
|
+
}, []);
|
|
2839
|
+
const start = useCallback2(() => {
|
|
2840
|
+
if (recognitionRef.current) return;
|
|
2841
|
+
const recognition = engineRef.current ? engineRef.current() : browserEngine();
|
|
2842
|
+
if (!recognition) return;
|
|
2843
|
+
recognition.lang = lang ?? (typeof document !== "undefined" ? document.documentElement.lang : "") ?? "";
|
|
2844
|
+
if (!recognition.lang) recognition.lang = "en-US";
|
|
2845
|
+
recognition.continuous = true;
|
|
2846
|
+
recognition.interimResults = true;
|
|
2847
|
+
recognition.onresult = (event) => {
|
|
2848
|
+
let pending = "";
|
|
2849
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
2850
|
+
const result = event.results[i];
|
|
2851
|
+
if (!result) continue;
|
|
2852
|
+
if (result.isFinal) onResultRef.current?.(result[0].transcript.trim());
|
|
2853
|
+
else pending += result[0].transcript;
|
|
2854
|
+
}
|
|
2855
|
+
setInterim(pending);
|
|
2856
|
+
};
|
|
2857
|
+
recognition.onerror = (event) => {
|
|
2858
|
+
setError(event.error);
|
|
2859
|
+
onErrorRef.current?.(event.error);
|
|
2860
|
+
};
|
|
2861
|
+
recognition.onend = () => {
|
|
2862
|
+
recognitionRef.current = null;
|
|
2863
|
+
setListening(false);
|
|
2864
|
+
setInterim("");
|
|
2865
|
+
};
|
|
2866
|
+
recognitionRef.current = recognition;
|
|
2867
|
+
setError(null);
|
|
2868
|
+
setInterim("");
|
|
2869
|
+
setListening(true);
|
|
2870
|
+
recognition.start();
|
|
2871
|
+
}, [lang]);
|
|
2872
|
+
const toggle = useCallback2(() => {
|
|
2873
|
+
if (recognitionRef.current) stop();
|
|
2874
|
+
else start();
|
|
2875
|
+
}, [start, stop]);
|
|
2876
|
+
useEffect6(() => () => recognitionRef.current?.abort(), []);
|
|
2877
|
+
return { supported, listening, interim, error, start, stop, toggle };
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
// src/patterns/ai/VoiceInputButton.tsx
|
|
2881
|
+
import { jsx as jsx43, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2882
|
+
var VoiceInputButton = forwardRef18(
|
|
2883
|
+
({
|
|
2884
|
+
onTranscript,
|
|
2885
|
+
onError,
|
|
2886
|
+
lang,
|
|
2887
|
+
engine,
|
|
2888
|
+
label,
|
|
2889
|
+
startLabel = "Start dictation",
|
|
2890
|
+
stopLabel = "Stop dictation",
|
|
2891
|
+
showInterim = false,
|
|
2892
|
+
size,
|
|
2893
|
+
disabled,
|
|
2894
|
+
className,
|
|
2895
|
+
...props
|
|
2896
|
+
}, ref) => {
|
|
2897
|
+
const { supported, listening, interim, toggle } = useSpeechRecognition({
|
|
2898
|
+
lang,
|
|
2899
|
+
onResult: onTranscript,
|
|
2900
|
+
onError,
|
|
2901
|
+
engine
|
|
2902
|
+
});
|
|
2903
|
+
const unsupportedTitle = "Speech recognition isn't available in this browser";
|
|
2904
|
+
return (
|
|
2905
|
+
// `title` lives on the wrapper, not the button: buttonVariants sets
|
|
2906
|
+
// `disabled:pointer-events-none`, so a disabled button never shows its own.
|
|
2907
|
+
/* @__PURE__ */ jsxs25("span", { className: "inline-flex min-w-0 items-center gap-2", title: supported ? void 0 : unsupportedTitle, children: [
|
|
2908
|
+
/* @__PURE__ */ jsxs25(
|
|
2909
|
+
Button,
|
|
2910
|
+
{
|
|
2911
|
+
ref,
|
|
2912
|
+
type: "button",
|
|
2913
|
+
variant: "outline",
|
|
2914
|
+
size: size ?? (label ? "default" : "icon"),
|
|
2915
|
+
onClick: toggle,
|
|
2916
|
+
disabled: disabled || !supported,
|
|
2917
|
+
"aria-label": label ? void 0 : supported ? listening ? stopLabel : startLabel : unsupportedTitle,
|
|
2918
|
+
"aria-pressed": supported ? listening : void 0,
|
|
2919
|
+
className: cn(
|
|
2920
|
+
listening && "border-destructive bg-destructive/10 text-red-800 hover:bg-destructive/15 hover:text-red-800 dark:text-red-300 dark:hover:text-red-300",
|
|
2921
|
+
className
|
|
2922
|
+
),
|
|
2923
|
+
...props,
|
|
2924
|
+
children: [
|
|
2925
|
+
supported ? /* @__PURE__ */ jsx43(Mic, { className: cn("h-4 w-4", listening && "animate-pulse motion-reduce:animate-none"), "aria-hidden": "true" }) : /* @__PURE__ */ jsx43(MicOff, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
2926
|
+
label
|
|
2927
|
+
]
|
|
2928
|
+
}
|
|
2929
|
+
),
|
|
2930
|
+
showInterim && interim ? /* @__PURE__ */ jsx43("span", { "aria-live": "polite", className: "truncate text-sm text-muted-foreground italic", children: interim }) : null
|
|
2931
|
+
] })
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
);
|
|
2935
|
+
VoiceInputButton.displayName = "VoiceInputButton";
|
|
2936
|
+
|
|
2937
|
+
// src/patterns/ai/VoiceTranscript.tsx
|
|
2938
|
+
import { Undo2, Wand2 } from "lucide-react";
|
|
2939
|
+
import { useState as useState14 } from "react";
|
|
2940
|
+
import { jsx as jsx44, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2941
|
+
function VoiceTranscript({
|
|
2942
|
+
value,
|
|
2943
|
+
onChange,
|
|
2944
|
+
onTransform,
|
|
2945
|
+
transformLabel = "Transform with AI",
|
|
2946
|
+
placeholder = "Speak or type\u2026",
|
|
2947
|
+
lang,
|
|
2948
|
+
engine,
|
|
2949
|
+
rows = 4,
|
|
2950
|
+
disabled,
|
|
2951
|
+
className,
|
|
2952
|
+
...props
|
|
2953
|
+
}) {
|
|
2954
|
+
const [loading, setLoading] = useState14(false);
|
|
2955
|
+
const [error, setError] = useState14(null);
|
|
2956
|
+
const [previous, setPrevious] = useState14(null);
|
|
2957
|
+
const append = (chunk) => {
|
|
2958
|
+
onChange(value ? `${value.trimEnd()} ${chunk}` : chunk);
|
|
2959
|
+
};
|
|
2960
|
+
const transform = async () => {
|
|
2961
|
+
if (!onTransform) return;
|
|
2962
|
+
const before = value;
|
|
2963
|
+
setLoading(true);
|
|
2964
|
+
setError(null);
|
|
2965
|
+
try {
|
|
2966
|
+
const next = await onTransform(before);
|
|
2967
|
+
setPrevious(before);
|
|
2968
|
+
onChange(next);
|
|
2969
|
+
} catch (err) {
|
|
2970
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2971
|
+
} finally {
|
|
2972
|
+
setLoading(false);
|
|
2973
|
+
}
|
|
2974
|
+
};
|
|
2975
|
+
const undo = () => {
|
|
2976
|
+
if (previous === null) return;
|
|
2977
|
+
onChange(previous);
|
|
2978
|
+
setPrevious(null);
|
|
2979
|
+
};
|
|
2980
|
+
return /* @__PURE__ */ jsxs26("div", { className: cn("flex flex-col gap-2", className), ...props, children: [
|
|
2981
|
+
/* @__PURE__ */ jsx44(
|
|
2982
|
+
Textarea,
|
|
2983
|
+
{
|
|
2984
|
+
value,
|
|
2985
|
+
onChange: (event) => onChange(event.target.value),
|
|
2986
|
+
placeholder,
|
|
2987
|
+
rows,
|
|
2988
|
+
disabled: disabled || loading,
|
|
2989
|
+
"aria-busy": loading || void 0
|
|
2990
|
+
}
|
|
2991
|
+
),
|
|
2992
|
+
/* @__PURE__ */ jsxs26("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2993
|
+
/* @__PURE__ */ jsx44(
|
|
2994
|
+
VoiceInputButton,
|
|
2995
|
+
{
|
|
2996
|
+
onTranscript: append,
|
|
2997
|
+
onError: (code) => setError(describeSpeechError(code)),
|
|
2998
|
+
lang,
|
|
2999
|
+
engine,
|
|
3000
|
+
label: "Dictate",
|
|
3001
|
+
size: "sm",
|
|
3002
|
+
showInterim: true,
|
|
3003
|
+
disabled: disabled || loading
|
|
3004
|
+
}
|
|
3005
|
+
),
|
|
3006
|
+
onTransform ? /* @__PURE__ */ jsx44(
|
|
3007
|
+
AiButton,
|
|
3008
|
+
{
|
|
3009
|
+
variant: "ai",
|
|
3010
|
+
size: "sm",
|
|
3011
|
+
icon: Wand2,
|
|
3012
|
+
loading,
|
|
3013
|
+
disabled: disabled || value.trim().length === 0,
|
|
3014
|
+
onClick: () => void transform(),
|
|
3015
|
+
children: transformLabel
|
|
3016
|
+
}
|
|
3017
|
+
) : null,
|
|
3018
|
+
previous !== null && !loading ? /* @__PURE__ */ jsxs26(Button, { type: "button", variant: "ghost", size: "sm", onClick: undo, children: [
|
|
3019
|
+
/* @__PURE__ */ jsx44(Undo2, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
3020
|
+
"Undo"
|
|
3021
|
+
] }) : null
|
|
3022
|
+
] }),
|
|
3023
|
+
error ? (
|
|
3024
|
+
// AlertBox has no `role` prop, so the live region wraps it; same shape
|
|
3025
|
+
// a consuming app's own form errors take.
|
|
3026
|
+
/* @__PURE__ */ jsx44("div", { role: "alert", children: /* @__PURE__ */ jsx44(AlertBox, { variant: "error", children: error }) })
|
|
3027
|
+
) : null
|
|
3028
|
+
] });
|
|
3029
|
+
}
|
|
3030
|
+
|
|
2613
3031
|
// src/patterns/stepper/Stepper.tsx
|
|
2614
|
-
import { useEffect as
|
|
3032
|
+
import { useEffect as useEffect7, useRef as useRef7 } from "react";
|
|
2615
3033
|
import { Check as Check5, Plus } from "lucide-react";
|
|
2616
|
-
import { jsx as
|
|
3034
|
+
import { jsx as jsx45, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2617
3035
|
function Stepper({
|
|
2618
3036
|
step,
|
|
2619
3037
|
maxStep,
|
|
@@ -2625,8 +3043,8 @@ function Stepper({
|
|
|
2625
3043
|
scrollTestId = "stepper-scroll",
|
|
2626
3044
|
newTestId
|
|
2627
3045
|
}) {
|
|
2628
|
-
const stepRefs =
|
|
2629
|
-
|
|
3046
|
+
const stepRefs = useRef7(/* @__PURE__ */ new Map());
|
|
3047
|
+
useEffect7(() => {
|
|
2630
3048
|
const activeStep = stepRefs.current.get(step);
|
|
2631
3049
|
activeStep?.scrollIntoView({ block: "nearest", inline: "center" });
|
|
2632
3050
|
}, [step]);
|
|
@@ -2634,19 +3052,19 @@ function Stepper({
|
|
|
2634
3052
|
// onNew is pinned outside the horizontally-scrolling region below (a sibling of
|
|
2635
3053
|
// it, not a child) so its own edge never scrolls off-screen along with the
|
|
2636
3054
|
// later steps once the scroller's min-width doesn't fit the viewport.
|
|
2637
|
-
/* @__PURE__ */
|
|
2638
|
-
/* @__PURE__ */
|
|
3055
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex w-full shrink-0 items-center gap-2", children: [
|
|
3056
|
+
/* @__PURE__ */ jsx45("nav", { "aria-label": navLabel, className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx45(
|
|
2639
3057
|
"div",
|
|
2640
3058
|
{
|
|
2641
3059
|
"data-testid": scrollTestId,
|
|
2642
3060
|
className: "flex w-full items-center gap-0 overflow-x-auto px-2 py-1",
|
|
2643
|
-
children: /* @__PURE__ */
|
|
3061
|
+
children: /* @__PURE__ */ jsx45("ol", { className: "flex min-w-[700px] flex-1 items-start", children: steps.map(({ n, label, phase }, i) => {
|
|
2644
3062
|
const done = step > n;
|
|
2645
3063
|
const active = step === n;
|
|
2646
3064
|
const navigable = n <= maxStep && n !== step;
|
|
2647
3065
|
const last = i === steps.length - 1;
|
|
2648
|
-
return /* @__PURE__ */
|
|
2649
|
-
/* @__PURE__ */
|
|
3066
|
+
return /* @__PURE__ */ jsxs27("li", { className: cn("flex items-start", !last && "flex-1"), children: [
|
|
3067
|
+
/* @__PURE__ */ jsxs27(
|
|
2650
3068
|
"button",
|
|
2651
3069
|
{
|
|
2652
3070
|
ref: (element) => {
|
|
@@ -2662,17 +3080,17 @@ function Stepper({
|
|
|
2662
3080
|
navigable ? "cursor-pointer hover:opacity-80" : "cursor-default"
|
|
2663
3081
|
),
|
|
2664
3082
|
children: [
|
|
2665
|
-
/* @__PURE__ */
|
|
3083
|
+
/* @__PURE__ */ jsx45(
|
|
2666
3084
|
"div",
|
|
2667
3085
|
{
|
|
2668
3086
|
className: cn(
|
|
2669
3087
|
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[11px] font-bold transition-colors",
|
|
2670
3088
|
done ? "bg-primary/15 text-primary" : active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"
|
|
2671
3089
|
),
|
|
2672
|
-
children: done ? /* @__PURE__ */
|
|
3090
|
+
children: done ? /* @__PURE__ */ jsx45(Check5, { className: "h-3 w-3" }) : n
|
|
2673
3091
|
}
|
|
2674
3092
|
),
|
|
2675
|
-
/* @__PURE__ */
|
|
3093
|
+
/* @__PURE__ */ jsx45(
|
|
2676
3094
|
"span",
|
|
2677
3095
|
{
|
|
2678
3096
|
className: cn(
|
|
@@ -2682,11 +3100,11 @@ function Stepper({
|
|
|
2682
3100
|
children: label
|
|
2683
3101
|
}
|
|
2684
3102
|
),
|
|
2685
|
-
/* @__PURE__ */
|
|
3103
|
+
/* @__PURE__ */ jsx45("span", { className: "text-[9px] tracking-[0.5px] text-muted-foreground/50 uppercase", children: phase })
|
|
2686
3104
|
]
|
|
2687
3105
|
}
|
|
2688
3106
|
),
|
|
2689
|
-
!last && /* @__PURE__ */
|
|
3107
|
+
!last && /* @__PURE__ */ jsx45(
|
|
2690
3108
|
"div",
|
|
2691
3109
|
{
|
|
2692
3110
|
className: cn(
|
|
@@ -2699,7 +3117,7 @@ function Stepper({
|
|
|
2699
3117
|
}) })
|
|
2700
3118
|
}
|
|
2701
3119
|
) }),
|
|
2702
|
-
onNew && /* @__PURE__ */
|
|
3120
|
+
onNew && /* @__PURE__ */ jsxs27(
|
|
2703
3121
|
"button",
|
|
2704
3122
|
{
|
|
2705
3123
|
"data-testid": newTestId,
|
|
@@ -2707,7 +3125,7 @@ function Stepper({
|
|
|
2707
3125
|
title: newLabel,
|
|
2708
3126
|
className: "flex shrink-0 items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-[12px] font-semibold text-foreground transition-colors hover:bg-muted/50",
|
|
2709
3127
|
children: [
|
|
2710
|
-
/* @__PURE__ */
|
|
3128
|
+
/* @__PURE__ */ jsx45(Plus, { className: "h-3.5 w-3.5" }),
|
|
2711
3129
|
newLabel
|
|
2712
3130
|
]
|
|
2713
3131
|
}
|
|
@@ -2716,6 +3134,8 @@ function Stepper({
|
|
|
2716
3134
|
);
|
|
2717
3135
|
}
|
|
2718
3136
|
export {
|
|
3137
|
+
AiButton,
|
|
3138
|
+
AiExplainButton,
|
|
2719
3139
|
AlertBox,
|
|
2720
3140
|
Badge,
|
|
2721
3141
|
Button,
|
|
@@ -2730,6 +3150,7 @@ export {
|
|
|
2730
3150
|
Combobox,
|
|
2731
3151
|
ConfirmDialog,
|
|
2732
3152
|
Dialog,
|
|
3153
|
+
DialogActions,
|
|
2733
3154
|
DialogContent,
|
|
2734
3155
|
DialogDescription,
|
|
2735
3156
|
DialogFooter,
|
|
@@ -2766,6 +3187,7 @@ export {
|
|
|
2766
3187
|
PopoverAnchor,
|
|
2767
3188
|
PopoverContent,
|
|
2768
3189
|
PopoverTrigger,
|
|
3190
|
+
QuestionDialog,
|
|
2769
3191
|
ResponsivePanel,
|
|
2770
3192
|
ScrollArea,
|
|
2771
3193
|
ScrollBar,
|
|
@@ -2813,12 +3235,18 @@ export {
|
|
|
2813
3235
|
TooltipContent,
|
|
2814
3236
|
TooltipProvider,
|
|
2815
3237
|
TooltipTrigger,
|
|
3238
|
+
VoiceInputButton,
|
|
3239
|
+
VoiceTranscript,
|
|
2816
3240
|
badgeVariants,
|
|
2817
3241
|
buttonVariants,
|
|
2818
3242
|
cn,
|
|
3243
|
+
describeSpeechError,
|
|
3244
|
+
getConfirmButtonPlacement,
|
|
3245
|
+
isWindowsPlatform,
|
|
2819
3246
|
loadingSpinnerIconVariants,
|
|
2820
3247
|
sheetVariants,
|
|
2821
3248
|
useMediaQuery,
|
|
2822
|
-
useSidebarCollapsed
|
|
3249
|
+
useSidebarCollapsed,
|
|
3250
|
+
useSpeechRecognition
|
|
2823
3251
|
};
|
|
2824
3252
|
//# sourceMappingURL=index.js.map
|