@vllnt/ui 0.1.11 → 0.2.0-canary.4abf494

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,161 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../lib/utils";
4
+ import { Button } from "../button/button";
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardFooter,
10
+ CardHeader,
11
+ CardTitle
12
+ } from "../card/card";
13
+ import { PlanBadge } from "../plan-badge/plan-badge";
14
+ function getStatusLabel(status) {
15
+ switch (status) {
16
+ case "active":
17
+ return "Active";
18
+ case "canceled":
19
+ return "Canceled";
20
+ case "past-due":
21
+ return "Past due";
22
+ case "trialing":
23
+ return "Trialing";
24
+ }
25
+ }
26
+ function getStatusClasses(status) {
27
+ switch (status) {
28
+ case "active":
29
+ return "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300";
30
+ case "canceled":
31
+ return "bg-muted text-muted-foreground";
32
+ case "past-due":
33
+ return "bg-amber-500/10 text-amber-700 dark:text-amber-300";
34
+ case "trialing":
35
+ return "bg-sky-500/10 text-sky-700 dark:text-sky-300";
36
+ }
37
+ }
38
+ function getPlanState(status) {
39
+ switch (status) {
40
+ case "active":
41
+ case "past-due":
42
+ return "current";
43
+ case "canceled":
44
+ return "legacy";
45
+ case "trialing":
46
+ return "trial";
47
+ }
48
+ }
49
+ function DetailRow({ label, value }) {
50
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 text-sm", children: [
51
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
52
+ /* @__PURE__ */ jsx("span", { className: "text-right font-medium", children: value })
53
+ ] });
54
+ }
55
+ function SubscriptionDetails({
56
+ note,
57
+ priceLabel,
58
+ renewalLabel,
59
+ seatsLabel,
60
+ usageLabel
61
+ }) {
62
+ return /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
63
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border/70 bg-background px-4 py-3", children: [
64
+ /* @__PURE__ */ jsx("p", { className: "text-xs uppercase tracking-[0.18em] text-muted-foreground", children: "Monthly total" }),
65
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-3xl font-semibold tracking-tight", children: priceLabel })
66
+ ] }),
67
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border border-border/70 bg-muted/20 px-4 py-4", children: [
68
+ /* @__PURE__ */ jsx(DetailRow, { label: "Renewal", value: renewalLabel }),
69
+ seatsLabel ? /* @__PURE__ */ jsx(DetailRow, { label: "Seats", value: seatsLabel }) : null,
70
+ usageLabel ? /* @__PURE__ */ jsx(DetailRow, { label: "Usage", value: usageLabel }) : null
71
+ ] }),
72
+ note ? /* @__PURE__ */ jsx("p", { className: "rounded-lg bg-muted px-4 py-3 text-sm text-muted-foreground", children: note }) : null
73
+ ] });
74
+ }
75
+ function SubscriptionActions({
76
+ primaryActionLabel,
77
+ secondaryActionLabel
78
+ }) {
79
+ if (!primaryActionLabel && !secondaryActionLabel) {
80
+ return null;
81
+ }
82
+ return /* @__PURE__ */ jsxs(CardFooter, { className: "flex flex-col gap-2 sm:flex-row sm:justify-end", children: [
83
+ secondaryActionLabel ? /* @__PURE__ */ jsx(Button, { className: "w-full sm:w-auto", variant: "outline", children: secondaryActionLabel }) : null,
84
+ primaryActionLabel ? /* @__PURE__ */ jsx(Button, { className: "w-full sm:w-auto", children: primaryActionLabel }) : null
85
+ ] });
86
+ }
87
+ const SubscriptionCard = React.forwardRef(
88
+ ({
89
+ className,
90
+ note,
91
+ plan,
92
+ priceLabel,
93
+ primaryActionLabel,
94
+ renewalLabel,
95
+ seatsLabel,
96
+ secondaryActionLabel,
97
+ status,
98
+ usageLabel,
99
+ ...props
100
+ }, reference) => {
101
+ return /* @__PURE__ */ jsxs(
102
+ Card,
103
+ {
104
+ className: cn(
105
+ "w-full max-w-md border-border/70 bg-card shadow-sm",
106
+ className
107
+ ),
108
+ ref: reference,
109
+ ...props,
110
+ children: [
111
+ /* @__PURE__ */ jsxs(CardHeader, { className: "space-y-4 pb-4", children: [
112
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
113
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
114
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-lg", children: "Subscription" }),
115
+ /* @__PURE__ */ jsx(CardDescription, { children: "Billing overview for the current workspace plan." })
116
+ ] }),
117
+ /* @__PURE__ */ jsx(
118
+ "span",
119
+ {
120
+ className: cn(
121
+ "inline-flex rounded-full px-2.5 py-1 text-xs font-medium",
122
+ getStatusClasses(status)
123
+ ),
124
+ children: getStatusLabel(status)
125
+ }
126
+ )
127
+ ] }),
128
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-border/70 bg-muted/30 px-4 py-3", children: [
129
+ /* @__PURE__ */ jsxs("div", { children: [
130
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Current plan" }),
131
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: renewalLabel })
132
+ ] }),
133
+ /* @__PURE__ */ jsx(PlanBadge, { state: getPlanState(status), tier: plan })
134
+ ] })
135
+ ] }),
136
+ /* @__PURE__ */ jsx(
137
+ SubscriptionDetails,
138
+ {
139
+ note,
140
+ priceLabel,
141
+ renewalLabel,
142
+ seatsLabel,
143
+ usageLabel
144
+ }
145
+ ),
146
+ /* @__PURE__ */ jsx(
147
+ SubscriptionActions,
148
+ {
149
+ primaryActionLabel,
150
+ secondaryActionLabel
151
+ }
152
+ )
153
+ ]
154
+ }
155
+ );
156
+ }
157
+ );
158
+ SubscriptionCard.displayName = "SubscriptionCard";
159
+ export {
160
+ SubscriptionCard
161
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ TickerTape
3
+ } from "./ticker-tape";
4
+ export {
5
+ TickerTape
6
+ };
@@ -0,0 +1,106 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { ArrowDownRight, ArrowUpRight, Dot } from "lucide-react";
4
+ import { cn } from "../../lib/utils";
5
+ import { Badge } from "../badge";
6
+ const tickerTapeKeyframes = `
7
+ @keyframes ticker-tape-scroll {
8
+ from {
9
+ transform: translateX(0);
10
+ }
11
+
12
+ to {
13
+ transform: translateX(-50%);
14
+ }
15
+ }
16
+ `;
17
+ function formatPrice(price) {
18
+ return typeof price === "number" ? price.toLocaleString() : price;
19
+ }
20
+ function formatChange(change) {
21
+ const sign = change > 0 ? "+" : "";
22
+ return `${sign}${change.toFixed(2)}%`;
23
+ }
24
+ function TickerTapeRow({ items }) {
25
+ return /* @__PURE__ */ jsx("div", { className: "flex min-w-max items-center gap-3 px-3 py-3", children: items.map((item) => {
26
+ const isPositive = item.change >= 0;
27
+ const TrendIcon = isPositive ? ArrowUpRight : ArrowDownRight;
28
+ return /* @__PURE__ */ jsxs(
29
+ "div",
30
+ {
31
+ className: "flex min-w-[12rem] items-center gap-3 rounded-full border border-border/70 bg-background/80 px-3 py-2 shadow-sm",
32
+ children: [
33
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
34
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-[0.24em] text-muted-foreground", children: item.symbol }),
35
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: formatPrice(item.price) })
36
+ ] }),
37
+ /* @__PURE__ */ jsxs(
38
+ Badge,
39
+ {
40
+ className: cn(
41
+ "ml-auto gap-1 rounded-full border px-2 py-0.5 text-[11px] tabular-nums",
42
+ 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"
43
+ ),
44
+ variant: "outline",
45
+ children: [
46
+ /* @__PURE__ */ jsx(TrendIcon, { className: "h-3 w-3" }),
47
+ formatChange(item.change)
48
+ ]
49
+ }
50
+ ),
51
+ item.volume ? /* @__PURE__ */ jsxs("span", { className: "hidden items-center text-xs text-muted-foreground sm:inline-flex", children: [
52
+ /* @__PURE__ */ jsx(Dot, { className: "h-3.5 w-3.5" }),
53
+ item.volume
54
+ ] }) : null
55
+ ]
56
+ },
57
+ `${item.symbol}-${item.price}-${item.change}`
58
+ );
59
+ }) });
60
+ }
61
+ const TickerTape = React.forwardRef(
62
+ ({ className, items, pauseOnHover = true, speedSeconds = 28, ...props }, reference) => {
63
+ if (items.length === 0) {
64
+ return null;
65
+ }
66
+ return /* @__PURE__ */ jsxs(
67
+ "div",
68
+ {
69
+ "aria-label": "TickerTape",
70
+ className: cn(
71
+ "relative overflow-hidden rounded-2xl border border-border bg-card/70 backdrop-blur-sm",
72
+ className
73
+ ),
74
+ ref: reference,
75
+ role: "region",
76
+ ...props,
77
+ children: [
78
+ /* @__PURE__ */ jsx("style", { children: tickerTapeKeyframes }),
79
+ /* @__PURE__ */ jsxs(
80
+ "div",
81
+ {
82
+ className: cn(
83
+ "flex w-max items-stretch",
84
+ pauseOnHover && "hover:[animation-play-state:paused]"
85
+ ),
86
+ style: {
87
+ animationDuration: `${speedSeconds}s`,
88
+ animationIterationCount: "infinite",
89
+ animationName: "ticker-tape-scroll",
90
+ animationTimingFunction: "linear"
91
+ },
92
+ children: [
93
+ /* @__PURE__ */ jsx(TickerTapeRow, { items }),
94
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", children: /* @__PURE__ */ jsx(TickerTapeRow, { items }) })
95
+ ]
96
+ }
97
+ )
98
+ ]
99
+ }
100
+ );
101
+ }
102
+ );
103
+ TickerTape.displayName = "TickerTape";
104
+ export {
105
+ TickerTape
106
+ };
@@ -0,0 +1,4 @@
1
+ import { Tour } from "./tour";
2
+ export {
3
+ Tour
4
+ };
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { cn } from "../../lib/utils";
5
+ import { Badge } from "../badge";
6
+ import { Button } from "../button";
7
+ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "../card";
8
+ function TourHeader({
9
+ progress,
10
+ step,
11
+ stepIndex,
12
+ totalSteps
13
+ }) {
14
+ return /* @__PURE__ */ jsxs(CardHeader, { className: "gap-4 border-b bg-background/70", children: [
15
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
16
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
17
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
18
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: "Tour" }),
19
+ step.badge ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: step.badge }) : null
20
+ ] }),
21
+ /* @__PURE__ */ jsx(CardTitle, { children: step.title })
22
+ ] }),
23
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
24
+ stepIndex + 1,
25
+ "/",
26
+ totalSteps
27
+ ] })
28
+ ] }),
29
+ /* @__PURE__ */ jsx("div", { className: "h-1 rounded-full bg-muted", children: /* @__PURE__ */ jsx(
30
+ "div",
31
+ {
32
+ className: "h-full rounded-full bg-primary transition-all",
33
+ style: { width: `${progress}%` }
34
+ }
35
+ ) })
36
+ ] });
37
+ }
38
+ function TourFooter({
39
+ currentStep,
40
+ isFirstStep,
41
+ isLastStep,
42
+ onComplete,
43
+ onStepSelect,
44
+ steps
45
+ }) {
46
+ return /* @__PURE__ */ jsxs(CardFooter, { className: "flex items-center justify-between gap-3 border-t bg-background/70 px-6 py-4", children: [
47
+ /* @__PURE__ */ jsx(
48
+ Button,
49
+ {
50
+ disabled: isFirstStep,
51
+ onClick: () => {
52
+ onStepSelect(currentStep - 1);
53
+ },
54
+ variant: "outline",
55
+ children: "Previous"
56
+ }
57
+ ),
58
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: steps.map((step, index) => /* @__PURE__ */ jsx(
59
+ "button",
60
+ {
61
+ "aria-label": `Go to ${step.title}`,
62
+ className: cn(
63
+ "h-2.5 w-2.5 rounded-full transition-colors",
64
+ index === currentStep ? "bg-primary" : "bg-muted-foreground/30"
65
+ ),
66
+ onClick: () => {
67
+ onStepSelect(index);
68
+ },
69
+ type: "button"
70
+ },
71
+ step.id
72
+ )) }),
73
+ isLastStep ? /* @__PURE__ */ jsx(
74
+ Button,
75
+ {
76
+ onClick: () => {
77
+ onComplete?.();
78
+ },
79
+ children: "Finish"
80
+ }
81
+ ) : /* @__PURE__ */ jsx(
82
+ Button,
83
+ {
84
+ onClick: () => {
85
+ onStepSelect(currentStep + 1);
86
+ },
87
+ children: "Next"
88
+ }
89
+ )
90
+ ] });
91
+ }
92
+ function Tour({
93
+ className,
94
+ defaultStep = 0,
95
+ onComplete,
96
+ onStepChange,
97
+ steps
98
+ }) {
99
+ const [currentStep, setCurrentStep] = useState(defaultStep);
100
+ if (steps.length === 0) {
101
+ return null;
102
+ }
103
+ const activeStep = steps[currentStep];
104
+ const isFirstStep = currentStep === 0;
105
+ const isLastStep = currentStep === steps.length - 1;
106
+ const progress = (currentStep + 1) / steps.length * 100;
107
+ if (!activeStep) {
108
+ return null;
109
+ }
110
+ const handleStepSelect = (stepIndex) => {
111
+ const nextStep = steps[stepIndex];
112
+ if (!nextStep) {
113
+ return;
114
+ }
115
+ setCurrentStep(stepIndex);
116
+ onStepChange?.(stepIndex, nextStep);
117
+ };
118
+ return /* @__PURE__ */ jsxs(
119
+ Card,
120
+ {
121
+ className: cn(
122
+ "my-6 overflow-hidden border-primary/20 bg-gradient-to-br from-background to-primary/5",
123
+ className
124
+ ),
125
+ children: [
126
+ /* @__PURE__ */ jsx(
127
+ TourHeader,
128
+ {
129
+ progress,
130
+ step: activeStep,
131
+ stepIndex: currentStep,
132
+ totalSteps: steps.length
133
+ }
134
+ ),
135
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4 p-6", children: [
136
+ activeStep.media ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-4", children: activeStep.media }) : null,
137
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground [&>p]:mb-3", children: activeStep.description }),
138
+ activeStep.hint ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed bg-muted/50 p-3 text-sm text-muted-foreground", children: activeStep.hint }) : null
139
+ ] }),
140
+ /* @__PURE__ */ jsx(
141
+ TourFooter,
142
+ {
143
+ currentStep,
144
+ isFirstStep,
145
+ isLastStep,
146
+ onComplete,
147
+ onStepSelect: handleStepSelect,
148
+ steps
149
+ }
150
+ )
151
+ ]
152
+ }
153
+ );
154
+ }
155
+ export {
156
+ Tour
157
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ UsageBreakdown
3
+ } from "./usage-breakdown";
4
+ export {
5
+ UsageBreakdown
6
+ };
@@ -0,0 +1,140 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useMemo } from "react";
3
+ import { ArrowDownRight, ArrowUpRight } from "lucide-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
+ const toneClasses = {
14
+ danger: "bg-destructive/10 text-destructive border-destructive/20 dark:text-destructive",
15
+ default: "bg-muted text-muted-foreground border-border",
16
+ success: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20 dark:text-emerald-300",
17
+ warning: "bg-amber-500/10 text-amber-700 border-amber-500/20 dark:text-amber-300"
18
+ };
19
+ function formatPercent(value) {
20
+ if (!Number.isFinite(value)) return "0%";
21
+ return `${Math.round(value)}%`;
22
+ }
23
+ function formatValue(value) {
24
+ return new Intl.NumberFormat("en-US", {
25
+ maximumFractionDigits: value >= 100 ? 0 : 1
26
+ }).format(value);
27
+ }
28
+ function getRelativeWidth(value, maxValue) {
29
+ if (maxValue <= 0) return 0;
30
+ return Math.max(value / maxValue * 100, 4);
31
+ }
32
+ function getShare(value, totalValue) {
33
+ if (totalValue <= 0) return 0;
34
+ return value / totalValue * 100;
35
+ }
36
+ function UsageBreakdownTrend({ item }) {
37
+ if (!item.trend) return null;
38
+ const trendTone = item.tone ?? "default";
39
+ const TrendIcon = item.trend.direction === "down" ? ArrowDownRight : ArrowUpRight;
40
+ return /* @__PURE__ */ jsxs(Badge, { className: cn("gap-1 border", toneClasses[trendTone]), children: [
41
+ /* @__PURE__ */ jsx(TrendIcon, { className: "h-3.5 w-3.5" }),
42
+ item.trend.label
43
+ ] });
44
+ }
45
+ function UsageBreakdownMeter({
46
+ maxValue,
47
+ value
48
+ }) {
49
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
50
+ /* @__PURE__ */ jsx("div", { className: "h-2 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
51
+ "div",
52
+ {
53
+ "aria-hidden": "true",
54
+ className: "h-full rounded-full bg-primary transition-[width]",
55
+ style: { width: `${getRelativeWidth(value, maxValue)}%` }
56
+ }
57
+ ) }),
58
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
59
+ /* @__PURE__ */ jsx("span", { children: "Relative usage" }),
60
+ /* @__PURE__ */ jsx("span", { children: formatPercent(getShare(value, maxValue)) })
61
+ ] })
62
+ ] });
63
+ }
64
+ function UsageBreakdownRow({
65
+ item,
66
+ maxValue,
67
+ rank,
68
+ totalValue
69
+ }) {
70
+ return /* @__PURE__ */ jsx("li", { className: "rounded-lg border bg-background/70 p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
71
+ /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-md border bg-muted text-sm font-semibold text-muted-foreground", children: item.icon ?? rank }),
72
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
73
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", 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: "truncate font-medium text-foreground", children: item.label }),
77
+ item.meta ? /* @__PURE__ */ jsx(Badge, { className: "border-border", variant: "outline", children: item.meta }) : null,
78
+ /* @__PURE__ */ jsx(UsageBreakdownTrend, { item })
79
+ ] }),
80
+ item.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: item.description }) : null
81
+ ] }),
82
+ /* @__PURE__ */ jsxs("div", { className: "text-left sm:text-right", children: [
83
+ /* @__PURE__ */ jsx("div", { className: "font-semibold text-foreground", children: item.valueLabel ?? formatValue(item.value) }),
84
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
85
+ formatPercent(getShare(item.value, totalValue)),
86
+ " of total"
87
+ ] })
88
+ ] })
89
+ ] }),
90
+ /* @__PURE__ */ jsx(UsageBreakdownMeter, { maxValue, value: item.value })
91
+ ] })
92
+ ] }) });
93
+ }
94
+ function getSortedItems(items, maxItems) {
95
+ const rankedItems = [...items].sort(
96
+ (left, right) => right.value - left.value
97
+ );
98
+ return typeof maxItems === "number" ? rankedItems.slice(0, maxItems) : rankedItems;
99
+ }
100
+ const UsageBreakdown = forwardRef(
101
+ ({
102
+ className,
103
+ description,
104
+ emptyMessage = "No usage data available.",
105
+ items,
106
+ maxItems,
107
+ title = "Usage breakdown",
108
+ ...props
109
+ }, ref) => {
110
+ const sortedItems = useMemo(
111
+ () => getSortedItems(items, maxItems),
112
+ [items, maxItems]
113
+ );
114
+ const totalValue = useMemo(
115
+ () => sortedItems.reduce((sum, item) => sum + item.value, 0),
116
+ [sortedItems]
117
+ );
118
+ const maxValue = sortedItems[0]?.value ?? 0;
119
+ return /* @__PURE__ */ jsxs(Card, { className: cn("w-full", className), ref, ...props, children: [
120
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
121
+ /* @__PURE__ */ jsx(CardTitle, { children: title }),
122
+ description ? /* @__PURE__ */ jsx(CardDescription, { children: description }) : null
123
+ ] }),
124
+ /* @__PURE__ */ jsx(CardContent, { children: sortedItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx("ol", { className: "space-y-3", children: sortedItems.map((item, index) => /* @__PURE__ */ jsx(
125
+ UsageBreakdownRow,
126
+ {
127
+ item,
128
+ maxValue,
129
+ rank: index + 1,
130
+ totalValue
131
+ },
132
+ item.id
133
+ )) }) })
134
+ ] });
135
+ }
136
+ );
137
+ UsageBreakdown.displayName = "UsageBreakdown";
138
+ export {
139
+ UsageBreakdown
140
+ };
@@ -0,0 +1,4 @@
1
+ import { WalletCard } from "./wallet-card";
2
+ export {
3
+ WalletCard
4
+ };