@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,215 @@
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
+ const DEFAULT_WIDTH = 760;
6
+ const DEFAULT_HEIGHT = 260;
7
+ function formatValue(value) {
8
+ return value.toLocaleString(void 0, {
9
+ maximumFractionDigits: 2,
10
+ minimumFractionDigits: 2
11
+ });
12
+ }
13
+ function buildMetrics(data, height, width) {
14
+ const allValues = data.flatMap((candle) => [
15
+ candle.high,
16
+ candle.low,
17
+ candle.open,
18
+ candle.close
19
+ ]);
20
+ const minPrice = Math.min(...allValues);
21
+ const maxPrice = Math.max(...allValues);
22
+ const range = maxPrice - minPrice || 1;
23
+ const topPadding = 20;
24
+ const bottomPadding = 30;
25
+ const chartHeight = height - topPadding - bottomPadding;
26
+ const columnWidth = width / data.length;
27
+ const bodyWidth = Math.max(columnWidth * 0.56, 8);
28
+ return {
29
+ bodyWidth,
30
+ bottomPadding,
31
+ chartHeight,
32
+ columnWidth,
33
+ maxPrice,
34
+ minPrice,
35
+ range,
36
+ topPadding
37
+ };
38
+ }
39
+ function getYForPrice(price, metrics) {
40
+ const ratio = (price - metrics.minPrice) / metrics.range;
41
+ return metrics.topPadding + metrics.chartHeight - ratio * metrics.chartHeight;
42
+ }
43
+ function PriceGrid({
44
+ metrics,
45
+ showGrid,
46
+ width
47
+ }) {
48
+ if (!showGrid) {
49
+ return null;
50
+ }
51
+ const ticks = Array.from({ length: 4 }, (_, index) => {
52
+ const ratio = index / 3;
53
+ return {
54
+ value: metrics.maxPrice - ratio * metrics.range,
55
+ y: metrics.topPadding + ratio * metrics.chartHeight
56
+ };
57
+ });
58
+ return ticks.map((tick) => /* @__PURE__ */ jsxs("g", { children: [
59
+ /* @__PURE__ */ jsx(
60
+ "line",
61
+ {
62
+ stroke: "hsl(var(--border))",
63
+ strokeDasharray: "4 6",
64
+ strokeOpacity: "0.8",
65
+ x1: "0",
66
+ x2: width,
67
+ y1: tick.y,
68
+ y2: tick.y
69
+ }
70
+ ),
71
+ /* @__PURE__ */ jsx(
72
+ "text",
73
+ {
74
+ fill: "hsl(var(--muted-foreground))",
75
+ fontSize: "11",
76
+ textAnchor: "end",
77
+ x: width - 6,
78
+ y: tick.y - 4,
79
+ children: formatValue(tick.value)
80
+ }
81
+ )
82
+ ] }, tick.value));
83
+ }
84
+ function CandleMarks({
85
+ data,
86
+ height,
87
+ metrics
88
+ }) {
89
+ return data.map((candle, index) => {
90
+ const centerX = metrics.columnWidth * index + metrics.columnWidth / 2;
91
+ const wickTop = getYForPrice(candle.high, metrics);
92
+ const wickBottom = getYForPrice(candle.low, metrics);
93
+ const openY = getYForPrice(candle.open, metrics);
94
+ const closeY = getYForPrice(candle.close, metrics);
95
+ const bodyY = Math.min(openY, closeY);
96
+ const bodyHeight = Math.max(Math.abs(openY - closeY), 3);
97
+ const isBullish = candle.close >= candle.open;
98
+ const fill = isBullish ? "hsl(142 71% 45%)" : "hsl(348 83% 47%)";
99
+ return /* @__PURE__ */ jsxs("g", { children: [
100
+ /* @__PURE__ */ jsx(
101
+ "line",
102
+ {
103
+ stroke: fill,
104
+ strokeLinecap: "round",
105
+ strokeWidth: 2,
106
+ x1: centerX,
107
+ x2: centerX,
108
+ y1: wickTop,
109
+ y2: wickBottom
110
+ }
111
+ ),
112
+ /* @__PURE__ */ jsx(
113
+ "rect",
114
+ {
115
+ fill,
116
+ fillOpacity: isBullish ? 0.25 : 0.18,
117
+ height: bodyHeight,
118
+ rx: 4,
119
+ stroke: fill,
120
+ strokeWidth: 1.5,
121
+ width: metrics.bodyWidth,
122
+ x: centerX - metrics.bodyWidth / 2,
123
+ y: bodyY,
124
+ children: /* @__PURE__ */ jsx("title", { children: `${candle.label}: O ${formatValue(candle.open)} H ${formatValue(candle.high)} L ${formatValue(candle.low)} C ${formatValue(candle.close)}` })
125
+ }
126
+ ),
127
+ /* @__PURE__ */ jsx(
128
+ "text",
129
+ {
130
+ fill: "hsl(var(--muted-foreground))",
131
+ fontSize: "11",
132
+ textAnchor: "middle",
133
+ x: centerX,
134
+ y: height - 8,
135
+ children: candle.label
136
+ }
137
+ )
138
+ ] }, candle.label);
139
+ });
140
+ }
141
+ function SessionPill({ sessionChange }) {
142
+ const isPositive = sessionChange >= 0;
143
+ const TrendIcon = isPositive ? ArrowUpRight : ArrowDownRight;
144
+ return /* @__PURE__ */ jsxs(
145
+ "div",
146
+ {
147
+ className: cn(
148
+ "inline-flex items-center gap-2 rounded-full border px-3 py-1 text-sm font-medium",
149
+ 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"
150
+ ),
151
+ children: [
152
+ /* @__PURE__ */ jsx(TrendIcon, { className: "h-4 w-4" }),
153
+ sessionChange >= 0 ? "+" : "",
154
+ formatValue(sessionChange)
155
+ ]
156
+ }
157
+ );
158
+ }
159
+ const CandlestickChart = React.forwardRef(
160
+ ({
161
+ className,
162
+ data,
163
+ height = DEFAULT_HEIGHT,
164
+ showGrid = true,
165
+ width = DEFAULT_WIDTH,
166
+ ...props
167
+ }, reference) => {
168
+ const firstCandle = data[0];
169
+ const finalCandle = data.at(-1);
170
+ if (!firstCandle || !finalCandle) {
171
+ return null;
172
+ }
173
+ const metrics = buildMetrics(data, height, width);
174
+ const sessionChange = finalCandle.close - firstCandle.open;
175
+ return /* @__PURE__ */ jsxs(
176
+ "div",
177
+ {
178
+ className: cn(
179
+ "rounded-2xl border border-border bg-card/80 p-4 shadow-sm",
180
+ className
181
+ ),
182
+ ref: reference,
183
+ ...props,
184
+ children: [
185
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-wrap items-start justify-between gap-3", children: [
186
+ /* @__PURE__ */ jsxs("div", { children: [
187
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-[0.28em] text-muted-foreground", children: "OHLC session" }),
188
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground", children: "Candlestick chart" })
189
+ ] }),
190
+ /* @__PURE__ */ jsx(SessionPill, { sessionChange })
191
+ ] }),
192
+ /* @__PURE__ */ jsxs(
193
+ "svg",
194
+ {
195
+ "aria-label": "Candlestick chart",
196
+ className: "h-full w-full",
197
+ height,
198
+ role: "img",
199
+ viewBox: `0 0 ${width} ${height}`,
200
+ width,
201
+ children: [
202
+ /* @__PURE__ */ jsx(PriceGrid, { metrics, showGrid, width }),
203
+ /* @__PURE__ */ jsx(CandleMarks, { data, height, metrics })
204
+ ]
205
+ }
206
+ )
207
+ ]
208
+ }
209
+ );
210
+ }
211
+ );
212
+ CandlestickChart.displayName = "CandlestickChart";
213
+ export {
214
+ CandlestickChart
215
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ CandlestickChart
3
+ } from "./candlestick-chart";
4
+ export {
5
+ CandlestickChart
6
+ };
@@ -0,0 +1,130 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Check, ChevronsUpDown } from "lucide-react";
5
+ import { cn } from "../../lib/utils";
6
+ import { Button } from "../button";
7
+ import {
8
+ Command,
9
+ CommandEmpty,
10
+ CommandGroup,
11
+ CommandInput,
12
+ CommandItem,
13
+ CommandList
14
+ } from "../command";
15
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover";
16
+ function useComboboxValue(value, onValueChange) {
17
+ const [internalValue, setInternalValue] = React.useState(value ?? "");
18
+ React.useEffect(() => {
19
+ if (value !== void 0) {
20
+ setInternalValue(value);
21
+ }
22
+ }, [value]);
23
+ const resolvedValue = value ?? internalValue;
24
+ const setResolvedValue = (nextValue) => {
25
+ if (value === void 0) {
26
+ setInternalValue(nextValue);
27
+ }
28
+ onValueChange?.(nextValue);
29
+ };
30
+ return { resolvedValue, setResolvedValue };
31
+ }
32
+ function ComboboxOptionItem({
33
+ onSelect,
34
+ option,
35
+ selectedValue
36
+ }) {
37
+ const keywords = option.keywords?.join(" ") ?? "";
38
+ return /* @__PURE__ */ jsxs(
39
+ CommandItem,
40
+ {
41
+ disabled: option.disabled,
42
+ onSelect: () => {
43
+ onSelect(option.value);
44
+ },
45
+ value: `${option.label} ${option.value} ${keywords}`,
46
+ children: [
47
+ /* @__PURE__ */ jsx(
48
+ Check,
49
+ {
50
+ className: cn(
51
+ "mr-2 h-4 w-4",
52
+ selectedValue === option.value ? "opacity-100" : "opacity-0"
53
+ )
54
+ }
55
+ ),
56
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: option.label })
57
+ ]
58
+ }
59
+ );
60
+ }
61
+ const Combobox = React.forwardRef(
62
+ ({
63
+ className,
64
+ commandClassName,
65
+ emptyText = "No option found.",
66
+ onValueChange,
67
+ options,
68
+ placeholder = "Select an option",
69
+ searchPlaceholder = "Search options...",
70
+ triggerClassName,
71
+ value
72
+ }, reference) => {
73
+ const [open, setOpen] = React.useState(false);
74
+ const { resolvedValue, setResolvedValue } = useComboboxValue(
75
+ value,
76
+ onValueChange
77
+ );
78
+ const selectedOption = options.find(
79
+ (option) => option.value === resolvedValue
80
+ );
81
+ const handleSelect = (nextValue) => {
82
+ setResolvedValue(nextValue === resolvedValue ? "" : nextValue);
83
+ setOpen(false);
84
+ };
85
+ return /* @__PURE__ */ jsxs(Popover, { onOpenChange: setOpen, open, children: [
86
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
87
+ Button,
88
+ {
89
+ "aria-expanded": open,
90
+ className: cn("w-full justify-between", triggerClassName),
91
+ ref: reference,
92
+ role: "combobox",
93
+ variant: "outline",
94
+ children: [
95
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedOption ? selectedOption.label : placeholder }),
96
+ /* @__PURE__ */ jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })
97
+ ]
98
+ }
99
+ ) }),
100
+ /* @__PURE__ */ jsx(
101
+ PopoverContent,
102
+ {
103
+ className: cn(
104
+ "w-[var(--radix-popover-trigger-width)] p-0",
105
+ className
106
+ ),
107
+ children: /* @__PURE__ */ jsxs(Command, { className: commandClassName, children: [
108
+ /* @__PURE__ */ jsx(CommandInput, { placeholder: searchPlaceholder }),
109
+ /* @__PURE__ */ jsxs(CommandList, { children: [
110
+ /* @__PURE__ */ jsx(CommandEmpty, { children: emptyText }),
111
+ /* @__PURE__ */ jsx(CommandGroup, { children: options.map((option) => /* @__PURE__ */ jsx(
112
+ ComboboxOptionItem,
113
+ {
114
+ onSelect: handleSelect,
115
+ option,
116
+ selectedValue: resolvedValue
117
+ },
118
+ option.value
119
+ )) })
120
+ ] })
121
+ ] })
122
+ }
123
+ )
124
+ ] });
125
+ }
126
+ );
127
+ Combobox.displayName = "Combobox";
128
+ export {
129
+ Combobox
130
+ };
@@ -0,0 +1,4 @@
1
+ import { Combobox } from "./combobox";
2
+ export {
3
+ Combobox
4
+ };
@@ -0,0 +1,184 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../lib/utils";
5
+ import { Badge } from "../badge";
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle
12
+ } from "../card";
13
+ function normalizeDate(input) {
14
+ if (input instanceof Date) {
15
+ return new Date(input.getTime());
16
+ }
17
+ return new Date(input);
18
+ }
19
+ function useLiveDate(now, tickMs) {
20
+ const fixedNow = React.useMemo(
21
+ () => now ? normalizeDate(now) : void 0,
22
+ [now]
23
+ );
24
+ const [liveNow, setLiveNow] = React.useState(fixedNow ?? /* @__PURE__ */ new Date());
25
+ React.useEffect(() => {
26
+ if (fixedNow) {
27
+ setLiveNow(fixedNow);
28
+ return;
29
+ }
30
+ const interval = window.setInterval(() => {
31
+ setLiveNow(/* @__PURE__ */ new Date());
32
+ }, tickMs);
33
+ return () => {
34
+ window.clearInterval(interval);
35
+ };
36
+ }, [fixedNow, tickMs]);
37
+ return liveNow;
38
+ }
39
+ function getRemainingMs(deadline, now) {
40
+ return deadline.getTime() - now.getTime();
41
+ }
42
+ function getStatus(remainingMs, warningThresholdMs) {
43
+ if (remainingMs <= 0) {
44
+ return {
45
+ badgeVariant: "destructive",
46
+ label: "Breached",
47
+ toneClassName: "bg-destructive"
48
+ };
49
+ }
50
+ if (remainingMs <= warningThresholdMs) {
51
+ return {
52
+ badgeVariant: "secondary",
53
+ label: "At risk",
54
+ toneClassName: "bg-amber-500"
55
+ };
56
+ }
57
+ return {
58
+ badgeVariant: "default",
59
+ label: "On track",
60
+ toneClassName: "bg-emerald-500"
61
+ };
62
+ }
63
+ function formatSegments(milliseconds) {
64
+ const totalSeconds = Math.max(0, Math.floor(milliseconds / 1e3));
65
+ const days = Math.floor(totalSeconds / 86400);
66
+ const hours = Math.floor(totalSeconds % 86400 / 3600);
67
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
68
+ const seconds = totalSeconds % 60;
69
+ return [
70
+ { label: "Days", value: String(days).padStart(2, "0") },
71
+ { label: "Hours", value: String(hours).padStart(2, "0") },
72
+ { label: "Minutes", value: String(minutes).padStart(2, "0") },
73
+ { label: "Seconds", value: String(seconds).padStart(2, "0") }
74
+ ];
75
+ }
76
+ function formatDeadline(date) {
77
+ return new Intl.DateTimeFormat("en-US", {
78
+ day: "numeric",
79
+ hour: "numeric",
80
+ minute: "2-digit",
81
+ month: "short",
82
+ timeZoneName: "short"
83
+ }).format(date);
84
+ }
85
+ function getProgress(deadline, now, startedAt) {
86
+ if (!startedAt) {
87
+ return 0;
88
+ }
89
+ const total = deadline.getTime() - startedAt.getTime();
90
+ if (total <= 0) {
91
+ return 100;
92
+ }
93
+ const elapsed = now.getTime() - startedAt.getTime();
94
+ return Math.min(100, Math.max(0, elapsed / total * 100));
95
+ }
96
+ function TimerSegments({ segments }) {
97
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: segments.map((segment) => /* @__PURE__ */ jsxs(
98
+ "div",
99
+ {
100
+ className: "rounded-lg border bg-background/80 px-3 py-4 text-center",
101
+ children: [
102
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-semibold tracking-tight", children: segment.value }),
103
+ /* @__PURE__ */ jsx("div", { className: "text-xs uppercase tracking-[0.18em] text-muted-foreground", children: segment.label })
104
+ ]
105
+ },
106
+ segment.label
107
+ )) });
108
+ }
109
+ function TimerProgress({
110
+ progress,
111
+ remainingMs,
112
+ segments,
113
+ startedAt,
114
+ toneClassName
115
+ }) {
116
+ const progressWidth = startedAt ? progress : remainingMs <= 0 ? 100 : 0;
117
+ const statusText = startedAt ? `${Math.round(progress)}% used` : segments.map((segment) => segment.value).join(":");
118
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
119
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
120
+ /* @__PURE__ */ jsx("span", { children: startedAt ? "SLA elapsed" : "Time remaining" }),
121
+ /* @__PURE__ */ jsx("span", { children: statusText })
122
+ ] }),
123
+ /* @__PURE__ */ jsx("div", { className: "h-2 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
124
+ "div",
125
+ {
126
+ className: cn("h-full transition-all", toneClassName),
127
+ style: { width: `${progressWidth}%` }
128
+ }
129
+ ) })
130
+ ] });
131
+ }
132
+ const CountdownTimer = React.forwardRef(
133
+ ({
134
+ className,
135
+ deadline,
136
+ description,
137
+ now,
138
+ startedAt,
139
+ tickMs = 1e3,
140
+ title = "Countdown timer",
141
+ warningThresholdMs = 15 * 60 * 1e3,
142
+ ...props
143
+ }, ref) => {
144
+ const deadlineDate = React.useMemo(
145
+ () => normalizeDate(deadline),
146
+ [deadline]
147
+ );
148
+ const startedAtDate = React.useMemo(
149
+ () => startedAt ? normalizeDate(startedAt) : void 0,
150
+ [startedAt]
151
+ );
152
+ const liveNow = useLiveDate(now, tickMs);
153
+ const remainingMs = getRemainingMs(deadlineDate, liveNow);
154
+ const status = getStatus(remainingMs, warningThresholdMs);
155
+ const segments = formatSegments(Math.abs(remainingMs));
156
+ const progress = getProgress(deadlineDate, liveNow, startedAtDate);
157
+ return /* @__PURE__ */ jsxs(Card, { className: cn("shadow-sm", className), ref, ...props, children: [
158
+ /* @__PURE__ */ jsx(CardHeader, { className: "space-y-2 pb-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
159
+ /* @__PURE__ */ jsxs("div", { children: [
160
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: title }),
161
+ /* @__PURE__ */ jsx(CardDescription, { children: description ?? `Deadline ${formatDeadline(deadlineDate)}` })
162
+ ] }),
163
+ /* @__PURE__ */ jsx(Badge, { variant: status.badgeVariant, children: status.label })
164
+ ] }) }),
165
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
166
+ /* @__PURE__ */ jsx(TimerSegments, { segments }),
167
+ /* @__PURE__ */ jsx(
168
+ TimerProgress,
169
+ {
170
+ progress,
171
+ remainingMs,
172
+ segments,
173
+ startedAt: startedAtDate,
174
+ toneClassName: status.toneClassName
175
+ }
176
+ )
177
+ ] })
178
+ ] });
179
+ }
180
+ );
181
+ CountdownTimer.displayName = "CountdownTimer";
182
+ export {
183
+ CountdownTimer
184
+ };
@@ -0,0 +1,4 @@
1
+ import { CountdownTimer } from "./countdown-timer";
2
+ export {
3
+ CountdownTimer
4
+ };
@@ -0,0 +1,59 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../lib/utils";
4
+ import { badgeVariants } from "../badge/badge";
5
+ function getStatusLabel(status) {
6
+ switch (status) {
7
+ case "depleted":
8
+ return "No credits left";
9
+ case "healthy":
10
+ return "Credits available";
11
+ case "low":
12
+ return "Credits running low";
13
+ case "overdue":
14
+ return "Balance overdue";
15
+ }
16
+ }
17
+ function getStatusClasses(status) {
18
+ switch (status) {
19
+ case "depleted":
20
+ return "border-slate-500/30 bg-slate-500/10 text-slate-700 dark:text-slate-300";
21
+ case "healthy":
22
+ return "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300";
23
+ case "low":
24
+ return "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300";
25
+ case "overdue":
26
+ return "border-destructive/30 bg-destructive/10 text-destructive";
27
+ }
28
+ }
29
+ const CreditBadge = React.forwardRef(
30
+ ({ amount, className, label, status, ...props }, reference) => {
31
+ return /* @__PURE__ */ jsxs(
32
+ "span",
33
+ {
34
+ className: cn(
35
+ badgeVariants({ variant: "outline" }),
36
+ "gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium shadow-none",
37
+ getStatusClasses(status),
38
+ className
39
+ ),
40
+ ref: reference,
41
+ ...props,
42
+ children: [
43
+ /* @__PURE__ */ jsx(
44
+ "span",
45
+ {
46
+ "aria-hidden": "true",
47
+ className: "h-1.5 w-1.5 rounded-full bg-current"
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsx("span", { children: amount ? `${amount} \u2022 ${label ?? getStatusLabel(status)}` : label ?? getStatusLabel(status) })
51
+ ]
52
+ }
53
+ );
54
+ }
55
+ );
56
+ CreditBadge.displayName = "CreditBadge";
57
+ export {
58
+ CreditBadge
59
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ CreditBadge
3
+ } from "./credit-badge";
4
+ export {
5
+ CreditBadge
6
+ };