@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.
Files changed (100) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +106 -1
  3. package/dist/components/activity-heatmap/activity-heatmap.js +168 -0
  4. package/dist/components/activity-heatmap/index.js +6 -0
  5. package/dist/components/activity-log/activity-log.js +256 -0
  6. package/dist/components/activity-log/index.js +6 -0
  7. package/dist/components/ai-chat-input/ai-chat-input.js +107 -0
  8. package/dist/components/ai-chat-input/index.js +4 -0
  9. package/dist/components/ai-message-bubble/ai-message-bubble.js +119 -0
  10. package/dist/components/ai-message-bubble/index.js +6 -0
  11. package/dist/components/ai-source-citation/ai-source-citation.js +39 -0
  12. package/dist/components/ai-source-citation/index.js +6 -0
  13. package/dist/components/ai-streaming-text/ai-streaming-text.js +41 -0
  14. package/dist/components/ai-streaming-text/index.js +6 -0
  15. package/dist/components/ai-tool-call-display/ai-tool-call-display.js +93 -0
  16. package/dist/components/ai-tool-call-display/index.js +6 -0
  17. package/dist/components/animated-text/animated-text.js +328 -0
  18. package/dist/components/animated-text/index.js +4 -0
  19. package/dist/components/annotation/annotation.js +49 -0
  20. package/dist/components/annotation/index.js +8 -0
  21. package/dist/components/avatar-group/avatar-group.js +82 -0
  22. package/dist/components/avatar-group/index.js +10 -0
  23. package/dist/components/border-beam/border-beam.js +51 -0
  24. package/dist/components/border-beam/index.js +4 -0
  25. package/dist/components/candlestick-chart/candlestick-chart.js +215 -0
  26. package/dist/components/candlestick-chart/index.js +6 -0
  27. package/dist/components/combobox/combobox.js +130 -0
  28. package/dist/components/combobox/index.js +4 -0
  29. package/dist/components/countdown-timer/countdown-timer.js +184 -0
  30. package/dist/components/countdown-timer/index.js +4 -0
  31. package/dist/components/credit-badge/credit-badge.js +59 -0
  32. package/dist/components/credit-badge/index.js +6 -0
  33. package/dist/components/data-list/data-list.js +99 -0
  34. package/dist/components/data-list/index.js +16 -0
  35. package/dist/components/data-table/data-table.js +242 -0
  36. package/dist/components/data-table/index.js +6 -0
  37. package/dist/components/date-picker/date-picker.js +74 -0
  38. package/dist/components/date-picker/index.js +4 -0
  39. package/dist/components/file-upload/file-upload.js +227 -0
  40. package/dist/components/file-upload/index.js +4 -0
  41. package/dist/components/flashcard/flashcard.js +66 -0
  42. package/dist/components/flashcard/index.js +4 -0
  43. package/dist/components/index.js +172 -1
  44. package/dist/components/live-feed/index.js +4 -0
  45. package/dist/components/live-feed/live-feed.js +168 -0
  46. package/dist/components/market-treemap/index.js +6 -0
  47. package/dist/components/market-treemap/market-treemap.js +100 -0
  48. package/dist/components/marquee/index.js +4 -0
  49. package/dist/components/marquee/marquee.js +98 -0
  50. package/dist/components/metric-gauge/index.js +6 -0
  51. package/dist/components/metric-gauge/metric-gauge.js +213 -0
  52. package/dist/components/model-selector/model-selector.js +11 -2
  53. package/dist/components/number-input/index.js +4 -0
  54. package/dist/components/number-input/number-input.js +167 -0
  55. package/dist/components/number-ticker/index.js +4 -0
  56. package/dist/components/number-ticker/number-ticker.js +63 -0
  57. package/dist/components/order-book/index.js +6 -0
  58. package/dist/components/order-book/order-book.js +128 -0
  59. package/dist/components/password-input/index.js +4 -0
  60. package/dist/components/password-input/password-input.js +45 -0
  61. package/dist/components/plan-badge/index.js +6 -0
  62. package/dist/components/plan-badge/plan-badge.js +67 -0
  63. package/dist/components/rating/index.js +4 -0
  64. package/dist/components/rating/rating.js +121 -0
  65. package/dist/components/role-badge/index.js +6 -0
  66. package/dist/components/role-badge/role-badge.js +50 -0
  67. package/dist/components/scope-selector/index.js +6 -0
  68. package/dist/components/scope-selector/scope-selector.js +336 -0
  69. package/dist/components/severity-badge/index.js +8 -0
  70. package/dist/components/severity-badge/severity-badge.js +163 -0
  71. package/dist/components/sparkline-grid/index.js +6 -0
  72. package/dist/components/sparkline-grid/sparkline-grid.js +92 -0
  73. package/dist/components/spinner/index.js +5 -1
  74. package/dist/components/spinner/unicode-spinner.js +708 -0
  75. package/dist/components/stat-card/index.js +5 -0
  76. package/dist/components/stat-card/stat-card.js +102 -0
  77. package/dist/components/status-board/index.js +6 -0
  78. package/dist/components/status-board/status-board.js +138 -0
  79. package/dist/components/status-indicator/index.js +10 -0
  80. package/dist/components/status-indicator/status-indicator.js +175 -0
  81. package/dist/components/stepper/index.js +4 -0
  82. package/dist/components/stepper/stepper.js +117 -0
  83. package/dist/components/subscription-card/index.js +6 -0
  84. package/dist/components/subscription-card/subscription-card.js +161 -0
  85. package/dist/components/ticker-tape/index.js +6 -0
  86. package/dist/components/ticker-tape/ticker-tape.js +106 -0
  87. package/dist/components/tour/index.js +4 -0
  88. package/dist/components/tour/tour.js +157 -0
  89. package/dist/components/usage-breakdown/index.js +6 -0
  90. package/dist/components/usage-breakdown/usage-breakdown.js +140 -0
  91. package/dist/components/wallet-card/index.js +4 -0
  92. package/dist/components/wallet-card/wallet-card.js +115 -0
  93. package/dist/components/watchlist/index.js +6 -0
  94. package/dist/components/watchlist/watchlist.js +110 -0
  95. package/dist/components/world-clock-bar/index.js +6 -0
  96. package/dist/components/world-clock-bar/world-clock-bar.js +101 -0
  97. package/dist/index.d.ts +1173 -7
  98. package/dist/test-setup.js +19 -0
  99. package/package.json +27 -6
  100. 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,4 @@
1
+ import { Marquee } from "./marquee";
2
+ export {
3
+ Marquee
4
+ };
@@ -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,6 @@
1
+ import {
2
+ MetricGauge
3
+ } from "./metric-gauge";
4
+ export {
5
+ MetricGauge
6
+ };
@@ -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 { Dialog, DialogContent, DialogHeader, DialogTitle } from "../dialog";
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__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Select Model" }) }),
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,4 @@
1
+ import { NumberInput } from "./number-input";
2
+ export {
3
+ NumberInput
4
+ };
@@ -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,4 @@
1
+ import { NumberTicker } from "./number-ticker";
2
+ export {
3
+ NumberTicker
4
+ };
@@ -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
+ };