@annondeveloper/ui-kit 0.1.0 → 0.2.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/{chunk-5OKSXPWK.js → chunk-2DWZVHZS.js} +2 -2
- package/dist/chunk-2DWZVHZS.js.map +1 -0
- package/dist/form.d.ts +6 -6
- package/dist/form.js +1 -1
- package/dist/form.js.map +1 -1
- package/dist/index.d.ts +508 -52
- package/dist/index.js +2927 -4
- package/dist/index.js.map +1 -1
- package/dist/{select-nnBJUO8U.d.ts → select-B2wXqqSM.d.ts} +2 -2
- package/package.json +1 -1
- package/src/components/animated-counter.tsx +2 -1
- package/src/components/avatar.tsx +2 -1
- package/src/components/badge.tsx +3 -2
- package/src/components/button.tsx +3 -2
- package/src/components/card.tsx +13 -12
- package/src/components/checkbox.tsx +3 -2
- package/src/components/color-input.tsx +414 -0
- package/src/components/command-bar.tsx +434 -0
- package/src/components/confidence-bar.tsx +115 -0
- package/src/components/confirm-dialog.tsx +2 -1
- package/src/components/copy-block.tsx +229 -0
- package/src/components/data-table.tsx +2 -1
- package/src/components/diff-viewer.tsx +319 -0
- package/src/components/dropdown-menu.tsx +2 -1
- package/src/components/empty-state.tsx +2 -1
- package/src/components/filter-pill.tsx +2 -1
- package/src/components/form-input.tsx +5 -4
- package/src/components/heatmap-calendar.tsx +213 -0
- package/src/components/infinite-scroll.tsx +243 -0
- package/src/components/kanban-column.tsx +198 -0
- package/src/components/live-feed.tsx +220 -0
- package/src/components/log-viewer.tsx +2 -1
- package/src/components/metric-card.tsx +2 -1
- package/src/components/notification-stack.tsx +226 -0
- package/src/components/pipeline-stage.tsx +2 -1
- package/src/components/popover.tsx +2 -1
- package/src/components/port-status-grid.tsx +2 -1
- package/src/components/progress.tsx +2 -1
- package/src/components/radio-group.tsx +2 -1
- package/src/components/realtime-value.tsx +283 -0
- package/src/components/select.tsx +2 -1
- package/src/components/severity-timeline.tsx +2 -1
- package/src/components/sheet.tsx +2 -1
- package/src/components/skeleton.tsx +4 -3
- package/src/components/slider.tsx +2 -1
- package/src/components/smart-table.tsx +383 -0
- package/src/components/sortable-list.tsx +268 -0
- package/src/components/sparkline.tsx +2 -1
- package/src/components/status-badge.tsx +2 -1
- package/src/components/status-pulse.tsx +2 -1
- package/src/components/step-wizard.tsx +372 -0
- package/src/components/streaming-text.tsx +163 -0
- package/src/components/success-checkmark.tsx +2 -1
- package/src/components/tabs.tsx +2 -1
- package/src/components/threshold-gauge.tsx +2 -1
- package/src/components/time-range-selector.tsx +2 -1
- package/src/components/toast.tsx +2 -1
- package/src/components/toggle-switch.tsx +2 -1
- package/src/components/tooltip.tsx +2 -1
- package/src/components/truncated-text.tsx +2 -1
- package/src/components/typing-indicator.tsx +123 -0
- package/src/components/uptime-tracker.tsx +2 -1
- package/src/components/utilization-bar.tsx +2 -1
- package/src/utils.ts +1 -1
- package/dist/chunk-5OKSXPWK.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { cn, clamp } from './chunk-
|
|
2
|
-
export { Checkbox, FormInput, INPUT_CLS, LABEL_CLS, Select, TEXTAREA_CLS, ToggleSwitch, clamp, cn, defaultUtilColorMap, fmtBps, fmtBytes, fmtCompact, fmtDuration, fmtPct, fmtRelative, fmtSpeed, fmtUptime, fmtUtil, stripCidr, utilColor } from './chunk-
|
|
1
|
+
import { cn, clamp } from './chunk-2DWZVHZS.js';
|
|
2
|
+
export { Checkbox, FormInput, INPUT_CLS, LABEL_CLS, Select, TEXTAREA_CLS, ToggleSwitch, clamp, cn, defaultUtilColorMap, fmtBps, fmtBytes, fmtCompact, fmtDuration, fmtPct, fmtRelative, fmtSpeed, fmtUptime, fmtUtil, stripCidr, utilColor } from './chunk-2DWZVHZS.js';
|
|
3
3
|
import { forwardRef, useRef, useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
-
import { Loader2, Check, Copy, AlertTriangle, Search, X, Filter, List, AlignJustify, LayoutList, Columns3, Download, ChevronUp, ChevronDown, ChevronsUpDown, TrendingUp, TrendingDown, ArrowDown, ChevronRight, GripVertical, Eye, EyeOff } from 'lucide-react';
|
|
4
|
+
import { Loader2, Check, Copy, AlertTriangle, Search, X, Filter, List, AlignJustify, LayoutList, Columns3, Download, ChevronUp, ChevronDown, ChevronsUpDown, TrendingUp, TrendingDown, ArrowDown, ChevronRight, Play, Pause, XCircle, CheckCircle2, Info, Plus, Sparkles, Hash, Regex, ArrowUp, Minus, WifiOff, Clock, CornerDownLeft, GripVertical, ChevronLeft, Eye, EyeOff } from 'lucide-react';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
6
|
import * as Tooltip from '@radix-ui/react-tooltip';
|
|
7
7
|
import * as AlertDialog from '@radix-ui/react-alert-dialog';
|
|
@@ -2806,7 +2806,2930 @@ function UptimeTracker({
|
|
|
2806
2806
|
] })
|
|
2807
2807
|
] });
|
|
2808
2808
|
}
|
|
2809
|
+
function formatSegments(text) {
|
|
2810
|
+
const segments = [];
|
|
2811
|
+
const regex = /(\*\*(.+?)\*\*|`([^`]+?)`)/g;
|
|
2812
|
+
let lastIndex = 0;
|
|
2813
|
+
let match;
|
|
2814
|
+
while ((match = regex.exec(text)) !== null) {
|
|
2815
|
+
if (match.index > lastIndex) {
|
|
2816
|
+
segments.push(text.slice(lastIndex, match.index));
|
|
2817
|
+
}
|
|
2818
|
+
if (match[2]) {
|
|
2819
|
+
segments.push(
|
|
2820
|
+
/* @__PURE__ */ jsx("strong", { className: "font-semibold", children: match[2] }, match.index)
|
|
2821
|
+
);
|
|
2822
|
+
} else if (match[3]) {
|
|
2823
|
+
segments.push(
|
|
2824
|
+
/* @__PURE__ */ jsx(
|
|
2825
|
+
"code",
|
|
2826
|
+
{
|
|
2827
|
+
className: "rounded px-1 py-0.5 text-[0.875em] bg-[hsl(var(--bg-overlay))] text-[hsl(var(--brand-primary))] font-mono",
|
|
2828
|
+
children: match[3]
|
|
2829
|
+
},
|
|
2830
|
+
match.index
|
|
2831
|
+
)
|
|
2832
|
+
);
|
|
2833
|
+
}
|
|
2834
|
+
lastIndex = match.index + match[0].length;
|
|
2835
|
+
}
|
|
2836
|
+
if (lastIndex < text.length) {
|
|
2837
|
+
segments.push(text.slice(lastIndex));
|
|
2838
|
+
}
|
|
2839
|
+
return segments;
|
|
2840
|
+
}
|
|
2841
|
+
function StreamingText({
|
|
2842
|
+
text,
|
|
2843
|
+
isStreaming,
|
|
2844
|
+
speed = 500,
|
|
2845
|
+
showCursor = true,
|
|
2846
|
+
onComplete,
|
|
2847
|
+
className
|
|
2848
|
+
}) {
|
|
2849
|
+
const reduced = useReducedMotion();
|
|
2850
|
+
const containerRef = useRef(null);
|
|
2851
|
+
const prevStreamingRef = useRef(isStreaming);
|
|
2852
|
+
const [copied, setCopied] = useState(false);
|
|
2853
|
+
useEffect(() => {
|
|
2854
|
+
if (prevStreamingRef.current && !isStreaming) {
|
|
2855
|
+
onComplete?.();
|
|
2856
|
+
}
|
|
2857
|
+
prevStreamingRef.current = isStreaming;
|
|
2858
|
+
}, [isStreaming, onComplete]);
|
|
2859
|
+
useEffect(() => {
|
|
2860
|
+
if (isStreaming && containerRef.current) {
|
|
2861
|
+
const el = containerRef.current;
|
|
2862
|
+
el.scrollTop = el.scrollHeight;
|
|
2863
|
+
}
|
|
2864
|
+
}, [text, isStreaming]);
|
|
2865
|
+
const handleCopy = useCallback(() => {
|
|
2866
|
+
void navigator.clipboard.writeText(text).then(() => {
|
|
2867
|
+
setCopied(true);
|
|
2868
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
2869
|
+
});
|
|
2870
|
+
}, [text]);
|
|
2871
|
+
const formatted = formatSegments(text);
|
|
2872
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
2873
|
+
/* @__PURE__ */ jsxs(
|
|
2874
|
+
"div",
|
|
2875
|
+
{
|
|
2876
|
+
ref: containerRef,
|
|
2877
|
+
className: "overflow-y-auto text-[hsl(var(--text-primary))] leading-relaxed whitespace-pre-wrap break-words",
|
|
2878
|
+
children: [
|
|
2879
|
+
formatted,
|
|
2880
|
+
showCursor && isStreaming && /* @__PURE__ */ jsx(
|
|
2881
|
+
"span",
|
|
2882
|
+
{
|
|
2883
|
+
className: "inline-block w-[2px] h-[1.1em] align-text-bottom ml-0.5 bg-[hsl(var(--brand-primary))]",
|
|
2884
|
+
style: reduced ? { opacity: 1 } : {
|
|
2885
|
+
animation: `streaming-cursor-blink ${speed}ms step-end infinite`
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
),
|
|
2889
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: showCursor && !isStreaming && text.length > 0 && /* @__PURE__ */ jsx(
|
|
2890
|
+
motion.span,
|
|
2891
|
+
{
|
|
2892
|
+
className: "inline-block w-[2px] h-[1.1em] align-text-bottom ml-0.5 bg-[hsl(var(--brand-primary))]",
|
|
2893
|
+
initial: { opacity: 1 },
|
|
2894
|
+
exit: { opacity: 0 },
|
|
2895
|
+
transition: { duration: reduced ? 0 : 0.4 }
|
|
2896
|
+
}
|
|
2897
|
+
) })
|
|
2898
|
+
]
|
|
2899
|
+
}
|
|
2900
|
+
),
|
|
2901
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: !isStreaming && text.length > 0 && /* @__PURE__ */ jsx(
|
|
2902
|
+
motion.button,
|
|
2903
|
+
{
|
|
2904
|
+
type: "button",
|
|
2905
|
+
initial: { opacity: 0, scale: 0.8 },
|
|
2906
|
+
animate: { opacity: 1, scale: 1 },
|
|
2907
|
+
exit: { opacity: 0, scale: 0.8 },
|
|
2908
|
+
transition: { duration: reduced ? 0 : 0.2 },
|
|
2909
|
+
onClick: handleCopy,
|
|
2910
|
+
className: cn(
|
|
2911
|
+
"absolute top-0 right-0 p-1.5 rounded-lg",
|
|
2912
|
+
"text-[hsl(var(--text-tertiary))] hover:text-[hsl(var(--text-primary))]",
|
|
2913
|
+
"bg-[hsl(var(--bg-surface))]/80 hover:bg-[hsl(var(--bg-elevated))]",
|
|
2914
|
+
"transition-colors duration-150 cursor-pointer"
|
|
2915
|
+
),
|
|
2916
|
+
"aria-label": copied ? "Copied" : "Copy to clipboard",
|
|
2917
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "size-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "size-3.5" })
|
|
2918
|
+
}
|
|
2919
|
+
) }),
|
|
2920
|
+
showCursor && isStreaming && !reduced && /* @__PURE__ */ jsx("style", { children: `
|
|
2921
|
+
@keyframes streaming-cursor-blink {
|
|
2922
|
+
0%, 100% { opacity: 1; }
|
|
2923
|
+
50% { opacity: 0; }
|
|
2924
|
+
}
|
|
2925
|
+
` })
|
|
2926
|
+
] });
|
|
2927
|
+
}
|
|
2928
|
+
var DOT_SIZES = { sm: "size-1.5", md: "size-2" };
|
|
2929
|
+
var FONT_SIZES = { sm: "text-xs", md: "text-sm" };
|
|
2930
|
+
function TypingIndicator({
|
|
2931
|
+
label,
|
|
2932
|
+
variant = "dots",
|
|
2933
|
+
size = "md",
|
|
2934
|
+
className
|
|
2935
|
+
}) {
|
|
2936
|
+
const reduced = useReducedMotion();
|
|
2937
|
+
return /* @__PURE__ */ jsxs(
|
|
2938
|
+
"div",
|
|
2939
|
+
{
|
|
2940
|
+
className: cn(
|
|
2941
|
+
"inline-flex items-center gap-2 text-[hsl(var(--text-secondary))]",
|
|
2942
|
+
FONT_SIZES[size],
|
|
2943
|
+
className
|
|
2944
|
+
),
|
|
2945
|
+
role: "status",
|
|
2946
|
+
"aria-label": label ?? "Typing",
|
|
2947
|
+
children: [
|
|
2948
|
+
variant === "dots" && /* @__PURE__ */ jsx(BouncingDots, { size, reduced: !!reduced }),
|
|
2949
|
+
variant === "pulse" && /* @__PURE__ */ jsx(PulseRing, { size, reduced: !!reduced }),
|
|
2950
|
+
variant === "text" && /* @__PURE__ */ jsx(AnimatedEllipsis, { reduced: !!reduced }),
|
|
2951
|
+
label && /* @__PURE__ */ jsx("span", { children: label })
|
|
2952
|
+
]
|
|
2953
|
+
}
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
function BouncingDots({ size, reduced }) {
|
|
2957
|
+
const dotClass = cn("rounded-full bg-current", DOT_SIZES[size]);
|
|
2958
|
+
if (reduced) {
|
|
2959
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
2960
|
+
/* @__PURE__ */ jsx("span", { className: dotClass }),
|
|
2961
|
+
/* @__PURE__ */ jsx("span", { className: dotClass }),
|
|
2962
|
+
/* @__PURE__ */ jsx("span", { className: dotClass })
|
|
2963
|
+
] });
|
|
2964
|
+
}
|
|
2965
|
+
return /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
|
|
2966
|
+
motion.span,
|
|
2967
|
+
{
|
|
2968
|
+
className: dotClass,
|
|
2969
|
+
animate: { y: [0, -4, 0] },
|
|
2970
|
+
transition: {
|
|
2971
|
+
duration: 0.6,
|
|
2972
|
+
repeat: Infinity,
|
|
2973
|
+
delay: i * 0.15,
|
|
2974
|
+
ease: "easeInOut"
|
|
2975
|
+
}
|
|
2976
|
+
},
|
|
2977
|
+
i
|
|
2978
|
+
)) });
|
|
2979
|
+
}
|
|
2980
|
+
function PulseRing({ size, reduced }) {
|
|
2981
|
+
const ringSize = size === "sm" ? "size-4" : "size-5";
|
|
2982
|
+
const dotSize = size === "sm" ? "size-2" : "size-2.5";
|
|
2983
|
+
return /* @__PURE__ */ jsxs("span", { className: cn("relative inline-flex items-center justify-center", ringSize), children: [
|
|
2984
|
+
!reduced && /* @__PURE__ */ jsx(
|
|
2985
|
+
motion.span,
|
|
2986
|
+
{
|
|
2987
|
+
className: cn("absolute inset-0 rounded-full border border-current"),
|
|
2988
|
+
animate: { scale: [1, 1.8], opacity: [0.6, 0] },
|
|
2989
|
+
transition: { duration: 1.2, repeat: Infinity, ease: "easeOut" }
|
|
2990
|
+
}
|
|
2991
|
+
),
|
|
2992
|
+
/* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-current", dotSize) })
|
|
2993
|
+
] });
|
|
2994
|
+
}
|
|
2995
|
+
function AnimatedEllipsis({ reduced }) {
|
|
2996
|
+
if (reduced) {
|
|
2997
|
+
return /* @__PURE__ */ jsx("span", { children: "..." });
|
|
2998
|
+
}
|
|
2999
|
+
return /* @__PURE__ */ jsx("span", { className: "inline-flex w-[1.2em]", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
|
|
3000
|
+
motion.span,
|
|
3001
|
+
{
|
|
3002
|
+
animate: { opacity: [0, 1, 0] },
|
|
3003
|
+
transition: {
|
|
3004
|
+
duration: 1.2,
|
|
3005
|
+
repeat: Infinity,
|
|
3006
|
+
delay: i * 0.3,
|
|
3007
|
+
ease: "easeInOut"
|
|
3008
|
+
},
|
|
3009
|
+
children: "."
|
|
3010
|
+
},
|
|
3011
|
+
i
|
|
3012
|
+
)) });
|
|
3013
|
+
}
|
|
3014
|
+
var SIZE_CLASSES = {
|
|
3015
|
+
sm: "h-1.5",
|
|
3016
|
+
md: "h-2.5"
|
|
3017
|
+
};
|
|
3018
|
+
function getBarColor2(value, thresholds) {
|
|
3019
|
+
if (value < thresholds.low) return "bg-[hsl(var(--status-critical))]";
|
|
3020
|
+
if (value < thresholds.medium) return "bg-[hsl(var(--status-warning))]";
|
|
3021
|
+
return "bg-[hsl(var(--status-ok))]";
|
|
3022
|
+
}
|
|
3023
|
+
function getTextColor(value, thresholds) {
|
|
3024
|
+
if (value < thresholds.low) return "text-[hsl(var(--status-critical))]";
|
|
3025
|
+
if (value < thresholds.medium) return "text-[hsl(var(--status-warning))]";
|
|
3026
|
+
return "text-[hsl(var(--status-ok))]";
|
|
3027
|
+
}
|
|
3028
|
+
function ConfidenceBar({
|
|
3029
|
+
value,
|
|
3030
|
+
label,
|
|
3031
|
+
showPercentage = true,
|
|
3032
|
+
thresholds = { low: 0.3, medium: 0.7 },
|
|
3033
|
+
size = "md",
|
|
3034
|
+
className
|
|
3035
|
+
}) {
|
|
3036
|
+
const reduced = useReducedMotion();
|
|
3037
|
+
const [hovered, setHovered] = useState(false);
|
|
3038
|
+
const clamped = Math.min(1, Math.max(0, value));
|
|
3039
|
+
const pct = clamped * 100;
|
|
3040
|
+
const barColor = getBarColor2(clamped, thresholds);
|
|
3041
|
+
const textColor = getTextColor(clamped, thresholds);
|
|
3042
|
+
return /* @__PURE__ */ jsxs(
|
|
3043
|
+
"div",
|
|
3044
|
+
{
|
|
3045
|
+
className: cn("w-full", className),
|
|
3046
|
+
onMouseEnter: () => setHovered(true),
|
|
3047
|
+
onMouseLeave: () => setHovered(false),
|
|
3048
|
+
children: [
|
|
3049
|
+
(label || showPercentage) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
|
|
3050
|
+
label && /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[hsl(var(--text-secondary))]", children: label }),
|
|
3051
|
+
showPercentage && /* @__PURE__ */ jsxs("span", { className: cn("text-xs font-medium tabular-nums", textColor), children: [
|
|
3052
|
+
pct.toFixed(1),
|
|
3053
|
+
"%"
|
|
3054
|
+
] })
|
|
3055
|
+
] }),
|
|
3056
|
+
/* @__PURE__ */ jsx(
|
|
3057
|
+
"div",
|
|
3058
|
+
{
|
|
3059
|
+
className: cn(
|
|
3060
|
+
"relative w-full overflow-hidden rounded-full bg-[hsl(var(--bg-overlay))]",
|
|
3061
|
+
SIZE_CLASSES[size]
|
|
3062
|
+
),
|
|
3063
|
+
role: "meter",
|
|
3064
|
+
"aria-valuenow": pct,
|
|
3065
|
+
"aria-valuemin": 0,
|
|
3066
|
+
"aria-valuemax": 100,
|
|
3067
|
+
"aria-label": label ?? `Confidence: ${pct.toFixed(1)}%`,
|
|
3068
|
+
children: reduced ? /* @__PURE__ */ jsx(
|
|
3069
|
+
"div",
|
|
3070
|
+
{
|
|
3071
|
+
className: cn("h-full rounded-full", barColor),
|
|
3072
|
+
style: { width: `${pct}%` }
|
|
3073
|
+
}
|
|
3074
|
+
) : /* @__PURE__ */ jsx(
|
|
3075
|
+
motion.div,
|
|
3076
|
+
{
|
|
3077
|
+
className: cn("h-full rounded-full", barColor),
|
|
3078
|
+
initial: { width: 0 },
|
|
3079
|
+
animate: { width: `${pct}%` },
|
|
3080
|
+
transition: { type: "spring", stiffness: 80, damping: 20 }
|
|
3081
|
+
}
|
|
3082
|
+
)
|
|
3083
|
+
}
|
|
3084
|
+
),
|
|
3085
|
+
hovered && /* @__PURE__ */ jsxs("div", { className: "mt-1 text-[10px] text-[hsl(var(--text-tertiary))] tabular-nums", children: [
|
|
3086
|
+
clamped.toFixed(4),
|
|
3087
|
+
" (",
|
|
3088
|
+
pct.toFixed(2),
|
|
3089
|
+
"%)"
|
|
3090
|
+
] })
|
|
3091
|
+
]
|
|
3092
|
+
}
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
var TYPE_BORDER = {
|
|
3096
|
+
info: "border-l-[hsl(var(--brand-secondary))]",
|
|
3097
|
+
success: "border-l-[hsl(var(--status-ok))]",
|
|
3098
|
+
warning: "border-l-[hsl(var(--status-warning))]",
|
|
3099
|
+
error: "border-l-[hsl(var(--status-critical))]"
|
|
3100
|
+
};
|
|
3101
|
+
function relativeTime(ts) {
|
|
3102
|
+
const diff = (Date.now() - new Date(ts).getTime()) / 1e3;
|
|
3103
|
+
if (diff < 5) return "now";
|
|
3104
|
+
if (diff < 60) return `${Math.floor(diff)}s ago`;
|
|
3105
|
+
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
3106
|
+
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
3107
|
+
return `${Math.floor(diff / 86400)}d ago`;
|
|
3108
|
+
}
|
|
3109
|
+
function LiveFeed({
|
|
3110
|
+
items,
|
|
3111
|
+
maxVisible = 50,
|
|
3112
|
+
showTimestamps = true,
|
|
3113
|
+
autoScroll: autoScrollProp = true,
|
|
3114
|
+
onItemClick,
|
|
3115
|
+
emptyMessage = "No events yet",
|
|
3116
|
+
className
|
|
3117
|
+
}) {
|
|
3118
|
+
const reduced = useReducedMotion();
|
|
3119
|
+
const scrollRef = useRef(null);
|
|
3120
|
+
const [paused, setPaused] = useState(false);
|
|
3121
|
+
const [userScrolled, setUserScrolled] = useState(false);
|
|
3122
|
+
const [newCount, setNewCount] = useState(0);
|
|
3123
|
+
const prevCountRef = useRef(items.length);
|
|
3124
|
+
useEffect(() => {
|
|
3125
|
+
const diff = items.length - prevCountRef.current;
|
|
3126
|
+
if (diff > 0 && (paused || userScrolled)) {
|
|
3127
|
+
setNewCount((c) => c + diff);
|
|
3128
|
+
}
|
|
3129
|
+
prevCountRef.current = items.length;
|
|
3130
|
+
}, [items.length, paused, userScrolled]);
|
|
3131
|
+
useEffect(() => {
|
|
3132
|
+
if (autoScrollProp && !paused && !userScrolled && scrollRef.current) {
|
|
3133
|
+
scrollRef.current.scrollTop = 0;
|
|
3134
|
+
}
|
|
3135
|
+
}, [items, autoScrollProp, paused, userScrolled]);
|
|
3136
|
+
const handleScroll = useCallback(() => {
|
|
3137
|
+
if (!scrollRef.current) return;
|
|
3138
|
+
const { scrollTop } = scrollRef.current;
|
|
3139
|
+
setUserScrolled(scrollTop > 40);
|
|
3140
|
+
}, []);
|
|
3141
|
+
const scrollToTop = useCallback(() => {
|
|
3142
|
+
if (scrollRef.current) {
|
|
3143
|
+
scrollRef.current.scrollTop = 0;
|
|
3144
|
+
}
|
|
3145
|
+
setUserScrolled(false);
|
|
3146
|
+
setNewCount(0);
|
|
3147
|
+
}, []);
|
|
3148
|
+
const togglePause = useCallback(() => {
|
|
3149
|
+
setPaused((p) => {
|
|
3150
|
+
if (p) {
|
|
3151
|
+
setNewCount(0);
|
|
3152
|
+
setUserScrolled(false);
|
|
3153
|
+
}
|
|
3154
|
+
return !p;
|
|
3155
|
+
});
|
|
3156
|
+
}, []);
|
|
3157
|
+
const [, setTick] = useState(0);
|
|
3158
|
+
useEffect(() => {
|
|
3159
|
+
if (!showTimestamps) return;
|
|
3160
|
+
const id = setInterval(() => setTick((t) => t + 1), 1e4);
|
|
3161
|
+
return () => clearInterval(id);
|
|
3162
|
+
}, [showTimestamps]);
|
|
3163
|
+
const visibleItems = items.slice(0, maxVisible);
|
|
3164
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative flex flex-col", className), children: [
|
|
3165
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2 border-b border-[hsl(var(--border-subtle))]", children: [
|
|
3166
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-[hsl(var(--text-secondary))]", children: [
|
|
3167
|
+
items.length,
|
|
3168
|
+
" events"
|
|
3169
|
+
] }),
|
|
3170
|
+
/* @__PURE__ */ jsxs(
|
|
3171
|
+
"button",
|
|
3172
|
+
{
|
|
3173
|
+
type: "button",
|
|
3174
|
+
onClick: togglePause,
|
|
3175
|
+
className: cn(
|
|
3176
|
+
"inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium cursor-pointer",
|
|
3177
|
+
"text-[hsl(var(--text-secondary))] hover:text-[hsl(var(--text-primary))]",
|
|
3178
|
+
"hover:bg-[hsl(var(--bg-overlay))] transition-colors duration-150"
|
|
3179
|
+
),
|
|
3180
|
+
"aria-label": paused ? "Resume auto-scroll" : "Pause auto-scroll",
|
|
3181
|
+
children: [
|
|
3182
|
+
paused ? /* @__PURE__ */ jsx(Play, { className: "size-3" }) : /* @__PURE__ */ jsx(Pause, { className: "size-3" }),
|
|
3183
|
+
paused ? "Resume" : "Pause"
|
|
3184
|
+
]
|
|
3185
|
+
}
|
|
3186
|
+
)
|
|
3187
|
+
] }),
|
|
3188
|
+
/* @__PURE__ */ jsx(
|
|
3189
|
+
"div",
|
|
3190
|
+
{
|
|
3191
|
+
ref: scrollRef,
|
|
3192
|
+
onScroll: handleScroll,
|
|
3193
|
+
className: "flex-1 overflow-y-auto",
|
|
3194
|
+
children: visibleItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12 text-sm text-[hsl(var(--text-tertiary))]", children: emptyMessage }) : /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: visibleItems.map((item) => /* @__PURE__ */ jsx(
|
|
3195
|
+
motion.div,
|
|
3196
|
+
{
|
|
3197
|
+
layout: !reduced,
|
|
3198
|
+
initial: reduced ? void 0 : { opacity: 0, y: -12, height: 0 },
|
|
3199
|
+
animate: { opacity: 1, y: 0, height: "auto" },
|
|
3200
|
+
exit: reduced ? void 0 : { opacity: 0, height: 0 },
|
|
3201
|
+
transition: { duration: reduced ? 0 : 0.2 },
|
|
3202
|
+
children: /* @__PURE__ */ jsxs(
|
|
3203
|
+
"div",
|
|
3204
|
+
{
|
|
3205
|
+
role: onItemClick ? "button" : void 0,
|
|
3206
|
+
tabIndex: onItemClick ? 0 : void 0,
|
|
3207
|
+
onClick: onItemClick ? () => onItemClick(item) : void 0,
|
|
3208
|
+
onKeyDown: onItemClick ? (e) => {
|
|
3209
|
+
if (e.key === "Enter" || e.key === " ") onItemClick(item);
|
|
3210
|
+
} : void 0,
|
|
3211
|
+
className: cn(
|
|
3212
|
+
"flex items-start gap-3 px-3 py-2.5 border-l-2",
|
|
3213
|
+
"border-b border-b-[hsl(var(--border-subtle))]",
|
|
3214
|
+
TYPE_BORDER[item.type ?? "info"],
|
|
3215
|
+
onItemClick && "cursor-pointer hover:bg-[hsl(var(--bg-surface))] transition-colors duration-100"
|
|
3216
|
+
),
|
|
3217
|
+
children: [
|
|
3218
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 text-sm text-[hsl(var(--text-primary))]", children: item.content }),
|
|
3219
|
+
showTimestamps && /* @__PURE__ */ jsx(
|
|
3220
|
+
"span",
|
|
3221
|
+
{
|
|
3222
|
+
className: "shrink-0 text-[10px] tabular-nums text-[hsl(var(--text-tertiary))] mt-0.5",
|
|
3223
|
+
title: new Date(item.timestamp).toISOString(),
|
|
3224
|
+
children: relativeTime(item.timestamp)
|
|
3225
|
+
}
|
|
3226
|
+
)
|
|
3227
|
+
]
|
|
3228
|
+
}
|
|
3229
|
+
)
|
|
3230
|
+
},
|
|
3231
|
+
item.id
|
|
3232
|
+
)) })
|
|
3233
|
+
}
|
|
3234
|
+
),
|
|
3235
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: newCount > 0 && (userScrolled || paused) && /* @__PURE__ */ jsxs(
|
|
3236
|
+
motion.button,
|
|
3237
|
+
{
|
|
3238
|
+
type: "button",
|
|
3239
|
+
initial: reduced ? void 0 : { opacity: 0, y: 8 },
|
|
3240
|
+
animate: { opacity: 1, y: 0 },
|
|
3241
|
+
exit: reduced ? void 0 : { opacity: 0, y: 8 },
|
|
3242
|
+
transition: { duration: reduced ? 0 : 0.15 },
|
|
3243
|
+
onClick: scrollToTop,
|
|
3244
|
+
className: cn(
|
|
3245
|
+
"absolute top-12 left-1/2 -translate-x-1/2 z-10",
|
|
3246
|
+
"inline-flex items-center gap-1 px-3 py-1.5 rounded-full",
|
|
3247
|
+
"bg-[hsl(var(--brand-primary))] text-white text-xs font-medium",
|
|
3248
|
+
"shadow-lg cursor-pointer hover:brightness-110 transition-[filter] duration-100"
|
|
3249
|
+
),
|
|
3250
|
+
children: [
|
|
3251
|
+
/* @__PURE__ */ jsx(ChevronUp, { className: "size-3" }),
|
|
3252
|
+
newCount,
|
|
3253
|
+
" new ",
|
|
3254
|
+
newCount === 1 ? "item" : "items"
|
|
3255
|
+
]
|
|
3256
|
+
}
|
|
3257
|
+
) })
|
|
3258
|
+
] });
|
|
3259
|
+
}
|
|
3260
|
+
function computeDiff(oldLines, newLines) {
|
|
3261
|
+
const m = oldLines.length;
|
|
3262
|
+
const n = newLines.length;
|
|
3263
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
3264
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
3265
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
3266
|
+
if (oldLines[i2 - 1] === newLines[j2 - 1]) {
|
|
3267
|
+
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
3268
|
+
} else {
|
|
3269
|
+
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
const result = [];
|
|
3274
|
+
let i = m;
|
|
3275
|
+
let j = n;
|
|
3276
|
+
while (i > 0 || j > 0) {
|
|
3277
|
+
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
3278
|
+
result.push({ type: "unchanged", content: oldLines[i - 1], oldLineNo: i, newLineNo: j });
|
|
3279
|
+
i--;
|
|
3280
|
+
j--;
|
|
3281
|
+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
3282
|
+
result.push({ type: "added", content: newLines[j - 1], newLineNo: j });
|
|
3283
|
+
j--;
|
|
3284
|
+
} else {
|
|
3285
|
+
result.push({ type: "removed", content: oldLines[i - 1], oldLineNo: i });
|
|
3286
|
+
i--;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
return result.reverse();
|
|
3290
|
+
}
|
|
3291
|
+
var TYPE_BG = {
|
|
3292
|
+
added: "bg-[hsl(var(--status-ok))]/10",
|
|
3293
|
+
removed: "bg-[hsl(var(--status-critical))]/10",
|
|
3294
|
+
unchanged: ""
|
|
3295
|
+
};
|
|
3296
|
+
var TYPE_PREFIX = {
|
|
3297
|
+
added: "+",
|
|
3298
|
+
removed: "-",
|
|
3299
|
+
unchanged: " "
|
|
3300
|
+
};
|
|
3301
|
+
var TYPE_PREFIX_COLOR = {
|
|
3302
|
+
added: "text-[hsl(var(--status-ok))]",
|
|
3303
|
+
removed: "text-[hsl(var(--status-critical))]",
|
|
3304
|
+
unchanged: "text-[hsl(var(--text-tertiary))]"
|
|
3305
|
+
};
|
|
3306
|
+
function DiffViewer({
|
|
3307
|
+
oldValue,
|
|
3308
|
+
newValue,
|
|
3309
|
+
mode = "inline",
|
|
3310
|
+
language,
|
|
3311
|
+
showLineNumbers = true,
|
|
3312
|
+
className
|
|
3313
|
+
}) {
|
|
3314
|
+
const [expandedSections, setExpandedSections] = useState(/* @__PURE__ */ new Set());
|
|
3315
|
+
const diffLines = useMemo(() => {
|
|
3316
|
+
const oldLines = oldValue.split("\n");
|
|
3317
|
+
const newLines = newValue.split("\n");
|
|
3318
|
+
return computeDiff(oldLines, newLines);
|
|
3319
|
+
}, [oldValue, newValue]);
|
|
3320
|
+
const sections = useMemo(() => {
|
|
3321
|
+
const groups = [];
|
|
3322
|
+
let current = [];
|
|
3323
|
+
let currentType = null;
|
|
3324
|
+
let startIdx = 0;
|
|
3325
|
+
for (let i = 0; i < diffLines.length; i++) {
|
|
3326
|
+
const lineType = diffLines[i].type === "unchanged" ? "unchanged" : "changes";
|
|
3327
|
+
if (lineType !== currentType) {
|
|
3328
|
+
if (current.length > 0 && currentType !== null) {
|
|
3329
|
+
groups.push({ type: currentType, lines: current, startIdx });
|
|
3330
|
+
}
|
|
3331
|
+
current = [diffLines[i]];
|
|
3332
|
+
currentType = lineType;
|
|
3333
|
+
startIdx = i;
|
|
3334
|
+
} else {
|
|
3335
|
+
current.push(diffLines[i]);
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
if (current.length > 0 && currentType !== null) {
|
|
3339
|
+
groups.push({ type: currentType, lines: current, startIdx });
|
|
3340
|
+
}
|
|
3341
|
+
return groups;
|
|
3342
|
+
}, [diffLines]);
|
|
3343
|
+
const toggleSection = (idx) => {
|
|
3344
|
+
setExpandedSections((prev) => {
|
|
3345
|
+
const next = new Set(prev);
|
|
3346
|
+
if (next.has(idx)) {
|
|
3347
|
+
next.delete(idx);
|
|
3348
|
+
} else {
|
|
3349
|
+
next.add(idx);
|
|
3350
|
+
}
|
|
3351
|
+
return next;
|
|
3352
|
+
});
|
|
3353
|
+
};
|
|
3354
|
+
if (mode === "side-by-side") {
|
|
3355
|
+
return /* @__PURE__ */ jsx(SideBySide, { diffLines, showLineNumbers, language, className });
|
|
3356
|
+
}
|
|
3357
|
+
return /* @__PURE__ */ jsx(
|
|
3358
|
+
"div",
|
|
3359
|
+
{
|
|
3360
|
+
className: cn(
|
|
3361
|
+
"w-full overflow-x-auto rounded-xl border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))]",
|
|
3362
|
+
"font-mono text-xs leading-5",
|
|
3363
|
+
className
|
|
3364
|
+
),
|
|
3365
|
+
"data-language": language,
|
|
3366
|
+
children: sections.map((section, sIdx) => {
|
|
3367
|
+
if (section.type === "unchanged" && section.lines.length > 6 && !expandedSections.has(sIdx)) {
|
|
3368
|
+
const topContext = section.lines.slice(0, 3);
|
|
3369
|
+
const bottomContext = section.lines.slice(-3);
|
|
3370
|
+
const hiddenCount = section.lines.length - 6;
|
|
3371
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
3372
|
+
topContext.map((line, i) => /* @__PURE__ */ jsx(InlineLine, { line, showLineNumbers }, `${sIdx}-t-${i}`)),
|
|
3373
|
+
/* @__PURE__ */ jsxs(
|
|
3374
|
+
"button",
|
|
3375
|
+
{
|
|
3376
|
+
type: "button",
|
|
3377
|
+
onClick: () => toggleSection(sIdx),
|
|
3378
|
+
className: cn(
|
|
3379
|
+
"w-full px-3 py-1 text-center text-[10px]",
|
|
3380
|
+
"text-[hsl(var(--text-tertiary))] bg-[hsl(var(--bg-overlay))]/50",
|
|
3381
|
+
"hover:bg-[hsl(var(--bg-overlay))] cursor-pointer transition-colors duration-100"
|
|
3382
|
+
),
|
|
3383
|
+
children: [
|
|
3384
|
+
"... ",
|
|
3385
|
+
hiddenCount,
|
|
3386
|
+
" unchanged ",
|
|
3387
|
+
hiddenCount === 1 ? "line" : "lines",
|
|
3388
|
+
" ..."
|
|
3389
|
+
]
|
|
3390
|
+
}
|
|
3391
|
+
),
|
|
3392
|
+
bottomContext.map((line, i) => /* @__PURE__ */ jsx(InlineLine, { line, showLineNumbers }, `${sIdx}-b-${i}`))
|
|
3393
|
+
] }, sIdx);
|
|
3394
|
+
}
|
|
3395
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
3396
|
+
section.type === "unchanged" && expandedSections.has(sIdx) && /* @__PURE__ */ jsxs(
|
|
3397
|
+
"button",
|
|
3398
|
+
{
|
|
3399
|
+
type: "button",
|
|
3400
|
+
onClick: () => toggleSection(sIdx),
|
|
3401
|
+
className: cn(
|
|
3402
|
+
"w-full px-3 py-0.5 text-center text-[10px]",
|
|
3403
|
+
"text-[hsl(var(--text-tertiary))] bg-[hsl(var(--bg-overlay))]/30",
|
|
3404
|
+
"hover:bg-[hsl(var(--bg-overlay))] cursor-pointer transition-colors duration-100"
|
|
3405
|
+
),
|
|
3406
|
+
children: [
|
|
3407
|
+
"collapse ",
|
|
3408
|
+
section.lines.length,
|
|
3409
|
+
" unchanged lines"
|
|
3410
|
+
]
|
|
3411
|
+
}
|
|
3412
|
+
),
|
|
3413
|
+
section.lines.map((line, i) => /* @__PURE__ */ jsx(InlineLine, { line, showLineNumbers }, `${sIdx}-${i}`))
|
|
3414
|
+
] }, sIdx);
|
|
3415
|
+
})
|
|
3416
|
+
}
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
function InlineLine({ line, showLineNumbers }) {
|
|
3420
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex", TYPE_BG[line.type]), children: [
|
|
3421
|
+
showLineNumbers && /* @__PURE__ */ jsx("span", { className: "shrink-0 w-9 px-1.5 text-right select-none text-[hsl(var(--text-tertiary))]/50 tabular-nums", children: line.oldLineNo ?? "" }),
|
|
3422
|
+
showLineNumbers && /* @__PURE__ */ jsx("span", { className: "shrink-0 w-9 px-1.5 text-right select-none text-[hsl(var(--text-tertiary))]/50 tabular-nums", children: line.newLineNo ?? "" }),
|
|
3423
|
+
/* @__PURE__ */ jsx("span", { className: cn("shrink-0 w-4 text-center select-none font-bold", TYPE_PREFIX_COLOR[line.type]), children: TYPE_PREFIX[line.type] }),
|
|
3424
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 px-2 whitespace-pre text-[hsl(var(--text-primary))]", children: line.content })
|
|
3425
|
+
] });
|
|
3426
|
+
}
|
|
3427
|
+
function SideBySide({
|
|
3428
|
+
diffLines,
|
|
3429
|
+
showLineNumbers,
|
|
3430
|
+
language,
|
|
3431
|
+
className
|
|
3432
|
+
}) {
|
|
3433
|
+
const pairs = [];
|
|
3434
|
+
let i = 0;
|
|
3435
|
+
while (i < diffLines.length) {
|
|
3436
|
+
const line = diffLines[i];
|
|
3437
|
+
if (line.type === "unchanged") {
|
|
3438
|
+
pairs.push({ left: line, right: line });
|
|
3439
|
+
i++;
|
|
3440
|
+
} else if (line.type === "removed") {
|
|
3441
|
+
if (i + 1 < diffLines.length && diffLines[i + 1].type === "added") {
|
|
3442
|
+
pairs.push({ left: line, right: diffLines[i + 1] });
|
|
3443
|
+
i += 2;
|
|
3444
|
+
} else {
|
|
3445
|
+
pairs.push({ left: line });
|
|
3446
|
+
i++;
|
|
3447
|
+
}
|
|
3448
|
+
} else {
|
|
3449
|
+
pairs.push({ right: line });
|
|
3450
|
+
i++;
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
return /* @__PURE__ */ jsx(
|
|
3454
|
+
"div",
|
|
3455
|
+
{
|
|
3456
|
+
className: cn(
|
|
3457
|
+
"w-full overflow-x-auto rounded-xl border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))]",
|
|
3458
|
+
"font-mono text-xs leading-5",
|
|
3459
|
+
className
|
|
3460
|
+
),
|
|
3461
|
+
"data-language": language,
|
|
3462
|
+
children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 divide-x divide-[hsl(var(--border-subtle))]", children: [
|
|
3463
|
+
/* @__PURE__ */ jsx("div", { children: pairs.map((pair, idx) => /* @__PURE__ */ jsxs(
|
|
3464
|
+
"div",
|
|
3465
|
+
{
|
|
3466
|
+
className: cn("flex", pair.left ? TYPE_BG[pair.left.type] : ""),
|
|
3467
|
+
children: [
|
|
3468
|
+
showLineNumbers && /* @__PURE__ */ jsx("span", { className: "shrink-0 w-9 px-1.5 text-right select-none text-[hsl(var(--text-tertiary))]/50 tabular-nums", children: pair.left?.oldLineNo ?? "" }),
|
|
3469
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 px-2 whitespace-pre text-[hsl(var(--text-primary))]", children: pair.left?.content ?? "" })
|
|
3470
|
+
]
|
|
3471
|
+
},
|
|
3472
|
+
`l-${idx}`
|
|
3473
|
+
)) }),
|
|
3474
|
+
/* @__PURE__ */ jsx("div", { children: pairs.map((pair, idx) => /* @__PURE__ */ jsxs(
|
|
3475
|
+
"div",
|
|
3476
|
+
{
|
|
3477
|
+
className: cn("flex", pair.right ? TYPE_BG[pair.right.type] : ""),
|
|
3478
|
+
children: [
|
|
3479
|
+
showLineNumbers && /* @__PURE__ */ jsx("span", { className: "shrink-0 w-9 px-1.5 text-right select-none text-[hsl(var(--text-tertiary))]/50 tabular-nums", children: pair.right?.newLineNo ?? "" }),
|
|
3480
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 px-2 whitespace-pre text-[hsl(var(--text-primary))]", children: pair.right?.content ?? "" })
|
|
3481
|
+
]
|
|
3482
|
+
},
|
|
3483
|
+
`r-${idx}`
|
|
3484
|
+
)) })
|
|
3485
|
+
] })
|
|
3486
|
+
}
|
|
3487
|
+
);
|
|
3488
|
+
}
|
|
3489
|
+
var DEFAULT_COLORS = [
|
|
3490
|
+
"bg-[hsl(var(--bg-overlay))]",
|
|
3491
|
+
"bg-[hsl(var(--status-ok))]/20",
|
|
3492
|
+
"bg-[hsl(var(--status-ok))]/40",
|
|
3493
|
+
"bg-[hsl(var(--status-ok))]/65",
|
|
3494
|
+
"bg-[hsl(var(--status-ok))]"
|
|
3495
|
+
];
|
|
3496
|
+
var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
3497
|
+
var DAY_LABELS = ["", "Mon", "", "Wed", "", "Fri", ""];
|
|
3498
|
+
function toDateKey(d) {
|
|
3499
|
+
return d.toISOString().slice(0, 10);
|
|
3500
|
+
}
|
|
3501
|
+
function parseDate(s) {
|
|
3502
|
+
const [y, m, d] = s.split("-").map(Number);
|
|
3503
|
+
return new Date(y, m - 1, d);
|
|
3504
|
+
}
|
|
3505
|
+
function HeatmapCalendar({
|
|
3506
|
+
data,
|
|
3507
|
+
startDate,
|
|
3508
|
+
endDate,
|
|
3509
|
+
colorScale = DEFAULT_COLORS,
|
|
3510
|
+
onDayClick,
|
|
3511
|
+
showMonthLabels = true,
|
|
3512
|
+
showDayLabels = true,
|
|
3513
|
+
tooltipFormat,
|
|
3514
|
+
className
|
|
3515
|
+
}) {
|
|
3516
|
+
const [hoveredDay, setHoveredDay] = useState(null);
|
|
3517
|
+
const { weeks, months, maxValue } = useMemo(() => {
|
|
3518
|
+
const end = endDate ? parseDate(endDate) : /* @__PURE__ */ new Date();
|
|
3519
|
+
const start = startDate ? parseDate(startDate) : new Date(end.getFullYear() - 1, end.getMonth(), end.getDate() + 1);
|
|
3520
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
3521
|
+
let maxVal = 0;
|
|
3522
|
+
for (const d of data) {
|
|
3523
|
+
lookup.set(d.date, d.value);
|
|
3524
|
+
if (d.value > maxVal) maxVal = d.value;
|
|
3525
|
+
}
|
|
3526
|
+
const weeks2 = [];
|
|
3527
|
+
const monthChanges = [];
|
|
3528
|
+
const cursor = new Date(start);
|
|
3529
|
+
cursor.setDate(cursor.getDate() - cursor.getDay());
|
|
3530
|
+
let prevMonth = -1;
|
|
3531
|
+
while (cursor <= end || weeks2.length === 0) {
|
|
3532
|
+
const week = [];
|
|
3533
|
+
for (let dow = 0; dow < 7; dow++) {
|
|
3534
|
+
const key = toDateKey(cursor);
|
|
3535
|
+
if (cursor >= start && cursor <= end) {
|
|
3536
|
+
week.push({ date: key, value: lookup.get(key) ?? 0 });
|
|
3537
|
+
} else {
|
|
3538
|
+
week.push(null);
|
|
3539
|
+
}
|
|
3540
|
+
if (cursor.getMonth() !== prevMonth && cursor >= start && cursor <= end) {
|
|
3541
|
+
if (dow === 0) {
|
|
3542
|
+
monthChanges.push({ col: weeks2.length, label: MONTH_NAMES[cursor.getMonth()] });
|
|
3543
|
+
}
|
|
3544
|
+
prevMonth = cursor.getMonth();
|
|
3545
|
+
}
|
|
3546
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
3547
|
+
}
|
|
3548
|
+
weeks2.push(week);
|
|
3549
|
+
}
|
|
3550
|
+
return { weeks: weeks2, months: monthChanges, maxValue: maxVal };
|
|
3551
|
+
}, [data, startDate, endDate]);
|
|
3552
|
+
const getColorClass = (value) => {
|
|
3553
|
+
if (value === 0 || maxValue === 0) return colorScale[0];
|
|
3554
|
+
const idx = Math.min(
|
|
3555
|
+
colorScale.length - 1,
|
|
3556
|
+
Math.ceil(value / maxValue * (colorScale.length - 1))
|
|
3557
|
+
);
|
|
3558
|
+
return colorScale[idx];
|
|
3559
|
+
};
|
|
3560
|
+
const defaultTooltip = (day) => `${day.date}: ${day.value}`;
|
|
3561
|
+
const formatTooltip = tooltipFormat ?? defaultTooltip;
|
|
3562
|
+
return /* @__PURE__ */ jsx("div", { className: cn("overflow-x-auto", className), children: /* @__PURE__ */ jsxs("div", { className: "inline-flex flex-col gap-0.5", children: [
|
|
3563
|
+
showMonthLabels && /* @__PURE__ */ jsx("div", { className: "flex", style: { marginLeft: showDayLabels ? "2rem" : 0 }, children: weeks.map((_, col) => {
|
|
3564
|
+
const monthEntry = months.find((m) => m.col === col);
|
|
3565
|
+
return /* @__PURE__ */ jsx(
|
|
3566
|
+
"div",
|
|
3567
|
+
{
|
|
3568
|
+
className: "text-[10px] text-[hsl(var(--text-tertiary))]",
|
|
3569
|
+
style: { width: 13, minWidth: 13 },
|
|
3570
|
+
children: monthEntry?.label ?? ""
|
|
3571
|
+
},
|
|
3572
|
+
`m-${col}`
|
|
3573
|
+
);
|
|
3574
|
+
}) }),
|
|
3575
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-0", children: [
|
|
3576
|
+
showDayLabels && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-[2px] mr-1", children: DAY_LABELS.map((label, i) => /* @__PURE__ */ jsx(
|
|
3577
|
+
"div",
|
|
3578
|
+
{
|
|
3579
|
+
className: "text-[10px] text-[hsl(var(--text-tertiary))] h-[11px] flex items-center justify-end pr-1",
|
|
3580
|
+
style: { width: "1.75rem" },
|
|
3581
|
+
children: label
|
|
3582
|
+
},
|
|
3583
|
+
i
|
|
3584
|
+
)) }),
|
|
3585
|
+
weeks.map((week, wIdx) => /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-[2px]", children: week.map((day, dIdx) => /* @__PURE__ */ jsx(
|
|
3586
|
+
"div",
|
|
3587
|
+
{
|
|
3588
|
+
className: cn(
|
|
3589
|
+
"w-[11px] h-[11px] rounded-sm",
|
|
3590
|
+
day ? getColorClass(day.value) : "bg-transparent",
|
|
3591
|
+
day && onDayClick && "cursor-pointer",
|
|
3592
|
+
day && "hover:ring-1 hover:ring-[hsl(var(--text-tertiary))]"
|
|
3593
|
+
),
|
|
3594
|
+
onClick: day && onDayClick ? () => onDayClick(day) : void 0,
|
|
3595
|
+
onMouseEnter: day ? () => setHoveredDay(day) : void 0,
|
|
3596
|
+
onMouseLeave: () => setHoveredDay(null),
|
|
3597
|
+
title: day ? formatTooltip(day) : void 0,
|
|
3598
|
+
role: day && onDayClick ? "button" : void 0,
|
|
3599
|
+
tabIndex: day && onDayClick ? 0 : void 0,
|
|
3600
|
+
onKeyDown: day && onDayClick ? (e) => {
|
|
3601
|
+
if (e.key === "Enter" || e.key === " ") onDayClick(day);
|
|
3602
|
+
} : void 0
|
|
3603
|
+
},
|
|
3604
|
+
dIdx
|
|
3605
|
+
)) }, wIdx))
|
|
3606
|
+
] }),
|
|
3607
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mt-1", style: { marginLeft: showDayLabels ? "2rem" : 0 }, children: [
|
|
3608
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-[hsl(var(--text-tertiary))] mr-1", children: "Less" }),
|
|
3609
|
+
colorScale.map((color, i) => /* @__PURE__ */ jsx("div", { className: cn("w-[11px] h-[11px] rounded-sm", color) }, i)),
|
|
3610
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-[hsl(var(--text-tertiary))] ml-1", children: "More" })
|
|
3611
|
+
] })
|
|
3612
|
+
] }) });
|
|
3613
|
+
}
|
|
3614
|
+
var TYPE_ICON = {
|
|
3615
|
+
info: Info,
|
|
3616
|
+
success: CheckCircle2,
|
|
3617
|
+
warning: AlertTriangle,
|
|
3618
|
+
error: XCircle
|
|
3619
|
+
};
|
|
3620
|
+
var TYPE_COLOR = {
|
|
3621
|
+
info: "border-l-[hsl(var(--brand-secondary))]",
|
|
3622
|
+
success: "border-l-[hsl(var(--status-ok))]",
|
|
3623
|
+
warning: "border-l-[hsl(var(--status-warning))]",
|
|
3624
|
+
error: "border-l-[hsl(var(--status-critical))]"
|
|
3625
|
+
};
|
|
3626
|
+
var TYPE_ICON_COLOR = {
|
|
3627
|
+
info: "text-[hsl(var(--brand-secondary))]",
|
|
3628
|
+
success: "text-[hsl(var(--status-ok))]",
|
|
3629
|
+
warning: "text-[hsl(var(--status-warning))]",
|
|
3630
|
+
error: "text-[hsl(var(--status-critical))]"
|
|
3631
|
+
};
|
|
3632
|
+
var POSITION_CLASSES = {
|
|
3633
|
+
"top-right": "top-4 right-4",
|
|
3634
|
+
"top-left": "top-4 left-4",
|
|
3635
|
+
"bottom-right": "bottom-4 right-4",
|
|
3636
|
+
"bottom-left": "bottom-4 left-4"
|
|
3637
|
+
};
|
|
3638
|
+
var SLIDE_FROM = {
|
|
3639
|
+
"top-right": { x: 80 },
|
|
3640
|
+
"top-left": { x: -80 },
|
|
3641
|
+
"bottom-right": { x: 80 },
|
|
3642
|
+
"bottom-left": { x: -80 }
|
|
3643
|
+
};
|
|
3644
|
+
function NotificationStack({
|
|
3645
|
+
notifications,
|
|
3646
|
+
onDismiss,
|
|
3647
|
+
position = "top-right",
|
|
3648
|
+
maxVisible = 5,
|
|
3649
|
+
className
|
|
3650
|
+
}) {
|
|
3651
|
+
const reduced = useReducedMotion();
|
|
3652
|
+
const visible = notifications.slice(0, maxVisible);
|
|
3653
|
+
const overflow = notifications.length - maxVisible;
|
|
3654
|
+
return /* @__PURE__ */ jsxs(
|
|
3655
|
+
"div",
|
|
3656
|
+
{
|
|
3657
|
+
className: cn(
|
|
3658
|
+
"fixed z-50 flex flex-col gap-2 w-[360px] max-w-[calc(100vw-2rem)]",
|
|
3659
|
+
POSITION_CLASSES[position],
|
|
3660
|
+
className
|
|
3661
|
+
),
|
|
3662
|
+
role: "region",
|
|
3663
|
+
"aria-label": "Notifications",
|
|
3664
|
+
children: [
|
|
3665
|
+
/* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: visible.map((notification, idx) => /* @__PURE__ */ jsx(
|
|
3666
|
+
motion.div,
|
|
3667
|
+
{
|
|
3668
|
+
layout: !reduced,
|
|
3669
|
+
initial: reduced ? { opacity: 1 } : { opacity: 0, ...SLIDE_FROM[position] },
|
|
3670
|
+
animate: { opacity: 1, x: 0 },
|
|
3671
|
+
exit: reduced ? { opacity: 0 } : { opacity: 0, ...SLIDE_FROM[position], transition: { duration: 0.15 } },
|
|
3672
|
+
transition: { type: "spring", stiffness: 300, damping: 30 },
|
|
3673
|
+
children: /* @__PURE__ */ jsx(
|
|
3674
|
+
NotificationCard,
|
|
3675
|
+
{
|
|
3676
|
+
notification,
|
|
3677
|
+
onDismiss,
|
|
3678
|
+
reduced: !!reduced
|
|
3679
|
+
}
|
|
3680
|
+
)
|
|
3681
|
+
},
|
|
3682
|
+
notification.id
|
|
3683
|
+
)) }),
|
|
3684
|
+
overflow > 0 && /* @__PURE__ */ jsxs("div", { className: "text-center text-xs text-[hsl(var(--text-tertiary))] py-1", children: [
|
|
3685
|
+
"+",
|
|
3686
|
+
overflow,
|
|
3687
|
+
" more ",
|
|
3688
|
+
overflow === 1 ? "notification" : "notifications"
|
|
3689
|
+
] })
|
|
3690
|
+
]
|
|
3691
|
+
}
|
|
3692
|
+
);
|
|
3693
|
+
}
|
|
3694
|
+
function NotificationCard({
|
|
3695
|
+
notification,
|
|
3696
|
+
onDismiss,
|
|
3697
|
+
reduced
|
|
3698
|
+
}) {
|
|
3699
|
+
const { id, title, message, type, action, dismissible = true, duration = 0 } = notification;
|
|
3700
|
+
const Icon = TYPE_ICON[type];
|
|
3701
|
+
const timerRef = useRef(null);
|
|
3702
|
+
const [progress, setProgress] = useState(100);
|
|
3703
|
+
const startTimeRef = useRef(Date.now());
|
|
3704
|
+
useEffect(() => {
|
|
3705
|
+
if (duration <= 0) return;
|
|
3706
|
+
startTimeRef.current = Date.now();
|
|
3707
|
+
const intervalId = setInterval(() => {
|
|
3708
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
3709
|
+
const remaining = Math.max(0, 100 - elapsed / duration * 100);
|
|
3710
|
+
setProgress(remaining);
|
|
3711
|
+
if (remaining <= 0) {
|
|
3712
|
+
clearInterval(intervalId);
|
|
3713
|
+
}
|
|
3714
|
+
}, 50);
|
|
3715
|
+
timerRef.current = setTimeout(() => {
|
|
3716
|
+
onDismiss(id);
|
|
3717
|
+
}, duration);
|
|
3718
|
+
return () => {
|
|
3719
|
+
clearInterval(intervalId);
|
|
3720
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
3721
|
+
};
|
|
3722
|
+
}, [id, duration, onDismiss]);
|
|
3723
|
+
const handleDismiss = useCallback(() => {
|
|
3724
|
+
onDismiss(id);
|
|
3725
|
+
}, [id, onDismiss]);
|
|
3726
|
+
return /* @__PURE__ */ jsxs(
|
|
3727
|
+
"div",
|
|
3728
|
+
{
|
|
3729
|
+
className: cn(
|
|
3730
|
+
"relative overflow-hidden rounded-xl border-l-[3px] shadow-lg",
|
|
3731
|
+
"bg-[hsl(var(--bg-elevated))] border border-[hsl(var(--border-subtle))]",
|
|
3732
|
+
TYPE_COLOR[type]
|
|
3733
|
+
),
|
|
3734
|
+
children: [
|
|
3735
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-4", children: [
|
|
3736
|
+
/* @__PURE__ */ jsx(Icon, { className: cn("size-5 shrink-0 mt-0.5", TYPE_ICON_COLOR[type]) }),
|
|
3737
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
3738
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-[hsl(var(--text-primary))]", children: title }),
|
|
3739
|
+
message && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-[hsl(var(--text-secondary))] line-clamp-2", children: message }),
|
|
3740
|
+
action && /* @__PURE__ */ jsx(
|
|
3741
|
+
"button",
|
|
3742
|
+
{
|
|
3743
|
+
type: "button",
|
|
3744
|
+
onClick: action.onClick,
|
|
3745
|
+
className: cn(
|
|
3746
|
+
"mt-2 text-xs font-medium cursor-pointer",
|
|
3747
|
+
"text-[hsl(var(--brand-primary))] hover:underline"
|
|
3748
|
+
),
|
|
3749
|
+
children: action.label
|
|
3750
|
+
}
|
|
3751
|
+
)
|
|
3752
|
+
] }),
|
|
3753
|
+
dismissible && /* @__PURE__ */ jsx(
|
|
3754
|
+
"button",
|
|
3755
|
+
{
|
|
3756
|
+
type: "button",
|
|
3757
|
+
onClick: handleDismiss,
|
|
3758
|
+
className: cn(
|
|
3759
|
+
"shrink-0 p-0.5 rounded cursor-pointer",
|
|
3760
|
+
"text-[hsl(var(--text-tertiary))] hover:text-[hsl(var(--text-primary))]",
|
|
3761
|
+
"hover:bg-[hsl(var(--bg-overlay))] transition-colors duration-100"
|
|
3762
|
+
),
|
|
3763
|
+
"aria-label": "Dismiss notification",
|
|
3764
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
|
|
3765
|
+
}
|
|
3766
|
+
)
|
|
3767
|
+
] }),
|
|
3768
|
+
duration > 0 && /* @__PURE__ */ jsx("div", { className: "h-0.5 bg-[hsl(var(--bg-overlay))]", children: /* @__PURE__ */ jsx(
|
|
3769
|
+
"div",
|
|
3770
|
+
{
|
|
3771
|
+
className: cn("h-full transition-[width] duration-100", TYPE_COLOR[type].replace("border-l-", "bg-")),
|
|
3772
|
+
style: { width: `${progress}%` }
|
|
3773
|
+
}
|
|
3774
|
+
) })
|
|
3775
|
+
]
|
|
3776
|
+
}
|
|
3777
|
+
);
|
|
3778
|
+
}
|
|
3779
|
+
var TAG_COLOR_MAP = {
|
|
3780
|
+
brand: "bg-[hsl(var(--brand-primary))]/15 text-[hsl(var(--brand-primary))]",
|
|
3781
|
+
blue: "bg-[hsl(var(--brand-secondary))]/15 text-[hsl(var(--brand-secondary))]",
|
|
3782
|
+
green: "bg-[hsl(var(--status-ok))]/15 text-[hsl(var(--status-ok))]",
|
|
3783
|
+
yellow: "bg-[hsl(var(--status-warning))]/15 text-[hsl(var(--status-warning))]",
|
|
3784
|
+
red: "bg-[hsl(var(--status-critical))]/15 text-[hsl(var(--status-critical))]",
|
|
3785
|
+
orange: "bg-[hsl(var(--status-warning))]/20 text-[hsl(var(--status-warning))]",
|
|
3786
|
+
purple: "bg-[hsl(270,60%,60%)]/15 text-[hsl(270,60%,65%)]",
|
|
3787
|
+
pink: "bg-[hsl(330,60%,60%)]/15 text-[hsl(330,60%,65%)]",
|
|
3788
|
+
teal: "bg-[hsl(180,60%,40%)]/15 text-[hsl(180,60%,55%)]",
|
|
3789
|
+
gray: "bg-[hsl(var(--bg-overlay))] text-[hsl(var(--text-secondary))]"
|
|
3790
|
+
};
|
|
3791
|
+
function KanbanColumn({
|
|
3792
|
+
title,
|
|
3793
|
+
items,
|
|
3794
|
+
count,
|
|
3795
|
+
color,
|
|
3796
|
+
onItemClick,
|
|
3797
|
+
onAddItem,
|
|
3798
|
+
className
|
|
3799
|
+
}) {
|
|
3800
|
+
const reduced = useReducedMotion();
|
|
3801
|
+
const displayCount = count ?? items.length;
|
|
3802
|
+
return /* @__PURE__ */ jsxs(
|
|
3803
|
+
"div",
|
|
3804
|
+
{
|
|
3805
|
+
className: cn(
|
|
3806
|
+
"flex flex-col w-72 min-w-[18rem] rounded-2xl",
|
|
3807
|
+
"bg-[hsl(var(--bg-surface))] border border-[hsl(var(--border-subtle))]",
|
|
3808
|
+
className
|
|
3809
|
+
),
|
|
3810
|
+
children: [
|
|
3811
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-[hsl(var(--border-subtle))]", children: [
|
|
3812
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3813
|
+
color && /* @__PURE__ */ jsx(
|
|
3814
|
+
"span",
|
|
3815
|
+
{
|
|
3816
|
+
className: "w-2 h-2 rounded-full shrink-0",
|
|
3817
|
+
style: { backgroundColor: color }
|
|
3818
|
+
}
|
|
3819
|
+
),
|
|
3820
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-[hsl(var(--text-primary))]", children: title }),
|
|
3821
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5 rounded-full text-[10px] font-medium tabular-nums bg-[hsl(var(--bg-overlay))] text-[hsl(var(--text-secondary))]", children: displayCount })
|
|
3822
|
+
] }),
|
|
3823
|
+
onAddItem && /* @__PURE__ */ jsx(
|
|
3824
|
+
"button",
|
|
3825
|
+
{
|
|
3826
|
+
type: "button",
|
|
3827
|
+
onClick: onAddItem,
|
|
3828
|
+
className: cn(
|
|
3829
|
+
"p-1 rounded-lg cursor-pointer",
|
|
3830
|
+
"text-[hsl(var(--text-tertiary))] hover:text-[hsl(var(--text-primary))]",
|
|
3831
|
+
"hover:bg-[hsl(var(--bg-overlay))] transition-colors duration-150"
|
|
3832
|
+
),
|
|
3833
|
+
"aria-label": `Add item to ${title}`,
|
|
3834
|
+
children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
|
|
3835
|
+
}
|
|
3836
|
+
)
|
|
3837
|
+
] }),
|
|
3838
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-2 space-y-2", children: items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8 text-xs text-[hsl(var(--text-tertiary))]", children: "No items" }) : items.map((item, idx) => /* @__PURE__ */ jsx(
|
|
3839
|
+
motion.div,
|
|
3840
|
+
{
|
|
3841
|
+
initial: reduced ? void 0 : { opacity: 0, y: 8 },
|
|
3842
|
+
animate: { opacity: 1, y: 0 },
|
|
3843
|
+
transition: reduced ? { duration: 0 } : { delay: idx * 0.04, duration: 0.2 },
|
|
3844
|
+
children: /* @__PURE__ */ jsx(KanbanCard, { item, onClick: onItemClick })
|
|
3845
|
+
},
|
|
3846
|
+
item.id
|
|
3847
|
+
)) })
|
|
3848
|
+
]
|
|
3849
|
+
}
|
|
3850
|
+
);
|
|
3851
|
+
}
|
|
3852
|
+
function KanbanCard({
|
|
3853
|
+
item,
|
|
3854
|
+
onClick
|
|
3855
|
+
}) {
|
|
3856
|
+
return /* @__PURE__ */ jsxs(
|
|
3857
|
+
"div",
|
|
3858
|
+
{
|
|
3859
|
+
role: onClick ? "button" : void 0,
|
|
3860
|
+
tabIndex: onClick ? 0 : void 0,
|
|
3861
|
+
onClick: onClick ? () => onClick(item) : void 0,
|
|
3862
|
+
onKeyDown: onClick ? (e) => {
|
|
3863
|
+
if (e.key === "Enter" || e.key === " ") onClick(item);
|
|
3864
|
+
} : void 0,
|
|
3865
|
+
className: cn(
|
|
3866
|
+
"rounded-xl p-3 border border-[hsl(var(--border-subtle))]",
|
|
3867
|
+
"bg-[hsl(var(--bg-base))]",
|
|
3868
|
+
onClick && "cursor-pointer hover:bg-[hsl(var(--bg-elevated))] hover:shadow-sm",
|
|
3869
|
+
"transition-all duration-150"
|
|
3870
|
+
),
|
|
3871
|
+
children: [
|
|
3872
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-[hsl(var(--text-primary))] line-clamp-2", children: item.title }),
|
|
3873
|
+
item.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-[hsl(var(--text-secondary))] line-clamp-2", children: item.description }),
|
|
3874
|
+
(item.tags?.length || item.assignee) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-2.5 gap-2", children: [
|
|
3875
|
+
item.tags && item.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 min-w-0", children: item.tags.map((tag, i) => /* @__PURE__ */ jsx(
|
|
3876
|
+
"span",
|
|
3877
|
+
{
|
|
3878
|
+
className: cn(
|
|
3879
|
+
"inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-medium whitespace-nowrap",
|
|
3880
|
+
TAG_COLOR_MAP[tag.color]
|
|
3881
|
+
),
|
|
3882
|
+
children: tag.label
|
|
3883
|
+
},
|
|
3884
|
+
i
|
|
3885
|
+
)) }),
|
|
3886
|
+
item.assignee && /* @__PURE__ */ jsx("div", { className: "shrink-0", title: item.assignee.name, children: item.assignee.avatar ? /* @__PURE__ */ jsx(
|
|
3887
|
+
"img",
|
|
3888
|
+
{
|
|
3889
|
+
src: item.assignee.avatar,
|
|
3890
|
+
alt: item.assignee.name,
|
|
3891
|
+
className: "size-6 rounded-full object-cover"
|
|
3892
|
+
}
|
|
3893
|
+
) : /* @__PURE__ */ jsx("div", { className: "size-6 rounded-full bg-[hsl(var(--bg-overlay))] flex items-center justify-center text-[10px] font-semibold text-[hsl(var(--text-secondary))]", children: item.assignee.name.slice(0, 2).toUpperCase() }) })
|
|
3894
|
+
] })
|
|
3895
|
+
]
|
|
3896
|
+
}
|
|
3897
|
+
);
|
|
3898
|
+
}
|
|
3899
|
+
function mean(nums) {
|
|
3900
|
+
if (nums.length === 0) return 0;
|
|
3901
|
+
return nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
3902
|
+
}
|
|
3903
|
+
function stdDev(nums) {
|
|
3904
|
+
if (nums.length < 2) return 0;
|
|
3905
|
+
const m = mean(nums);
|
|
3906
|
+
const variance = nums.reduce((sum, n) => sum + (n - m) ** 2, 0) / nums.length;
|
|
3907
|
+
return Math.sqrt(variance);
|
|
3908
|
+
}
|
|
3909
|
+
function analyzeColumn(columnId, columnHeader, data, getValue) {
|
|
3910
|
+
const suggestions = [];
|
|
3911
|
+
const values = data.map(getValue).filter((v) => v != null);
|
|
3912
|
+
if (values.length === 0) return suggestions;
|
|
3913
|
+
const numericValues = values.map((v) => typeof v === "number" ? v : typeof v === "string" && v !== "" && !isNaN(Number(v)) ? Number(v) : null).filter((v) => v !== null);
|
|
3914
|
+
if (numericValues.length > values.length * 0.7) {
|
|
3915
|
+
const m = mean(numericValues);
|
|
3916
|
+
const sd = stdDev(numericValues);
|
|
3917
|
+
if (sd > 0) {
|
|
3918
|
+
const outliers = numericValues.filter((v) => Math.abs(v - m) > 2 * sd);
|
|
3919
|
+
if (outliers.length > 0 && outliers.length < numericValues.length * 0.3) {
|
|
3920
|
+
suggestions.push({
|
|
3921
|
+
column: columnId,
|
|
3922
|
+
type: "outlier",
|
|
3923
|
+
label: `${outliers.length} outlier${outliers.length > 1 ? "s" : ""} in ${columnHeader}`,
|
|
3924
|
+
filter: () => {
|
|
3925
|
+
}
|
|
3926
|
+
});
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
const sorted = [...numericValues].sort((a, b) => a - b);
|
|
3930
|
+
const median = sorted[Math.floor(sorted.length / 2)] ?? 0;
|
|
3931
|
+
const aboveMedian = numericValues.filter((v) => v > median);
|
|
3932
|
+
if (aboveMedian.length > 0 && aboveMedian.length < numericValues.length * 0.2) {
|
|
3933
|
+
suggestions.push({
|
|
3934
|
+
column: columnId,
|
|
3935
|
+
type: "threshold",
|
|
3936
|
+
label: `Top ${aboveMedian.length} high values in ${columnHeader}`,
|
|
3937
|
+
filter: () => {
|
|
3938
|
+
}
|
|
3939
|
+
});
|
|
3940
|
+
}
|
|
3941
|
+
} else {
|
|
3942
|
+
const strValues = values.map(String);
|
|
3943
|
+
const freq = /* @__PURE__ */ new Map();
|
|
3944
|
+
for (const v of strValues) {
|
|
3945
|
+
freq.set(v, (freq.get(v) ?? 0) + 1);
|
|
3946
|
+
}
|
|
3947
|
+
for (const [val, count] of freq) {
|
|
3948
|
+
if (count / strValues.length >= 0.9 && freq.size > 1) {
|
|
3949
|
+
const otherCount = strValues.length - count;
|
|
3950
|
+
suggestions.push({
|
|
3951
|
+
column: columnId,
|
|
3952
|
+
type: "pattern",
|
|
3953
|
+
label: `Show non-"${val.length > 20 ? val.slice(0, 20) + "\u2026" : val}" (${otherCount})`,
|
|
3954
|
+
filter: () => {
|
|
3955
|
+
}
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
if (freq.size > 5) {
|
|
3960
|
+
const topEntries = [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
3961
|
+
const topTotal = topEntries.reduce((s, e) => s + e[1], 0);
|
|
3962
|
+
suggestions.push({
|
|
3963
|
+
column: columnId,
|
|
3964
|
+
type: "top-n",
|
|
3965
|
+
label: `Top 5 ${columnHeader} (${Math.round(topTotal / strValues.length * 100)}%)`,
|
|
3966
|
+
filter: () => {
|
|
3967
|
+
}
|
|
3968
|
+
});
|
|
3969
|
+
}
|
|
3970
|
+
const minorityValues = [...freq.entries()].filter(
|
|
3971
|
+
([, count]) => count / strValues.length < 0.12 && count > 0
|
|
3972
|
+
);
|
|
3973
|
+
if (minorityValues.length > 0 && minorityValues.length < freq.size) {
|
|
3974
|
+
const totalMinority = minorityValues.reduce((s, [, c]) => s + c, 0);
|
|
3975
|
+
suggestions.push({
|
|
3976
|
+
column: columnId,
|
|
3977
|
+
type: "pattern",
|
|
3978
|
+
label: `Rare ${columnHeader} values (${totalMinority} rows)`,
|
|
3979
|
+
filter: () => {
|
|
3980
|
+
}
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
return suggestions;
|
|
3985
|
+
}
|
|
3986
|
+
var SUGGESTION_ICONS = {
|
|
3987
|
+
outlier: AlertTriangle,
|
|
3988
|
+
"top-n": TrendingUp,
|
|
3989
|
+
pattern: Regex,
|
|
3990
|
+
threshold: Hash
|
|
3991
|
+
};
|
|
3992
|
+
var SUGGESTION_COLORS = {
|
|
3993
|
+
outlier: "bg-[hsl(var(--status-warning)/0.15)] text-[hsl(var(--status-warning))] border-[hsl(var(--status-warning)/0.3)]",
|
|
3994
|
+
"top-n": "bg-[hsl(var(--brand-primary)/0.15)] text-[hsl(var(--brand-primary))] border-[hsl(var(--brand-primary)/0.3)]",
|
|
3995
|
+
pattern: "bg-[hsl(var(--brand-secondary)/0.15)] text-[hsl(var(--brand-secondary))] border-[hsl(var(--brand-secondary)/0.3)]",
|
|
3996
|
+
threshold: "bg-[hsl(var(--status-critical)/0.15)] text-[hsl(var(--status-critical))] border-[hsl(var(--status-critical)/0.3)]"
|
|
3997
|
+
};
|
|
3998
|
+
function SmartTable({
|
|
3999
|
+
columns,
|
|
4000
|
+
data,
|
|
4001
|
+
onFilterSuggestion,
|
|
4002
|
+
maxSuggestions = 6,
|
|
4003
|
+
...tableProps
|
|
4004
|
+
}) {
|
|
4005
|
+
const prefersReducedMotion = useReducedMotion();
|
|
4006
|
+
const [dismissed, setDismissed] = useState(/* @__PURE__ */ new Set());
|
|
4007
|
+
const [appliedFilter, setAppliedFilter] = useState(null);
|
|
4008
|
+
const [filteredData, setFilteredData] = useState(null);
|
|
4009
|
+
const suggestions = useMemo(() => {
|
|
4010
|
+
if (data.length < 3) return [];
|
|
4011
|
+
const allSuggestions = [];
|
|
4012
|
+
for (const colDef of columns) {
|
|
4013
|
+
const col = colDef;
|
|
4014
|
+
const columnId = col.accessorKey ?? col.id ?? "";
|
|
4015
|
+
const columnHeader = typeof col.header === "string" ? col.header : columnId;
|
|
4016
|
+
if (!columnId) continue;
|
|
4017
|
+
const getValue = col.accessorFn ? col.accessorFn : (row) => row[columnId];
|
|
4018
|
+
const columnSuggestions = analyzeColumn(columnId, columnHeader, data, getValue);
|
|
4019
|
+
for (const s of columnSuggestions) {
|
|
4020
|
+
s.filter = () => {
|
|
4021
|
+
const vals = data.map(getValue).filter((v) => v != null);
|
|
4022
|
+
let filtered;
|
|
4023
|
+
switch (s.type) {
|
|
4024
|
+
case "outlier": {
|
|
4025
|
+
const nums = vals.map((v) => typeof v === "number" ? v : Number(v)).filter((v) => !isNaN(v));
|
|
4026
|
+
const m = mean(nums);
|
|
4027
|
+
const sd = stdDev(nums);
|
|
4028
|
+
filtered = data.filter((row) => {
|
|
4029
|
+
const v = getValue(row);
|
|
4030
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
4031
|
+
return !isNaN(n) && Math.abs(n - m) > 2 * sd;
|
|
4032
|
+
});
|
|
4033
|
+
break;
|
|
4034
|
+
}
|
|
4035
|
+
case "top-n": {
|
|
4036
|
+
const freq = /* @__PURE__ */ new Map();
|
|
4037
|
+
for (const v of vals) freq.set(String(v), (freq.get(String(v)) ?? 0) + 1);
|
|
4038
|
+
const topKeys = new Set([...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map((e) => e[0]));
|
|
4039
|
+
filtered = data.filter((row) => topKeys.has(String(getValue(row))));
|
|
4040
|
+
break;
|
|
4041
|
+
}
|
|
4042
|
+
case "threshold": {
|
|
4043
|
+
const nums = vals.map((v) => typeof v === "number" ? v : Number(v)).filter((v) => !isNaN(v));
|
|
4044
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
4045
|
+
const median = sorted[Math.floor(sorted.length / 2)] ?? 0;
|
|
4046
|
+
filtered = data.filter((row) => {
|
|
4047
|
+
const v = getValue(row);
|
|
4048
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
4049
|
+
return !isNaN(n) && n > median;
|
|
4050
|
+
});
|
|
4051
|
+
break;
|
|
4052
|
+
}
|
|
4053
|
+
case "pattern": {
|
|
4054
|
+
const freq = /* @__PURE__ */ new Map();
|
|
4055
|
+
for (const v of vals) freq.set(String(v), (freq.get(String(v)) ?? 0) + 1);
|
|
4056
|
+
let dominant = "";
|
|
4057
|
+
let maxCount = 0;
|
|
4058
|
+
for (const [k, c] of freq) {
|
|
4059
|
+
if (c > maxCount) {
|
|
4060
|
+
dominant = k;
|
|
4061
|
+
maxCount = c;
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
if (maxCount / vals.length >= 0.9) {
|
|
4065
|
+
filtered = data.filter((row) => String(getValue(row)) !== dominant);
|
|
4066
|
+
} else {
|
|
4067
|
+
const rareKeys = new Set(
|
|
4068
|
+
[...freq.entries()].filter(([, c]) => c / vals.length < 0.12).map((e) => e[0])
|
|
4069
|
+
);
|
|
4070
|
+
filtered = data.filter((row) => rareKeys.has(String(getValue(row))));
|
|
4071
|
+
}
|
|
4072
|
+
break;
|
|
4073
|
+
}
|
|
4074
|
+
default:
|
|
4075
|
+
filtered = data;
|
|
4076
|
+
}
|
|
4077
|
+
setFilteredData(filtered);
|
|
4078
|
+
setAppliedFilter(s.label);
|
|
4079
|
+
onFilterSuggestion?.(s);
|
|
4080
|
+
};
|
|
4081
|
+
}
|
|
4082
|
+
allSuggestions.push(...columnSuggestions);
|
|
4083
|
+
}
|
|
4084
|
+
return allSuggestions.slice(0, maxSuggestions);
|
|
4085
|
+
}, [data, columns, maxSuggestions, onFilterSuggestion]);
|
|
4086
|
+
const visibleSuggestions = suggestions.filter((s) => !dismissed.has(s.label));
|
|
4087
|
+
const handleDismiss = useCallback((label) => {
|
|
4088
|
+
setDismissed((prev) => new Set(prev).add(label));
|
|
4089
|
+
}, []);
|
|
4090
|
+
const handleClearFilter = useCallback(() => {
|
|
4091
|
+
setFilteredData(null);
|
|
4092
|
+
setAppliedFilter(null);
|
|
4093
|
+
}, []);
|
|
4094
|
+
const displayData = filteredData ?? data;
|
|
4095
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
4096
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: visibleSuggestions.length > 0 && !appliedFilter && /* @__PURE__ */ jsxs(
|
|
4097
|
+
motion.div,
|
|
4098
|
+
{
|
|
4099
|
+
initial: prefersReducedMotion ? void 0 : { opacity: 0, y: -8 },
|
|
4100
|
+
animate: prefersReducedMotion ? void 0 : { opacity: 1, y: 0 },
|
|
4101
|
+
exit: prefersReducedMotion ? void 0 : { opacity: 0, y: -8 },
|
|
4102
|
+
transition: { duration: 0.2 },
|
|
4103
|
+
className: "mb-3 flex flex-wrap items-center gap-2",
|
|
4104
|
+
children: [
|
|
4105
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-[11px] font-medium text-[hsl(var(--text-tertiary))] uppercase tracking-wider", children: [
|
|
4106
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-[hsl(var(--brand-primary))]" }),
|
|
4107
|
+
"Suggested Filters"
|
|
4108
|
+
] }),
|
|
4109
|
+
visibleSuggestions.map((suggestion) => {
|
|
4110
|
+
const Icon = SUGGESTION_ICONS[suggestion.type];
|
|
4111
|
+
return /* @__PURE__ */ jsxs(
|
|
4112
|
+
motion.button,
|
|
4113
|
+
{
|
|
4114
|
+
layout: !prefersReducedMotion,
|
|
4115
|
+
initial: prefersReducedMotion ? void 0 : { opacity: 0, scale: 0.9 },
|
|
4116
|
+
animate: prefersReducedMotion ? void 0 : { opacity: 1, scale: 1 },
|
|
4117
|
+
exit: prefersReducedMotion ? void 0 : { opacity: 0, scale: 0.9 },
|
|
4118
|
+
transition: { duration: 0.15 },
|
|
4119
|
+
onClick: suggestion.filter,
|
|
4120
|
+
className: cn(
|
|
4121
|
+
"group inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-[12px] font-medium",
|
|
4122
|
+
"transition-all hover:shadow-sm cursor-pointer",
|
|
4123
|
+
SUGGESTION_COLORS[suggestion.type]
|
|
4124
|
+
),
|
|
4125
|
+
children: [
|
|
4126
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
|
|
4127
|
+
suggestion.label,
|
|
4128
|
+
/* @__PURE__ */ jsx(
|
|
4129
|
+
"span",
|
|
4130
|
+
{
|
|
4131
|
+
role: "button",
|
|
4132
|
+
tabIndex: 0,
|
|
4133
|
+
onClick: (e) => {
|
|
4134
|
+
e.stopPropagation();
|
|
4135
|
+
handleDismiss(suggestion.label);
|
|
4136
|
+
},
|
|
4137
|
+
onKeyDown: (e) => {
|
|
4138
|
+
if (e.key === "Enter") {
|
|
4139
|
+
e.stopPropagation();
|
|
4140
|
+
handleDismiss(suggestion.label);
|
|
4141
|
+
}
|
|
4142
|
+
},
|
|
4143
|
+
className: "ml-0.5 opacity-0 group-hover:opacity-100 transition-opacity rounded-full p-0.5 hover:bg-[hsl(var(--bg-overlay)/0.3)]",
|
|
4144
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-2.5 w-2.5" })
|
|
4145
|
+
}
|
|
4146
|
+
)
|
|
4147
|
+
]
|
|
4148
|
+
},
|
|
4149
|
+
suggestion.label
|
|
4150
|
+
);
|
|
4151
|
+
})
|
|
4152
|
+
]
|
|
4153
|
+
}
|
|
4154
|
+
) }),
|
|
4155
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: appliedFilter && /* @__PURE__ */ jsxs(
|
|
4156
|
+
motion.div,
|
|
4157
|
+
{
|
|
4158
|
+
initial: prefersReducedMotion ? void 0 : { opacity: 0, y: -4 },
|
|
4159
|
+
animate: prefersReducedMotion ? void 0 : { opacity: 1, y: 0 },
|
|
4160
|
+
exit: prefersReducedMotion ? void 0 : { opacity: 0, y: -4 },
|
|
4161
|
+
transition: { duration: 0.15 },
|
|
4162
|
+
className: "mb-3 flex items-center gap-2",
|
|
4163
|
+
children: [
|
|
4164
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-[hsl(var(--brand-primary)/0.15)] border border-[hsl(var(--brand-primary)/0.3)] px-3 py-1 text-[12px] font-medium text-[hsl(var(--brand-primary))]", children: [
|
|
4165
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
|
|
4166
|
+
appliedFilter,
|
|
4167
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-1 tabular-nums text-[11px] opacity-70", children: [
|
|
4168
|
+
"(",
|
|
4169
|
+
displayData.length,
|
|
4170
|
+
" row",
|
|
4171
|
+
displayData.length !== 1 ? "s" : "",
|
|
4172
|
+
")"
|
|
4173
|
+
] })
|
|
4174
|
+
] }),
|
|
4175
|
+
/* @__PURE__ */ jsxs(
|
|
4176
|
+
"button",
|
|
4177
|
+
{
|
|
4178
|
+
onClick: handleClearFilter,
|
|
4179
|
+
className: "inline-flex items-center gap-1 rounded-full px-2 py-1 text-[11px] font-medium text-[hsl(var(--text-secondary))] hover:text-[hsl(var(--text-primary))] hover:bg-[hsl(var(--bg-elevated)/0.5)] transition-colors",
|
|
4180
|
+
children: [
|
|
4181
|
+
/* @__PURE__ */ jsx(X, { className: "h-3 w-3" }),
|
|
4182
|
+
"Clear"
|
|
4183
|
+
]
|
|
4184
|
+
}
|
|
4185
|
+
)
|
|
4186
|
+
]
|
|
4187
|
+
}
|
|
4188
|
+
) }),
|
|
4189
|
+
/* @__PURE__ */ jsx(
|
|
4190
|
+
DataTable,
|
|
4191
|
+
{
|
|
4192
|
+
columns,
|
|
4193
|
+
data: displayData,
|
|
4194
|
+
...tableProps
|
|
4195
|
+
}
|
|
4196
|
+
)
|
|
4197
|
+
] });
|
|
4198
|
+
}
|
|
4199
|
+
function easeOutCubic2(t) {
|
|
4200
|
+
return 1 - Math.pow(1 - t, 3);
|
|
4201
|
+
}
|
|
4202
|
+
function formatRelativeSeconds(ms) {
|
|
4203
|
+
const secs = Math.floor(ms / 1e3);
|
|
4204
|
+
if (secs < 1) return "just now";
|
|
4205
|
+
if (secs < 60) return `${secs}s ago`;
|
|
4206
|
+
if (secs < 3600) return `${Math.floor(secs / 60)}m ago`;
|
|
4207
|
+
return `${Math.floor(secs / 3600)}h ago`;
|
|
4208
|
+
}
|
|
4209
|
+
var SIZE_CLASSES2 = {
|
|
4210
|
+
sm: { value: "text-lg", label: "text-[11px]", delta: "text-[10px]", dot: "h-1.5 w-1.5", gap: "gap-1" },
|
|
4211
|
+
md: { value: "text-2xl", label: "text-xs", delta: "text-[11px]", dot: "h-2 w-2", gap: "gap-1.5" },
|
|
4212
|
+
lg: { value: "text-3xl", label: "text-sm", delta: "text-xs", dot: "h-2.5 w-2.5", gap: "gap-2" },
|
|
4213
|
+
xl: { value: "text-4xl", label: "text-base", delta: "text-sm", dot: "h-3 w-3", gap: "gap-2" }
|
|
4214
|
+
};
|
|
4215
|
+
function AnimatedNumber({
|
|
4216
|
+
value,
|
|
4217
|
+
format,
|
|
4218
|
+
duration = 400,
|
|
4219
|
+
animateEnabled,
|
|
4220
|
+
reduced,
|
|
4221
|
+
className
|
|
4222
|
+
}) {
|
|
4223
|
+
const prevRef = useRef(value);
|
|
4224
|
+
const rafRef = useRef(null);
|
|
4225
|
+
const [displayed, setDisplayed] = useState(value);
|
|
4226
|
+
useEffect(() => {
|
|
4227
|
+
const from = prevRef.current;
|
|
4228
|
+
const to = value;
|
|
4229
|
+
prevRef.current = value;
|
|
4230
|
+
if (reduced || !animateEnabled || from === to) {
|
|
4231
|
+
setDisplayed(to);
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
const start = performance.now();
|
|
4235
|
+
function tick(now) {
|
|
4236
|
+
const elapsed = now - start;
|
|
4237
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
4238
|
+
const eased = easeOutCubic2(progress);
|
|
4239
|
+
setDisplayed(from + (to - from) * eased);
|
|
4240
|
+
if (progress < 1) {
|
|
4241
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
4242
|
+
} else {
|
|
4243
|
+
setDisplayed(to);
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
4247
|
+
return () => {
|
|
4248
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
4249
|
+
};
|
|
4250
|
+
}, [value, duration, reduced, animateEnabled]);
|
|
4251
|
+
const formatted = format ? format(displayed) : Number.isInteger(value) ? Math.round(displayed).toString() : displayed.toFixed(value.toString().split(".")[1]?.length ?? 1);
|
|
4252
|
+
return /* @__PURE__ */ jsx("span", { className: cn("tabular-nums", className), children: formatted });
|
|
4253
|
+
}
|
|
4254
|
+
function RealtimeValue({
|
|
4255
|
+
value,
|
|
4256
|
+
label,
|
|
4257
|
+
format,
|
|
4258
|
+
lastUpdated,
|
|
4259
|
+
staleAfterMs = 3e4,
|
|
4260
|
+
connectionState = "connected",
|
|
4261
|
+
previousValue,
|
|
4262
|
+
animate = true,
|
|
4263
|
+
size = "md",
|
|
4264
|
+
className
|
|
4265
|
+
}) {
|
|
4266
|
+
const prefersReducedMotion = useReducedMotion();
|
|
4267
|
+
const sizeClasses6 = SIZE_CLASSES2[size];
|
|
4268
|
+
const [staleness, setStaleness] = useState(0);
|
|
4269
|
+
useEffect(() => {
|
|
4270
|
+
if (!lastUpdated) return;
|
|
4271
|
+
const getMs = () => Date.now() - new Date(lastUpdated).getTime();
|
|
4272
|
+
setStaleness(getMs());
|
|
4273
|
+
const interval = setInterval(() => setStaleness(getMs()), 1e3);
|
|
4274
|
+
return () => clearInterval(interval);
|
|
4275
|
+
}, [lastUpdated]);
|
|
4276
|
+
const isStale = lastUpdated ? staleness > staleAfterMs : false;
|
|
4277
|
+
const isVeryStale = lastUpdated ? staleness > staleAfterMs * 2 : false;
|
|
4278
|
+
const freshnessColor = useMemo(() => {
|
|
4279
|
+
if (connectionState === "disconnected") return "bg-[hsl(var(--status-critical))]";
|
|
4280
|
+
if (isVeryStale) return "bg-[hsl(var(--status-critical))]";
|
|
4281
|
+
if (isStale) return "bg-[hsl(var(--status-warning))]";
|
|
4282
|
+
return "bg-[hsl(var(--status-ok))]";
|
|
4283
|
+
}, [connectionState, isStale, isVeryStale]);
|
|
4284
|
+
const delta = typeof value === "number" && previousValue !== void 0 ? value - previousValue : null;
|
|
4285
|
+
const deltaSign = delta !== null ? delta > 0 ? "+" : delta < 0 ? "" : "" : null;
|
|
4286
|
+
const isNumeric = typeof value === "number";
|
|
4287
|
+
return /* @__PURE__ */ jsxs(
|
|
4288
|
+
"div",
|
|
4289
|
+
{
|
|
4290
|
+
className: cn(
|
|
4291
|
+
"relative inline-flex flex-col",
|
|
4292
|
+
sizeClasses6.gap,
|
|
4293
|
+
isStale && "opacity-60",
|
|
4294
|
+
className
|
|
4295
|
+
),
|
|
4296
|
+
title: lastUpdated ? `Last updated: ${new Date(lastUpdated).toLocaleString()} (${formatRelativeSeconds(staleness)})` : void 0,
|
|
4297
|
+
children: [
|
|
4298
|
+
label && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: /* @__PURE__ */ jsx("span", { className: cn("font-medium text-[hsl(var(--text-secondary))]", sizeClasses6.label), children: label }) }),
|
|
4299
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4300
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
|
|
4301
|
+
/* @__PURE__ */ jsx("span", { className: cn("rounded-full", sizeClasses6.dot, freshnessColor) }),
|
|
4302
|
+
connectionState === "connected" && !isStale && /* @__PURE__ */ jsx(
|
|
4303
|
+
"span",
|
|
4304
|
+
{
|
|
4305
|
+
className: cn(
|
|
4306
|
+
"absolute rounded-full animate-ping",
|
|
4307
|
+
sizeClasses6.dot,
|
|
4308
|
+
"bg-[hsl(var(--status-ok)/0.5)]"
|
|
4309
|
+
),
|
|
4310
|
+
style: { animationDuration: "2s" }
|
|
4311
|
+
}
|
|
4312
|
+
)
|
|
4313
|
+
] }),
|
|
4314
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
|
|
4315
|
+
isNumeric ? /* @__PURE__ */ jsx(
|
|
4316
|
+
AnimatedNumber,
|
|
4317
|
+
{
|
|
4318
|
+
value,
|
|
4319
|
+
format,
|
|
4320
|
+
animateEnabled: animate,
|
|
4321
|
+
reduced: prefersReducedMotion,
|
|
4322
|
+
className: cn("font-semibold text-[hsl(var(--text-primary))]", sizeClasses6.value)
|
|
4323
|
+
}
|
|
4324
|
+
) : /* @__PURE__ */ jsx("span", { className: cn("font-semibold text-[hsl(var(--text-primary))] tabular-nums", sizeClasses6.value), children: value }),
|
|
4325
|
+
/* @__PURE__ */ jsxs(AnimatePresence, { children: [
|
|
4326
|
+
delta !== null && delta !== 0 && /* @__PURE__ */ jsxs(
|
|
4327
|
+
motion.span,
|
|
4328
|
+
{
|
|
4329
|
+
initial: prefersReducedMotion ? void 0 : { opacity: 0, x: -4 },
|
|
4330
|
+
animate: prefersReducedMotion ? void 0 : { opacity: 1, x: 0 },
|
|
4331
|
+
exit: prefersReducedMotion ? void 0 : { opacity: 0, x: -4 },
|
|
4332
|
+
transition: { duration: 0.15 },
|
|
4333
|
+
className: cn(
|
|
4334
|
+
"inline-flex items-center gap-0.5 font-medium tabular-nums",
|
|
4335
|
+
sizeClasses6.delta,
|
|
4336
|
+
delta > 0 ? "text-[hsl(var(--status-ok))]" : "text-[hsl(var(--status-critical))]"
|
|
4337
|
+
),
|
|
4338
|
+
children: [
|
|
4339
|
+
delta > 0 ? /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "h-3 w-3" }),
|
|
4340
|
+
deltaSign,
|
|
4341
|
+
format ? format(Math.abs(delta)) : Math.abs(delta).toLocaleString()
|
|
4342
|
+
]
|
|
4343
|
+
}
|
|
4344
|
+
),
|
|
4345
|
+
delta === 0 && /* @__PURE__ */ jsxs(
|
|
4346
|
+
motion.span,
|
|
4347
|
+
{
|
|
4348
|
+
initial: prefersReducedMotion ? void 0 : { opacity: 0 },
|
|
4349
|
+
animate: prefersReducedMotion ? void 0 : { opacity: 1 },
|
|
4350
|
+
className: cn(
|
|
4351
|
+
"inline-flex items-center gap-0.5 font-medium text-[hsl(var(--text-tertiary))]",
|
|
4352
|
+
sizeClasses6.delta
|
|
4353
|
+
),
|
|
4354
|
+
children: [
|
|
4355
|
+
/* @__PURE__ */ jsx(Minus, { className: "h-3 w-3" }),
|
|
4356
|
+
"0"
|
|
4357
|
+
]
|
|
4358
|
+
}
|
|
4359
|
+
)
|
|
4360
|
+
] })
|
|
4361
|
+
] }),
|
|
4362
|
+
connectionState !== "connected" && /* @__PURE__ */ jsxs("span", { className: "ml-1", children: [
|
|
4363
|
+
connectionState === "reconnecting" && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 text-[hsl(var(--status-warning))] animate-spin" }),
|
|
4364
|
+
connectionState === "disconnected" && /* @__PURE__ */ jsx(WifiOff, { className: "h-4 w-4 text-[hsl(var(--status-critical))]" })
|
|
4365
|
+
] })
|
|
4366
|
+
] }),
|
|
4367
|
+
lastUpdated && /* @__PURE__ */ jsx("span", { className: cn("text-[hsl(var(--text-tertiary))] tabular-nums", sizeClasses6.delta), children: formatRelativeSeconds(staleness) })
|
|
4368
|
+
]
|
|
4369
|
+
}
|
|
4370
|
+
);
|
|
4371
|
+
}
|
|
4372
|
+
function fuzzyScore(query, target) {
|
|
4373
|
+
const q = query.toLowerCase();
|
|
4374
|
+
const t = target.toLowerCase();
|
|
4375
|
+
if (t === q) return 100;
|
|
4376
|
+
if (t.startsWith(q)) return 80;
|
|
4377
|
+
if (t.includes(q)) return 60;
|
|
4378
|
+
let qi = 0;
|
|
4379
|
+
let score = 0;
|
|
4380
|
+
for (let ti = 0; ti < t.length && qi < q.length; ti++) {
|
|
4381
|
+
if (t[ti] === q[qi]) {
|
|
4382
|
+
score += 10;
|
|
4383
|
+
qi++;
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
return qi === q.length ? score : 0;
|
|
4387
|
+
}
|
|
4388
|
+
function scoreItem(query, item) {
|
|
4389
|
+
if (!query) return 0;
|
|
4390
|
+
let best = fuzzyScore(query, item.label);
|
|
4391
|
+
if (item.description) best = Math.max(best, fuzzyScore(query, item.description) * 0.8);
|
|
4392
|
+
if (item.keywords) {
|
|
4393
|
+
for (const kw of item.keywords) {
|
|
4394
|
+
best = Math.max(best, fuzzyScore(query, kw) * 0.9);
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
return best;
|
|
4398
|
+
}
|
|
4399
|
+
function CommandBar({
|
|
4400
|
+
items,
|
|
4401
|
+
placeholder = "Type a command\u2026",
|
|
4402
|
+
hotkey = "k",
|
|
4403
|
+
onSearch,
|
|
4404
|
+
recentKey = "ui-kit-command-recent",
|
|
4405
|
+
maxRecent = 5,
|
|
4406
|
+
className
|
|
4407
|
+
}) {
|
|
4408
|
+
const prefersReducedMotion = useReducedMotion();
|
|
4409
|
+
const [open, setOpen] = useState(false);
|
|
4410
|
+
const [query, setQuery] = useState("");
|
|
4411
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
4412
|
+
const [asyncResults, setAsyncResults] = useState([]);
|
|
4413
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
4414
|
+
const inputRef = useRef(null);
|
|
4415
|
+
const listRef = useRef(null);
|
|
4416
|
+
const [recentIds, setRecentIds] = useState(() => {
|
|
4417
|
+
if (typeof window === "undefined") return [];
|
|
4418
|
+
try {
|
|
4419
|
+
return JSON.parse(localStorage.getItem(recentKey) ?? "[]");
|
|
4420
|
+
} catch {
|
|
4421
|
+
return [];
|
|
4422
|
+
}
|
|
4423
|
+
});
|
|
4424
|
+
const saveRecent = useCallback(
|
|
4425
|
+
(id) => {
|
|
4426
|
+
const updated = [id, ...recentIds.filter((r) => r !== id)].slice(0, maxRecent);
|
|
4427
|
+
setRecentIds(updated);
|
|
4428
|
+
try {
|
|
4429
|
+
localStorage.setItem(recentKey, JSON.stringify(updated));
|
|
4430
|
+
} catch {
|
|
4431
|
+
}
|
|
4432
|
+
},
|
|
4433
|
+
[recentIds, recentKey, maxRecent]
|
|
4434
|
+
);
|
|
4435
|
+
useEffect(() => {
|
|
4436
|
+
const handler = (e) => {
|
|
4437
|
+
if ((e.metaKey || e.ctrlKey) && e.key === hotkey) {
|
|
4438
|
+
e.preventDefault();
|
|
4439
|
+
setOpen((o) => !o);
|
|
4440
|
+
}
|
|
4441
|
+
if (e.key === "Escape" && open) {
|
|
4442
|
+
setOpen(false);
|
|
4443
|
+
}
|
|
4444
|
+
};
|
|
4445
|
+
document.addEventListener("keydown", handler);
|
|
4446
|
+
return () => document.removeEventListener("keydown", handler);
|
|
4447
|
+
}, [hotkey, open]);
|
|
4448
|
+
useEffect(() => {
|
|
4449
|
+
if (open) {
|
|
4450
|
+
setQuery("");
|
|
4451
|
+
setActiveIndex(0);
|
|
4452
|
+
setAsyncResults([]);
|
|
4453
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
4454
|
+
}
|
|
4455
|
+
}, [open]);
|
|
4456
|
+
useEffect(() => {
|
|
4457
|
+
if (open) {
|
|
4458
|
+
const prev = document.body.style.overflow;
|
|
4459
|
+
document.body.style.overflow = "hidden";
|
|
4460
|
+
return () => {
|
|
4461
|
+
document.body.style.overflow = prev;
|
|
4462
|
+
};
|
|
4463
|
+
}
|
|
4464
|
+
}, [open]);
|
|
4465
|
+
useEffect(() => {
|
|
4466
|
+
if (!onSearch || !query) {
|
|
4467
|
+
setAsyncResults([]);
|
|
4468
|
+
return;
|
|
4469
|
+
}
|
|
4470
|
+
setIsSearching(true);
|
|
4471
|
+
const timer = setTimeout(async () => {
|
|
4472
|
+
try {
|
|
4473
|
+
const results = await onSearch(query);
|
|
4474
|
+
setAsyncResults(results);
|
|
4475
|
+
} catch {
|
|
4476
|
+
setAsyncResults([]);
|
|
4477
|
+
} finally {
|
|
4478
|
+
setIsSearching(false);
|
|
4479
|
+
}
|
|
4480
|
+
}, 200);
|
|
4481
|
+
return () => clearTimeout(timer);
|
|
4482
|
+
}, [query, onSearch]);
|
|
4483
|
+
const displayItems = useMemo(() => {
|
|
4484
|
+
const allItems = [...items, ...asyncResults];
|
|
4485
|
+
if (!query) {
|
|
4486
|
+
const recentItems = recentIds.map((id) => allItems.find((i) => i.id === id)).filter((i) => i !== void 0);
|
|
4487
|
+
const rest = allItems.filter((i) => !recentIds.includes(i.id));
|
|
4488
|
+
return [
|
|
4489
|
+
...recentItems.map((i) => ({ ...i, group: "Recent" })),
|
|
4490
|
+
...rest
|
|
4491
|
+
];
|
|
4492
|
+
}
|
|
4493
|
+
return allItems.map((item) => ({ item, score: scoreItem(query, item) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).map(({ item }) => item);
|
|
4494
|
+
}, [items, asyncResults, query, recentIds]);
|
|
4495
|
+
const groups = useMemo(() => {
|
|
4496
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4497
|
+
for (const item of displayItems) {
|
|
4498
|
+
const group = item.group ?? "";
|
|
4499
|
+
const arr = grouped.get(group);
|
|
4500
|
+
if (arr) arr.push(item);
|
|
4501
|
+
else grouped.set(group, [item]);
|
|
4502
|
+
}
|
|
4503
|
+
return grouped;
|
|
4504
|
+
}, [displayItems]);
|
|
4505
|
+
const flatItems = displayItems;
|
|
4506
|
+
useEffect(() => {
|
|
4507
|
+
setActiveIndex(0);
|
|
4508
|
+
}, [query]);
|
|
4509
|
+
const handleSelect = useCallback(
|
|
4510
|
+
(item) => {
|
|
4511
|
+
saveRecent(item.id);
|
|
4512
|
+
setOpen(false);
|
|
4513
|
+
item.onSelect();
|
|
4514
|
+
},
|
|
4515
|
+
[saveRecent]
|
|
4516
|
+
);
|
|
4517
|
+
const handleKeyDown = useCallback(
|
|
4518
|
+
(e) => {
|
|
4519
|
+
if (e.key === "ArrowDown") {
|
|
4520
|
+
e.preventDefault();
|
|
4521
|
+
setActiveIndex((i) => Math.min(i + 1, flatItems.length - 1));
|
|
4522
|
+
} else if (e.key === "ArrowUp") {
|
|
4523
|
+
e.preventDefault();
|
|
4524
|
+
setActiveIndex((i) => Math.max(i - 1, 0));
|
|
4525
|
+
} else if (e.key === "Enter") {
|
|
4526
|
+
e.preventDefault();
|
|
4527
|
+
const item = flatItems[activeIndex];
|
|
4528
|
+
if (item) handleSelect(item);
|
|
4529
|
+
}
|
|
4530
|
+
},
|
|
4531
|
+
[flatItems, activeIndex, handleSelect]
|
|
4532
|
+
);
|
|
4533
|
+
useEffect(() => {
|
|
4534
|
+
if (!listRef.current) return;
|
|
4535
|
+
const active = listRef.current.querySelector('[data-active="true"]');
|
|
4536
|
+
active?.scrollIntoView({ block: "nearest" });
|
|
4537
|
+
}, [activeIndex]);
|
|
4538
|
+
const isMac = typeof navigator !== "undefined" && /Mac|iPhone/.test(navigator.userAgent ?? "");
|
|
4539
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50", children: [
|
|
4540
|
+
/* @__PURE__ */ jsx(
|
|
4541
|
+
motion.div,
|
|
4542
|
+
{
|
|
4543
|
+
initial: { opacity: 0 },
|
|
4544
|
+
animate: { opacity: 1 },
|
|
4545
|
+
exit: { opacity: 0 },
|
|
4546
|
+
transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.15 },
|
|
4547
|
+
className: "absolute inset-0 bg-[hsl(var(--bg-base)/0.6)] backdrop-blur-sm",
|
|
4548
|
+
onClick: () => setOpen(false)
|
|
4549
|
+
}
|
|
4550
|
+
),
|
|
4551
|
+
/* @__PURE__ */ jsxs(
|
|
4552
|
+
motion.div,
|
|
4553
|
+
{
|
|
4554
|
+
initial: prefersReducedMotion ? void 0 : { opacity: 0, scale: 0.96, y: -20 },
|
|
4555
|
+
animate: prefersReducedMotion ? void 0 : { opacity: 1, scale: 1, y: 0 },
|
|
4556
|
+
exit: prefersReducedMotion ? void 0 : { opacity: 0, scale: 0.96, y: -20 },
|
|
4557
|
+
transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.2, ease: [0.16, 1, 0.3, 1] },
|
|
4558
|
+
className: cn(
|
|
4559
|
+
"absolute left-1/2 top-[15%] -translate-x-1/2",
|
|
4560
|
+
"w-full max-w-lg rounded-2xl overflow-hidden",
|
|
4561
|
+
"border border-[hsl(var(--border-default))]",
|
|
4562
|
+
"bg-[hsl(var(--bg-elevated))] shadow-2xl",
|
|
4563
|
+
"flex flex-col max-h-[70vh]",
|
|
4564
|
+
className
|
|
4565
|
+
),
|
|
4566
|
+
onKeyDown: handleKeyDown,
|
|
4567
|
+
children: [
|
|
4568
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-[hsl(var(--border-subtle)/0.5)]", children: [
|
|
4569
|
+
/* @__PURE__ */ jsx(Search, { className: "h-5 w-5 text-[hsl(var(--text-tertiary))] shrink-0" }),
|
|
4570
|
+
/* @__PURE__ */ jsx(
|
|
4571
|
+
"input",
|
|
4572
|
+
{
|
|
4573
|
+
ref: inputRef,
|
|
4574
|
+
type: "text",
|
|
4575
|
+
value: query,
|
|
4576
|
+
onChange: (e) => setQuery(e.target.value),
|
|
4577
|
+
placeholder,
|
|
4578
|
+
className: "flex-1 bg-transparent text-[hsl(var(--text-primary))] text-sm placeholder:text-[hsl(var(--text-tertiary))] outline-none"
|
|
4579
|
+
}
|
|
4580
|
+
),
|
|
4581
|
+
/* @__PURE__ */ jsx("kbd", { className: "hidden sm:inline-flex items-center gap-1 rounded-md border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))] px-1.5 py-0.5 text-[10px] text-[hsl(var(--text-tertiary))] font-mono", children: "Esc" })
|
|
4582
|
+
] }),
|
|
4583
|
+
/* @__PURE__ */ jsxs("div", { ref: listRef, className: "flex-1 overflow-y-auto py-2", children: [
|
|
4584
|
+
flatItems.length === 0 && !isSearching && /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center text-sm text-[hsl(var(--text-tertiary))]", children: query ? "No results found." : "No commands available." }),
|
|
4585
|
+
isSearching && flatItems.length === 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-8 flex items-center justify-center gap-2 text-sm text-[hsl(var(--text-tertiary))]", children: [
|
|
4586
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 rounded-full border-2 border-[hsl(var(--brand-primary))] border-t-transparent animate-spin" }),
|
|
4587
|
+
"Searching..."
|
|
4588
|
+
] }),
|
|
4589
|
+
[...groups.entries()].map(([groupName, groupItems]) => {
|
|
4590
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
4591
|
+
groupName && /* @__PURE__ */ jsx("div", { className: "px-4 pt-2 pb-1", children: /* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-[hsl(var(--text-tertiary))]", children: groupName }) }),
|
|
4592
|
+
groupItems.map((item) => {
|
|
4593
|
+
const globalIdx = flatItems.indexOf(item);
|
|
4594
|
+
const isActive = globalIdx === activeIndex;
|
|
4595
|
+
const Icon = item.icon;
|
|
4596
|
+
const isRecent = item.group === "Recent";
|
|
4597
|
+
return /* @__PURE__ */ jsxs(
|
|
4598
|
+
"button",
|
|
4599
|
+
{
|
|
4600
|
+
"data-active": isActive,
|
|
4601
|
+
onClick: () => handleSelect(item),
|
|
4602
|
+
onMouseEnter: () => setActiveIndex(globalIdx),
|
|
4603
|
+
className: cn(
|
|
4604
|
+
"w-full flex items-center gap-3 px-4 py-2.5 text-left transition-colors",
|
|
4605
|
+
isActive ? "bg-[hsl(var(--brand-primary)/0.1)]" : "hover:bg-[hsl(var(--bg-surface)/0.5)]"
|
|
4606
|
+
),
|
|
4607
|
+
children: [
|
|
4608
|
+
Icon ? /* @__PURE__ */ jsx(Icon, { className: cn(
|
|
4609
|
+
"h-4 w-4 shrink-0",
|
|
4610
|
+
isActive ? "text-[hsl(var(--brand-primary))]" : "text-[hsl(var(--text-tertiary))]"
|
|
4611
|
+
) }) : isRecent ? /* @__PURE__ */ jsx(Clock, { className: cn(
|
|
4612
|
+
"h-4 w-4 shrink-0",
|
|
4613
|
+
isActive ? "text-[hsl(var(--brand-primary))]" : "text-[hsl(var(--text-tertiary))]"
|
|
4614
|
+
) }) : /* @__PURE__ */ jsx("div", { className: "h-4 w-4 shrink-0" }),
|
|
4615
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
4616
|
+
/* @__PURE__ */ jsx("div", { className: cn(
|
|
4617
|
+
"text-sm font-medium truncate",
|
|
4618
|
+
isActive ? "text-[hsl(var(--text-primary))]" : "text-[hsl(var(--text-primary))]"
|
|
4619
|
+
), children: item.label }),
|
|
4620
|
+
item.description && /* @__PURE__ */ jsx("div", { className: "text-[11px] text-[hsl(var(--text-tertiary))] truncate mt-0.5", children: item.description })
|
|
4621
|
+
] }),
|
|
4622
|
+
item.shortcut && /* @__PURE__ */ jsx("kbd", { className: "flex items-center gap-0.5 rounded-md border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))] px-1.5 py-0.5 text-[10px] text-[hsl(var(--text-tertiary))] font-mono shrink-0", children: item.shortcut }),
|
|
4623
|
+
isActive && /* @__PURE__ */ jsx(CornerDownLeft, { className: "h-3.5 w-3.5 text-[hsl(var(--text-tertiary))] shrink-0" })
|
|
4624
|
+
]
|
|
4625
|
+
},
|
|
4626
|
+
item.id
|
|
4627
|
+
);
|
|
4628
|
+
})
|
|
4629
|
+
] }, groupName || "__ungrouped");
|
|
4630
|
+
})
|
|
4631
|
+
] }),
|
|
4632
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 px-4 py-2 border-t border-[hsl(var(--border-subtle)/0.5)] text-[10px] text-[hsl(var(--text-tertiary))]", children: [
|
|
4633
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
4634
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))] px-1 py-0.5 font-mono", children: "\u2191\u2193" }),
|
|
4635
|
+
"Navigate"
|
|
4636
|
+
] }),
|
|
4637
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
4638
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))] px-1 py-0.5 font-mono", children: "\u21B5" }),
|
|
4639
|
+
"Select"
|
|
4640
|
+
] }),
|
|
4641
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
4642
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))] px-1 py-0.5 font-mono", children: "Esc" }),
|
|
4643
|
+
"Close"
|
|
4644
|
+
] }),
|
|
4645
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto inline-flex items-center gap-1", children: [
|
|
4646
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))] px-1 py-0.5 font-mono", children: isMac ? "\u2318" : "Ctrl+" }),
|
|
4647
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))] px-1 py-0.5 font-mono uppercase", children: hotkey }),
|
|
4648
|
+
"Toggle"
|
|
4649
|
+
] })
|
|
4650
|
+
] })
|
|
4651
|
+
]
|
|
4652
|
+
}
|
|
4653
|
+
)
|
|
4654
|
+
] }) });
|
|
4655
|
+
}
|
|
4656
|
+
function SortableList({
|
|
4657
|
+
items,
|
|
4658
|
+
onReorder,
|
|
4659
|
+
renderItem,
|
|
4660
|
+
direction = "vertical",
|
|
4661
|
+
className
|
|
4662
|
+
}) {
|
|
4663
|
+
const prefersReducedMotion = useReducedMotion();
|
|
4664
|
+
const [dragIdx, setDragIdx] = useState(null);
|
|
4665
|
+
const [overIdx, setOverIdx] = useState(null);
|
|
4666
|
+
const [kbPickedIdx, setKbPickedIdx] = useState(null);
|
|
4667
|
+
const containerRef = useRef(null);
|
|
4668
|
+
const startPos = useRef({ x: 0, y: 0 });
|
|
4669
|
+
const dragItemId = useRef(null);
|
|
4670
|
+
const handlePointerDown = useCallback(
|
|
4671
|
+
(index) => (e) => {
|
|
4672
|
+
e.preventDefault();
|
|
4673
|
+
if (e.button !== 0) return;
|
|
4674
|
+
setDragIdx(index);
|
|
4675
|
+
setOverIdx(index);
|
|
4676
|
+
dragItemId.current = items[index]?.id ?? null;
|
|
4677
|
+
startPos.current = { x: e.clientX, y: e.clientY };
|
|
4678
|
+
const handlePointerMove = (ev) => {
|
|
4679
|
+
if (!containerRef.current) return;
|
|
4680
|
+
const container = containerRef.current;
|
|
4681
|
+
const children = Array.from(container.children);
|
|
4682
|
+
for (let i = 0; i < children.length; i++) {
|
|
4683
|
+
const rect = children[i].getBoundingClientRect();
|
|
4684
|
+
const midX = rect.left + rect.width / 2;
|
|
4685
|
+
const midY = rect.top + rect.height / 2;
|
|
4686
|
+
const isOver = direction === "vertical" ? ev.clientY < midY + rect.height / 2 && ev.clientY > midY - rect.height / 2 : ev.clientX < midX + rect.width / 2 && ev.clientX > midX - rect.width / 2;
|
|
4687
|
+
if (isOver) {
|
|
4688
|
+
setOverIdx(i);
|
|
4689
|
+
break;
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
};
|
|
4693
|
+
const handlePointerUp = () => {
|
|
4694
|
+
document.removeEventListener("pointermove", handlePointerMove);
|
|
4695
|
+
document.removeEventListener("pointerup", handlePointerUp);
|
|
4696
|
+
setDragIdx((prev) => {
|
|
4697
|
+
setOverIdx((over) => {
|
|
4698
|
+
if (prev !== null && over !== null && prev !== over) {
|
|
4699
|
+
const newItems = [...items];
|
|
4700
|
+
const [moved] = newItems.splice(prev, 1);
|
|
4701
|
+
if (moved) newItems.splice(over, 0, moved);
|
|
4702
|
+
setTimeout(() => onReorder(newItems), 0);
|
|
4703
|
+
}
|
|
4704
|
+
return null;
|
|
4705
|
+
});
|
|
4706
|
+
return null;
|
|
4707
|
+
});
|
|
4708
|
+
};
|
|
4709
|
+
document.addEventListener("pointermove", handlePointerMove);
|
|
4710
|
+
document.addEventListener("pointerup", handlePointerUp);
|
|
4711
|
+
},
|
|
4712
|
+
[items, onReorder, direction]
|
|
4713
|
+
);
|
|
4714
|
+
const handleKeyDown = useCallback(
|
|
4715
|
+
(index) => (e) => {
|
|
4716
|
+
if (e.key === " " || e.key === "Enter") {
|
|
4717
|
+
e.preventDefault();
|
|
4718
|
+
if (kbPickedIdx === null) {
|
|
4719
|
+
setKbPickedIdx(index);
|
|
4720
|
+
} else {
|
|
4721
|
+
if (kbPickedIdx !== index) {
|
|
4722
|
+
const newItems = [...items];
|
|
4723
|
+
const [moved] = newItems.splice(kbPickedIdx, 1);
|
|
4724
|
+
if (moved) newItems.splice(index, 0, moved);
|
|
4725
|
+
onReorder(newItems);
|
|
4726
|
+
}
|
|
4727
|
+
setKbPickedIdx(null);
|
|
4728
|
+
}
|
|
4729
|
+
} else if (e.key === "Escape") {
|
|
4730
|
+
setKbPickedIdx(null);
|
|
4731
|
+
} else if (kbPickedIdx !== null) {
|
|
4732
|
+
const isUp = direction === "vertical" ? e.key === "ArrowUp" : e.key === "ArrowLeft";
|
|
4733
|
+
const isDown = direction === "vertical" ? e.key === "ArrowDown" : e.key === "ArrowRight";
|
|
4734
|
+
if (isUp && kbPickedIdx > 0) {
|
|
4735
|
+
e.preventDefault();
|
|
4736
|
+
const newItems = [...items];
|
|
4737
|
+
const [moved] = newItems.splice(kbPickedIdx, 1);
|
|
4738
|
+
const newIdx = kbPickedIdx - 1;
|
|
4739
|
+
if (moved) newItems.splice(newIdx, 0, moved);
|
|
4740
|
+
onReorder(newItems);
|
|
4741
|
+
setKbPickedIdx(newIdx);
|
|
4742
|
+
} else if (isDown && kbPickedIdx < items.length - 1) {
|
|
4743
|
+
e.preventDefault();
|
|
4744
|
+
const newItems = [...items];
|
|
4745
|
+
const [moved] = newItems.splice(kbPickedIdx, 1);
|
|
4746
|
+
const newIdx = kbPickedIdx + 1;
|
|
4747
|
+
if (moved) newItems.splice(newIdx, 0, moved);
|
|
4748
|
+
onReorder(newItems);
|
|
4749
|
+
setKbPickedIdx(newIdx);
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
},
|
|
4753
|
+
[items, onReorder, kbPickedIdx, direction]
|
|
4754
|
+
);
|
|
4755
|
+
const getVisualItems = useCallback(() => {
|
|
4756
|
+
if (dragIdx === null || overIdx === null || dragIdx === overIdx) return items;
|
|
4757
|
+
const visual = [...items];
|
|
4758
|
+
const [moved] = visual.splice(dragIdx, 1);
|
|
4759
|
+
if (moved) visual.splice(overIdx, 0, moved);
|
|
4760
|
+
return visual;
|
|
4761
|
+
}, [items, dragIdx, overIdx]);
|
|
4762
|
+
const visualItems = dragIdx !== null ? getVisualItems() : items;
|
|
4763
|
+
return /* @__PURE__ */ jsx(
|
|
4764
|
+
"div",
|
|
4765
|
+
{
|
|
4766
|
+
ref: containerRef,
|
|
4767
|
+
className: cn(
|
|
4768
|
+
"flex",
|
|
4769
|
+
direction === "vertical" ? "flex-col" : "flex-row flex-wrap",
|
|
4770
|
+
className
|
|
4771
|
+
),
|
|
4772
|
+
role: "listbox",
|
|
4773
|
+
"aria-label": "Sortable list",
|
|
4774
|
+
children: /* @__PURE__ */ jsx(AnimatePresence, { children: visualItems.map((item, index) => {
|
|
4775
|
+
const isDragging = dragIdx !== null && item.id === dragItemId.current;
|
|
4776
|
+
const isKbPicked = kbPickedIdx !== null && items[kbPickedIdx]?.id === item.id;
|
|
4777
|
+
const dragHandleProps = {
|
|
4778
|
+
onPointerDown: handlePointerDown(items.findIndex((i) => i.id === item.id)),
|
|
4779
|
+
isDragging: isDragging || isKbPicked,
|
|
4780
|
+
onKeyDown: handleKeyDown(items.findIndex((i) => i.id === item.id)),
|
|
4781
|
+
tabIndex: 0,
|
|
4782
|
+
role: "option",
|
|
4783
|
+
"aria-roledescription": "sortable item"
|
|
4784
|
+
};
|
|
4785
|
+
return /* @__PURE__ */ jsxs(
|
|
4786
|
+
motion.div,
|
|
4787
|
+
{
|
|
4788
|
+
layout: !prefersReducedMotion,
|
|
4789
|
+
transition: prefersReducedMotion ? { duration: 0 } : { type: "spring", stiffness: 500, damping: 35, mass: 0.5 },
|
|
4790
|
+
className: cn(
|
|
4791
|
+
"relative",
|
|
4792
|
+
isDragging && "z-10 opacity-80",
|
|
4793
|
+
isKbPicked && "ring-2 ring-[hsl(var(--brand-primary))] rounded-lg"
|
|
4794
|
+
),
|
|
4795
|
+
role: "option",
|
|
4796
|
+
"aria-selected": isKbPicked,
|
|
4797
|
+
children: [
|
|
4798
|
+
dragIdx !== null && overIdx === index && !isDragging && /* @__PURE__ */ jsx(
|
|
4799
|
+
"div",
|
|
4800
|
+
{
|
|
4801
|
+
className: cn(
|
|
4802
|
+
"absolute z-20 bg-[hsl(var(--brand-primary))] rounded-full",
|
|
4803
|
+
direction === "vertical" ? "left-0 right-0 -top-px h-0.5" : "top-0 bottom-0 -left-px w-0.5"
|
|
4804
|
+
)
|
|
4805
|
+
}
|
|
4806
|
+
),
|
|
4807
|
+
renderItem(item, index, dragHandleProps)
|
|
4808
|
+
]
|
|
4809
|
+
},
|
|
4810
|
+
item.id
|
|
4811
|
+
);
|
|
4812
|
+
}) })
|
|
4813
|
+
}
|
|
4814
|
+
);
|
|
4815
|
+
}
|
|
4816
|
+
function DragHandle(props) {
|
|
4817
|
+
return /* @__PURE__ */ jsx(
|
|
4818
|
+
"span",
|
|
4819
|
+
{
|
|
4820
|
+
onPointerDown: props.onPointerDown,
|
|
4821
|
+
onKeyDown: props.onKeyDown,
|
|
4822
|
+
tabIndex: props.tabIndex,
|
|
4823
|
+
role: props.role,
|
|
4824
|
+
"aria-roledescription": props["aria-roledescription"],
|
|
4825
|
+
className: cn(
|
|
4826
|
+
"inline-flex items-center justify-center p-1 rounded cursor-grab touch-none select-none",
|
|
4827
|
+
"text-[hsl(var(--text-disabled))] hover:text-[hsl(var(--text-secondary))] transition-colors",
|
|
4828
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--brand-primary))]",
|
|
4829
|
+
props.isDragging && "cursor-grabbing text-[hsl(var(--brand-primary))]"
|
|
4830
|
+
),
|
|
4831
|
+
children: /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4" })
|
|
4832
|
+
}
|
|
4833
|
+
);
|
|
4834
|
+
}
|
|
4835
|
+
function InfiniteScroll({
|
|
4836
|
+
items,
|
|
4837
|
+
renderItem,
|
|
4838
|
+
loadMore,
|
|
4839
|
+
hasMore,
|
|
4840
|
+
isLoading = false,
|
|
4841
|
+
threshold = 200,
|
|
4842
|
+
itemHeight,
|
|
4843
|
+
emptyState,
|
|
4844
|
+
className
|
|
4845
|
+
}) {
|
|
4846
|
+
const prefersReducedMotion = useReducedMotion();
|
|
4847
|
+
const containerRef = useRef(null);
|
|
4848
|
+
const sentinelRef = useRef(null);
|
|
4849
|
+
const [showScrollTop, setShowScrollTop] = useState(false);
|
|
4850
|
+
const loadingRef = useRef(false);
|
|
4851
|
+
useEffect(() => {
|
|
4852
|
+
const sentinel = sentinelRef.current;
|
|
4853
|
+
if (!sentinel) return;
|
|
4854
|
+
const observer = new IntersectionObserver(
|
|
4855
|
+
(entries) => {
|
|
4856
|
+
const entry = entries[0];
|
|
4857
|
+
if (entry?.isIntersecting && hasMore && !isLoading && !loadingRef.current) {
|
|
4858
|
+
loadingRef.current = true;
|
|
4859
|
+
const result = loadMore();
|
|
4860
|
+
if (result && typeof result.then === "function") {
|
|
4861
|
+
result.then(() => {
|
|
4862
|
+
loadingRef.current = false;
|
|
4863
|
+
}).catch(() => {
|
|
4864
|
+
loadingRef.current = false;
|
|
4865
|
+
});
|
|
4866
|
+
} else {
|
|
4867
|
+
loadingRef.current = false;
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
},
|
|
4871
|
+
{
|
|
4872
|
+
root: containerRef.current,
|
|
4873
|
+
rootMargin: `0px 0px ${threshold}px 0px`
|
|
4874
|
+
}
|
|
4875
|
+
);
|
|
4876
|
+
observer.observe(sentinel);
|
|
4877
|
+
return () => observer.disconnect();
|
|
4878
|
+
}, [hasMore, isLoading, loadMore, threshold]);
|
|
4879
|
+
useEffect(() => {
|
|
4880
|
+
if (!isLoading) loadingRef.current = false;
|
|
4881
|
+
}, [isLoading]);
|
|
4882
|
+
useEffect(() => {
|
|
4883
|
+
const container = containerRef.current;
|
|
4884
|
+
if (!container) return;
|
|
4885
|
+
const handler = () => {
|
|
4886
|
+
setShowScrollTop(container.scrollTop > 400);
|
|
4887
|
+
};
|
|
4888
|
+
container.addEventListener("scroll", handler, { passive: true });
|
|
4889
|
+
return () => container.removeEventListener("scroll", handler);
|
|
4890
|
+
}, []);
|
|
4891
|
+
const scrollToTop = useCallback(() => {
|
|
4892
|
+
containerRef.current?.scrollTo({ top: 0, behavior: prefersReducedMotion ? "instant" : "smooth" });
|
|
4893
|
+
}, [prefersReducedMotion]);
|
|
4894
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
4895
|
+
const containerHeight = useRef(0);
|
|
4896
|
+
useEffect(() => {
|
|
4897
|
+
if (!itemHeight) return;
|
|
4898
|
+
const container = containerRef.current;
|
|
4899
|
+
if (!container) return;
|
|
4900
|
+
containerHeight.current = container.clientHeight;
|
|
4901
|
+
const handler = () => {
|
|
4902
|
+
setScrollTop(container.scrollTop);
|
|
4903
|
+
containerHeight.current = container.clientHeight;
|
|
4904
|
+
};
|
|
4905
|
+
container.addEventListener("scroll", handler, { passive: true });
|
|
4906
|
+
return () => container.removeEventListener("scroll", handler);
|
|
4907
|
+
}, [itemHeight]);
|
|
4908
|
+
const virtualizedContent = useMemo(() => {
|
|
4909
|
+
if (!itemHeight) return null;
|
|
4910
|
+
const visibleHeight = containerHeight.current || 600;
|
|
4911
|
+
const buffer = 5;
|
|
4912
|
+
const startIdx = Math.max(0, Math.floor(scrollTop / itemHeight) - buffer);
|
|
4913
|
+
const endIdx = Math.min(
|
|
4914
|
+
items.length,
|
|
4915
|
+
Math.ceil((scrollTop + visibleHeight) / itemHeight) + buffer
|
|
4916
|
+
);
|
|
4917
|
+
const totalHeight = items.length * itemHeight;
|
|
4918
|
+
const offsetTop = startIdx * itemHeight;
|
|
4919
|
+
return {
|
|
4920
|
+
totalHeight,
|
|
4921
|
+
offsetTop,
|
|
4922
|
+
visibleItems: items.slice(startIdx, endIdx),
|
|
4923
|
+
startIdx
|
|
4924
|
+
};
|
|
4925
|
+
}, [items, itemHeight, scrollTop]);
|
|
4926
|
+
if (items.length === 0 && !isLoading && !hasMore) {
|
|
4927
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex items-center justify-center min-h-[200px]", className), children: emptyState ?? /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-[hsl(var(--text-tertiary))]", children: "No items to display." }) }) });
|
|
4928
|
+
}
|
|
4929
|
+
return /* @__PURE__ */ jsxs(
|
|
4930
|
+
"div",
|
|
4931
|
+
{
|
|
4932
|
+
ref: containerRef,
|
|
4933
|
+
className: cn("relative overflow-y-auto", className),
|
|
4934
|
+
children: [
|
|
4935
|
+
virtualizedContent ? /* @__PURE__ */ jsx("div", { style: { height: virtualizedContent.totalHeight, position: "relative" }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: virtualizedContent.offsetTop, left: 0, right: 0 }, children: virtualizedContent.visibleItems.map((item, i) => /* @__PURE__ */ jsx("div", { style: { height: itemHeight }, children: renderItem(item, virtualizedContent.startIdx + i) }, virtualizedContent.startIdx + i)) }) }) : (
|
|
4936
|
+
/* Non-virtualized rendering */
|
|
4937
|
+
items.map((item, index) => /* @__PURE__ */ jsx("div", { children: renderItem(item, index) }, index))
|
|
4938
|
+
),
|
|
4939
|
+
isLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center py-6 gap-2", children: [
|
|
4940
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 text-[hsl(var(--brand-primary))] animate-spin" }),
|
|
4941
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-[hsl(var(--text-tertiary))]", children: "Loading more..." })
|
|
4942
|
+
] }),
|
|
4943
|
+
isLoading && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "space-y-3 p-4", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "skeleton-shimmer h-16 rounded-xl" }, i)) }),
|
|
4944
|
+
!hasMore && items.length > 0 && /* @__PURE__ */ jsx("div", { className: "py-4 text-center", children: /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-[hsl(var(--text-tertiary))]", children: [
|
|
4945
|
+
"All ",
|
|
4946
|
+
items.length,
|
|
4947
|
+
" item",
|
|
4948
|
+
items.length !== 1 ? "s" : "",
|
|
4949
|
+
" loaded"
|
|
4950
|
+
] }) }),
|
|
4951
|
+
/* @__PURE__ */ jsx("div", { ref: sentinelRef, className: "h-px w-full", "aria-hidden": "true" }),
|
|
4952
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: showScrollTop && /* @__PURE__ */ jsxs(
|
|
4953
|
+
motion.button,
|
|
4954
|
+
{
|
|
4955
|
+
initial: prefersReducedMotion ? void 0 : { opacity: 0, scale: 0.8 },
|
|
4956
|
+
animate: prefersReducedMotion ? void 0 : { opacity: 1, scale: 1 },
|
|
4957
|
+
exit: prefersReducedMotion ? void 0 : { opacity: 0, scale: 0.8 },
|
|
4958
|
+
transition: { duration: 0.15 },
|
|
4959
|
+
onClick: scrollToTop,
|
|
4960
|
+
className: cn(
|
|
4961
|
+
"sticky bottom-4 left-1/2 -translate-x-1/2 z-10",
|
|
4962
|
+
"inline-flex items-center gap-1.5 rounded-full",
|
|
4963
|
+
"px-3 py-2 text-[11px] font-medium",
|
|
4964
|
+
"bg-[hsl(var(--bg-elevated))] border border-[hsl(var(--border-default))]",
|
|
4965
|
+
"text-[hsl(var(--text-secondary))] shadow-lg",
|
|
4966
|
+
"hover:bg-[hsl(var(--bg-overlay))] hover:text-[hsl(var(--text-primary))] transition-colors",
|
|
4967
|
+
"cursor-pointer"
|
|
4968
|
+
),
|
|
4969
|
+
children: [
|
|
4970
|
+
/* @__PURE__ */ jsx(ArrowUp, { className: "h-3.5 w-3.5" }),
|
|
4971
|
+
"Back to top"
|
|
4972
|
+
]
|
|
4973
|
+
}
|
|
4974
|
+
) })
|
|
4975
|
+
]
|
|
4976
|
+
}
|
|
4977
|
+
);
|
|
4978
|
+
}
|
|
4979
|
+
function hexToRgb(hex) {
|
|
4980
|
+
const clean = hex.replace("#", "");
|
|
4981
|
+
const full = clean.length === 3 ? clean.split("").map((c) => c + c).join("") : clean;
|
|
4982
|
+
const num = parseInt(full, 16);
|
|
4983
|
+
return { r: num >> 16 & 255, g: num >> 8 & 255, b: num & 255 };
|
|
4984
|
+
}
|
|
4985
|
+
function rgbToHex(r, g, b) {
|
|
4986
|
+
return "#" + [r, g, b].map((c) => Math.round(c).toString(16).padStart(2, "0")).join("");
|
|
4987
|
+
}
|
|
4988
|
+
function rgbToHsl(r, g, b) {
|
|
4989
|
+
const rn = r / 255, gn = g / 255, bn = b / 255;
|
|
4990
|
+
const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
|
|
4991
|
+
const l = (max + min) / 2;
|
|
4992
|
+
if (max === min) return { h: 0, s: 0, l };
|
|
4993
|
+
const d = max - min;
|
|
4994
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
4995
|
+
let h = 0;
|
|
4996
|
+
if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
|
|
4997
|
+
else if (max === gn) h = ((bn - rn) / d + 2) / 6;
|
|
4998
|
+
else h = ((rn - gn) / d + 4) / 6;
|
|
4999
|
+
return { h, s, l };
|
|
5000
|
+
}
|
|
5001
|
+
function hslToRgb(h, s, l) {
|
|
5002
|
+
if (s === 0) {
|
|
5003
|
+
const v = Math.round(l * 255);
|
|
5004
|
+
return { r: v, g: v, b: v };
|
|
5005
|
+
}
|
|
5006
|
+
const hue2rgb = (p2, q2, t) => {
|
|
5007
|
+
const tt = t < 0 ? t + 1 : t > 1 ? t - 1 : t;
|
|
5008
|
+
if (tt < 1 / 6) return p2 + (q2 - p2) * 6 * tt;
|
|
5009
|
+
if (tt < 1 / 2) return q2;
|
|
5010
|
+
if (tt < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - tt) * 6;
|
|
5011
|
+
return p2;
|
|
5012
|
+
};
|
|
5013
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
5014
|
+
const p = 2 * l - q;
|
|
5015
|
+
return {
|
|
5016
|
+
r: Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
|
|
5017
|
+
g: Math.round(hue2rgb(p, q, h) * 255),
|
|
5018
|
+
b: Math.round(hue2rgb(p, q, h - 1 / 3) * 255)
|
|
5019
|
+
};
|
|
5020
|
+
}
|
|
5021
|
+
function formatColor(hex, fmt) {
|
|
5022
|
+
if (fmt === "hex") return hex;
|
|
5023
|
+
const { r, g, b } = hexToRgb(hex);
|
|
5024
|
+
if (fmt === "rgb") return `rgb(${r}, ${g}, ${b})`;
|
|
5025
|
+
const { h, s, l } = rgbToHsl(r, g, b);
|
|
5026
|
+
return `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
|
|
5027
|
+
}
|
|
5028
|
+
var RECENT_COLORS_KEY = "ui-kit-recent-colors";
|
|
5029
|
+
var MAX_RECENT = 8;
|
|
5030
|
+
function ColorInput({
|
|
5031
|
+
value,
|
|
5032
|
+
onChange,
|
|
5033
|
+
label,
|
|
5034
|
+
presets,
|
|
5035
|
+
showAlpha = false,
|
|
5036
|
+
format = "hex",
|
|
5037
|
+
className
|
|
5038
|
+
}) {
|
|
5039
|
+
const prefersReducedMotion = useReducedMotion();
|
|
5040
|
+
const [open, setOpen] = useState(false);
|
|
5041
|
+
const [copied, setCopied] = useState(false);
|
|
5042
|
+
const [textInput, setTextInput] = useState("");
|
|
5043
|
+
const [alpha, setAlpha] = useState(1);
|
|
5044
|
+
const panelRef = useRef(null);
|
|
5045
|
+
const satAreaRef = useRef(null);
|
|
5046
|
+
const [recentColors, setRecentColors] = useState(() => {
|
|
5047
|
+
if (typeof window === "undefined") return [];
|
|
5048
|
+
try {
|
|
5049
|
+
return JSON.parse(localStorage.getItem(RECENT_COLORS_KEY) ?? "[]");
|
|
5050
|
+
} catch {
|
|
5051
|
+
return [];
|
|
5052
|
+
}
|
|
5053
|
+
});
|
|
5054
|
+
const addRecent = useCallback((color) => {
|
|
5055
|
+
setRecentColors((prev) => {
|
|
5056
|
+
const updated = [color, ...prev.filter((c) => c !== color)].slice(0, MAX_RECENT);
|
|
5057
|
+
try {
|
|
5058
|
+
localStorage.setItem(RECENT_COLORS_KEY, JSON.stringify(updated));
|
|
5059
|
+
} catch {
|
|
5060
|
+
}
|
|
5061
|
+
return updated;
|
|
5062
|
+
});
|
|
5063
|
+
}, []);
|
|
5064
|
+
const { r, g, b } = useMemo(() => hexToRgb(value), [value]);
|
|
5065
|
+
const hsl = useMemo(() => rgbToHsl(r, g, b), [r, g, b]);
|
|
5066
|
+
useEffect(() => {
|
|
5067
|
+
setTextInput(formatColor(value, format));
|
|
5068
|
+
}, [value, format]);
|
|
5069
|
+
useEffect(() => {
|
|
5070
|
+
if (!open) return;
|
|
5071
|
+
const handler = (e) => {
|
|
5072
|
+
if (panelRef.current && !panelRef.current.contains(e.target)) {
|
|
5073
|
+
setOpen(false);
|
|
5074
|
+
addRecent(value);
|
|
5075
|
+
}
|
|
5076
|
+
};
|
|
5077
|
+
document.addEventListener("mousedown", handler);
|
|
5078
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
5079
|
+
}, [open, value, addRecent]);
|
|
5080
|
+
const handleSatAreaPointer = useCallback(
|
|
5081
|
+
(e) => {
|
|
5082
|
+
if (!satAreaRef.current) return;
|
|
5083
|
+
const rect = satAreaRef.current.getBoundingClientRect();
|
|
5084
|
+
const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
|
5085
|
+
const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
|
|
5086
|
+
const s = x;
|
|
5087
|
+
const l = 1 - y;
|
|
5088
|
+
const adjustedL = 0.05 + l * 0.9;
|
|
5089
|
+
const rgb = hslToRgb(hsl.h, s, adjustedL);
|
|
5090
|
+
onChange(rgbToHex(rgb.r, rgb.g, rgb.b));
|
|
5091
|
+
},
|
|
5092
|
+
[hsl.h, onChange]
|
|
5093
|
+
);
|
|
5094
|
+
const handleSatAreaDown = useCallback(
|
|
5095
|
+
(e) => {
|
|
5096
|
+
e.preventDefault();
|
|
5097
|
+
handleSatAreaPointer(e);
|
|
5098
|
+
const move = (ev) => handleSatAreaPointer(ev);
|
|
5099
|
+
const up = () => {
|
|
5100
|
+
document.removeEventListener("pointermove", move);
|
|
5101
|
+
document.removeEventListener("pointerup", up);
|
|
5102
|
+
};
|
|
5103
|
+
document.addEventListener("pointermove", move);
|
|
5104
|
+
document.addEventListener("pointerup", up);
|
|
5105
|
+
},
|
|
5106
|
+
[handleSatAreaPointer]
|
|
5107
|
+
);
|
|
5108
|
+
const handleHueChange = useCallback(
|
|
5109
|
+
(e) => {
|
|
5110
|
+
const h = Number(e.target.value) / 360;
|
|
5111
|
+
const rgb = hslToRgb(h, hsl.s || 0.5, hsl.l || 0.5);
|
|
5112
|
+
onChange(rgbToHex(rgb.r, rgb.g, rgb.b));
|
|
5113
|
+
},
|
|
5114
|
+
[hsl.s, hsl.l, onChange]
|
|
5115
|
+
);
|
|
5116
|
+
const handleTextCommit = useCallback(() => {
|
|
5117
|
+
const v = textInput.trim();
|
|
5118
|
+
if (/^#?[0-9a-f]{3,6}$/i.test(v)) {
|
|
5119
|
+
const hex = v.startsWith("#") ? v : "#" + v;
|
|
5120
|
+
onChange(hex);
|
|
5121
|
+
return;
|
|
5122
|
+
}
|
|
5123
|
+
const rgbMatch = v.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
|
|
5124
|
+
if (rgbMatch) {
|
|
5125
|
+
onChange(rgbToHex(Number(rgbMatch[1]), Number(rgbMatch[2]), Number(rgbMatch[3])));
|
|
5126
|
+
return;
|
|
5127
|
+
}
|
|
5128
|
+
const hslMatch = v.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?\s*\)/);
|
|
5129
|
+
if (hslMatch) {
|
|
5130
|
+
const rgb = hslToRgb(Number(hslMatch[1]) / 360, Number(hslMatch[2]) / 100, Number(hslMatch[3]) / 100);
|
|
5131
|
+
onChange(rgbToHex(rgb.r, rgb.g, rgb.b));
|
|
5132
|
+
return;
|
|
5133
|
+
}
|
|
5134
|
+
setTextInput(formatColor(value, format));
|
|
5135
|
+
}, [textInput, value, format, onChange]);
|
|
5136
|
+
const handleCopy = useCallback(async () => {
|
|
5137
|
+
try {
|
|
5138
|
+
await navigator.clipboard.writeText(formatColor(value, format));
|
|
5139
|
+
setCopied(true);
|
|
5140
|
+
setTimeout(() => setCopied(false), 1500);
|
|
5141
|
+
} catch {
|
|
5142
|
+
}
|
|
5143
|
+
}, [value, format]);
|
|
5144
|
+
const markerX = hsl.s * 100;
|
|
5145
|
+
const markerY = (1 - (hsl.l - 0.05) / 0.9) * 100;
|
|
5146
|
+
return /* @__PURE__ */ jsxs("div", { ref: panelRef, className: cn("relative inline-block", className), children: [
|
|
5147
|
+
label && /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-[hsl(var(--text-secondary))] mb-1.5", children: label }),
|
|
5148
|
+
/* @__PURE__ */ jsxs(
|
|
5149
|
+
"button",
|
|
5150
|
+
{
|
|
5151
|
+
onClick: () => setOpen((o) => !o),
|
|
5152
|
+
className: cn(
|
|
5153
|
+
"inline-flex items-center gap-2 rounded-lg border border-[hsl(var(--border-subtle))]",
|
|
5154
|
+
"bg-[hsl(var(--bg-surface))] px-3 py-2 text-sm",
|
|
5155
|
+
"hover:border-[hsl(var(--border-default))] transition-colors",
|
|
5156
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--brand-primary))]"
|
|
5157
|
+
),
|
|
5158
|
+
children: [
|
|
5159
|
+
/* @__PURE__ */ jsx(
|
|
5160
|
+
"span",
|
|
5161
|
+
{
|
|
5162
|
+
className: "h-5 w-5 rounded-md border border-[hsl(var(--border-subtle))]",
|
|
5163
|
+
style: { backgroundColor: value }
|
|
5164
|
+
}
|
|
5165
|
+
),
|
|
5166
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-[hsl(var(--text-primary))]", children: formatColor(value, format) })
|
|
5167
|
+
]
|
|
5168
|
+
}
|
|
5169
|
+
),
|
|
5170
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsxs(
|
|
5171
|
+
motion.div,
|
|
5172
|
+
{
|
|
5173
|
+
initial: prefersReducedMotion ? void 0 : { opacity: 0, scale: 0.96, y: -4 },
|
|
5174
|
+
animate: prefersReducedMotion ? void 0 : { opacity: 1, scale: 1, y: 0 },
|
|
5175
|
+
exit: prefersReducedMotion ? void 0 : { opacity: 0, scale: 0.96, y: -4 },
|
|
5176
|
+
transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.15 },
|
|
5177
|
+
className: cn(
|
|
5178
|
+
"absolute z-50 mt-2 w-64 rounded-xl overflow-hidden",
|
|
5179
|
+
"border border-[hsl(var(--border-default))]",
|
|
5180
|
+
"bg-[hsl(var(--bg-elevated))] shadow-xl",
|
|
5181
|
+
"p-3"
|
|
5182
|
+
),
|
|
5183
|
+
children: [
|
|
5184
|
+
/* @__PURE__ */ jsx(
|
|
5185
|
+
"div",
|
|
5186
|
+
{
|
|
5187
|
+
ref: satAreaRef,
|
|
5188
|
+
onPointerDown: handleSatAreaDown,
|
|
5189
|
+
className: "relative h-36 w-full rounded-lg cursor-crosshair overflow-hidden mb-3",
|
|
5190
|
+
style: {
|
|
5191
|
+
background: `linear-gradient(to top, #000, transparent),
|
|
5192
|
+
linear-gradient(to right, #fff, hsl(${Math.round(hsl.h * 360)}, 100%, 50%))`
|
|
5193
|
+
},
|
|
5194
|
+
children: /* @__PURE__ */ jsx(
|
|
5195
|
+
"div",
|
|
5196
|
+
{
|
|
5197
|
+
className: "absolute w-4 h-4 rounded-full border-2 border-white shadow-md -translate-x-1/2 -translate-y-1/2 pointer-events-none",
|
|
5198
|
+
style: {
|
|
5199
|
+
left: `${markerX}%`,
|
|
5200
|
+
top: `${Math.max(0, Math.min(100, markerY))}%`,
|
|
5201
|
+
backgroundColor: value
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
)
|
|
5205
|
+
}
|
|
5206
|
+
),
|
|
5207
|
+
/* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(
|
|
5208
|
+
"input",
|
|
5209
|
+
{
|
|
5210
|
+
type: "range",
|
|
5211
|
+
min: 0,
|
|
5212
|
+
max: 360,
|
|
5213
|
+
value: Math.round(hsl.h * 360),
|
|
5214
|
+
onChange: handleHueChange,
|
|
5215
|
+
className: "w-full h-3 rounded-full appearance-none cursor-pointer",
|
|
5216
|
+
style: {
|
|
5217
|
+
background: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)"
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
) }),
|
|
5221
|
+
showAlpha && /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(
|
|
5222
|
+
"input",
|
|
5223
|
+
{
|
|
5224
|
+
type: "range",
|
|
5225
|
+
min: 0,
|
|
5226
|
+
max: 100,
|
|
5227
|
+
value: Math.round(alpha * 100),
|
|
5228
|
+
onChange: (e) => setAlpha(Number(e.target.value) / 100),
|
|
5229
|
+
className: "w-full h-3 rounded-full appearance-none cursor-pointer",
|
|
5230
|
+
style: {
|
|
5231
|
+
background: `linear-gradient(to right, transparent, ${value})`
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
) }),
|
|
5235
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
|
|
5236
|
+
/* @__PURE__ */ jsx(
|
|
5237
|
+
"input",
|
|
5238
|
+
{
|
|
5239
|
+
type: "text",
|
|
5240
|
+
value: textInput,
|
|
5241
|
+
onChange: (e) => setTextInput(e.target.value),
|
|
5242
|
+
onBlur: handleTextCommit,
|
|
5243
|
+
onKeyDown: (e) => {
|
|
5244
|
+
if (e.key === "Enter") handleTextCommit();
|
|
5245
|
+
},
|
|
5246
|
+
className: cn(
|
|
5247
|
+
"flex-1 rounded-md border border-[hsl(var(--border-subtle))]",
|
|
5248
|
+
"bg-[hsl(var(--bg-surface))] px-2 py-1 text-xs font-mono",
|
|
5249
|
+
"text-[hsl(var(--text-primary))] outline-none",
|
|
5250
|
+
"focus:border-[hsl(var(--brand-primary))] transition-colors"
|
|
5251
|
+
)
|
|
5252
|
+
}
|
|
5253
|
+
),
|
|
5254
|
+
/* @__PURE__ */ jsx(
|
|
5255
|
+
"button",
|
|
5256
|
+
{
|
|
5257
|
+
onClick: handleCopy,
|
|
5258
|
+
className: cn(
|
|
5259
|
+
"p-1.5 rounded-md transition-colors",
|
|
5260
|
+
"text-[hsl(var(--text-tertiary))] hover:text-[hsl(var(--text-primary))]",
|
|
5261
|
+
"hover:bg-[hsl(var(--bg-surface))]"
|
|
5262
|
+
),
|
|
5263
|
+
title: "Copy color",
|
|
5264
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-[hsl(var(--status-ok))]" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
|
|
5265
|
+
}
|
|
5266
|
+
)
|
|
5267
|
+
] }),
|
|
5268
|
+
presets && presets.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
|
|
5269
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-[hsl(var(--text-tertiary))] mb-1.5 block", children: "Presets" }),
|
|
5270
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: presets.map((color) => /* @__PURE__ */ jsx(
|
|
5271
|
+
"button",
|
|
5272
|
+
{
|
|
5273
|
+
onClick: () => {
|
|
5274
|
+
onChange(color);
|
|
5275
|
+
addRecent(color);
|
|
5276
|
+
},
|
|
5277
|
+
className: cn(
|
|
5278
|
+
"h-6 w-6 rounded-md border transition-all",
|
|
5279
|
+
value === color ? "border-[hsl(var(--brand-primary))] ring-2 ring-[hsl(var(--brand-primary)/0.3)] scale-110" : "border-[hsl(var(--border-subtle))] hover:scale-110"
|
|
5280
|
+
),
|
|
5281
|
+
style: { backgroundColor: color },
|
|
5282
|
+
title: color
|
|
5283
|
+
},
|
|
5284
|
+
color
|
|
5285
|
+
)) })
|
|
5286
|
+
] }),
|
|
5287
|
+
recentColors.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
5288
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-[hsl(var(--text-tertiary))] mb-1.5 block", children: "Recent" }),
|
|
5289
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: recentColors.map((color) => /* @__PURE__ */ jsx(
|
|
5290
|
+
"button",
|
|
5291
|
+
{
|
|
5292
|
+
onClick: () => onChange(color),
|
|
5293
|
+
className: cn(
|
|
5294
|
+
"h-6 w-6 rounded-md border border-[hsl(var(--border-subtle))]",
|
|
5295
|
+
"hover:scale-110 transition-transform"
|
|
5296
|
+
),
|
|
5297
|
+
style: { backgroundColor: color },
|
|
5298
|
+
title: color
|
|
5299
|
+
},
|
|
5300
|
+
color
|
|
5301
|
+
)) })
|
|
5302
|
+
] })
|
|
5303
|
+
]
|
|
5304
|
+
}
|
|
5305
|
+
) })
|
|
5306
|
+
] });
|
|
5307
|
+
}
|
|
5308
|
+
var SESSION_KEY_PREFIX = "ui-kit-wizard-";
|
|
5309
|
+
function StepWizard({
|
|
5310
|
+
steps,
|
|
5311
|
+
onComplete,
|
|
5312
|
+
onStepChange,
|
|
5313
|
+
orientation = "horizontal",
|
|
5314
|
+
allowSkip = false,
|
|
5315
|
+
showSummary = false,
|
|
5316
|
+
className
|
|
5317
|
+
}) {
|
|
5318
|
+
const prefersReducedMotion = useReducedMotion();
|
|
5319
|
+
const sessionKey = useMemo(() => SESSION_KEY_PREFIX + steps.map((s) => s.id).join("-"), [steps]);
|
|
5320
|
+
const [currentStep, setCurrentStep] = useState(() => {
|
|
5321
|
+
if (typeof window === "undefined") return 0;
|
|
5322
|
+
try {
|
|
5323
|
+
const saved = sessionStorage.getItem(sessionKey);
|
|
5324
|
+
if (saved) {
|
|
5325
|
+
const parsed = JSON.parse(saved);
|
|
5326
|
+
return Math.min(parsed.step, steps.length - 1);
|
|
5327
|
+
}
|
|
5328
|
+
} catch {
|
|
5329
|
+
}
|
|
5330
|
+
return 0;
|
|
5331
|
+
});
|
|
5332
|
+
const [completed, setCompleted] = useState(() => {
|
|
5333
|
+
if (typeof window === "undefined") return /* @__PURE__ */ new Set();
|
|
5334
|
+
try {
|
|
5335
|
+
const saved = sessionStorage.getItem(sessionKey);
|
|
5336
|
+
if (saved) {
|
|
5337
|
+
const parsed = JSON.parse(saved);
|
|
5338
|
+
return new Set(parsed.completed);
|
|
5339
|
+
}
|
|
5340
|
+
} catch {
|
|
5341
|
+
}
|
|
5342
|
+
return /* @__PURE__ */ new Set();
|
|
5343
|
+
});
|
|
5344
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
5345
|
+
const [direction, setDirection] = useState(1);
|
|
5346
|
+
const [isComplete, setIsComplete] = useState(false);
|
|
5347
|
+
const contentRef = useRef(null);
|
|
5348
|
+
useEffect(() => {
|
|
5349
|
+
try {
|
|
5350
|
+
sessionStorage.setItem(sessionKey, JSON.stringify({
|
|
5351
|
+
step: currentStep,
|
|
5352
|
+
completed: [...completed]
|
|
5353
|
+
}));
|
|
5354
|
+
} catch {
|
|
5355
|
+
}
|
|
5356
|
+
}, [currentStep, completed, sessionKey]);
|
|
5357
|
+
const totalSteps = steps.length;
|
|
5358
|
+
const progress = totalSteps === 0 ? 100 : Math.round(completed.size / totalSteps * 100);
|
|
5359
|
+
const goToStep = useCallback((idx) => {
|
|
5360
|
+
setDirection(idx > currentStep ? 1 : -1);
|
|
5361
|
+
setCurrentStep(idx);
|
|
5362
|
+
onStepChange?.(idx);
|
|
5363
|
+
}, [currentStep, onStepChange]);
|
|
5364
|
+
const handleNext = useCallback(async () => {
|
|
5365
|
+
const step = steps[currentStep];
|
|
5366
|
+
if (step?.validate) {
|
|
5367
|
+
setIsValidating(true);
|
|
5368
|
+
try {
|
|
5369
|
+
const valid = await step.validate();
|
|
5370
|
+
if (!valid) {
|
|
5371
|
+
setIsValidating(false);
|
|
5372
|
+
return;
|
|
5373
|
+
}
|
|
5374
|
+
} catch {
|
|
5375
|
+
setIsValidating(false);
|
|
5376
|
+
return;
|
|
5377
|
+
}
|
|
5378
|
+
setIsValidating(false);
|
|
5379
|
+
}
|
|
5380
|
+
setCompleted((prev) => new Set(prev).add(currentStep));
|
|
5381
|
+
if (currentStep < totalSteps - 1) {
|
|
5382
|
+
goToStep(currentStep + 1);
|
|
5383
|
+
} else {
|
|
5384
|
+
if (showSummary) {
|
|
5385
|
+
setIsComplete(true);
|
|
5386
|
+
}
|
|
5387
|
+
onComplete();
|
|
5388
|
+
try {
|
|
5389
|
+
sessionStorage.removeItem(sessionKey);
|
|
5390
|
+
} catch {
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
}, [currentStep, steps, totalSteps, goToStep, onComplete, showSummary, sessionKey]);
|
|
5394
|
+
const handleBack = useCallback(() => {
|
|
5395
|
+
if (currentStep > 0) {
|
|
5396
|
+
goToStep(currentStep - 1);
|
|
5397
|
+
}
|
|
5398
|
+
}, [currentStep, goToStep]);
|
|
5399
|
+
const handleStepClick = useCallback((idx) => {
|
|
5400
|
+
if (completed.has(idx) || allowSkip || idx < currentStep) {
|
|
5401
|
+
goToStep(idx);
|
|
5402
|
+
}
|
|
5403
|
+
}, [completed, allowSkip, currentStep, goToStep]);
|
|
5404
|
+
useEffect(() => {
|
|
5405
|
+
const handler = (e) => {
|
|
5406
|
+
if (e.key === "Enter" && !e.shiftKey && !(e.target instanceof HTMLTextAreaElement)) {
|
|
5407
|
+
handleNext();
|
|
5408
|
+
}
|
|
5409
|
+
};
|
|
5410
|
+
document.addEventListener("keydown", handler);
|
|
5411
|
+
return () => document.removeEventListener("keydown", handler);
|
|
5412
|
+
}, [handleNext]);
|
|
5413
|
+
const slideVariants2 = {
|
|
5414
|
+
enter: (dir) => ({
|
|
5415
|
+
x: prefersReducedMotion ? 0 : dir > 0 ? 40 : -40,
|
|
5416
|
+
opacity: prefersReducedMotion ? 1 : 0
|
|
5417
|
+
}),
|
|
5418
|
+
center: { x: 0, opacity: 1 },
|
|
5419
|
+
exit: (dir) => ({
|
|
5420
|
+
x: prefersReducedMotion ? 0 : dir > 0 ? -40 : 40,
|
|
5421
|
+
opacity: prefersReducedMotion ? 1 : 0
|
|
5422
|
+
})
|
|
5423
|
+
};
|
|
5424
|
+
const isHorizontal = orientation === "horizontal";
|
|
5425
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
|
|
5426
|
+
/* @__PURE__ */ jsx("div", { className: cn(
|
|
5427
|
+
"mb-6",
|
|
5428
|
+
isHorizontal ? "flex items-center" : "flex flex-col gap-1"
|
|
5429
|
+
), children: steps.map((step, idx) => {
|
|
5430
|
+
const isActive = idx === currentStep;
|
|
5431
|
+
const isDone = completed.has(idx) || isComplete;
|
|
5432
|
+
const isClickable = isDone || allowSkip || idx < currentStep;
|
|
5433
|
+
const Icon = step.icon;
|
|
5434
|
+
return /* @__PURE__ */ jsxs(
|
|
5435
|
+
"div",
|
|
5436
|
+
{
|
|
5437
|
+
className: cn(
|
|
5438
|
+
isHorizontal ? "flex items-center flex-1" : "flex items-center gap-3"
|
|
5439
|
+
),
|
|
5440
|
+
children: [
|
|
5441
|
+
/* @__PURE__ */ jsxs(
|
|
5442
|
+
"button",
|
|
5443
|
+
{
|
|
5444
|
+
onClick: () => handleStepClick(idx),
|
|
5445
|
+
disabled: !isClickable,
|
|
5446
|
+
className: cn(
|
|
5447
|
+
"flex items-center gap-2 group",
|
|
5448
|
+
isClickable ? "cursor-pointer" : "cursor-default"
|
|
5449
|
+
),
|
|
5450
|
+
children: [
|
|
5451
|
+
/* @__PURE__ */ jsx(
|
|
5452
|
+
"div",
|
|
5453
|
+
{
|
|
5454
|
+
className: cn(
|
|
5455
|
+
"flex items-center justify-center rounded-full transition-all",
|
|
5456
|
+
"h-8 w-8 text-xs font-semibold shrink-0",
|
|
5457
|
+
isDone ? "bg-[hsl(var(--status-ok))] text-[hsl(var(--text-on-brand))]" : isActive ? "bg-[hsl(var(--brand-primary))] text-[hsl(var(--text-on-brand))] ring-4 ring-[hsl(var(--brand-primary)/0.2)]" : "bg-[hsl(var(--bg-overlay))] text-[hsl(var(--text-tertiary))]",
|
|
5458
|
+
isClickable && !isActive && !isDone && "group-hover:bg-[hsl(var(--bg-elevated))]"
|
|
5459
|
+
),
|
|
5460
|
+
children: isDone ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) : Icon ? /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) : idx + 1
|
|
5461
|
+
}
|
|
5462
|
+
),
|
|
5463
|
+
/* @__PURE__ */ jsxs("div", { className: cn(
|
|
5464
|
+
isHorizontal ? "hidden sm:block" : "block"
|
|
5465
|
+
), children: [
|
|
5466
|
+
/* @__PURE__ */ jsx("div", { className: cn(
|
|
5467
|
+
"text-xs font-medium leading-tight",
|
|
5468
|
+
isActive ? "text-[hsl(var(--text-primary))]" : "text-[hsl(var(--text-secondary))]"
|
|
5469
|
+
), children: step.title }),
|
|
5470
|
+
step.description && !isHorizontal && /* @__PURE__ */ jsx("div", { className: "text-[10px] text-[hsl(var(--text-tertiary))]", children: step.description })
|
|
5471
|
+
] })
|
|
5472
|
+
]
|
|
5473
|
+
}
|
|
5474
|
+
),
|
|
5475
|
+
isHorizontal && idx < totalSteps - 1 && /* @__PURE__ */ jsx("div", { className: cn(
|
|
5476
|
+
"flex-1 h-0.5 mx-2 rounded-full transition-colors",
|
|
5477
|
+
completed.has(idx) ? "bg-[hsl(var(--status-ok))]" : "bg-[hsl(var(--border-subtle))]"
|
|
5478
|
+
) })
|
|
5479
|
+
]
|
|
5480
|
+
},
|
|
5481
|
+
step.id
|
|
5482
|
+
);
|
|
5483
|
+
}) }),
|
|
5484
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-1 rounded-full bg-[hsl(var(--bg-overlay))] mb-4 overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
5485
|
+
motion.div,
|
|
5486
|
+
{
|
|
5487
|
+
className: "h-full rounded-full bg-[hsl(var(--brand-primary))]",
|
|
5488
|
+
initial: { width: 0 },
|
|
5489
|
+
animate: { width: `${progress}%` },
|
|
5490
|
+
transition: prefersReducedMotion ? { duration: 0 } : { type: "spring", stiffness: 300, damping: 30 }
|
|
5491
|
+
}
|
|
5492
|
+
) }),
|
|
5493
|
+
/* @__PURE__ */ jsx("div", { ref: contentRef, className: "relative min-h-[200px]", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", custom: direction, children: isComplete && showSummary ? /* @__PURE__ */ jsxs(
|
|
5494
|
+
motion.div,
|
|
5495
|
+
{
|
|
5496
|
+
custom: 1,
|
|
5497
|
+
variants: slideVariants2,
|
|
5498
|
+
initial: "enter",
|
|
5499
|
+
animate: "center",
|
|
5500
|
+
exit: "exit",
|
|
5501
|
+
transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.25 },
|
|
5502
|
+
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
5503
|
+
children: [
|
|
5504
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-16 h-16 rounded-full bg-[hsl(var(--status-ok)/0.15)] mb-4", children: /* @__PURE__ */ jsx(Check, { className: "h-8 w-8 text-[hsl(var(--status-ok))]" }) }),
|
|
5505
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-[hsl(var(--text-primary))] mb-1", children: "All steps completed" }),
|
|
5506
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-[hsl(var(--text-secondary))]", children: [
|
|
5507
|
+
"All ",
|
|
5508
|
+
totalSteps,
|
|
5509
|
+
" steps have been successfully completed."
|
|
5510
|
+
] })
|
|
5511
|
+
]
|
|
5512
|
+
},
|
|
5513
|
+
"summary"
|
|
5514
|
+
) : /* @__PURE__ */ jsx(
|
|
5515
|
+
motion.div,
|
|
5516
|
+
{
|
|
5517
|
+
custom: direction,
|
|
5518
|
+
variants: slideVariants2,
|
|
5519
|
+
initial: "enter",
|
|
5520
|
+
animate: "center",
|
|
5521
|
+
exit: "exit",
|
|
5522
|
+
transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.25 },
|
|
5523
|
+
children: steps[currentStep]?.content
|
|
5524
|
+
},
|
|
5525
|
+
currentStep
|
|
5526
|
+
) }) }),
|
|
5527
|
+
!isComplete && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-6 pt-4 border-t border-[hsl(var(--border-subtle)/0.5)]", children: [
|
|
5528
|
+
/* @__PURE__ */ jsxs(
|
|
5529
|
+
"button",
|
|
5530
|
+
{
|
|
5531
|
+
onClick: handleBack,
|
|
5532
|
+
disabled: currentStep === 0,
|
|
5533
|
+
className: cn(
|
|
5534
|
+
"inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors",
|
|
5535
|
+
"border border-[hsl(var(--border-default))]",
|
|
5536
|
+
"text-[hsl(var(--text-primary))]",
|
|
5537
|
+
"hover:bg-[hsl(var(--bg-overlay))]",
|
|
5538
|
+
"disabled:opacity-40 disabled:pointer-events-none"
|
|
5539
|
+
),
|
|
5540
|
+
children: [
|
|
5541
|
+
/* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
|
|
5542
|
+
"Back"
|
|
5543
|
+
]
|
|
5544
|
+
}
|
|
5545
|
+
),
|
|
5546
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[11px] text-[hsl(var(--text-tertiary))] tabular-nums", children: [
|
|
5547
|
+
"Step ",
|
|
5548
|
+
currentStep + 1,
|
|
5549
|
+
" of ",
|
|
5550
|
+
totalSteps
|
|
5551
|
+
] }),
|
|
5552
|
+
/* @__PURE__ */ jsx(
|
|
5553
|
+
"button",
|
|
5554
|
+
{
|
|
5555
|
+
onClick: handleNext,
|
|
5556
|
+
disabled: isValidating,
|
|
5557
|
+
className: cn(
|
|
5558
|
+
"inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors",
|
|
5559
|
+
"bg-[hsl(var(--brand-primary))] text-[hsl(var(--text-on-brand))]",
|
|
5560
|
+
"hover:bg-[hsl(var(--brand-primary))]/90",
|
|
5561
|
+
"disabled:opacity-70 disabled:cursor-not-allowed"
|
|
5562
|
+
),
|
|
5563
|
+
children: isValidating ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5564
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 rounded-full border-2 border-[hsl(var(--text-on-brand))] border-t-transparent animate-spin" }),
|
|
5565
|
+
"Validating..."
|
|
5566
|
+
] }) : currentStep === totalSteps - 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5567
|
+
"Complete",
|
|
5568
|
+
/* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
|
|
5569
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5570
|
+
"Next",
|
|
5571
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
|
|
5572
|
+
] })
|
|
5573
|
+
}
|
|
5574
|
+
)
|
|
5575
|
+
] })
|
|
5576
|
+
] });
|
|
5577
|
+
}
|
|
5578
|
+
function CopyBlock({
|
|
5579
|
+
content,
|
|
5580
|
+
language,
|
|
5581
|
+
showLineNumbers = false,
|
|
5582
|
+
maxHeight,
|
|
5583
|
+
label,
|
|
5584
|
+
className
|
|
5585
|
+
}) {
|
|
5586
|
+
const prefersReducedMotion = useReducedMotion();
|
|
5587
|
+
const [copied, setCopied] = useState(false);
|
|
5588
|
+
const [isCollapsed, setIsCollapsed] = useState(true);
|
|
5589
|
+
const [needsCollapse, setNeedsCollapse] = useState(false);
|
|
5590
|
+
const contentRef = useRef(null);
|
|
5591
|
+
useEffect(() => {
|
|
5592
|
+
if (!maxHeight || !contentRef.current) return;
|
|
5593
|
+
setNeedsCollapse(contentRef.current.scrollHeight > maxHeight);
|
|
5594
|
+
}, [content, maxHeight]);
|
|
5595
|
+
const lines = useMemo(() => content.split("\n"), [content]);
|
|
5596
|
+
const handleCopy = useCallback(async () => {
|
|
5597
|
+
try {
|
|
5598
|
+
await navigator.clipboard.writeText(content);
|
|
5599
|
+
setCopied(true);
|
|
5600
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
5601
|
+
} catch {
|
|
5602
|
+
const textarea = document.createElement("textarea");
|
|
5603
|
+
textarea.value = content;
|
|
5604
|
+
textarea.style.position = "fixed";
|
|
5605
|
+
textarea.style.opacity = "0";
|
|
5606
|
+
document.body.appendChild(textarea);
|
|
5607
|
+
textarea.select();
|
|
5608
|
+
document.execCommand("copy");
|
|
5609
|
+
document.body.removeChild(textarea);
|
|
5610
|
+
setCopied(true);
|
|
5611
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
5612
|
+
}
|
|
5613
|
+
}, [content]);
|
|
5614
|
+
const shouldCollapse = maxHeight && needsCollapse && isCollapsed;
|
|
5615
|
+
return /* @__PURE__ */ jsxs(
|
|
5616
|
+
"div",
|
|
5617
|
+
{
|
|
5618
|
+
className: cn(
|
|
5619
|
+
"relative group rounded-xl overflow-hidden",
|
|
5620
|
+
"border border-[hsl(var(--border-subtle))]",
|
|
5621
|
+
"bg-[hsl(var(--bg-base))]",
|
|
5622
|
+
className
|
|
5623
|
+
),
|
|
5624
|
+
children: [
|
|
5625
|
+
(label || language) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-[hsl(var(--border-subtle)/0.5)] bg-[hsl(var(--bg-surface)/0.3)]", children: [
|
|
5626
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5627
|
+
label && /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[hsl(var(--text-secondary))]", children: label }),
|
|
5628
|
+
language && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-md bg-[hsl(var(--bg-overlay)/0.5)] px-1.5 py-0.5 text-[10px] font-mono text-[hsl(var(--text-tertiary))]", children: language })
|
|
5629
|
+
] }),
|
|
5630
|
+
/* @__PURE__ */ jsx(
|
|
5631
|
+
"button",
|
|
5632
|
+
{
|
|
5633
|
+
onClick: handleCopy,
|
|
5634
|
+
className: cn(
|
|
5635
|
+
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium transition-all",
|
|
5636
|
+
copied ? "text-[hsl(var(--status-ok))] bg-[hsl(var(--status-ok)/0.1)]" : "text-[hsl(var(--text-tertiary))] hover:text-[hsl(var(--text-primary))] hover:bg-[hsl(var(--bg-elevated))]"
|
|
5637
|
+
),
|
|
5638
|
+
children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: copied ? /* @__PURE__ */ jsxs(
|
|
5639
|
+
motion.span,
|
|
5640
|
+
{
|
|
5641
|
+
initial: prefersReducedMotion ? void 0 : { scale: 0.5, opacity: 0 },
|
|
5642
|
+
animate: prefersReducedMotion ? void 0 : { scale: 1, opacity: 1 },
|
|
5643
|
+
exit: prefersReducedMotion ? void 0 : { scale: 0.5, opacity: 0 },
|
|
5644
|
+
transition: { duration: 0.15 },
|
|
5645
|
+
className: "inline-flex items-center gap-1",
|
|
5646
|
+
children: [
|
|
5647
|
+
/* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
5648
|
+
"Copied!"
|
|
5649
|
+
]
|
|
5650
|
+
},
|
|
5651
|
+
"check"
|
|
5652
|
+
) : /* @__PURE__ */ jsxs(
|
|
5653
|
+
motion.span,
|
|
5654
|
+
{
|
|
5655
|
+
initial: prefersReducedMotion ? void 0 : { scale: 0.5, opacity: 0 },
|
|
5656
|
+
animate: prefersReducedMotion ? void 0 : { scale: 1, opacity: 1 },
|
|
5657
|
+
exit: prefersReducedMotion ? void 0 : { scale: 0.5, opacity: 0 },
|
|
5658
|
+
transition: { duration: 0.15 },
|
|
5659
|
+
className: "inline-flex items-center gap-1",
|
|
5660
|
+
children: [
|
|
5661
|
+
/* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }),
|
|
5662
|
+
"Copy"
|
|
5663
|
+
]
|
|
5664
|
+
},
|
|
5665
|
+
"copy"
|
|
5666
|
+
) })
|
|
5667
|
+
}
|
|
5668
|
+
)
|
|
5669
|
+
] }),
|
|
5670
|
+
!label && !language && /* @__PURE__ */ jsx(
|
|
5671
|
+
"button",
|
|
5672
|
+
{
|
|
5673
|
+
onClick: handleCopy,
|
|
5674
|
+
className: cn(
|
|
5675
|
+
"absolute top-2 right-2 z-10 rounded-md p-1.5 transition-all",
|
|
5676
|
+
"opacity-0 group-hover:opacity-100",
|
|
5677
|
+
copied ? "text-[hsl(var(--status-ok))] bg-[hsl(var(--status-ok)/0.1)]" : "text-[hsl(var(--text-tertiary))] hover:text-[hsl(var(--text-primary))] bg-[hsl(var(--bg-elevated)/0.8)] hover:bg-[hsl(var(--bg-elevated))]"
|
|
5678
|
+
),
|
|
5679
|
+
title: copied ? "Copied!" : "Copy to clipboard",
|
|
5680
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" })
|
|
5681
|
+
}
|
|
5682
|
+
),
|
|
5683
|
+
/* @__PURE__ */ jsx(
|
|
5684
|
+
"div",
|
|
5685
|
+
{
|
|
5686
|
+
className: "overflow-x-auto",
|
|
5687
|
+
style: shouldCollapse ? { maxHeight, overflow: "hidden" } : void 0,
|
|
5688
|
+
children: /* @__PURE__ */ jsx(
|
|
5689
|
+
"pre",
|
|
5690
|
+
{
|
|
5691
|
+
ref: contentRef,
|
|
5692
|
+
className: cn(
|
|
5693
|
+
"p-4 text-[13px] leading-relaxed font-mono",
|
|
5694
|
+
"text-[hsl(var(--text-primary))]",
|
|
5695
|
+
"whitespace-pre overflow-x-auto"
|
|
5696
|
+
),
|
|
5697
|
+
children: showLineNumbers ? /* @__PURE__ */ jsx("table", { className: "border-collapse w-full", children: /* @__PURE__ */ jsx("tbody", { children: lines.map((line, i) => /* @__PURE__ */ jsxs("tr", { className: "hover:bg-[hsl(var(--bg-surface)/0.3)]", children: [
|
|
5698
|
+
/* @__PURE__ */ jsx("td", { className: "select-none text-right pr-4 text-[hsl(var(--text-disabled))] text-[11px] tabular-nums w-8 align-top", children: i + 1 }),
|
|
5699
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-pre", children: line })
|
|
5700
|
+
] }, i)) }) }) : content
|
|
5701
|
+
}
|
|
5702
|
+
)
|
|
5703
|
+
}
|
|
5704
|
+
),
|
|
5705
|
+
maxHeight && needsCollapse && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5706
|
+
isCollapsed && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-[hsl(var(--bg-base))] to-transparent pointer-events-none" }),
|
|
5707
|
+
/* @__PURE__ */ jsx("div", { className: "relative border-t border-[hsl(var(--border-subtle)/0.3)]", children: /* @__PURE__ */ jsx(
|
|
5708
|
+
"button",
|
|
5709
|
+
{
|
|
5710
|
+
onClick: () => setIsCollapsed((c) => !c),
|
|
5711
|
+
className: cn(
|
|
5712
|
+
"w-full flex items-center justify-center gap-1.5 py-2 text-[11px] font-medium",
|
|
5713
|
+
"text-[hsl(var(--text-secondary))] hover:text-[hsl(var(--text-primary))]",
|
|
5714
|
+
"hover:bg-[hsl(var(--bg-surface)/0.3)] transition-colors"
|
|
5715
|
+
),
|
|
5716
|
+
children: isCollapsed ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5717
|
+
"Show more (",
|
|
5718
|
+
lines.length,
|
|
5719
|
+
" lines)",
|
|
5720
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
|
|
5721
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5722
|
+
"Show less",
|
|
5723
|
+
/* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
|
|
5724
|
+
] })
|
|
5725
|
+
}
|
|
5726
|
+
) })
|
|
5727
|
+
] })
|
|
5728
|
+
]
|
|
5729
|
+
}
|
|
5730
|
+
);
|
|
5731
|
+
}
|
|
2809
5732
|
|
|
2810
|
-
export { AnimatedCounter, Avatar, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ConfirmDialog, DataTable, DropdownMenu, EmptyState, FilterPill, LogViewer, MetricCard, PipelineStage, Popover, PortStatusGrid, Progress, RadioGroup, SeverityTimeline, Sheet, Skeleton, SkeletonCard, SkeletonText, Slider, Sparkline, StatusBadge, StatusPulse, SuccessCheckmark, Tabs, ThresholdGauge, TimeRangeSelector, Toaster, Tooltip2 as Tooltip, TruncatedText, UptimeTracker, UtilizationBar, createBadgeVariant, defaultPulseConfigMap, defaultStatusMap };
|
|
5733
|
+
export { AnimatedCounter, Avatar, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ColorInput, CommandBar, ConfidenceBar, ConfirmDialog, CopyBlock, DataTable, DiffViewer, DragHandle, DropdownMenu, EmptyState, FilterPill, HeatmapCalendar, InfiniteScroll, KanbanColumn, LiveFeed, LogViewer, MetricCard, NotificationStack, PipelineStage, Popover, PortStatusGrid, Progress, RadioGroup, RealtimeValue, SeverityTimeline, Sheet, Skeleton, SkeletonCard, SkeletonText, Slider, SmartTable, SortableList, Sparkline, StatusBadge, StatusPulse, StepWizard, StreamingText, SuccessCheckmark, Tabs, ThresholdGauge, TimeRangeSelector, Toaster, Tooltip2 as Tooltip, TruncatedText, TypingIndicator, UptimeTracker, UtilizationBar, createBadgeVariant, defaultPulseConfigMap, defaultStatusMap };
|
|
2811
5734
|
//# sourceMappingURL=index.js.map
|
|
2812
5735
|
//# sourceMappingURL=index.js.map
|