@bmsuisse/ui 0.15.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 +205 -2
- package/dist/index.js +356 -22
- 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",
|
|
@@ -2187,8 +2196,10 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2187
2196
|
progressLabel,
|
|
2188
2197
|
segments,
|
|
2189
2198
|
centerValue,
|
|
2199
|
+
background,
|
|
2190
2200
|
testId
|
|
2191
2201
|
}) {
|
|
2202
|
+
const bgStyle = background ? { background } : void 0;
|
|
2192
2203
|
if (variant === "hero") {
|
|
2193
2204
|
return /* @__PURE__ */ jsxs18(
|
|
2194
2205
|
"div",
|
|
@@ -2196,8 +2207,8 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2196
2207
|
"data-testid": testId,
|
|
2197
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]",
|
|
2198
2209
|
style: {
|
|
2199
|
-
background: "var(--color-primary)",
|
|
2200
|
-
boxShadow:
|
|
2210
|
+
background: background ?? "var(--color-primary)",
|
|
2211
|
+
boxShadow: `0 2px 10px color-mix(in oklch, ${background ?? "var(--color-primary)"} 18%, transparent)`
|
|
2201
2212
|
},
|
|
2202
2213
|
children: [
|
|
2203
2214
|
/* @__PURE__ */ jsxs18("div", { className: "relative flex items-start justify-between", children: [
|
|
@@ -2241,6 +2252,7 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2241
2252
|
"div",
|
|
2242
2253
|
{
|
|
2243
2254
|
"data-testid": testId,
|
|
2255
|
+
style: bgStyle,
|
|
2244
2256
|
className: cn(
|
|
2245
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",
|
|
2246
2258
|
href && "hover:border-primary/20 hover:shadow-md"
|
|
@@ -2282,7 +2294,7 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2282
2294
|
}
|
|
2283
2295
|
if (variant === "donut") {
|
|
2284
2296
|
const total = segments?.reduce((sum, s) => sum + s.value, 0) ?? 0;
|
|
2285
|
-
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: [
|
|
2286
2298
|
/* @__PURE__ */ jsxs18("div", { className: rowBetween, children: [
|
|
2287
2299
|
/* @__PURE__ */ jsx35("p", { className: "text-[11px] font-bold tracking-[0.12em] text-muted-foreground uppercase", children: label }),
|
|
2288
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" }) })
|
|
@@ -2319,7 +2331,7 @@ var KpiCard = memo(function KpiCard2({
|
|
|
2319
2331
|
const prefix = isSuffix ? null : parts?.[0];
|
|
2320
2332
|
const number = isSuffix ? parts[0] : parts?.[1] ?? parts?.[0];
|
|
2321
2333
|
const suffix = isSuffix ? parts[1] : null;
|
|
2322
|
-
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: [
|
|
2323
2335
|
/* @__PURE__ */ jsxs18("div", { className: rowBetween, children: [
|
|
2324
2336
|
/* @__PURE__ */ jsx35("p", { className: "text-[11px] font-bold tracking-[0.12em] text-muted-foreground uppercase", children: label }),
|
|
2325
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" }) })
|
|
@@ -2700,10 +2712,326 @@ function SidebarNav({ children, className, ...rest }) {
|
|
|
2700
2712
|
] });
|
|
2701
2713
|
}
|
|
2702
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
|
+
|
|
2703
3031
|
// src/patterns/stepper/Stepper.tsx
|
|
2704
|
-
import { useEffect as
|
|
3032
|
+
import { useEffect as useEffect7, useRef as useRef7 } from "react";
|
|
2705
3033
|
import { Check as Check5, Plus } from "lucide-react";
|
|
2706
|
-
import { jsx as
|
|
3034
|
+
import { jsx as jsx45, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2707
3035
|
function Stepper({
|
|
2708
3036
|
step,
|
|
2709
3037
|
maxStep,
|
|
@@ -2715,8 +3043,8 @@ function Stepper({
|
|
|
2715
3043
|
scrollTestId = "stepper-scroll",
|
|
2716
3044
|
newTestId
|
|
2717
3045
|
}) {
|
|
2718
|
-
const stepRefs =
|
|
2719
|
-
|
|
3046
|
+
const stepRefs = useRef7(/* @__PURE__ */ new Map());
|
|
3047
|
+
useEffect7(() => {
|
|
2720
3048
|
const activeStep = stepRefs.current.get(step);
|
|
2721
3049
|
activeStep?.scrollIntoView({ block: "nearest", inline: "center" });
|
|
2722
3050
|
}, [step]);
|
|
@@ -2724,19 +3052,19 @@ function Stepper({
|
|
|
2724
3052
|
// onNew is pinned outside the horizontally-scrolling region below (a sibling of
|
|
2725
3053
|
// it, not a child) so its own edge never scrolls off-screen along with the
|
|
2726
3054
|
// later steps once the scroller's min-width doesn't fit the viewport.
|
|
2727
|
-
/* @__PURE__ */
|
|
2728
|
-
/* @__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(
|
|
2729
3057
|
"div",
|
|
2730
3058
|
{
|
|
2731
3059
|
"data-testid": scrollTestId,
|
|
2732
3060
|
className: "flex w-full items-center gap-0 overflow-x-auto px-2 py-1",
|
|
2733
|
-
children: /* @__PURE__ */
|
|
3061
|
+
children: /* @__PURE__ */ jsx45("ol", { className: "flex min-w-[700px] flex-1 items-start", children: steps.map(({ n, label, phase }, i) => {
|
|
2734
3062
|
const done = step > n;
|
|
2735
3063
|
const active = step === n;
|
|
2736
3064
|
const navigable = n <= maxStep && n !== step;
|
|
2737
3065
|
const last = i === steps.length - 1;
|
|
2738
|
-
return /* @__PURE__ */
|
|
2739
|
-
/* @__PURE__ */
|
|
3066
|
+
return /* @__PURE__ */ jsxs27("li", { className: cn("flex items-start", !last && "flex-1"), children: [
|
|
3067
|
+
/* @__PURE__ */ jsxs27(
|
|
2740
3068
|
"button",
|
|
2741
3069
|
{
|
|
2742
3070
|
ref: (element) => {
|
|
@@ -2752,17 +3080,17 @@ function Stepper({
|
|
|
2752
3080
|
navigable ? "cursor-pointer hover:opacity-80" : "cursor-default"
|
|
2753
3081
|
),
|
|
2754
3082
|
children: [
|
|
2755
|
-
/* @__PURE__ */
|
|
3083
|
+
/* @__PURE__ */ jsx45(
|
|
2756
3084
|
"div",
|
|
2757
3085
|
{
|
|
2758
3086
|
className: cn(
|
|
2759
3087
|
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[11px] font-bold transition-colors",
|
|
2760
3088
|
done ? "bg-primary/15 text-primary" : active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"
|
|
2761
3089
|
),
|
|
2762
|
-
children: done ? /* @__PURE__ */
|
|
3090
|
+
children: done ? /* @__PURE__ */ jsx45(Check5, { className: "h-3 w-3" }) : n
|
|
2763
3091
|
}
|
|
2764
3092
|
),
|
|
2765
|
-
/* @__PURE__ */
|
|
3093
|
+
/* @__PURE__ */ jsx45(
|
|
2766
3094
|
"span",
|
|
2767
3095
|
{
|
|
2768
3096
|
className: cn(
|
|
@@ -2772,11 +3100,11 @@ function Stepper({
|
|
|
2772
3100
|
children: label
|
|
2773
3101
|
}
|
|
2774
3102
|
),
|
|
2775
|
-
/* @__PURE__ */
|
|
3103
|
+
/* @__PURE__ */ jsx45("span", { className: "text-[9px] tracking-[0.5px] text-muted-foreground/50 uppercase", children: phase })
|
|
2776
3104
|
]
|
|
2777
3105
|
}
|
|
2778
3106
|
),
|
|
2779
|
-
!last && /* @__PURE__ */
|
|
3107
|
+
!last && /* @__PURE__ */ jsx45(
|
|
2780
3108
|
"div",
|
|
2781
3109
|
{
|
|
2782
3110
|
className: cn(
|
|
@@ -2789,7 +3117,7 @@ function Stepper({
|
|
|
2789
3117
|
}) })
|
|
2790
3118
|
}
|
|
2791
3119
|
) }),
|
|
2792
|
-
onNew && /* @__PURE__ */
|
|
3120
|
+
onNew && /* @__PURE__ */ jsxs27(
|
|
2793
3121
|
"button",
|
|
2794
3122
|
{
|
|
2795
3123
|
"data-testid": newTestId,
|
|
@@ -2797,7 +3125,7 @@ function Stepper({
|
|
|
2797
3125
|
title: newLabel,
|
|
2798
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",
|
|
2799
3127
|
children: [
|
|
2800
|
-
/* @__PURE__ */
|
|
3128
|
+
/* @__PURE__ */ jsx45(Plus, { className: "h-3.5 w-3.5" }),
|
|
2801
3129
|
newLabel
|
|
2802
3130
|
]
|
|
2803
3131
|
}
|
|
@@ -2806,6 +3134,8 @@ function Stepper({
|
|
|
2806
3134
|
);
|
|
2807
3135
|
}
|
|
2808
3136
|
export {
|
|
3137
|
+
AiButton,
|
|
3138
|
+
AiExplainButton,
|
|
2809
3139
|
AlertBox,
|
|
2810
3140
|
Badge,
|
|
2811
3141
|
Button,
|
|
@@ -2905,14 +3235,18 @@ export {
|
|
|
2905
3235
|
TooltipContent,
|
|
2906
3236
|
TooltipProvider,
|
|
2907
3237
|
TooltipTrigger,
|
|
3238
|
+
VoiceInputButton,
|
|
3239
|
+
VoiceTranscript,
|
|
2908
3240
|
badgeVariants,
|
|
2909
3241
|
buttonVariants,
|
|
2910
3242
|
cn,
|
|
3243
|
+
describeSpeechError,
|
|
2911
3244
|
getConfirmButtonPlacement,
|
|
2912
3245
|
isWindowsPlatform,
|
|
2913
3246
|
loadingSpinnerIconVariants,
|
|
2914
3247
|
sheetVariants,
|
|
2915
3248
|
useMediaQuery,
|
|
2916
|
-
useSidebarCollapsed
|
|
3249
|
+
useSidebarCollapsed,
|
|
3250
|
+
useSpeechRecognition
|
|
2917
3251
|
};
|
|
2918
3252
|
//# sourceMappingURL=index.js.map
|