@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.
- package/LICENSE +21 -0
- package/dist/components/activity-heatmap/activity-heatmap.js +168 -0
- package/dist/components/activity-heatmap/index.js +6 -0
- package/dist/components/activity-log/activity-log.js +256 -0
- package/dist/components/activity-log/index.js +6 -0
- package/dist/components/ai-chat-input/ai-chat-input.js +107 -0
- package/dist/components/ai-chat-input/index.js +4 -0
- package/dist/components/ai-message-bubble/ai-message-bubble.js +119 -0
- package/dist/components/ai-message-bubble/index.js +6 -0
- package/dist/components/ai-source-citation/ai-source-citation.js +39 -0
- package/dist/components/ai-source-citation/index.js +6 -0
- package/dist/components/ai-streaming-text/ai-streaming-text.js +41 -0
- package/dist/components/ai-streaming-text/index.js +6 -0
- package/dist/components/ai-tool-call-display/ai-tool-call-display.js +93 -0
- package/dist/components/ai-tool-call-display/index.js +6 -0
- package/dist/components/animated-text/animated-text.js +328 -0
- package/dist/components/animated-text/index.js +4 -0
- package/dist/components/annotation/annotation.js +49 -0
- package/dist/components/annotation/index.js +8 -0
- package/dist/components/avatar-group/avatar-group.js +82 -0
- package/dist/components/avatar-group/index.js +10 -0
- package/dist/components/border-beam/border-beam.js +51 -0
- package/dist/components/border-beam/index.js +4 -0
- package/dist/components/candlestick-chart/candlestick-chart.js +215 -0
- package/dist/components/candlestick-chart/index.js +6 -0
- package/dist/components/combobox/combobox.js +130 -0
- package/dist/components/combobox/index.js +4 -0
- package/dist/components/countdown-timer/countdown-timer.js +184 -0
- package/dist/components/countdown-timer/index.js +4 -0
- package/dist/components/credit-badge/credit-badge.js +59 -0
- package/dist/components/credit-badge/index.js +6 -0
- package/dist/components/data-list/data-list.js +99 -0
- package/dist/components/data-list/index.js +16 -0
- package/dist/components/data-table/data-table.js +242 -0
- package/dist/components/data-table/index.js +6 -0
- package/dist/components/date-picker/date-picker.js +74 -0
- package/dist/components/date-picker/index.js +4 -0
- package/dist/components/file-upload/file-upload.js +227 -0
- package/dist/components/file-upload/index.js +4 -0
- package/dist/components/flashcard/flashcard.js +66 -0
- package/dist/components/flashcard/index.js +4 -0
- package/dist/components/index.js +172 -1
- package/dist/components/live-feed/index.js +4 -0
- package/dist/components/live-feed/live-feed.js +168 -0
- package/dist/components/market-treemap/index.js +6 -0
- package/dist/components/market-treemap/market-treemap.js +100 -0
- package/dist/components/marquee/index.js +4 -0
- package/dist/components/marquee/marquee.js +98 -0
- package/dist/components/metric-gauge/index.js +6 -0
- package/dist/components/metric-gauge/metric-gauge.js +213 -0
- package/dist/components/model-selector/model-selector.js +11 -2
- package/dist/components/number-input/index.js +4 -0
- package/dist/components/number-input/number-input.js +167 -0
- package/dist/components/number-ticker/index.js +4 -0
- package/dist/components/number-ticker/number-ticker.js +63 -0
- package/dist/components/order-book/index.js +6 -0
- package/dist/components/order-book/order-book.js +128 -0
- package/dist/components/password-input/index.js +4 -0
- package/dist/components/password-input/password-input.js +45 -0
- package/dist/components/plan-badge/index.js +6 -0
- package/dist/components/plan-badge/plan-badge.js +67 -0
- package/dist/components/rating/index.js +4 -0
- package/dist/components/rating/rating.js +121 -0
- package/dist/components/role-badge/index.js +6 -0
- package/dist/components/role-badge/role-badge.js +50 -0
- package/dist/components/scope-selector/index.js +6 -0
- package/dist/components/scope-selector/scope-selector.js +336 -0
- package/dist/components/severity-badge/index.js +8 -0
- package/dist/components/severity-badge/severity-badge.js +163 -0
- package/dist/components/sparkline-grid/index.js +6 -0
- package/dist/components/sparkline-grid/sparkline-grid.js +92 -0
- package/dist/components/spinner/index.js +5 -1
- package/dist/components/spinner/unicode-spinner.js +708 -0
- package/dist/components/stat-card/index.js +5 -0
- package/dist/components/stat-card/stat-card.js +102 -0
- package/dist/components/status-board/index.js +6 -0
- package/dist/components/status-board/status-board.js +138 -0
- package/dist/components/status-indicator/index.js +10 -0
- package/dist/components/status-indicator/status-indicator.js +175 -0
- package/dist/components/stepper/index.js +4 -0
- package/dist/components/stepper/stepper.js +117 -0
- package/dist/components/subscription-card/index.js +6 -0
- package/dist/components/subscription-card/subscription-card.js +161 -0
- package/dist/components/ticker-tape/index.js +6 -0
- package/dist/components/ticker-tape/ticker-tape.js +106 -0
- package/dist/components/tour/index.js +4 -0
- package/dist/components/tour/tour.js +157 -0
- package/dist/components/usage-breakdown/index.js +6 -0
- package/dist/components/usage-breakdown/usage-breakdown.js +140 -0
- package/dist/components/wallet-card/index.js +4 -0
- package/dist/components/wallet-card/wallet-card.js +115 -0
- package/dist/components/watchlist/index.js +6 -0
- package/dist/components/watchlist/watchlist.js +110 -0
- package/dist/components/world-clock-bar/index.js +6 -0
- package/dist/components/world-clock-bar/world-clock-bar.js +101 -0
- package/dist/index.d.ts +1173 -7
- package/dist/test-setup.js +19 -0
- package/package.json +45 -41
- package/styles.css +55 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { ExternalLink, Quote } from "lucide-react";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
const AISourceCitation = forwardRef(
|
|
6
|
+
({ className, href, snippet, source, target = "_blank", title, ...props }, ref) => {
|
|
7
|
+
return /* @__PURE__ */ jsxs(
|
|
8
|
+
"a",
|
|
9
|
+
{
|
|
10
|
+
className: cn(
|
|
11
|
+
"group inline-flex max-w-full flex-col gap-2 rounded-xl border border-border/70 bg-background px-3 py-2 text-left shadow-sm transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
12
|
+
className
|
|
13
|
+
),
|
|
14
|
+
href,
|
|
15
|
+
ref,
|
|
16
|
+
rel: target === "_blank" ? "noreferrer" : void 0,
|
|
17
|
+
target,
|
|
18
|
+
...props,
|
|
19
|
+
children: [
|
|
20
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
|
|
21
|
+
/* @__PURE__ */ jsx(Quote, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
22
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
23
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
24
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: title }),
|
|
25
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: source })
|
|
26
|
+
] }),
|
|
27
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" })
|
|
28
|
+
] }) })
|
|
29
|
+
] }),
|
|
30
|
+
snippet ? /* @__PURE__ */ jsx("p", { className: "line-clamp-3 text-sm leading-6 text-muted-foreground", children: snippet }) : null
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
AISourceCitation.displayName = "AISourceCitation";
|
|
37
|
+
export {
|
|
38
|
+
AISourceCitation
|
|
39
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
const AIStreamingText = forwardRef(
|
|
5
|
+
({
|
|
6
|
+
className,
|
|
7
|
+
cursor = "\u258D",
|
|
8
|
+
isStreaming = false,
|
|
9
|
+
showCursor = true,
|
|
10
|
+
text,
|
|
11
|
+
...props
|
|
12
|
+
}, ref) => {
|
|
13
|
+
return /* @__PURE__ */ jsxs(
|
|
14
|
+
"div",
|
|
15
|
+
{
|
|
16
|
+
"aria-live": isStreaming ? "polite" : void 0,
|
|
17
|
+
className: cn(
|
|
18
|
+
"text-sm leading-6 text-foreground whitespace-pre-wrap",
|
|
19
|
+
className
|
|
20
|
+
),
|
|
21
|
+
ref,
|
|
22
|
+
...props,
|
|
23
|
+
children: [
|
|
24
|
+
text,
|
|
25
|
+
isStreaming && showCursor ? /* @__PURE__ */ jsx(
|
|
26
|
+
"span",
|
|
27
|
+
{
|
|
28
|
+
"aria-hidden": "true",
|
|
29
|
+
className: "ml-0.5 inline-block animate-pulse text-muted-foreground",
|
|
30
|
+
children: cursor
|
|
31
|
+
}
|
|
32
|
+
) : null
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
AIStreamingText.displayName = "AIStreamingText";
|
|
39
|
+
export {
|
|
40
|
+
AIStreamingText
|
|
41
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { AlertCircle, CheckCircle2, Clock3, Wrench } from "lucide-react";
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
import { Badge } from "../badge";
|
|
7
|
+
const statusVariants = cva(
|
|
8
|
+
"rounded-full px-2 py-0 text-[10px] uppercase tracking-wide",
|
|
9
|
+
{
|
|
10
|
+
defaultVariants: {
|
|
11
|
+
status: "queued"
|
|
12
|
+
},
|
|
13
|
+
variants: {
|
|
14
|
+
status: {
|
|
15
|
+
complete: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
16
|
+
error: "bg-red-500/10 text-red-700 dark:text-red-300",
|
|
17
|
+
queued: "bg-muted text-muted-foreground",
|
|
18
|
+
running: "bg-blue-500/10 text-blue-700 dark:text-blue-300"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
const statusIconMap = {
|
|
24
|
+
complete: /* @__PURE__ */ jsx(CheckCircle2, { className: "h-4 w-4" }),
|
|
25
|
+
error: /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
|
|
26
|
+
queued: /* @__PURE__ */ jsx(Clock3, { className: "h-4 w-4" }),
|
|
27
|
+
running: /* @__PURE__ */ jsx(Wrench, { className: "h-4 w-4 animate-pulse" })
|
|
28
|
+
};
|
|
29
|
+
const AIToolCallDisplay = forwardRef(
|
|
30
|
+
({
|
|
31
|
+
className,
|
|
32
|
+
description,
|
|
33
|
+
duration,
|
|
34
|
+
input,
|
|
35
|
+
output,
|
|
36
|
+
status = "queued",
|
|
37
|
+
toolName,
|
|
38
|
+
...props
|
|
39
|
+
}, ref) => {
|
|
40
|
+
return /* @__PURE__ */ jsxs(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
className: cn(
|
|
44
|
+
"rounded-2xl border border-border/70 bg-card p-4 shadow-sm",
|
|
45
|
+
className
|
|
46
|
+
),
|
|
47
|
+
ref,
|
|
48
|
+
...props,
|
|
49
|
+
children: [
|
|
50
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
|
|
51
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
|
|
52
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
53
|
+
statusIconMap[status],
|
|
54
|
+
/* @__PURE__ */ jsx("span", { children: toolName })
|
|
55
|
+
] }),
|
|
56
|
+
description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
|
|
57
|
+
] }),
|
|
58
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
59
|
+
duration ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: duration }) : null,
|
|
60
|
+
/* @__PURE__ */ jsx(Badge, { className: statusVariants({ status }), variant: "secondary", children: status })
|
|
61
|
+
] })
|
|
62
|
+
] }),
|
|
63
|
+
input ? /* @__PURE__ */ jsxs(
|
|
64
|
+
"details",
|
|
65
|
+
{
|
|
66
|
+
className: "mt-4 rounded-xl border border-border/60 bg-muted/20 p-3",
|
|
67
|
+
open: status !== "complete",
|
|
68
|
+
children: [
|
|
69
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Tool input" }),
|
|
70
|
+
/* @__PURE__ */ jsx("pre", { className: "mt-3 overflow-x-auto whitespace-pre-wrap text-xs leading-5 text-foreground", children: input })
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
) : null,
|
|
74
|
+
output ? /* @__PURE__ */ jsxs(
|
|
75
|
+
"details",
|
|
76
|
+
{
|
|
77
|
+
className: "mt-3 rounded-xl border border-border/60 bg-muted/20 p-3",
|
|
78
|
+
open: status !== "complete",
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Tool output" }),
|
|
81
|
+
/* @__PURE__ */ jsx("pre", { className: "mt-3 overflow-x-auto whitespace-pre-wrap text-xs leading-5 text-foreground", children: output })
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
) : null
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
AIToolCallDisplay.displayName = "AIToolCallDisplay";
|
|
91
|
+
export {
|
|
92
|
+
AIToolCallDisplay
|
|
93
|
+
};
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
const GLYPH_SEGMENTER = new Intl.Segmenter(void 0, {
|
|
5
|
+
granularity: "grapheme"
|
|
6
|
+
});
|
|
7
|
+
const ASCII_RANDOM_CHARACTERS = Array.from(
|
|
8
|
+
{ length: 94 },
|
|
9
|
+
(_, index) => String.fromCodePoint(index + 33)
|
|
10
|
+
).join("");
|
|
11
|
+
const TERMINAL_RANDOM_CHARACTERS = "\u2502\u2503\u2500\u2501\u2504\u2505\u2508\u2509\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u256D\u256E\u256F\u2570\u2571\u2572\u2573";
|
|
12
|
+
const BLOCK_RANDOM_CHARACTERS = "\u2591\u2592\u2593\u2588\u258C\u2590\u2580\u2584\u25A0\u25A1\u25AA\u25AB\u2596\u2597\u2598\u2599\u259A\u259B\u259C\u259D\u259E\u259F";
|
|
13
|
+
const UNICODE_SYMBOL_RANDOM_CHARACTERS = "\u25C6\u25C7\u25C8\u25CB\u25CF\u25CE\u25C9\u25CC\u25CD\u25D0\u25D1\u25D2\u25D3\u25D4\u25D5\u25E2\u25E3\u25E4\u25E5\u25E6\u203B\u2726\u2727\u2731\u2736\u2737\u2739";
|
|
14
|
+
const MATRIX_RANDOM_CHARACTERS = `${ASCII_RANDOM_CHARACTERS}${TERMINAL_RANDOM_CHARACTERS}${BLOCK_RANDOM_CHARACTERS}${UNICODE_SYMBOL_RANDOM_CHARACTERS}`;
|
|
15
|
+
const ANIMATED_TEXT_RANDOM_CHARACTER_PRESETS = {
|
|
16
|
+
ascii: ASCII_RANDOM_CHARACTERS,
|
|
17
|
+
binary: "01",
|
|
18
|
+
blocks: BLOCK_RANDOM_CHARACTERS,
|
|
19
|
+
matrix: MATRIX_RANDOM_CHARACTERS,
|
|
20
|
+
symbols: UNICODE_SYMBOL_RANDOM_CHARACTERS,
|
|
21
|
+
terminal: TERMINAL_RANDOM_CHARACTERS
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_RANDOM_CHARACTERS = ANIMATED_TEXT_RANDOM_CHARACTER_PRESETS.matrix;
|
|
24
|
+
function getSegments(text, splitBy) {
|
|
25
|
+
if (splitBy === "character") {
|
|
26
|
+
const segmenter = new Intl.Segmenter(void 0, {
|
|
27
|
+
granularity: "grapheme"
|
|
28
|
+
});
|
|
29
|
+
return Array.from(segmenter.segment(text), ({ segment }) => segment);
|
|
30
|
+
}
|
|
31
|
+
return text.match(/\S+\s*/g) ?? [];
|
|
32
|
+
}
|
|
33
|
+
function getGlyphs(text) {
|
|
34
|
+
return Array.from(GLYPH_SEGMENTER.segment(text), ({ segment }) => segment);
|
|
35
|
+
}
|
|
36
|
+
function getRandomMatrixGlyph(randomCharacters) {
|
|
37
|
+
const glyphs = getGlyphs(randomCharacters);
|
|
38
|
+
return glyphs[Math.floor(Math.random() * glyphs.length)] ?? glyphs[0] ?? "0";
|
|
39
|
+
}
|
|
40
|
+
function getResolvedRandomCharacters(randomCharacters, randomCharactersPreset) {
|
|
41
|
+
if (randomCharacters && randomCharacters.length > 0) {
|
|
42
|
+
return randomCharacters;
|
|
43
|
+
}
|
|
44
|
+
return ANIMATED_TEXT_RANDOM_CHARACTER_PRESETS[randomCharactersPreset] ?? DEFAULT_RANDOM_CHARACTERS;
|
|
45
|
+
}
|
|
46
|
+
function getCursorToneClass(variant) {
|
|
47
|
+
return variant === "matrix" || variant === "decipher" ? "text-primary" : "text-foreground";
|
|
48
|
+
}
|
|
49
|
+
function buildRevealFrames(segments, stagger) {
|
|
50
|
+
return segments.map((segment, index) => ({
|
|
51
|
+
content: segment,
|
|
52
|
+
isRevealed: true,
|
|
53
|
+
key: `${segment}-${index}`,
|
|
54
|
+
style: {
|
|
55
|
+
animationDelay: `${index * stagger}ms`
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
function buildIndexOrder(direction, length) {
|
|
60
|
+
if (direction === "end") {
|
|
61
|
+
return Array.from({ length }, (_, index) => length - index - 1);
|
|
62
|
+
}
|
|
63
|
+
if (direction === "random") {
|
|
64
|
+
return Array.from({ length }, (_, index) => index).sort(
|
|
65
|
+
() => Math.random() - 0.5
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (direction === "center-out") {
|
|
69
|
+
const center = (length - 1) / 2;
|
|
70
|
+
return Array.from({ length }, (_, index) => index).sort((left, right) => {
|
|
71
|
+
const leftDistance = Math.abs(left - center);
|
|
72
|
+
const rightDistance = Math.abs(right - center);
|
|
73
|
+
if (leftDistance === rightDistance) {
|
|
74
|
+
return left - right;
|
|
75
|
+
}
|
|
76
|
+
return leftDistance - rightDistance;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return Array.from({ length }, (_, index) => index);
|
|
80
|
+
}
|
|
81
|
+
function buildRevealPlan(direction, length, randomness) {
|
|
82
|
+
const orderedIndices = buildIndexOrder(direction, length);
|
|
83
|
+
const revealPlan = Array.from({ length }, () => 0);
|
|
84
|
+
const jitterRange = Math.max(0, Math.round(randomness * 4));
|
|
85
|
+
orderedIndices.forEach((segmentIndex, revealIndex) => {
|
|
86
|
+
const jitter = jitterRange > 0 ? Math.floor(Math.random() * (jitterRange + 1)) : 0;
|
|
87
|
+
revealPlan[segmentIndex] = revealIndex + jitter;
|
|
88
|
+
});
|
|
89
|
+
return revealPlan;
|
|
90
|
+
}
|
|
91
|
+
function useRevealProgress(active, length, stagger) {
|
|
92
|
+
const [progress, setProgress] = React.useState(0);
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
if (!active) {
|
|
95
|
+
setProgress(length);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
setProgress(0);
|
|
99
|
+
const revealInterval = window.setInterval(
|
|
100
|
+
() => {
|
|
101
|
+
setProgress((current) => {
|
|
102
|
+
if (current >= length + 4) {
|
|
103
|
+
window.clearInterval(revealInterval);
|
|
104
|
+
return current;
|
|
105
|
+
}
|
|
106
|
+
return current + 1;
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
Math.max(16, stagger)
|
|
110
|
+
);
|
|
111
|
+
return () => {
|
|
112
|
+
window.clearInterval(revealInterval);
|
|
113
|
+
};
|
|
114
|
+
}, [active, length, stagger]);
|
|
115
|
+
return progress;
|
|
116
|
+
}
|
|
117
|
+
function useMatrixFrame({
|
|
118
|
+
active,
|
|
119
|
+
progress,
|
|
120
|
+
randomCharacters,
|
|
121
|
+
revealPlan,
|
|
122
|
+
segments
|
|
123
|
+
}) {
|
|
124
|
+
const [matrixFrame, setMatrixFrame] = React.useState(
|
|
125
|
+
() => segments.map(() => getRandomMatrixGlyph(randomCharacters))
|
|
126
|
+
);
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
if (!active) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
setMatrixFrame(segments.map(() => getRandomMatrixGlyph(randomCharacters)));
|
|
132
|
+
const scrambleInterval = window.setInterval(() => {
|
|
133
|
+
setMatrixFrame(
|
|
134
|
+
(current) => current.map((glyph, index) => {
|
|
135
|
+
const isWhitespace = /^\s+$/.test(segments[index] ?? "");
|
|
136
|
+
const isRevealed = progress >= (revealPlan[index] ?? 0);
|
|
137
|
+
if (isWhitespace || isRevealed) {
|
|
138
|
+
return glyph;
|
|
139
|
+
}
|
|
140
|
+
return getRandomMatrixGlyph(randomCharacters);
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
}, 48);
|
|
144
|
+
return () => {
|
|
145
|
+
window.clearInterval(scrambleInterval);
|
|
146
|
+
};
|
|
147
|
+
}, [active, progress, randomCharacters, revealPlan, segments]);
|
|
148
|
+
return matrixFrame;
|
|
149
|
+
}
|
|
150
|
+
function buildOldSchoolFrames({
|
|
151
|
+
matrixFrame,
|
|
152
|
+
progress,
|
|
153
|
+
randomCharacters,
|
|
154
|
+
revealPlan,
|
|
155
|
+
segments,
|
|
156
|
+
variant
|
|
157
|
+
}) {
|
|
158
|
+
return segments.map((segment, index) => {
|
|
159
|
+
const isWhitespace = /^\s+$/.test(segment);
|
|
160
|
+
const revealStep = revealPlan[index] ?? 0;
|
|
161
|
+
const isRevealed = progress >= revealStep;
|
|
162
|
+
let content = "";
|
|
163
|
+
if (variant === "matrix" || variant === "decipher") {
|
|
164
|
+
content = isWhitespace ? segment : isRevealed ? segment : matrixFrame[index] ?? getRandomMatrixGlyph(randomCharacters);
|
|
165
|
+
} else if (isRevealed) {
|
|
166
|
+
content = segment;
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
content,
|
|
170
|
+
isRevealed,
|
|
171
|
+
key: `${segment}-${index}`
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function useAnimatedTextFrames({
|
|
176
|
+
direction,
|
|
177
|
+
randomCharacters,
|
|
178
|
+
randomness,
|
|
179
|
+
segments,
|
|
180
|
+
stagger,
|
|
181
|
+
variant
|
|
182
|
+
}) {
|
|
183
|
+
const isOldSchool = variant !== "reveal";
|
|
184
|
+
const revealPlan = React.useMemo(
|
|
185
|
+
() => isOldSchool ? buildRevealPlan(direction, segments.length, randomness) : Array.from({ length: segments.length }, (_, index) => index),
|
|
186
|
+
[direction, isOldSchool, randomness, segments.length]
|
|
187
|
+
);
|
|
188
|
+
const progress = useRevealProgress(isOldSchool, segments.length, stagger);
|
|
189
|
+
const matrixFrame = useMatrixFrame({
|
|
190
|
+
active: variant === "matrix" || variant === "decipher",
|
|
191
|
+
progress,
|
|
192
|
+
randomCharacters,
|
|
193
|
+
revealPlan,
|
|
194
|
+
segments
|
|
195
|
+
});
|
|
196
|
+
return React.useMemo(() => {
|
|
197
|
+
if (!isOldSchool) {
|
|
198
|
+
return buildRevealFrames(segments, stagger);
|
|
199
|
+
}
|
|
200
|
+
return buildOldSchoolFrames({
|
|
201
|
+
matrixFrame,
|
|
202
|
+
progress,
|
|
203
|
+
randomCharacters,
|
|
204
|
+
revealPlan,
|
|
205
|
+
segments,
|
|
206
|
+
variant
|
|
207
|
+
});
|
|
208
|
+
}, [
|
|
209
|
+
isOldSchool,
|
|
210
|
+
matrixFrame,
|
|
211
|
+
progress,
|
|
212
|
+
randomCharacters,
|
|
213
|
+
revealPlan,
|
|
214
|
+
segments,
|
|
215
|
+
stagger,
|
|
216
|
+
variant
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
function getSegmentClasses(variant, isRevealed) {
|
|
220
|
+
if (variant === "reveal") {
|
|
221
|
+
return "inline-block whitespace-pre opacity-0 [animation-duration:var(--vllnt-animated-text-duration)] [animation-fill-mode:forwards] [animation-name:vllnt-animated-text-reveal] [animation-timing-function:cubic-bezier(0.16,1,0.3,1)]";
|
|
222
|
+
}
|
|
223
|
+
if (variant === "matrix" || variant === "decipher") {
|
|
224
|
+
return cn(
|
|
225
|
+
"inline-block whitespace-pre font-mono tracking-[0.08em] transition-colors duration-150",
|
|
226
|
+
isRevealed ? "text-foreground" : "text-primary/75"
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return "inline-block whitespace-pre font-mono";
|
|
230
|
+
}
|
|
231
|
+
function getContainerClasses(variant) {
|
|
232
|
+
if (variant === "matrix" || variant === "decipher") {
|
|
233
|
+
return "flex flex-wrap font-mono leading-relaxed tracking-[0.08em]";
|
|
234
|
+
}
|
|
235
|
+
if (variant === "terminal" || variant === "typewriter") {
|
|
236
|
+
return "flex flex-wrap font-mono leading-relaxed";
|
|
237
|
+
}
|
|
238
|
+
return "flex flex-wrap leading-relaxed";
|
|
239
|
+
}
|
|
240
|
+
function AnimatedTextCursor({
|
|
241
|
+
cursorChar,
|
|
242
|
+
cursorToneClass
|
|
243
|
+
}) {
|
|
244
|
+
return /* @__PURE__ */ jsx(
|
|
245
|
+
"span",
|
|
246
|
+
{
|
|
247
|
+
"aria-hidden": "true",
|
|
248
|
+
className: cn(
|
|
249
|
+
"ml-0.5 inline-block whitespace-pre font-mono [animation:vllnt-terminal-cursor-blink_1s_steps(1,end)_infinite]",
|
|
250
|
+
cursorToneClass
|
|
251
|
+
),
|
|
252
|
+
children: cursorChar
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const AnimatedText = React.forwardRef(
|
|
257
|
+
({
|
|
258
|
+
className,
|
|
259
|
+
cursor = true,
|
|
260
|
+
cursorChar = "\u2588",
|
|
261
|
+
direction = "start",
|
|
262
|
+
duration = 600,
|
|
263
|
+
randomCharacters,
|
|
264
|
+
randomCharactersPreset = "matrix",
|
|
265
|
+
randomness = 0,
|
|
266
|
+
splitBy = "word",
|
|
267
|
+
stagger = 70,
|
|
268
|
+
text,
|
|
269
|
+
variant = "terminal",
|
|
270
|
+
...props
|
|
271
|
+
}, ref) => {
|
|
272
|
+
const resolvedRandomCharacters = getResolvedRandomCharacters(
|
|
273
|
+
randomCharacters,
|
|
274
|
+
randomCharactersPreset
|
|
275
|
+
);
|
|
276
|
+
const resolvedSplitBy = variant === "reveal" ? splitBy : "character";
|
|
277
|
+
const segments = React.useMemo(
|
|
278
|
+
() => getSegments(text, resolvedSplitBy),
|
|
279
|
+
[resolvedSplitBy, text]
|
|
280
|
+
);
|
|
281
|
+
const segmentFrames = useAnimatedTextFrames({
|
|
282
|
+
direction,
|
|
283
|
+
randomCharacters: resolvedRandomCharacters,
|
|
284
|
+
randomness,
|
|
285
|
+
segments,
|
|
286
|
+
stagger,
|
|
287
|
+
variant
|
|
288
|
+
});
|
|
289
|
+
const showCursor = cursor && variant !== "reveal" && segmentFrames.some((frame) => !frame.isRevealed);
|
|
290
|
+
const cursorToneClass = getCursorToneClass(variant);
|
|
291
|
+
return /* @__PURE__ */ jsxs(
|
|
292
|
+
"p",
|
|
293
|
+
{
|
|
294
|
+
"aria-label": text,
|
|
295
|
+
className: cn(getContainerClasses(variant), className),
|
|
296
|
+
ref,
|
|
297
|
+
style: {
|
|
298
|
+
["--vllnt-animated-text-duration"]: `${duration}ms`
|
|
299
|
+
},
|
|
300
|
+
...props,
|
|
301
|
+
children: [
|
|
302
|
+
segmentFrames.map((segmentFrame) => /* @__PURE__ */ jsx(
|
|
303
|
+
"span",
|
|
304
|
+
{
|
|
305
|
+
"aria-hidden": "true",
|
|
306
|
+
className: getSegmentClasses(variant, segmentFrame.isRevealed),
|
|
307
|
+
style: segmentFrame.style,
|
|
308
|
+
children: segmentFrame.content
|
|
309
|
+
},
|
|
310
|
+
segmentFrame.key
|
|
311
|
+
)),
|
|
312
|
+
showCursor ? /* @__PURE__ */ jsx(
|
|
313
|
+
AnimatedTextCursor,
|
|
314
|
+
{
|
|
315
|
+
cursorChar,
|
|
316
|
+
cursorToneClass
|
|
317
|
+
}
|
|
318
|
+
) : null
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
AnimatedText.displayName = "AnimatedText";
|
|
325
|
+
export {
|
|
326
|
+
ANIMATED_TEXT_RANDOM_CHARACTER_PRESETS,
|
|
327
|
+
AnimatedText
|
|
328
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
|
5
|
+
const toneClasses = {
|
|
6
|
+
amber: "bg-amber-500/20 text-amber-950 dark:text-amber-100",
|
|
7
|
+
emerald: "bg-emerald-500/20 text-emerald-950 dark:text-emerald-100",
|
|
8
|
+
rose: "bg-rose-500/20 text-rose-950 dark:text-rose-100",
|
|
9
|
+
sky: "bg-sky-500/20 text-sky-950 dark:text-sky-100"
|
|
10
|
+
};
|
|
11
|
+
function Highlight({
|
|
12
|
+
children,
|
|
13
|
+
className,
|
|
14
|
+
tone = "amber"
|
|
15
|
+
}) {
|
|
16
|
+
return /* @__PURE__ */ jsx("mark", { className: cn("rounded px-1 py-0.5", toneClasses[tone], className), children });
|
|
17
|
+
}
|
|
18
|
+
function Annotation({
|
|
19
|
+
annotation,
|
|
20
|
+
children,
|
|
21
|
+
className,
|
|
22
|
+
label = "Annotation",
|
|
23
|
+
tone = "amber"
|
|
24
|
+
}) {
|
|
25
|
+
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
26
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
27
|
+
"button",
|
|
28
|
+
{
|
|
29
|
+
className: cn(
|
|
30
|
+
"inline-flex items-center gap-1 rounded-sm text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
31
|
+
className
|
|
32
|
+
),
|
|
33
|
+
type: "button",
|
|
34
|
+
children: [
|
|
35
|
+
/* @__PURE__ */ jsx(Highlight, { tone, children }),
|
|
36
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold text-primary align-super", children: "+" })
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
) }),
|
|
40
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "max-w-xs space-y-2", children: [
|
|
41
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground", children: label }),
|
|
42
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground [&>p]:mb-2", children: annotation })
|
|
43
|
+
] })
|
|
44
|
+
] });
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
Annotation,
|
|
48
|
+
Highlight
|
|
49
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../avatar";
|
|
6
|
+
const avatarGroupVariants = cva("flex items-center", {
|
|
7
|
+
defaultVariants: {
|
|
8
|
+
size: "md"
|
|
9
|
+
},
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
lg: "-space-x-4",
|
|
13
|
+
md: "-space-x-3",
|
|
14
|
+
sm: "-space-x-2.5"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const avatarItemVariants = cva(
|
|
19
|
+
"ring-2 ring-background border border-border bg-muted text-muted-foreground",
|
|
20
|
+
{
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
size: "md"
|
|
23
|
+
},
|
|
24
|
+
variants: {
|
|
25
|
+
size: {
|
|
26
|
+
lg: "h-12 w-12 text-sm",
|
|
27
|
+
md: "h-10 w-10 text-xs",
|
|
28
|
+
sm: "h-8 w-8 text-[11px]"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
const overflowBadgeVariants = cva(
|
|
34
|
+
"inline-flex shrink-0 items-center justify-center rounded-full border border-border bg-muted font-medium text-muted-foreground ring-2 ring-background",
|
|
35
|
+
{
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
size: "md"
|
|
38
|
+
},
|
|
39
|
+
variants: {
|
|
40
|
+
size: {
|
|
41
|
+
lg: "h-12 min-w-12 px-3 text-sm",
|
|
42
|
+
md: "h-10 min-w-10 px-2.5 text-xs",
|
|
43
|
+
sm: "h-8 min-w-8 px-2 text-[11px]"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
const AvatarGroup = React.forwardRef(
|
|
49
|
+
({ className, items, max, overflowLabel, size, ...props }, reference) => {
|
|
50
|
+
const visibleItems = max === void 0 ? items : items.slice(0, max);
|
|
51
|
+
const hiddenCount = max === void 0 ? 0 : Math.max(items.length - max, 0);
|
|
52
|
+
return /* @__PURE__ */ jsxs(
|
|
53
|
+
"div",
|
|
54
|
+
{
|
|
55
|
+
className: cn(avatarGroupVariants({ size }), className),
|
|
56
|
+
ref: reference,
|
|
57
|
+
...props,
|
|
58
|
+
children: [
|
|
59
|
+
visibleItems.map((item, index) => /* @__PURE__ */ jsxs(
|
|
60
|
+
Avatar,
|
|
61
|
+
{
|
|
62
|
+
className: avatarItemVariants({ size }),
|
|
63
|
+
style: { zIndex: visibleItems.length - index },
|
|
64
|
+
children: [
|
|
65
|
+
item.src ? /* @__PURE__ */ jsx(AvatarImage, { alt: item.alt, src: item.src }) : null,
|
|
66
|
+
/* @__PURE__ */ jsx(AvatarFallback, { children: item.fallback })
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
`${item.alt}-${index}`
|
|
70
|
+
)),
|
|
71
|
+
hiddenCount > 0 ? /* @__PURE__ */ jsx("span", { className: overflowBadgeVariants({ size }), children: overflowLabel ? overflowLabel(hiddenCount) : `+${hiddenCount}` }) : null
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
AvatarGroup.displayName = "AvatarGroup";
|
|
78
|
+
export {
|
|
79
|
+
AvatarGroup,
|
|
80
|
+
avatarGroupVariants,
|
|
81
|
+
avatarItemVariants
|
|
82
|
+
};
|