@vllnt/ui 0.1.8 → 0.1.11-canary.54f8a77

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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/dist/components/activity-heatmap/activity-heatmap.js +168 -0
  3. package/dist/components/activity-heatmap/index.js +6 -0
  4. package/dist/components/activity-log/activity-log.js +256 -0
  5. package/dist/components/activity-log/index.js +6 -0
  6. package/dist/components/ai-chat-input/ai-chat-input.js +107 -0
  7. package/dist/components/ai-chat-input/index.js +4 -0
  8. package/dist/components/ai-message-bubble/ai-message-bubble.js +119 -0
  9. package/dist/components/ai-message-bubble/index.js +6 -0
  10. package/dist/components/ai-source-citation/ai-source-citation.js +39 -0
  11. package/dist/components/ai-source-citation/index.js +6 -0
  12. package/dist/components/ai-streaming-text/ai-streaming-text.js +41 -0
  13. package/dist/components/ai-streaming-text/index.js +6 -0
  14. package/dist/components/ai-tool-call-display/ai-tool-call-display.js +93 -0
  15. package/dist/components/ai-tool-call-display/index.js +6 -0
  16. package/dist/components/animated-text/animated-text.js +328 -0
  17. package/dist/components/animated-text/index.js +4 -0
  18. package/dist/components/annotation/annotation.js +49 -0
  19. package/dist/components/annotation/index.js +8 -0
  20. package/dist/components/avatar-group/avatar-group.js +82 -0
  21. package/dist/components/avatar-group/index.js +10 -0
  22. package/dist/components/border-beam/border-beam.js +51 -0
  23. package/dist/components/border-beam/index.js +4 -0
  24. package/dist/components/candlestick-chart/candlestick-chart.js +215 -0
  25. package/dist/components/candlestick-chart/index.js +6 -0
  26. package/dist/components/combobox/combobox.js +130 -0
  27. package/dist/components/combobox/index.js +4 -0
  28. package/dist/components/countdown-timer/countdown-timer.js +184 -0
  29. package/dist/components/countdown-timer/index.js +4 -0
  30. package/dist/components/credit-badge/credit-badge.js +59 -0
  31. package/dist/components/credit-badge/index.js +6 -0
  32. package/dist/components/data-list/data-list.js +99 -0
  33. package/dist/components/data-list/index.js +16 -0
  34. package/dist/components/data-table/data-table.js +242 -0
  35. package/dist/components/data-table/index.js +6 -0
  36. package/dist/components/date-picker/date-picker.js +74 -0
  37. package/dist/components/date-picker/index.js +4 -0
  38. package/dist/components/file-upload/file-upload.js +227 -0
  39. package/dist/components/file-upload/index.js +4 -0
  40. package/dist/components/flashcard/flashcard.js +66 -0
  41. package/dist/components/flashcard/index.js +4 -0
  42. package/dist/components/index.js +172 -1
  43. package/dist/components/live-feed/index.js +4 -0
  44. package/dist/components/live-feed/live-feed.js +168 -0
  45. package/dist/components/market-treemap/index.js +6 -0
  46. package/dist/components/market-treemap/market-treemap.js +100 -0
  47. package/dist/components/marquee/index.js +4 -0
  48. package/dist/components/marquee/marquee.js +98 -0
  49. package/dist/components/metric-gauge/index.js +6 -0
  50. package/dist/components/metric-gauge/metric-gauge.js +213 -0
  51. package/dist/components/model-selector/model-selector.js +11 -2
  52. package/dist/components/number-input/index.js +4 -0
  53. package/dist/components/number-input/number-input.js +167 -0
  54. package/dist/components/number-ticker/index.js +4 -0
  55. package/dist/components/number-ticker/number-ticker.js +63 -0
  56. package/dist/components/order-book/index.js +6 -0
  57. package/dist/components/order-book/order-book.js +128 -0
  58. package/dist/components/password-input/index.js +4 -0
  59. package/dist/components/password-input/password-input.js +45 -0
  60. package/dist/components/plan-badge/index.js +6 -0
  61. package/dist/components/plan-badge/plan-badge.js +67 -0
  62. package/dist/components/rating/index.js +4 -0
  63. package/dist/components/rating/rating.js +121 -0
  64. package/dist/components/role-badge/index.js +6 -0
  65. package/dist/components/role-badge/role-badge.js +50 -0
  66. package/dist/components/scope-selector/index.js +6 -0
  67. package/dist/components/scope-selector/scope-selector.js +336 -0
  68. package/dist/components/severity-badge/index.js +8 -0
  69. package/dist/components/severity-badge/severity-badge.js +163 -0
  70. package/dist/components/sparkline-grid/index.js +6 -0
  71. package/dist/components/sparkline-grid/sparkline-grid.js +92 -0
  72. package/dist/components/spinner/index.js +5 -1
  73. package/dist/components/spinner/unicode-spinner.js +708 -0
  74. package/dist/components/stat-card/index.js +5 -0
  75. package/dist/components/stat-card/stat-card.js +102 -0
  76. package/dist/components/status-board/index.js +6 -0
  77. package/dist/components/status-board/status-board.js +138 -0
  78. package/dist/components/status-indicator/index.js +10 -0
  79. package/dist/components/status-indicator/status-indicator.js +175 -0
  80. package/dist/components/stepper/index.js +4 -0
  81. package/dist/components/stepper/stepper.js +117 -0
  82. package/dist/components/subscription-card/index.js +6 -0
  83. package/dist/components/subscription-card/subscription-card.js +161 -0
  84. package/dist/components/ticker-tape/index.js +6 -0
  85. package/dist/components/ticker-tape/ticker-tape.js +106 -0
  86. package/dist/components/tour/index.js +4 -0
  87. package/dist/components/tour/tour.js +157 -0
  88. package/dist/components/usage-breakdown/index.js +6 -0
  89. package/dist/components/usage-breakdown/usage-breakdown.js +140 -0
  90. package/dist/components/wallet-card/index.js +4 -0
  91. package/dist/components/wallet-card/wallet-card.js +115 -0
  92. package/dist/components/watchlist/index.js +6 -0
  93. package/dist/components/watchlist/watchlist.js +110 -0
  94. package/dist/components/world-clock-bar/index.js +6 -0
  95. package/dist/components/world-clock-bar/world-clock-bar.js +101 -0
  96. package/dist/index.d.ts +1173 -7
  97. package/dist/test-setup.js +19 -0
  98. package/package.json +45 -41
  99. package/styles.css +55 -0
@@ -0,0 +1,336 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useMemo, useState } from "react";
4
+ import { Check, ChevronRight, Search } from "lucide-react";
5
+ import { cn } from "../../lib/utils";
6
+ import { Badge } from "../badge";
7
+ import { Button } from "../button";
8
+ import { Input } from "../input";
9
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover";
10
+ import { ScrollArea } from "../scroll-area";
11
+ import { Separator } from "../separator";
12
+ function flattenNodes(nodes, parentPath = []) {
13
+ return nodes.flatMap((node) => {
14
+ const path = [...parentPath, node];
15
+ const current = [{ node, path }];
16
+ const descendants = node.children ? flattenNodes(node.children, path) : [];
17
+ return [...current, ...descendants];
18
+ });
19
+ }
20
+ function findSelection(nodes, value) {
21
+ if (!value) return void 0;
22
+ const flattened = flattenNodes(nodes);
23
+ const match = flattened.find((entry) => entry.node.id === value);
24
+ return match ? { node: match.node, path: match.path } : void 0;
25
+ }
26
+ function getVisibleNodes(tree, currentPath) {
27
+ const currentNode = currentPath.at(-1);
28
+ return currentNode?.children ?? tree;
29
+ }
30
+ function isSelectableNode(node) {
31
+ if (node.disabled) return false;
32
+ if (node.selectable !== void 0) return node.selectable;
33
+ return !node.children || node.children.length === 0;
34
+ }
35
+ function getPathLabel(path) {
36
+ return path.map((node) => node.label).join(" / ");
37
+ }
38
+ function filterScopeResults(flattenedNodes, query) {
39
+ const normalizedQuery = query.trim().toLowerCase();
40
+ if (!normalizedQuery) return [];
41
+ return flattenedNodes.filter(({ node, path }) => {
42
+ const pathLabel = getPathLabel(path).toLowerCase();
43
+ const description = node.description?.toLowerCase() ?? "";
44
+ return node.label.toLowerCase().includes(normalizedQuery) || description.includes(normalizedQuery) || pathLabel.includes(normalizedQuery);
45
+ });
46
+ }
47
+ function ScopeOptionButton({
48
+ node,
49
+ onBrowse,
50
+ onSelect,
51
+ path,
52
+ selectedValue,
53
+ showPathLabel
54
+ }) {
55
+ const selectable = isSelectableNode(node);
56
+ return /* @__PURE__ */ jsxs(
57
+ "button",
58
+ {
59
+ className: cn(
60
+ "flex w-full items-start justify-between rounded-md border px-3 py-3 text-left transition-colors hover:bg-accent hover:text-accent-foreground",
61
+ selectedValue === node.id && "border-primary bg-accent",
62
+ node.disabled && "cursor-not-allowed opacity-50"
63
+ ),
64
+ disabled: node.disabled,
65
+ onClick: () => {
66
+ if (selectable) {
67
+ onSelect({ node, path });
68
+ return;
69
+ }
70
+ onBrowse(node);
71
+ },
72
+ type: "button",
73
+ children: [
74
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
75
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
76
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: node.label }),
77
+ node.badge ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: node.badge }) : null
78
+ ] }),
79
+ showPathLabel ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: getPathLabel(path) }) : null,
80
+ node.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: node.description }) : null
81
+ ] }),
82
+ selectedValue === node.id ? /* @__PURE__ */ jsx(Check, { className: "mt-0.5 h-4 w-4 shrink-0 text-primary" }) : node.children && node.children.length > 0 ? /* @__PURE__ */ jsx(ChevronRight, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }) : null
83
+ ]
84
+ },
85
+ node.id
86
+ );
87
+ }
88
+ function ScopeSearchResults({
89
+ onBrowse,
90
+ onSelect,
91
+ results,
92
+ selectedValue
93
+ }) {
94
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2 px-1 py-1", children: results.map(({ node, path }) => /* @__PURE__ */ jsx(
95
+ ScopeOptionButton,
96
+ {
97
+ node,
98
+ onBrowse,
99
+ onSelect,
100
+ path,
101
+ selectedValue,
102
+ showPathLabel: true
103
+ },
104
+ getPathLabel(path)
105
+ )) });
106
+ }
107
+ function ScopeCurrentLevel({
108
+ currentPath,
109
+ nodes,
110
+ onBrowse,
111
+ onSelect,
112
+ selectedValue
113
+ }) {
114
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2 px-1 py-1", children: nodes.map((node) => /* @__PURE__ */ jsx(
115
+ ScopeOptionButton,
116
+ {
117
+ node,
118
+ onBrowse,
119
+ onSelect,
120
+ path: [...currentPath, node],
121
+ selectedValue
122
+ },
123
+ node.id
124
+ )) });
125
+ }
126
+ function ScopePanel({
127
+ currentPath,
128
+ emptyMessage,
129
+ nodes,
130
+ onBrowse,
131
+ onSelect,
132
+ query,
133
+ searchResults,
134
+ selectedValue
135
+ }) {
136
+ const normalizedQuery = query.trim().toLowerCase();
137
+ if (normalizedQuery) {
138
+ if (searchResults.length === 0) {
139
+ return /* @__PURE__ */ jsx("div", { className: "px-3 py-8 text-center text-sm text-muted-foreground", children: "No scopes match your search." });
140
+ }
141
+ return /* @__PURE__ */ jsx(
142
+ ScopeSearchResults,
143
+ {
144
+ onBrowse,
145
+ onSelect,
146
+ results: searchResults,
147
+ selectedValue
148
+ }
149
+ );
150
+ }
151
+ if (nodes.length === 0) {
152
+ return /* @__PURE__ */ jsx("div", { className: "px-3 py-8 text-center text-sm text-muted-foreground", children: emptyMessage });
153
+ }
154
+ return /* @__PURE__ */ jsx(
155
+ ScopeCurrentLevel,
156
+ {
157
+ currentPath,
158
+ nodes,
159
+ onBrowse,
160
+ onSelect,
161
+ selectedValue
162
+ }
163
+ );
164
+ }
165
+ function ScopeSelectorBreadcrumb({
166
+ currentPath,
167
+ onBack
168
+ }) {
169
+ if (currentPath.length === 0) return null;
170
+ return /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-2 text-xs text-muted-foreground", children: [
171
+ /* @__PURE__ */ jsxs(Button, { className: "h-7 px-2", onClick: onBack, size: "sm", variant: "ghost", children: [
172
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 rotate-180" }),
173
+ "Back"
174
+ ] }),
175
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: getPathLabel(currentPath) })
176
+ ] });
177
+ }
178
+ function ScopeSelectorPopoverBody({
179
+ emptyMessage,
180
+ searchPlaceholder,
181
+ state
182
+ }) {
183
+ return /* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-[380px] p-0", sideOffset: 8, children: [
184
+ /* @__PURE__ */ jsxs("div", { className: "border-b p-3", children: [
185
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
186
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
187
+ /* @__PURE__ */ jsx(
188
+ Input,
189
+ {
190
+ className: "pl-9",
191
+ onChange: (event) => {
192
+ state.setQuery(event.target.value);
193
+ },
194
+ placeholder: searchPlaceholder,
195
+ value: state.query
196
+ }
197
+ )
198
+ ] }),
199
+ state.query.trim() ? null : /* @__PURE__ */ jsx(
200
+ ScopeSelectorBreadcrumb,
201
+ {
202
+ currentPath: state.currentPath,
203
+ onBack: () => {
204
+ state.setCurrentPath(state.currentPath.slice(0, -1));
205
+ }
206
+ }
207
+ )
208
+ ] }),
209
+ /* @__PURE__ */ jsx(ScrollArea, { className: "max-h-[320px]", children: /* @__PURE__ */ jsx(
210
+ ScopePanel,
211
+ {
212
+ currentPath: state.currentPath,
213
+ emptyMessage,
214
+ nodes: state.currentLevelNodes,
215
+ onBrowse: state.handleBrowse,
216
+ onSelect: state.handleSelect,
217
+ query: state.query,
218
+ searchResults: state.searchResults,
219
+ selectedValue: state.selectedValue
220
+ }
221
+ ) }),
222
+ state.selectedSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
223
+ /* @__PURE__ */ jsx(Separator, {}),
224
+ /* @__PURE__ */ jsxs("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: [
225
+ "Selected: ",
226
+ state.selectedPathLabel
227
+ ] })
228
+ ] }) : null
229
+ ] });
230
+ }
231
+ function useScopeSelectorState({
232
+ defaultValue,
233
+ nodes,
234
+ onValueChange,
235
+ value
236
+ }) {
237
+ const [open, setOpen] = useState(false);
238
+ const [query, setQuery] = useState("");
239
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
240
+ const [currentPath, setCurrentPath] = useState([]);
241
+ const selectedValue = value ?? uncontrolledValue;
242
+ const selectedSelection = useMemo(
243
+ () => findSelection(nodes, selectedValue),
244
+ [nodes, selectedValue]
245
+ );
246
+ const flattenedNodes = useMemo(() => flattenNodes(nodes), [nodes]);
247
+ const searchResults = useMemo(
248
+ () => filterScopeResults(flattenedNodes, query),
249
+ [flattenedNodes, query]
250
+ );
251
+ const currentLevelNodes = getVisibleNodes(nodes, currentPath);
252
+ function handleSelect(selection) {
253
+ if (value === void 0) {
254
+ setUncontrolledValue(selection.node.id);
255
+ }
256
+ setCurrentPath(selection.path.slice(0, -1));
257
+ setQuery("");
258
+ setOpen(false);
259
+ onValueChange?.(selection);
260
+ }
261
+ function handleBrowse(node) {
262
+ const nextPath = findSelection(nodes, node.id)?.path ?? [];
263
+ setCurrentPath(nextPath);
264
+ }
265
+ function handleOpenChange(nextOpen) {
266
+ setOpen(nextOpen);
267
+ if (nextOpen) {
268
+ setCurrentPath(selectedSelection?.path.slice(0, -1) ?? []);
269
+ return;
270
+ }
271
+ setQuery("");
272
+ }
273
+ return {
274
+ currentLevelNodes,
275
+ currentPath,
276
+ currentPathLabel: getPathLabel(currentPath),
277
+ handleBrowse,
278
+ handleOpenChange,
279
+ handleSelect,
280
+ open,
281
+ query,
282
+ searchResults,
283
+ selectedPathLabel: selectedSelection ? getPathLabel(selectedSelection.path) : void 0,
284
+ selectedSelection,
285
+ selectedValue,
286
+ setCurrentPath,
287
+ setQuery
288
+ };
289
+ }
290
+ const ScopeSelector = forwardRef(
291
+ ({
292
+ className,
293
+ defaultValue,
294
+ disabled,
295
+ emptyMessage = "No scopes available.",
296
+ nodes,
297
+ onValueChange,
298
+ placeholder = "Select scope",
299
+ searchPlaceholder = "Search scopes...",
300
+ value
301
+ }, ref) => {
302
+ const state = useScopeSelectorState({
303
+ defaultValue,
304
+ nodes,
305
+ onValueChange,
306
+ value
307
+ });
308
+ return /* @__PURE__ */ jsxs(Popover, { onOpenChange: state.handleOpenChange, open: state.open, children: [
309
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
310
+ Button,
311
+ {
312
+ className: cn("w-full justify-between", className),
313
+ disabled,
314
+ ref,
315
+ variant: "outline",
316
+ children: [
317
+ /* @__PURE__ */ jsx("span", { className: "truncate text-left", children: state.selectedPathLabel ?? placeholder }),
318
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 shrink-0 rotate-90 text-muted-foreground" })
319
+ ]
320
+ }
321
+ ) }),
322
+ /* @__PURE__ */ jsx(
323
+ ScopeSelectorPopoverBody,
324
+ {
325
+ emptyMessage,
326
+ searchPlaceholder,
327
+ state
328
+ }
329
+ )
330
+ ] });
331
+ }
332
+ );
333
+ ScopeSelector.displayName = "ScopeSelector";
334
+ export {
335
+ ScopeSelector
336
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ SeverityBadge,
3
+ severityBadgeVariants
4
+ } from "./severity-badge";
5
+ export {
6
+ SeverityBadge,
7
+ severityBadgeVariants
8
+ };
@@ -0,0 +1,163 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cva } from "class-variance-authority";
3
+ import { cn } from "../../lib/utils";
4
+ const severityBadgeVariants = cva(
5
+ "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
6
+ {
7
+ compoundVariants: [
8
+ {
9
+ className: "border-transparent bg-destructive text-destructive-foreground",
10
+ level: "critical",
11
+ tone: "solid"
12
+ },
13
+ {
14
+ className: "border-destructive/30 bg-destructive/10 text-destructive",
15
+ level: "critical",
16
+ tone: "soft"
17
+ },
18
+ {
19
+ className: "border-destructive/40 text-destructive",
20
+ level: "critical",
21
+ tone: "outline"
22
+ },
23
+ {
24
+ className: "border-transparent bg-orange-500 text-white",
25
+ level: "high",
26
+ tone: "solid"
27
+ },
28
+ {
29
+ className: "border-orange-500/30 bg-orange-500/10 text-orange-600",
30
+ level: "high",
31
+ tone: "soft"
32
+ },
33
+ {
34
+ className: "border-orange-500/40 text-orange-600",
35
+ level: "high",
36
+ tone: "outline"
37
+ },
38
+ {
39
+ className: "border-transparent bg-amber-500 text-white",
40
+ level: "medium",
41
+ tone: "solid"
42
+ },
43
+ {
44
+ className: "border-amber-500/30 bg-amber-500/10 text-amber-600",
45
+ level: "medium",
46
+ tone: "soft"
47
+ },
48
+ {
49
+ className: "border-amber-500/40 text-amber-600",
50
+ level: "medium",
51
+ tone: "outline"
52
+ },
53
+ {
54
+ className: "border-transparent bg-sky-500 text-white",
55
+ level: "low",
56
+ tone: "solid"
57
+ },
58
+ {
59
+ className: "border-sky-500/30 bg-sky-500/10 text-sky-600",
60
+ level: "low",
61
+ tone: "soft"
62
+ },
63
+ {
64
+ className: "border-sky-500/40 text-sky-600",
65
+ level: "low",
66
+ tone: "outline"
67
+ },
68
+ {
69
+ className: "border-transparent bg-muted text-muted-foreground",
70
+ level: "info",
71
+ tone: "solid"
72
+ },
73
+ {
74
+ className: "border-border bg-muted/50 text-muted-foreground",
75
+ level: "info",
76
+ tone: "soft"
77
+ },
78
+ {
79
+ className: "border-border text-muted-foreground",
80
+ level: "info",
81
+ tone: "outline"
82
+ }
83
+ ],
84
+ defaultVariants: {
85
+ level: "info",
86
+ tone: "soft"
87
+ },
88
+ variants: {
89
+ level: {
90
+ critical: "",
91
+ high: "",
92
+ info: "",
93
+ low: "",
94
+ medium: ""
95
+ },
96
+ tone: {
97
+ outline: "",
98
+ soft: "",
99
+ solid: ""
100
+ }
101
+ }
102
+ }
103
+ );
104
+ const DOT_COLOR = {
105
+ critical: "bg-destructive",
106
+ high: "bg-orange-500",
107
+ info: "bg-muted-foreground",
108
+ low: "bg-sky-500",
109
+ medium: "bg-amber-500"
110
+ };
111
+ const DEFAULT_LABEL = {
112
+ critical: "Critical",
113
+ high: "High",
114
+ info: "Info",
115
+ low: "Low",
116
+ medium: "Medium"
117
+ };
118
+ function SeverityBadge({
119
+ children,
120
+ className,
121
+ level,
122
+ pulse = false,
123
+ showDot = true,
124
+ tone,
125
+ ...props
126
+ }) {
127
+ const label = children ?? DEFAULT_LABEL[level];
128
+ return /* @__PURE__ */ jsxs(
129
+ "span",
130
+ {
131
+ className: cn(severityBadgeVariants({ level, tone }), className),
132
+ "data-level": level,
133
+ ...props,
134
+ children: [
135
+ showDot ? /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "relative flex h-2 w-2", children: [
136
+ pulse ? /* @__PURE__ */ jsx(
137
+ "span",
138
+ {
139
+ className: cn(
140
+ "absolute inline-flex h-full w-full animate-ping rounded-full opacity-60",
141
+ DOT_COLOR[level]
142
+ )
143
+ }
144
+ ) : null,
145
+ /* @__PURE__ */ jsx(
146
+ "span",
147
+ {
148
+ className: cn(
149
+ "relative inline-flex h-2 w-2 rounded-full",
150
+ DOT_COLOR[level]
151
+ )
152
+ }
153
+ )
154
+ ] }) : null,
155
+ /* @__PURE__ */ jsx("span", { children: label })
156
+ ]
157
+ }
158
+ );
159
+ }
160
+ export {
161
+ SeverityBadge,
162
+ severityBadgeVariants
163
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ SparklineGrid
3
+ } from "./sparkline-grid";
4
+ export {
5
+ SparklineGrid
6
+ };
@@ -0,0 +1,92 @@
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 buildSparklinePath(data, width, height) {
6
+ const min = Math.min(...data);
7
+ const max = Math.max(...data);
8
+ const range = max - min || 1;
9
+ return data.map((value, index) => {
10
+ const x = data.length === 1 ? width / 2 : index / (data.length - 1) * width;
11
+ const y = height - (value - min) / range * height;
12
+ return `${index === 0 ? "M" : "L"}${x},${y}`;
13
+ }).join(" ");
14
+ }
15
+ const gridColumns = {
16
+ 2: "md:grid-cols-2",
17
+ 3: "md:grid-cols-2 xl:grid-cols-3",
18
+ 4: "md:grid-cols-2 xl:grid-cols-4"
19
+ };
20
+ const SparklineGrid = React.forwardRef(({ className, columns = 2, items, ...props }, reference) => {
21
+ if (items.length === 0) {
22
+ return null;
23
+ }
24
+ return /* @__PURE__ */ jsx(
25
+ "div",
26
+ {
27
+ className: cn("grid grid-cols-1 gap-4", gridColumns[columns], className),
28
+ ref: reference,
29
+ ...props,
30
+ children: items.map((item) => {
31
+ const isPositive = item.change >= 0;
32
+ const TrendIcon = isPositive ? ArrowUpRight : ArrowDownRight;
33
+ const stroke = isPositive ? "hsl(142 71% 45%)" : "hsl(348 83% 47%)";
34
+ return /* @__PURE__ */ jsxs(
35
+ "section",
36
+ {
37
+ className: "rounded-2xl border border-border bg-card/80 p-4 shadow-sm",
38
+ children: [
39
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
40
+ /* @__PURE__ */ jsxs("div", { children: [
41
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-muted-foreground", children: item.label }),
42
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-2xl font-semibold tracking-tight text-foreground", children: item.value })
43
+ ] }),
44
+ /* @__PURE__ */ jsxs(
45
+ "div",
46
+ {
47
+ className: cn(
48
+ "inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-xs font-medium tabular-nums",
49
+ isPositive ? "border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400" : "border-rose-500/30 bg-rose-500/10 text-rose-600 dark:text-rose-400"
50
+ ),
51
+ children: [
52
+ /* @__PURE__ */ jsx(TrendIcon, { className: "h-3.5 w-3.5" }),
53
+ item.change > 0 ? "+" : "",
54
+ item.change.toFixed(2),
55
+ "%"
56
+ ]
57
+ }
58
+ )
59
+ ] }),
60
+ /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-border/60 bg-muted/20 p-3", children: /* @__PURE__ */ jsx(
61
+ "svg",
62
+ {
63
+ "aria-label": `${item.label} sparkline`,
64
+ className: "h-16 w-full",
65
+ role: "img",
66
+ viewBox: "0 0 120 48",
67
+ children: /* @__PURE__ */ jsx(
68
+ "path",
69
+ {
70
+ d: buildSparklinePath(item.data, 120, 48),
71
+ fill: "none",
72
+ stroke,
73
+ strokeLinecap: "round",
74
+ strokeLinejoin: "round",
75
+ strokeWidth: "2.5",
76
+ vectorEffect: "non-scaling-stroke"
77
+ }
78
+ )
79
+ }
80
+ ) })
81
+ ]
82
+ },
83
+ item.label
84
+ );
85
+ })
86
+ }
87
+ );
88
+ });
89
+ SparklineGrid.displayName = "SparklineGrid";
90
+ export {
91
+ SparklineGrid
92
+ };
@@ -1,4 +1,8 @@
1
1
  import { Spinner } from "./spinner";
2
+ import {
3
+ UnicodeSpinner
4
+ } from "./unicode-spinner";
2
5
  export {
3
- Spinner
6
+ Spinner,
7
+ UnicodeSpinner
4
8
  };