@vllnt/ui 0.1.11 → 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/CHANGELOG.md +104 -0
- package/README.md +106 -1
- package/dist/components/activity-heatmap/activity-heatmap.js +168 -0
- package/dist/components/activity-heatmap/index.js +6 -0
- package/dist/components/activity-log/activity-log.js +256 -0
- package/dist/components/activity-log/index.js +6 -0
- package/dist/components/ai-chat-input/ai-chat-input.js +107 -0
- package/dist/components/ai-chat-input/index.js +4 -0
- package/dist/components/ai-message-bubble/ai-message-bubble.js +119 -0
- package/dist/components/ai-message-bubble/index.js +6 -0
- package/dist/components/ai-source-citation/ai-source-citation.js +39 -0
- package/dist/components/ai-source-citation/index.js +6 -0
- package/dist/components/ai-streaming-text/ai-streaming-text.js +41 -0
- package/dist/components/ai-streaming-text/index.js +6 -0
- package/dist/components/ai-tool-call-display/ai-tool-call-display.js +93 -0
- package/dist/components/ai-tool-call-display/index.js +6 -0
- package/dist/components/animated-text/animated-text.js +328 -0
- package/dist/components/animated-text/index.js +4 -0
- package/dist/components/annotation/annotation.js +49 -0
- package/dist/components/annotation/index.js +8 -0
- package/dist/components/avatar-group/avatar-group.js +82 -0
- package/dist/components/avatar-group/index.js +10 -0
- package/dist/components/border-beam/border-beam.js +51 -0
- package/dist/components/border-beam/index.js +4 -0
- package/dist/components/candlestick-chart/candlestick-chart.js +215 -0
- package/dist/components/candlestick-chart/index.js +6 -0
- package/dist/components/combobox/combobox.js +130 -0
- package/dist/components/combobox/index.js +4 -0
- package/dist/components/countdown-timer/countdown-timer.js +184 -0
- package/dist/components/countdown-timer/index.js +4 -0
- package/dist/components/credit-badge/credit-badge.js +59 -0
- package/dist/components/credit-badge/index.js +6 -0
- package/dist/components/data-list/data-list.js +99 -0
- package/dist/components/data-list/index.js +16 -0
- package/dist/components/data-table/data-table.js +242 -0
- package/dist/components/data-table/index.js +6 -0
- package/dist/components/date-picker/date-picker.js +74 -0
- package/dist/components/date-picker/index.js +4 -0
- package/dist/components/file-upload/file-upload.js +227 -0
- package/dist/components/file-upload/index.js +4 -0
- package/dist/components/flashcard/flashcard.js +66 -0
- package/dist/components/flashcard/index.js +4 -0
- package/dist/components/index.js +172 -1
- package/dist/components/live-feed/index.js +4 -0
- package/dist/components/live-feed/live-feed.js +168 -0
- package/dist/components/market-treemap/index.js +6 -0
- package/dist/components/market-treemap/market-treemap.js +100 -0
- package/dist/components/marquee/index.js +4 -0
- package/dist/components/marquee/marquee.js +98 -0
- package/dist/components/metric-gauge/index.js +6 -0
- package/dist/components/metric-gauge/metric-gauge.js +213 -0
- package/dist/components/model-selector/model-selector.js +11 -2
- package/dist/components/number-input/index.js +4 -0
- package/dist/components/number-input/number-input.js +167 -0
- package/dist/components/number-ticker/index.js +4 -0
- package/dist/components/number-ticker/number-ticker.js +63 -0
- package/dist/components/order-book/index.js +6 -0
- package/dist/components/order-book/order-book.js +128 -0
- package/dist/components/password-input/index.js +4 -0
- package/dist/components/password-input/password-input.js +45 -0
- package/dist/components/plan-badge/index.js +6 -0
- package/dist/components/plan-badge/plan-badge.js +67 -0
- package/dist/components/rating/index.js +4 -0
- package/dist/components/rating/rating.js +121 -0
- package/dist/components/role-badge/index.js +6 -0
- package/dist/components/role-badge/role-badge.js +50 -0
- package/dist/components/scope-selector/index.js +6 -0
- package/dist/components/scope-selector/scope-selector.js +336 -0
- package/dist/components/severity-badge/index.js +8 -0
- package/dist/components/severity-badge/severity-badge.js +163 -0
- package/dist/components/sparkline-grid/index.js +6 -0
- package/dist/components/sparkline-grid/sparkline-grid.js +92 -0
- package/dist/components/spinner/index.js +5 -1
- package/dist/components/spinner/unicode-spinner.js +708 -0
- package/dist/components/stat-card/index.js +5 -0
- package/dist/components/stat-card/stat-card.js +102 -0
- package/dist/components/status-board/index.js +6 -0
- package/dist/components/status-board/status-board.js +138 -0
- package/dist/components/status-indicator/index.js +10 -0
- package/dist/components/status-indicator/status-indicator.js +175 -0
- package/dist/components/stepper/index.js +4 -0
- package/dist/components/stepper/stepper.js +117 -0
- package/dist/components/subscription-card/index.js +6 -0
- package/dist/components/subscription-card/subscription-card.js +161 -0
- package/dist/components/ticker-tape/index.js +6 -0
- package/dist/components/ticker-tape/ticker-tape.js +106 -0
- package/dist/components/tour/index.js +4 -0
- package/dist/components/tour/tour.js +157 -0
- package/dist/components/usage-breakdown/index.js +6 -0
- package/dist/components/usage-breakdown/usage-breakdown.js +140 -0
- package/dist/components/wallet-card/index.js +4 -0
- package/dist/components/wallet-card/wallet-card.js +115 -0
- package/dist/components/watchlist/index.js +6 -0
- package/dist/components/watchlist/watchlist.js +110 -0
- package/dist/components/world-clock-bar/index.js +6 -0
- package/dist/components/world-clock-bar/world-clock-bar.js +101 -0
- package/dist/index.d.ts +1173 -7
- package/dist/test-setup.js +19 -0
- package/package.json +27 -6
- package/styles.css +55 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { ArrowDownRight, ArrowUpRight } from "lucide-react";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
function getSpan(value, maxValue) {
|
|
6
|
+
const normalized = value / Math.max(maxValue, 1);
|
|
7
|
+
if (normalized >= 0.7) {
|
|
8
|
+
return "md:col-span-2 md:row-span-2";
|
|
9
|
+
}
|
|
10
|
+
if (normalized >= 0.4) {
|
|
11
|
+
return "md:col-span-2";
|
|
12
|
+
}
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
function getTone(change) {
|
|
16
|
+
const isPositive = change >= 0;
|
|
17
|
+
return {
|
|
18
|
+
isPositive,
|
|
19
|
+
tileClassName: isPositive ? "border-emerald-500/30 bg-emerald-500/10" : "border-rose-500/30 bg-rose-500/10",
|
|
20
|
+
trendClassName: isPositive ? "text-emerald-600 dark:text-emerald-400" : "text-rose-600 dark:text-rose-400"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function MarketTreemapTile({
|
|
24
|
+
item,
|
|
25
|
+
maxValue
|
|
26
|
+
}) {
|
|
27
|
+
const tone = getTone(item.change);
|
|
28
|
+
const TrendIcon = tone.isPositive ? ArrowUpRight : ArrowDownRight;
|
|
29
|
+
return /* @__PURE__ */ jsxs(
|
|
30
|
+
"article",
|
|
31
|
+
{
|
|
32
|
+
className: cn(
|
|
33
|
+
"flex min-h-[120px] flex-col justify-between rounded-2xl border p-4 transition-transform duration-200 hover:-translate-y-0.5",
|
|
34
|
+
tone.tileClassName,
|
|
35
|
+
getSpan(item.value, maxValue)
|
|
36
|
+
),
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
39
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
40
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-[0.24em] text-muted-foreground", children: item.sector ?? "Market" }),
|
|
41
|
+
/* @__PURE__ */ jsx("h3", { className: "mt-1 text-xl font-semibold text-foreground", children: item.label })
|
|
42
|
+
] }),
|
|
43
|
+
/* @__PURE__ */ jsxs(
|
|
44
|
+
"div",
|
|
45
|
+
{
|
|
46
|
+
className: cn(
|
|
47
|
+
"inline-flex items-center gap-1 text-sm font-medium",
|
|
48
|
+
tone.trendClassName
|
|
49
|
+
),
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx(TrendIcon, { className: "h-4 w-4" }),
|
|
52
|
+
item.change > 0 ? "+" : "",
|
|
53
|
+
item.change.toFixed(2),
|
|
54
|
+
"%"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
] }) }),
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end justify-between gap-3 text-sm text-muted-foreground", children: [
|
|
60
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
61
|
+
"Weight ",
|
|
62
|
+
item.value.toLocaleString()
|
|
63
|
+
] }),
|
|
64
|
+
/* @__PURE__ */ jsx("span", { className: "rounded-full bg-background/70 px-2 py-1 text-xs uppercase tracking-[0.2em] text-foreground", children: tone.isPositive ? "Advancing" : "Declining" })
|
|
65
|
+
] })
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const MarketTreemap = React.forwardRef(({ className, items, ...props }, reference) => {
|
|
71
|
+
if (items.length === 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const maxValue = Math.max(...items.map((item) => item.value));
|
|
75
|
+
return /* @__PURE__ */ jsxs(
|
|
76
|
+
"div",
|
|
77
|
+
{
|
|
78
|
+
className: cn(
|
|
79
|
+
"rounded-2xl border border-border bg-card/80 p-4 shadow-sm",
|
|
80
|
+
className
|
|
81
|
+
),
|
|
82
|
+
ref: reference,
|
|
83
|
+
...props,
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-3", children: [
|
|
86
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
87
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-[0.28em] text-muted-foreground", children: "Sector heatmap" }),
|
|
88
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Market treemap" })
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Tile size maps market cap proxy; color reflects session change." })
|
|
91
|
+
] }),
|
|
92
|
+
/* @__PURE__ */ jsx("div", { className: "grid auto-rows-[120px] grid-cols-1 gap-3 md:auto-rows-[120px] md:grid-cols-4", children: items.map((item) => /* @__PURE__ */ jsx(MarketTreemapTile, { item, maxValue }, item.label)) })
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
MarketTreemap.displayName = "MarketTreemap";
|
|
98
|
+
export {
|
|
99
|
+
MarketTreemap
|
|
100
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
function getGapValue(gap) {
|
|
5
|
+
return typeof gap === "number" ? `${gap}px` : gap;
|
|
6
|
+
}
|
|
7
|
+
function getMaskImage(vertical) {
|
|
8
|
+
return vertical ? "linear-gradient(to bottom, transparent, black 12%, black 88%, transparent)" : "linear-gradient(to right, transparent, black 12%, black 88%, transparent)";
|
|
9
|
+
}
|
|
10
|
+
function getTrackItems(children, repeat) {
|
|
11
|
+
const items = React.Children.toArray(children);
|
|
12
|
+
return Array.from(
|
|
13
|
+
{ length: Math.max(1, repeat) },
|
|
14
|
+
(_, copyIndex) => items.map((item, itemIndex) => /* @__PURE__ */ jsx("div", { className: "shrink-0", children: item }, `${copyIndex}-${itemIndex}`))
|
|
15
|
+
).flat();
|
|
16
|
+
}
|
|
17
|
+
function getDuration(duration, speed) {
|
|
18
|
+
if (duration !== void 0) {
|
|
19
|
+
return duration;
|
|
20
|
+
}
|
|
21
|
+
switch (speed) {
|
|
22
|
+
case "fast":
|
|
23
|
+
return 10;
|
|
24
|
+
case "normal":
|
|
25
|
+
return 20;
|
|
26
|
+
case "slow":
|
|
27
|
+
return 32;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const Marquee = React.forwardRef(
|
|
31
|
+
({
|
|
32
|
+
children,
|
|
33
|
+
className,
|
|
34
|
+
duration,
|
|
35
|
+
fade = true,
|
|
36
|
+
gap = "1rem",
|
|
37
|
+
pauseOnHover = false,
|
|
38
|
+
repeat = 1,
|
|
39
|
+
reverse = false,
|
|
40
|
+
speed = "normal",
|
|
41
|
+
style,
|
|
42
|
+
vertical = false,
|
|
43
|
+
...props
|
|
44
|
+
}, ref) => {
|
|
45
|
+
const resolvedGap = getGapValue(gap);
|
|
46
|
+
const resolvedDuration = getDuration(duration, speed);
|
|
47
|
+
const trackItems = getTrackItems(children, repeat);
|
|
48
|
+
const animationStyle = {
|
|
49
|
+
animationDirection: reverse ? "reverse" : "normal",
|
|
50
|
+
animationDuration: `${resolvedDuration}s`,
|
|
51
|
+
animationIterationCount: "infinite",
|
|
52
|
+
animationName: vertical ? "vllnt-marquee-y" : "vllnt-marquee-x",
|
|
53
|
+
animationTimingFunction: "linear"
|
|
54
|
+
};
|
|
55
|
+
const maskImage = getMaskImage(vertical);
|
|
56
|
+
return /* @__PURE__ */ jsx(
|
|
57
|
+
"div",
|
|
58
|
+
{
|
|
59
|
+
className: cn(
|
|
60
|
+
"group relative overflow-hidden",
|
|
61
|
+
vertical ? "flex h-full flex-col" : "flex w-full flex-row",
|
|
62
|
+
className
|
|
63
|
+
),
|
|
64
|
+
ref,
|
|
65
|
+
style: fade ? { ...style, maskImage, WebkitMaskImage: maskImage } : style,
|
|
66
|
+
...props,
|
|
67
|
+
children: /* @__PURE__ */ jsx(
|
|
68
|
+
"div",
|
|
69
|
+
{
|
|
70
|
+
className: cn(
|
|
71
|
+
"flex shrink-0 will-change-transform motion-reduce:animate-none motion-reduce:transform-none",
|
|
72
|
+
pauseOnHover && "group-hover:[animation-play-state:paused]",
|
|
73
|
+
vertical ? "min-h-full flex-col" : "min-w-full flex-row"
|
|
74
|
+
),
|
|
75
|
+
style: animationStyle,
|
|
76
|
+
children: [0, 1].map((groupIndex) => /* @__PURE__ */ jsx(
|
|
77
|
+
"div",
|
|
78
|
+
{
|
|
79
|
+
"aria-hidden": groupIndex === 1,
|
|
80
|
+
className: cn(
|
|
81
|
+
"flex shrink-0",
|
|
82
|
+
vertical ? "flex-col items-stretch" : "flex-row items-center"
|
|
83
|
+
),
|
|
84
|
+
style: { gap: resolvedGap },
|
|
85
|
+
children: trackItems
|
|
86
|
+
},
|
|
87
|
+
groupIndex
|
|
88
|
+
))
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
Marquee.displayName = "Marquee";
|
|
96
|
+
export {
|
|
97
|
+
Marquee
|
|
98
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
import { Badge } from "../badge";
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle
|
|
11
|
+
} from "../card";
|
|
12
|
+
const DEFAULT_THRESHOLDS = [
|
|
13
|
+
{ colorClassName: "text-emerald-500", label: "Nominal", value: 60 },
|
|
14
|
+
{ colorClassName: "text-amber-500", label: "Elevated", value: 85 },
|
|
15
|
+
{ colorClassName: "text-destructive", label: "Critical", value: 100 }
|
|
16
|
+
];
|
|
17
|
+
const GAUGE_CENTER = { x: 100, y: 100 };
|
|
18
|
+
function clamp(value, min, max) {
|
|
19
|
+
return Math.min(Math.max(value, min), max);
|
|
20
|
+
}
|
|
21
|
+
function polarToCartesian(radius, angle, center) {
|
|
22
|
+
const radians = (angle - 90) * Math.PI / 180;
|
|
23
|
+
return {
|
|
24
|
+
x: center.x + radius * Math.cos(radians),
|
|
25
|
+
y: center.y + radius * Math.sin(radians)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function describeArc(radius, angles, center) {
|
|
29
|
+
const start = polarToCartesian(radius, angles.end, center);
|
|
30
|
+
const end = polarToCartesian(radius, angles.start, center);
|
|
31
|
+
const largeArcFlag = angles.end - angles.start <= 180 ? 0 : 1;
|
|
32
|
+
return `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 0 ${end.x} ${end.y}`;
|
|
33
|
+
}
|
|
34
|
+
function formatMetricValue(value, unit) {
|
|
35
|
+
const rounded = Number.isInteger(value) ? value.toString() : value.toFixed(1);
|
|
36
|
+
return unit ? `${rounded}${unit}` : rounded;
|
|
37
|
+
}
|
|
38
|
+
function getActiveThreshold(percent, thresholds) {
|
|
39
|
+
return thresholds.find((threshold) => percent <= threshold.value) ?? thresholds.at(-1);
|
|
40
|
+
}
|
|
41
|
+
function GaugeDialSvg({
|
|
42
|
+
activeThreshold,
|
|
43
|
+
endAngle,
|
|
44
|
+
label
|
|
45
|
+
}) {
|
|
46
|
+
const gaugePath = describeArc(72, { end: 90, start: -90 }, GAUGE_CENTER);
|
|
47
|
+
const activePath = describeArc(
|
|
48
|
+
72,
|
|
49
|
+
{ end: endAngle, start: -90 },
|
|
50
|
+
GAUGE_CENTER
|
|
51
|
+
);
|
|
52
|
+
const needlePoint = polarToCartesian(60, endAngle, GAUGE_CENTER);
|
|
53
|
+
return /* @__PURE__ */ jsxs("svg", { "aria-label": label, className: "h-auto w-full", viewBox: "0 0 200 128", children: [
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
"path",
|
|
56
|
+
{
|
|
57
|
+
d: gaugePath,
|
|
58
|
+
fill: "none",
|
|
59
|
+
stroke: "currentColor",
|
|
60
|
+
strokeLinecap: "round",
|
|
61
|
+
strokeOpacity: "0.12",
|
|
62
|
+
strokeWidth: "14"
|
|
63
|
+
}
|
|
64
|
+
),
|
|
65
|
+
/* @__PURE__ */ jsx(
|
|
66
|
+
"path",
|
|
67
|
+
{
|
|
68
|
+
className: cn(activeThreshold.colorClassName),
|
|
69
|
+
d: activePath,
|
|
70
|
+
fill: "none",
|
|
71
|
+
stroke: "currentColor",
|
|
72
|
+
strokeLinecap: "round",
|
|
73
|
+
strokeWidth: "14"
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
"line",
|
|
78
|
+
{
|
|
79
|
+
className: cn(activeThreshold.colorClassName),
|
|
80
|
+
stroke: "currentColor",
|
|
81
|
+
strokeLinecap: "round",
|
|
82
|
+
strokeWidth: "4",
|
|
83
|
+
x1: GAUGE_CENTER.x,
|
|
84
|
+
x2: needlePoint.x,
|
|
85
|
+
y1: GAUGE_CENTER.y,
|
|
86
|
+
y2: needlePoint.y
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
/* @__PURE__ */ jsx(
|
|
90
|
+
"circle",
|
|
91
|
+
{
|
|
92
|
+
className: "fill-background stroke-border",
|
|
93
|
+
cx: GAUGE_CENTER.x,
|
|
94
|
+
cy: GAUGE_CENTER.y,
|
|
95
|
+
r: "8"
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
] });
|
|
99
|
+
}
|
|
100
|
+
function GaugeDial({
|
|
101
|
+
activeThreshold,
|
|
102
|
+
endAngle,
|
|
103
|
+
label,
|
|
104
|
+
max,
|
|
105
|
+
min,
|
|
106
|
+
percent,
|
|
107
|
+
safeValue,
|
|
108
|
+
unit
|
|
109
|
+
}) {
|
|
110
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
111
|
+
/* @__PURE__ */ jsxs("div", { className: "relative mx-auto w-full max-w-[280px]", children: [
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
GaugeDialSvg,
|
|
114
|
+
{
|
|
115
|
+
activeThreshold,
|
|
116
|
+
endAngle,
|
|
117
|
+
label
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute inset-x-0 top-12 text-center", children: [
|
|
121
|
+
/* @__PURE__ */ jsx("div", { className: "text-3xl font-semibold tracking-tight", children: formatMetricValue(safeValue, unit) }),
|
|
122
|
+
/* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
|
|
123
|
+
"Range ",
|
|
124
|
+
formatMetricValue(min, unit),
|
|
125
|
+
"\u2013",
|
|
126
|
+
formatMetricValue(max, unit)
|
|
127
|
+
] })
|
|
128
|
+
] })
|
|
129
|
+
] }),
|
|
130
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
131
|
+
/* @__PURE__ */ jsx("span", { children: formatMetricValue(min, unit) }),
|
|
132
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
133
|
+
Math.round(percent),
|
|
134
|
+
"%"
|
|
135
|
+
] }),
|
|
136
|
+
/* @__PURE__ */ jsx("span", { children: formatMetricValue(max, unit) })
|
|
137
|
+
] })
|
|
138
|
+
] });
|
|
139
|
+
}
|
|
140
|
+
function GaugeLegend({ thresholds }) {
|
|
141
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: thresholds.map((threshold) => /* @__PURE__ */ jsxs(
|
|
142
|
+
"div",
|
|
143
|
+
{
|
|
144
|
+
className: "flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs",
|
|
145
|
+
children: [
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
"span",
|
|
148
|
+
{
|
|
149
|
+
"aria-hidden": "true",
|
|
150
|
+
className: cn(
|
|
151
|
+
"h-2 w-2 rounded-full bg-current",
|
|
152
|
+
threshold.colorClassName
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
/* @__PURE__ */ jsx("span", { children: threshold.label }),
|
|
157
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
158
|
+
"\u2264 ",
|
|
159
|
+
threshold.value,
|
|
160
|
+
"%"
|
|
161
|
+
] })
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
`${threshold.label}-${threshold.value}`
|
|
165
|
+
)) });
|
|
166
|
+
}
|
|
167
|
+
const MetricGauge = React.forwardRef(
|
|
168
|
+
({
|
|
169
|
+
className,
|
|
170
|
+
description,
|
|
171
|
+
label,
|
|
172
|
+
max,
|
|
173
|
+
min = 0,
|
|
174
|
+
thresholds = DEFAULT_THRESHOLDS,
|
|
175
|
+
unit,
|
|
176
|
+
value,
|
|
177
|
+
...props
|
|
178
|
+
}, ref) => {
|
|
179
|
+
const safeValue = clamp(value, min, max);
|
|
180
|
+
const percent = max === min ? 0 : (safeValue - min) / (max - min) * 100;
|
|
181
|
+
const endAngle = -90 + 180 * (percent / 100);
|
|
182
|
+
const activeThreshold = getActiveThreshold(percent, thresholds);
|
|
183
|
+
return /* @__PURE__ */ jsxs(Card, { className: cn("shadow-sm", className), ref, ...props, children: [
|
|
184
|
+
/* @__PURE__ */ jsx(CardHeader, { className: "space-y-2 pb-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
185
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
186
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: label }),
|
|
187
|
+
description ? /* @__PURE__ */ jsx(CardDescription, { children: description }) : null
|
|
188
|
+
] }),
|
|
189
|
+
activeThreshold ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: activeThreshold.label }) : null
|
|
190
|
+
] }) }),
|
|
191
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
|
|
192
|
+
activeThreshold ? /* @__PURE__ */ jsx(
|
|
193
|
+
GaugeDial,
|
|
194
|
+
{
|
|
195
|
+
activeThreshold,
|
|
196
|
+
endAngle,
|
|
197
|
+
label,
|
|
198
|
+
max,
|
|
199
|
+
min,
|
|
200
|
+
percent,
|
|
201
|
+
safeValue,
|
|
202
|
+
unit
|
|
203
|
+
}
|
|
204
|
+
) : null,
|
|
205
|
+
/* @__PURE__ */ jsx(GaugeLegend, { thresholds })
|
|
206
|
+
] })
|
|
207
|
+
] });
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
MetricGauge.displayName = "MetricGauge";
|
|
211
|
+
export {
|
|
212
|
+
MetricGauge
|
|
213
|
+
};
|
|
@@ -12,7 +12,13 @@ import {
|
|
|
12
12
|
CommandItem,
|
|
13
13
|
CommandList
|
|
14
14
|
} from "../command";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
Dialog,
|
|
17
|
+
DialogContent,
|
|
18
|
+
DialogDescription,
|
|
19
|
+
DialogHeader,
|
|
20
|
+
DialogTitle
|
|
21
|
+
} from "../dialog";
|
|
16
22
|
import {
|
|
17
23
|
DropdownMenu,
|
|
18
24
|
DropdownMenuContent,
|
|
@@ -346,7 +352,10 @@ function ModelSelector(props) {
|
|
|
346
352
|
sortBy
|
|
347
353
|
} = useModelSelectorState(props);
|
|
348
354
|
return /* @__PURE__ */ jsx(Dialog, { onOpenChange: handleClose, open: props.open, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-2xl max-h-[80vh] flex flex-col", children: [
|
|
349
|
-
/* @__PURE__ */
|
|
355
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
356
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: "Select Model" }),
|
|
357
|
+
/* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: "Search, filter, and select an AI model" })
|
|
358
|
+
] }),
|
|
350
359
|
/* @__PURE__ */ jsx(
|
|
351
360
|
ModelSelectorFilters,
|
|
352
361
|
{
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Minus, Plus } from "lucide-react";
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
import { Button } from "../button";
|
|
7
|
+
function getNumericBound(bound) {
|
|
8
|
+
if (bound === void 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const parsedBound = Number(bound);
|
|
12
|
+
return Number.isNaN(parsedBound) ? void 0 : parsedBound;
|
|
13
|
+
}
|
|
14
|
+
function useNumberInputState(controlledValue, defaultValue, onValueChange) {
|
|
15
|
+
const [internalValue, setInternalValue] = React.useState(
|
|
16
|
+
defaultValue
|
|
17
|
+
);
|
|
18
|
+
const resolvedValue = controlledValue ?? internalValue;
|
|
19
|
+
const commitValue = (nextValue) => {
|
|
20
|
+
if (controlledValue === void 0) {
|
|
21
|
+
setInternalValue(nextValue);
|
|
22
|
+
}
|
|
23
|
+
onValueChange?.(nextValue);
|
|
24
|
+
};
|
|
25
|
+
return { commitValue, resolvedValue };
|
|
26
|
+
}
|
|
27
|
+
function clampNumber(nextValue, min, max) {
|
|
28
|
+
let result = nextValue;
|
|
29
|
+
if (min !== void 0) {
|
|
30
|
+
result = Math.max(min, result);
|
|
31
|
+
}
|
|
32
|
+
if (max !== void 0) {
|
|
33
|
+
result = Math.min(max, result);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
function StepButton({
|
|
38
|
+
direction,
|
|
39
|
+
disabled,
|
|
40
|
+
onClick
|
|
41
|
+
}) {
|
|
42
|
+
return /* @__PURE__ */ jsx(
|
|
43
|
+
Button,
|
|
44
|
+
{
|
|
45
|
+
className: cn(
|
|
46
|
+
"h-full px-3",
|
|
47
|
+
direction === "decrement" ? "rounded-r-none border-r" : "rounded-l-none border-l"
|
|
48
|
+
),
|
|
49
|
+
disabled,
|
|
50
|
+
onClick,
|
|
51
|
+
tabIndex: -1,
|
|
52
|
+
type: "button",
|
|
53
|
+
variant: "ghost",
|
|
54
|
+
children: direction === "decrement" ? /* @__PURE__ */ jsx(Minus, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
function NumberInputField({
|
|
59
|
+
disabled,
|
|
60
|
+
onValueChange,
|
|
61
|
+
placeholder,
|
|
62
|
+
reference,
|
|
63
|
+
resolvedValue,
|
|
64
|
+
...props
|
|
65
|
+
}) {
|
|
66
|
+
return /* @__PURE__ */ jsx(
|
|
67
|
+
"input",
|
|
68
|
+
{
|
|
69
|
+
...props,
|
|
70
|
+
className: "h-full w-full border-0 bg-transparent px-3 text-center text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed",
|
|
71
|
+
disabled,
|
|
72
|
+
inputMode: "decimal",
|
|
73
|
+
onChange: (event) => {
|
|
74
|
+
if (event.target.value === "") {
|
|
75
|
+
onValueChange();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const parsedValue = Number(event.target.value);
|
|
79
|
+
if (!Number.isNaN(parsedValue)) {
|
|
80
|
+
onValueChange(parsedValue);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
placeholder,
|
|
84
|
+
ref: reference,
|
|
85
|
+
type: "number",
|
|
86
|
+
value: resolvedValue ?? ""
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
function NumberInputComponent({
|
|
91
|
+
className,
|
|
92
|
+
defaultValue,
|
|
93
|
+
disabled,
|
|
94
|
+
max,
|
|
95
|
+
min,
|
|
96
|
+
onValueChange,
|
|
97
|
+
placeholder,
|
|
98
|
+
step = 1,
|
|
99
|
+
value,
|
|
100
|
+
...props
|
|
101
|
+
}, reference) {
|
|
102
|
+
const { commitValue, resolvedValue } = useNumberInputState(
|
|
103
|
+
value,
|
|
104
|
+
defaultValue,
|
|
105
|
+
onValueChange
|
|
106
|
+
);
|
|
107
|
+
const parsedMin = getNumericBound(min);
|
|
108
|
+
const parsedMax = getNumericBound(max);
|
|
109
|
+
const handleStepChange = (direction) => {
|
|
110
|
+
const baseValue = resolvedValue ?? parsedMin ?? 0;
|
|
111
|
+
commitValue(
|
|
112
|
+
clampNumber(baseValue + direction * step, parsedMin, parsedMax)
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
return /* @__PURE__ */ jsxs(
|
|
116
|
+
"div",
|
|
117
|
+
{
|
|
118
|
+
className: cn(
|
|
119
|
+
"flex h-10 w-full items-center rounded-md border border-input bg-background ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
|
|
120
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
121
|
+
className
|
|
122
|
+
),
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
StepButton,
|
|
126
|
+
{
|
|
127
|
+
direction: "decrement",
|
|
128
|
+
disabled,
|
|
129
|
+
onClick: () => {
|
|
130
|
+
handleStepChange(-1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
NumberInputField,
|
|
136
|
+
{
|
|
137
|
+
...props,
|
|
138
|
+
disabled,
|
|
139
|
+
onValueChange: (nextValue) => {
|
|
140
|
+
commitValue(
|
|
141
|
+
nextValue === void 0 ? void 0 : clampNumber(nextValue, parsedMin, parsedMax)
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
placeholder,
|
|
145
|
+
reference,
|
|
146
|
+
resolvedValue
|
|
147
|
+
}
|
|
148
|
+
),
|
|
149
|
+
/* @__PURE__ */ jsx(
|
|
150
|
+
StepButton,
|
|
151
|
+
{
|
|
152
|
+
direction: "increment",
|
|
153
|
+
disabled,
|
|
154
|
+
onClick: () => {
|
|
155
|
+
handleStepChange(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const NumberInput = React.forwardRef(NumberInputComponent);
|
|
164
|
+
NumberInput.displayName = "NumberInput";
|
|
165
|
+
export {
|
|
166
|
+
NumberInput
|
|
167
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
const NumberTicker = React.forwardRef(
|
|
6
|
+
({
|
|
7
|
+
className,
|
|
8
|
+
delay = 0,
|
|
9
|
+
duration = 1.2,
|
|
10
|
+
formatOptions,
|
|
11
|
+
from = 0,
|
|
12
|
+
locale,
|
|
13
|
+
value,
|
|
14
|
+
...props
|
|
15
|
+
}, ref) => {
|
|
16
|
+
const [currentValue, setCurrentValue] = React.useState(from);
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
const reducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
19
|
+
if (reducedMotion || duration <= 0) {
|
|
20
|
+
setCurrentValue(value);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let animationFrame = 0;
|
|
24
|
+
let timeoutId = 0;
|
|
25
|
+
const startDelay = Math.max(0, delay * 1e3);
|
|
26
|
+
const durationMs = duration * 1e3;
|
|
27
|
+
timeoutId = window.setTimeout(() => {
|
|
28
|
+
const startTime = performance.now();
|
|
29
|
+
const tick = (timestamp) => {
|
|
30
|
+
const elapsed = timestamp - startTime;
|
|
31
|
+
const progress = Math.min(elapsed / durationMs, 1);
|
|
32
|
+
const nextValue = from + (value - from) * progress;
|
|
33
|
+
setCurrentValue(nextValue);
|
|
34
|
+
if (progress < 1) {
|
|
35
|
+
animationFrame = window.requestAnimationFrame(tick);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
animationFrame = window.requestAnimationFrame(tick);
|
|
39
|
+
}, startDelay);
|
|
40
|
+
return () => {
|
|
41
|
+
window.clearTimeout(timeoutId);
|
|
42
|
+
window.cancelAnimationFrame(animationFrame);
|
|
43
|
+
};
|
|
44
|
+
}, [delay, duration, from, value]);
|
|
45
|
+
const formatter = React.useMemo(
|
|
46
|
+
() => new Intl.NumberFormat(locale, formatOptions),
|
|
47
|
+
[formatOptions, locale]
|
|
48
|
+
);
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
"span",
|
|
51
|
+
{
|
|
52
|
+
className: cn("tabular-nums tracking-tight", className),
|
|
53
|
+
ref,
|
|
54
|
+
...props,
|
|
55
|
+
children: formatter.format(currentValue)
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
NumberTicker.displayName = "NumberTicker";
|
|
61
|
+
export {
|
|
62
|
+
NumberTicker
|
|
63
|
+
};
|