@alpic-ai/ui 0.0.0-dev.g172fb9f → 0.0.0-dev.g1759f2e
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/dist/components/badge.d.mts +1 -1
- package/dist/components/bar-list.mjs +6 -5
- package/dist/components/button.d.mts +1 -1
- package/dist/components/donut-chart.mjs +5 -5
- package/dist/components/spinner.d.mts +1 -1
- package/dist/components/wizard.d.mts +1 -19
- package/dist/components/wizard.mjs +1 -19
- package/package.json +23 -23
- package/src/components/bar-list.tsx +5 -4
- package/src/components/donut-chart.tsx +5 -5
- package/src/components/wizard.tsx +1 -35
- package/src/stories/area-chart.stories.tsx +1 -3
- package/src/stories/bar-chart.stories.tsx +1 -3
- package/src/stories/bar-list.stories.tsx +1 -3
- package/src/stories/donut-chart.stories.tsx +1 -3
- package/src/stories/heatmap-chart.stories.tsx +1 -3
- package/src/stories/line-chart.stories.tsx +1 -3
- package/src/stories/wizard.stories.tsx +22 -4
- package/src/styles/tokens.css +0 -45
- package/dist/components/grid-fx.d.mts +0 -13
- package/dist/components/grid-fx.mjs +0 -188
- package/src/components/grid-fx.tsx +0 -238
|
@@ -2,7 +2,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
2
2
|
|
|
3
3
|
//#region src/components/badge.d.ts
|
|
4
4
|
declare const badgeVariants: (props?: ({
|
|
5
|
-
variant?: "warning" | "success" | "
|
|
5
|
+
variant?: "warning" | "success" | "secondary" | "primary" | "error" | null | undefined;
|
|
6
6
|
size?: "sm" | "md" | null | undefined;
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
8
|
interface BadgeProps extends React.ComponentProps<"span">, VariantProps<typeof badgeVariants> {}
|
|
@@ -52,7 +52,7 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, loading =
|
|
|
52
52
|
});
|
|
53
53
|
return /* @__PURE__ */ jsx("div", {
|
|
54
54
|
"data-slot": "bar-list",
|
|
55
|
-
className: cn("flex w-full flex-col
|
|
55
|
+
className: cn("flex w-full flex-col", className),
|
|
56
56
|
children: rows.map((row, slot) => {
|
|
57
57
|
const fraction = maxValue > 0 ? row.value / maxValue : 0;
|
|
58
58
|
const fillWidth = !reducedMotion && !mounted ? "0%" : `${fraction * 100}%`;
|
|
@@ -61,19 +61,20 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, loading =
|
|
|
61
61
|
return /* @__PURE__ */ jsxs("div", {
|
|
62
62
|
onMouseEnter: () => setActive(slot),
|
|
63
63
|
onMouseLeave: () => setActive(null),
|
|
64
|
-
className: "flex items-center gap-3",
|
|
64
|
+
className: "flex items-center gap-3 py-1",
|
|
65
65
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
66
66
|
className: "relative h-[30px] flex-1 overflow-hidden rounded-md bg-muted",
|
|
67
67
|
children: [/* @__PURE__ */ jsx("span", {
|
|
68
68
|
className: "pointer-events-none absolute inset-x-3 top-1/2 z-0 -translate-y-1/2 truncate type-text-xs font-medium text-foreground",
|
|
69
69
|
children: formatName(row.name)
|
|
70
70
|
}), /* @__PURE__ */ jsx("div", {
|
|
71
|
-
className: "absolute inset-y-0 left-0 z-10 overflow-hidden rounded-md
|
|
71
|
+
className: "absolute inset-y-0 left-0 z-10 overflow-hidden rounded-md",
|
|
72
72
|
style: {
|
|
73
73
|
width: fillWidth,
|
|
74
74
|
background: `linear-gradient(90deg, ${row.color}, color-mix(in oklab, ${row.color} 82%, transparent))`,
|
|
75
75
|
boxShadow: `inset 0 0 0 1px ${row.color}`,
|
|
76
|
-
opacity: dimmed ? .45 : 1
|
|
76
|
+
opacity: dimmed ? .45 : 1,
|
|
77
|
+
transition: reducedMotion ? void 0 : "width 700ms ease-out"
|
|
77
78
|
},
|
|
78
79
|
children: /* @__PURE__ */ jsx("span", {
|
|
79
80
|
className: "pointer-events-none absolute top-1/2 -translate-y-1/2 truncate type-text-xs font-medium text-white",
|
|
@@ -86,7 +87,7 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, loading =
|
|
|
86
87
|
})
|
|
87
88
|
})]
|
|
88
89
|
}), /* @__PURE__ */ jsx("span", {
|
|
89
|
-
className: "w-
|
|
90
|
+
className: "min-w-[4rem] shrink-0 whitespace-nowrap text-right font-mono text-text-xs tabular-nums motion-safe:transition-colors",
|
|
90
91
|
style: { color: isActive ? row.color : void 0 },
|
|
91
92
|
children: isActive ? formatShare(total > 0 ? row.value / total : 0) : valueFormatter(row.value)
|
|
92
93
|
})]
|
|
@@ -3,7 +3,7 @@ import * as React$1 from "react";
|
|
|
3
3
|
|
|
4
4
|
//#region src/components/button.d.ts
|
|
5
5
|
declare const buttonVariants: (props?: ({
|
|
6
|
-
variant?: "destructive" | "
|
|
6
|
+
variant?: "destructive" | "secondary" | "primary" | "tertiary" | "link" | "link-muted" | "cta" | null | undefined;
|
|
7
7
|
size?: "default" | "icon" | "icon-rounded" | "pill" | null | undefined;
|
|
8
8
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
9
9
|
interface ButtonProps extends React$1.ComponentProps<"button">, VariantProps<typeof buttonVariants> {
|
|
@@ -117,7 +117,7 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
|
|
|
117
117
|
className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-1 text-center",
|
|
118
118
|
children: [
|
|
119
119
|
/* @__PURE__ */ jsx("span", {
|
|
120
|
-
className: "max-w-[
|
|
120
|
+
className: "max-w-[52%] truncate font-mono text-[10px] text-quaternary-foreground uppercase tracking-[0.18em]",
|
|
121
121
|
children: centerTitle
|
|
122
122
|
}),
|
|
123
123
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -143,7 +143,7 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
|
|
|
143
143
|
onMouseLeave: () => setActive(null),
|
|
144
144
|
className: cn("flex flex-col gap-1.5 border-border/40 border-b px-2 py-2 text-text-xs last:border-b-0 motion-safe:transition-colors", active === slot ? "bg-muted/50" : "bg-transparent"),
|
|
145
145
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
146
|
-
className: "flex items-center justify-between gap-
|
|
146
|
+
className: "flex items-center justify-between gap-2",
|
|
147
147
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
148
148
|
className: "inline-flex min-w-0 items-center gap-2 text-muted-foreground",
|
|
149
149
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -155,12 +155,12 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
|
|
|
155
155
|
children: formatName(slice.name)
|
|
156
156
|
})]
|
|
157
157
|
}), /* @__PURE__ */ jsxs("span", {
|
|
158
|
-
className: "flex shrink-0 items-center gap-
|
|
158
|
+
className: "flex shrink-0 items-center gap-2 font-mono tabular-nums",
|
|
159
159
|
children: [/* @__PURE__ */ jsx("span", {
|
|
160
|
-
className: "min-w-[
|
|
160
|
+
className: "min-w-[2.25rem] text-right font-semibold text-foreground",
|
|
161
161
|
children: valueFormatter(slice.value)
|
|
162
162
|
}), /* @__PURE__ */ jsx("span", {
|
|
163
|
-
className: "w-
|
|
163
|
+
className: "w-9 text-right text-quaternary-foreground",
|
|
164
164
|
children: formatShare(slice.value / total)
|
|
165
165
|
})]
|
|
166
166
|
})]
|
|
@@ -2,7 +2,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
2
2
|
|
|
3
3
|
//#region src/components/spinner.d.ts
|
|
4
4
|
declare const spinnerVariants: (props?: ({
|
|
5
|
-
variant?: "
|
|
5
|
+
variant?: "secondary" | "primary" | null | undefined;
|
|
6
6
|
size?: "sm" | "md" | "lg" | "xl" | null | undefined;
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
8
|
interface SpinnerProps extends Omit<React.ComponentProps<"svg">, "children">, VariantProps<typeof spinnerVariants> {}
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
import * as React$1 from "react";
|
|
2
2
|
|
|
3
3
|
//#region src/components/wizard.d.ts
|
|
4
|
-
interface WizardStep {
|
|
5
|
-
id: string;
|
|
6
|
-
label: string;
|
|
7
|
-
}
|
|
8
|
-
interface WizardStepsProps {
|
|
9
|
-
steps: readonly WizardStep[];
|
|
10
|
-
activeIdx: number;
|
|
11
|
-
onSelect: (idx: number) => void;
|
|
12
|
-
ariaLabel?: string;
|
|
13
|
-
className?: string;
|
|
14
|
-
}
|
|
15
|
-
declare function WizardSteps({
|
|
16
|
-
steps,
|
|
17
|
-
activeIdx,
|
|
18
|
-
onSelect,
|
|
19
|
-
ariaLabel,
|
|
20
|
-
className
|
|
21
|
-
}: WizardStepsProps): React$1.JSX.Element;
|
|
22
4
|
interface WizardProgressProps extends React$1.ComponentProps<"div"> {
|
|
23
5
|
current: number;
|
|
24
6
|
total: number;
|
|
@@ -30,4 +12,4 @@ declare function WizardProgress({
|
|
|
30
12
|
...props
|
|
31
13
|
}: WizardProgressProps): React$1.JSX.Element;
|
|
32
14
|
//#endregion
|
|
33
|
-
export { WizardProgress
|
|
15
|
+
export { WizardProgress };
|
|
@@ -1,25 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
|
-
import { TabsNav, TabsNavList, TabsNavTrigger } from "./tabs.mjs";
|
|
4
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
4
|
//#region src/components/wizard.tsx
|
|
6
|
-
function WizardSteps({ steps, activeIdx, onSelect, ariaLabel = "Wizard steps", className }) {
|
|
7
|
-
return /* @__PURE__ */ jsx(TabsNav, {
|
|
8
|
-
orientation: "vertical",
|
|
9
|
-
"aria-label": ariaLabel,
|
|
10
|
-
className,
|
|
11
|
-
children: /* @__PURE__ */ jsx(TabsNavList, { children: steps.map((step, idx) => /* @__PURE__ */ jsx(TabsNavTrigger, {
|
|
12
|
-
active: idx === activeIdx,
|
|
13
|
-
asChild: true,
|
|
14
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
15
|
-
type: "button",
|
|
16
|
-
onClick: () => onSelect(idx),
|
|
17
|
-
className: "w-full justify-start text-left",
|
|
18
|
-
children: step.label
|
|
19
|
-
})
|
|
20
|
-
}, step.id)) })
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
5
|
function WizardProgress({ current, total, className, ...props }) {
|
|
24
6
|
const percent = total > 0 ? Math.round(current / total * 100) : 0;
|
|
25
7
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -43,4 +25,4 @@ function WizardProgress({ current, total, className, ...props }) {
|
|
|
43
25
|
});
|
|
44
26
|
}
|
|
45
27
|
//#endregion
|
|
46
|
-
export { WizardProgress
|
|
28
|
+
export { WizardProgress };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpic-ai/ui",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.g1759f2e",
|
|
4
4
|
"description": "Alpic design system — shared UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -23,32 +23,32 @@
|
|
|
23
23
|
"src"
|
|
24
24
|
],
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"lucide-react": "^1.
|
|
26
|
+
"lucide-react": "^1.21.0",
|
|
27
27
|
"react": "^19.2.7",
|
|
28
28
|
"react-dom": "^19.2.7",
|
|
29
|
-
"react-hook-form": "^7.
|
|
29
|
+
"react-hook-form": "^7.80.0",
|
|
30
30
|
"sonner": "^2.0.7",
|
|
31
31
|
"tailwindcss": "^4.3.1",
|
|
32
32
|
"tw-animate-css": "^1.4.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@radix-ui/react-accordion": "^1.2.
|
|
36
|
-
"@radix-ui/react-avatar": "^1.
|
|
37
|
-
"@radix-ui/react-checkbox": "^1.3.
|
|
38
|
-
"@radix-ui/react-collapsible": "^1.1.
|
|
39
|
-
"@radix-ui/react-dialog": "^1.1.
|
|
40
|
-
"@radix-ui/react-dropdown-menu": "^2.1.
|
|
41
|
-
"@radix-ui/react-label": "^2.1.
|
|
42
|
-
"@radix-ui/react-popover": "^1.1.
|
|
43
|
-
"@radix-ui/react-radio-group": "^1.4.
|
|
44
|
-
"@radix-ui/react-scroll-area": "^1.2.
|
|
45
|
-
"@radix-ui/react-select": "^2.3.
|
|
46
|
-
"@radix-ui/react-separator": "^1.1.
|
|
47
|
-
"@radix-ui/react-slot": "^1.
|
|
48
|
-
"@radix-ui/react-switch": "^1.3.
|
|
49
|
-
"@radix-ui/react-tabs": "^1.1.
|
|
50
|
-
"@radix-ui/react-toggle-group": "^1.1.
|
|
51
|
-
"@radix-ui/react-tooltip": "^1.2.
|
|
35
|
+
"@radix-ui/react-accordion": "^1.2.14",
|
|
36
|
+
"@radix-ui/react-avatar": "^1.2.0",
|
|
37
|
+
"@radix-ui/react-checkbox": "^1.3.5",
|
|
38
|
+
"@radix-ui/react-collapsible": "^1.1.14",
|
|
39
|
+
"@radix-ui/react-dialog": "^1.1.17",
|
|
40
|
+
"@radix-ui/react-dropdown-menu": "^2.1.18",
|
|
41
|
+
"@radix-ui/react-label": "^2.1.10",
|
|
42
|
+
"@radix-ui/react-popover": "^1.1.17",
|
|
43
|
+
"@radix-ui/react-radio-group": "^1.4.1",
|
|
44
|
+
"@radix-ui/react-scroll-area": "^1.2.12",
|
|
45
|
+
"@radix-ui/react-select": "^2.3.1",
|
|
46
|
+
"@radix-ui/react-separator": "^1.1.10",
|
|
47
|
+
"@radix-ui/react-slot": "^1.3.0",
|
|
48
|
+
"@radix-ui/react-switch": "^1.3.1",
|
|
49
|
+
"@radix-ui/react-tabs": "^1.1.15",
|
|
50
|
+
"@radix-ui/react-toggle-group": "^1.1.13",
|
|
51
|
+
"@radix-ui/react-tooltip": "^1.2.10",
|
|
52
52
|
"class-variance-authority": "^0.7.1",
|
|
53
53
|
"clsx": "^2.1.1",
|
|
54
54
|
"cmdk": "^1.1.1",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"@tailwindcss/postcss": "^4.3.1",
|
|
61
61
|
"@types/react": "19.2.17",
|
|
62
62
|
"@types/react-dom": "19.2.3",
|
|
63
|
-
"lucide-react": "^1.
|
|
64
|
-
"react-hook-form": "^7.
|
|
63
|
+
"lucide-react": "^1.21.0",
|
|
64
|
+
"react-hook-form": "^7.80.0",
|
|
65
65
|
"shx": "^0.4.0",
|
|
66
66
|
"sonner": "^2.0.7",
|
|
67
67
|
"tailwindcss": "^4.3.1",
|
|
68
|
-
"tsdown": "^0.22.
|
|
68
|
+
"tsdown": "^0.22.3",
|
|
69
69
|
"tw-animate-css": "^1.4.0",
|
|
70
70
|
"typescript": "^6.0.3"
|
|
71
71
|
},
|
|
@@ -95,7 +95,7 @@ function BarList({
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
return (
|
|
98
|
-
<div data-slot="bar-list" className={cn("flex w-full flex-col
|
|
98
|
+
<div data-slot="bar-list" className={cn("flex w-full flex-col", className)}>
|
|
99
99
|
{rows.map((row, slot) => {
|
|
100
100
|
const fraction = maxValue > 0 ? row.value / maxValue : 0;
|
|
101
101
|
const fillWidth = !reducedMotion && !mounted ? "0%" : `${fraction * 100}%`;
|
|
@@ -107,19 +107,20 @@ function BarList({
|
|
|
107
107
|
key={row.name}
|
|
108
108
|
onMouseEnter={() => setActive(slot)}
|
|
109
109
|
onMouseLeave={() => setActive(null)}
|
|
110
|
-
className="flex items-center gap-3"
|
|
110
|
+
className="flex items-center gap-3 py-1"
|
|
111
111
|
>
|
|
112
112
|
<div className="relative h-[30px] flex-1 overflow-hidden rounded-md bg-muted">
|
|
113
113
|
<span className="pointer-events-none absolute inset-x-3 top-1/2 z-0 -translate-y-1/2 truncate type-text-xs font-medium text-foreground">
|
|
114
114
|
{formatName(row.name)}
|
|
115
115
|
</span>
|
|
116
116
|
<div
|
|
117
|
-
className="absolute inset-y-0 left-0 z-10 overflow-hidden rounded-md
|
|
117
|
+
className="absolute inset-y-0 left-0 z-10 overflow-hidden rounded-md"
|
|
118
118
|
style={{
|
|
119
119
|
width: fillWidth,
|
|
120
120
|
background: `linear-gradient(90deg, ${row.color}, color-mix(in oklab, ${row.color} 82%, transparent))`,
|
|
121
121
|
boxShadow: `inset 0 0 0 1px ${row.color}`,
|
|
122
122
|
opacity: dimmed ? 0.45 : 1,
|
|
123
|
+
transition: reducedMotion ? undefined : "width 700ms ease-out",
|
|
123
124
|
}}
|
|
124
125
|
>
|
|
125
126
|
<span
|
|
@@ -135,7 +136,7 @@ function BarList({
|
|
|
135
136
|
</div>
|
|
136
137
|
</div>
|
|
137
138
|
<span
|
|
138
|
-
className="w-
|
|
139
|
+
className="min-w-[4rem] shrink-0 whitespace-nowrap text-right font-mono text-text-xs tabular-nums motion-safe:transition-colors"
|
|
139
140
|
style={{ color: isActive ? row.color : undefined }}
|
|
140
141
|
>
|
|
141
142
|
{isActive ? formatShare(total > 0 ? row.value / total : 0) : valueFormatter(row.value)}
|
|
@@ -142,7 +142,7 @@ function DonutChart({
|
|
|
142
142
|
</ResponsiveContainer>
|
|
143
143
|
|
|
144
144
|
<div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-1 text-center">
|
|
145
|
-
<span className="max-w-[
|
|
145
|
+
<span className="max-w-[52%] truncate font-mono text-[10px] text-quaternary-foreground uppercase tracking-[0.18em]">
|
|
146
146
|
{centerTitle}
|
|
147
147
|
</span>
|
|
148
148
|
<span className="font-mono font-semibold text-[28px] text-foreground leading-none tabular-nums">
|
|
@@ -176,7 +176,7 @@ function DonutChart({
|
|
|
176
176
|
active === slot ? "bg-muted/50" : "bg-transparent",
|
|
177
177
|
)}
|
|
178
178
|
>
|
|
179
|
-
<div className="flex items-center justify-between gap-
|
|
179
|
+
<div className="flex items-center justify-between gap-2">
|
|
180
180
|
<span className="inline-flex min-w-0 items-center gap-2 text-muted-foreground">
|
|
181
181
|
<span
|
|
182
182
|
aria-hidden
|
|
@@ -185,11 +185,11 @@ function DonutChart({
|
|
|
185
185
|
/>
|
|
186
186
|
<span className="truncate">{formatName(slice.name)}</span>
|
|
187
187
|
</span>
|
|
188
|
-
<span className="flex shrink-0 items-center gap-
|
|
189
|
-
<span className="min-w-[
|
|
188
|
+
<span className="flex shrink-0 items-center gap-2 font-mono tabular-nums">
|
|
189
|
+
<span className="min-w-[2.25rem] text-right font-semibold text-foreground">
|
|
190
190
|
{valueFormatter(slice.value)}
|
|
191
191
|
</span>
|
|
192
|
-
<span className="w-
|
|
192
|
+
<span className="w-9 text-right text-quaternary-foreground">
|
|
193
193
|
{formatShare(slice.value / total)}
|
|
194
194
|
</span>
|
|
195
195
|
</span>
|
|
@@ -3,45 +3,12 @@
|
|
|
3
3
|
/*
|
|
4
4
|
* Wizard family — primitives for multi-step flows.
|
|
5
5
|
*
|
|
6
|
-
* - WizardSteps — vertical step rail (controlled by activeIdx + onSelect)
|
|
7
6
|
* - WizardProgress — step counter + progress bar
|
|
8
|
-
*
|
|
9
|
-
* Consumers compose these inside whatever container they need (sticky aside, modal, etc.).
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
9
|
import type * as React from "react";
|
|
13
10
|
|
|
14
11
|
import { cn } from "../lib/cn";
|
|
15
|
-
import { TabsNav, TabsNavList, TabsNavTrigger } from "./tabs";
|
|
16
|
-
|
|
17
|
-
interface WizardStep {
|
|
18
|
-
id: string;
|
|
19
|
-
label: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface WizardStepsProps {
|
|
23
|
-
steps: readonly WizardStep[];
|
|
24
|
-
activeIdx: number;
|
|
25
|
-
onSelect: (idx: number) => void;
|
|
26
|
-
ariaLabel?: string;
|
|
27
|
-
className?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function WizardSteps({ steps, activeIdx, onSelect, ariaLabel = "Wizard steps", className }: WizardStepsProps) {
|
|
31
|
-
return (
|
|
32
|
-
<TabsNav orientation="vertical" aria-label={ariaLabel} className={className}>
|
|
33
|
-
<TabsNavList>
|
|
34
|
-
{steps.map((step, idx) => (
|
|
35
|
-
<TabsNavTrigger key={step.id} active={idx === activeIdx} asChild>
|
|
36
|
-
<button type="button" onClick={() => onSelect(idx)} className="w-full justify-start text-left">
|
|
37
|
-
{step.label}
|
|
38
|
-
</button>
|
|
39
|
-
</TabsNavTrigger>
|
|
40
|
-
))}
|
|
41
|
-
</TabsNavList>
|
|
42
|
-
</TabsNav>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
12
|
|
|
46
13
|
interface WizardProgressProps extends React.ComponentProps<"div"> {
|
|
47
14
|
current: number;
|
|
@@ -65,5 +32,4 @@ function WizardProgress({ current, total, className, ...props }: WizardProgressP
|
|
|
65
32
|
);
|
|
66
33
|
}
|
|
67
34
|
|
|
68
|
-
export
|
|
69
|
-
export { WizardProgress, WizardSteps };
|
|
35
|
+
export { WizardProgress };
|
|
@@ -2,7 +2,6 @@ import type { Story } from "@ladle/react";
|
|
|
2
2
|
|
|
3
3
|
import { AreaChart } from "../components/area-chart";
|
|
4
4
|
import { ChartCard } from "../components/chart-card";
|
|
5
|
-
import { GridFx } from "../components/grid-fx";
|
|
6
5
|
import { Stat } from "../components/stat";
|
|
7
6
|
|
|
8
7
|
export default { title: "Charts/Area Chart" };
|
|
@@ -90,8 +89,7 @@ const latencyPeak = latency.reduce((best, row) => (row.p95 > best.p95 ? row : be
|
|
|
90
89
|
const errorsPeak = errors.reduce((best, row) => (row.mcp + row.tool > best.mcp + best.tool ? row : best));
|
|
91
90
|
|
|
92
91
|
export const AllVariants: Story = () => (
|
|
93
|
-
<div className="
|
|
94
|
-
<GridFx />
|
|
92
|
+
<div className="mx-auto max-w-[1600px] p-8">
|
|
95
93
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
96
94
|
<ChartCard
|
|
97
95
|
palette="magenta"
|
|
@@ -2,7 +2,6 @@ import type { Story } from "@ladle/react";
|
|
|
2
2
|
|
|
3
3
|
import { BarChart } from "../components/bar-chart";
|
|
4
4
|
import { ChartCard } from "../components/chart-card";
|
|
5
|
-
import { GridFx } from "../components/grid-fx";
|
|
6
5
|
import { Stat } from "../components/stat";
|
|
7
6
|
|
|
8
7
|
export default { title: "Charts/Bar Chart" };
|
|
@@ -77,8 +76,7 @@ const sessionsSpark = stacked.map((row) => CLIENTS.reduce((acc, client) => acc +
|
|
|
77
76
|
const errorsPeak = errors.reduce((best, row) => (row.mcp + row.tool > best.mcp + best.tool ? row : best));
|
|
78
77
|
|
|
79
78
|
export const AllVariants: Story = () => (
|
|
80
|
-
<div className="
|
|
81
|
-
<GridFx />
|
|
79
|
+
<div className="mx-auto max-w-[1600px] p-8">
|
|
82
80
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
83
81
|
<ChartCard
|
|
84
82
|
palette="magenta"
|
|
@@ -2,7 +2,6 @@ import type { Story } from "@ladle/react";
|
|
|
2
2
|
|
|
3
3
|
import { BarList } from "../components/bar-list";
|
|
4
4
|
import { ChartCard } from "../components/chart-card";
|
|
5
|
-
import { GridFx } from "../components/grid-fx";
|
|
6
5
|
import { Stat } from "../components/stat";
|
|
7
6
|
|
|
8
7
|
export default { title: "Charts/Bar List" };
|
|
@@ -53,8 +52,7 @@ const fmtK = (value: number) => {
|
|
|
53
52
|
const toolCallsTotal = TOP_TOOLS.reduce((sum, row) => sum + row.calls, 0);
|
|
54
53
|
|
|
55
54
|
export const AllVariants: Story = () => (
|
|
56
|
-
<div className="
|
|
57
|
-
<GridFx />
|
|
55
|
+
<div className="mx-auto max-w-[1600px] p-8">
|
|
58
56
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
59
57
|
<ChartCard palette="magenta" kicker="Last 7d" title="Top tools" description="Ranked · magenta ramp">
|
|
60
58
|
<Stat value={fmtK(toolCallsTotal)} unit="calls" delta={{ value: 12.4, direction: "up" }} />
|
|
@@ -2,7 +2,6 @@ import type { Story } from "@ladle/react";
|
|
|
2
2
|
|
|
3
3
|
import { ChartCard } from "../components/chart-card";
|
|
4
4
|
import { DonutChart } from "../components/donut-chart";
|
|
5
|
-
import { GridFx } from "../components/grid-fx";
|
|
6
5
|
import { Stat } from "../components/stat";
|
|
7
6
|
|
|
8
7
|
export default { title: "Charts/Donut Chart" };
|
|
@@ -73,8 +72,7 @@ const fmtK = (value: number) => {
|
|
|
73
72
|
const clientsTotal = clients.reduce((sum, row) => sum + row.sessions, 0);
|
|
74
73
|
|
|
75
74
|
export const AllVariants: Story = () => (
|
|
76
|
-
<div className="
|
|
77
|
-
<GridFx />
|
|
75
|
+
<div className="mx-auto max-w-[1600px] p-8">
|
|
78
76
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
79
77
|
<ChartCard palette="magenta" kicker="Last 7d" title="Sessions by client" description="Donut · share readout">
|
|
80
78
|
<Stat value={fmtK(clientsTotal)} unit="sessions" delta={{ value: 6.2, direction: "up" }} />
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Story } from "@ladle/react";
|
|
2
2
|
|
|
3
3
|
import { ChartCard } from "../components/chart-card";
|
|
4
|
-
import { GridFx } from "../components/grid-fx";
|
|
5
4
|
import { HeatmapChart } from "../components/heatmap-chart";
|
|
6
5
|
|
|
7
6
|
export default { title: "Charts/Heatmap" };
|
|
@@ -40,8 +39,7 @@ const HOURS = Array.from({ length: 24 }, (_, hour) => String(hour).padStart(2, "
|
|
|
40
39
|
const nf = (value: number) => value.toLocaleString("en-US");
|
|
41
40
|
|
|
42
41
|
export const AllVariants: Story = () => (
|
|
43
|
-
<div className="
|
|
44
|
-
<GridFx />
|
|
42
|
+
<div className="mx-auto max-w-[1600px] p-8">
|
|
45
43
|
<div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
|
|
46
44
|
<ChartCard palette="magenta" accent="left" kicker="Last 7d" title="Activity" description="Square · hour × day">
|
|
47
45
|
<HeatmapChart
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Story } from "@ladle/react";
|
|
2
2
|
|
|
3
3
|
import { ChartCard } from "../components/chart-card";
|
|
4
|
-
import { GridFx } from "../components/grid-fx";
|
|
5
4
|
import { LineChart } from "../components/line-chart";
|
|
6
5
|
import { Stat } from "../components/stat";
|
|
7
6
|
|
|
@@ -64,8 +63,7 @@ const tokensSpark = tokens.map((row) => row.v);
|
|
|
64
63
|
const latencyPeak = latency.reduce((best, row) => (row.p95 > best.p95 ? row : best));
|
|
65
64
|
|
|
66
65
|
export const AllVariants: Story = () => (
|
|
67
|
-
<div className="
|
|
68
|
-
<GridFx />
|
|
66
|
+
<div className="mx-auto max-w-[1600px] p-8">
|
|
69
67
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
70
68
|
<ChartCard
|
|
71
69
|
palette="cyan"
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Story } from "@ladle/react";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { TabsNav, TabsNavList, TabsNavTrigger } from "../components/tabs";
|
|
5
|
+
import { WizardProgress } from "../components/wizard";
|
|
5
6
|
|
|
6
7
|
const SECTION_HEADER = "type-text-xs font-medium text-muted-foreground uppercase tracking-wide pt-4";
|
|
7
8
|
|
|
8
|
-
const steps
|
|
9
|
+
const steps = [
|
|
9
10
|
{ id: "overview", label: "Overview" },
|
|
10
11
|
{ id: "branding", label: "Branding & metadata" },
|
|
11
12
|
{ id: "auth", label: "Authentication" },
|
|
@@ -13,6 +14,23 @@ const steps: WizardStep[] = [
|
|
|
13
14
|
{ id: "review", label: "Review & submit" },
|
|
14
15
|
];
|
|
15
16
|
|
|
17
|
+
/** The step rail is a vertical `TabsNav` composed with the consumer's selection state. */
|
|
18
|
+
function StepRail({ activeIdx, onSelect }: { activeIdx: number; onSelect: (idx: number) => void }) {
|
|
19
|
+
return (
|
|
20
|
+
<TabsNav orientation="vertical" aria-label="Submission steps">
|
|
21
|
+
<TabsNavList>
|
|
22
|
+
{steps.map((step, idx) => (
|
|
23
|
+
<TabsNavTrigger key={step.id} active={idx === activeIdx} asChild>
|
|
24
|
+
<button type="button" onClick={() => onSelect(idx)} className="w-full justify-start text-left">
|
|
25
|
+
{step.label}
|
|
26
|
+
</button>
|
|
27
|
+
</TabsNavTrigger>
|
|
28
|
+
))}
|
|
29
|
+
</TabsNavList>
|
|
30
|
+
</TabsNav>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
export const AllVariants: Story = () => {
|
|
17
35
|
const [activeIdx, setActiveIdx] = useState(1);
|
|
18
36
|
|
|
@@ -23,7 +41,7 @@ export const AllVariants: Story = () => {
|
|
|
23
41
|
<p className={SECTION_HEADER}>Full rail — steps + progress</p>
|
|
24
42
|
<div className="mt-4 flex gap-6">
|
|
25
43
|
<aside className="basis-56 shrink-0 flex flex-col gap-4 self-start">
|
|
26
|
-
<
|
|
44
|
+
<StepRail activeIdx={activeIdx} onSelect={setActiveIdx} />
|
|
27
45
|
<WizardProgress current={activeIdx + 1} total={steps.length} />
|
|
28
46
|
</aside>
|
|
29
47
|
<div className="flex-1 rounded-md border p-4">
|
|
@@ -36,7 +54,7 @@ export const AllVariants: Story = () => {
|
|
|
36
54
|
<div>
|
|
37
55
|
<p className={SECTION_HEADER}>Steps only</p>
|
|
38
56
|
<div className="mt-4 max-w-56">
|
|
39
|
-
<
|
|
57
|
+
<StepRail activeIdx={activeIdx} onSelect={setActiveIdx} />
|
|
40
58
|
</div>
|
|
41
59
|
</div>
|
|
42
60
|
|
package/src/styles/tokens.css
CHANGED
|
@@ -447,51 +447,6 @@
|
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
-
/* ─── Chart canvas — control-room atmosphere for analytics surfaces ───────────
|
|
451
|
-
Apply `.chart-canvas` to a dashboard/page container holding chart cards: a
|
|
452
|
-
dotted grid faded from the top, plus a dual brand glow in dark mode. The grid
|
|
453
|
-
is masked away so it never bleeds into the cards stacked above it. */
|
|
454
|
-
|
|
455
|
-
.chart-canvas {
|
|
456
|
-
position: relative;
|
|
457
|
-
isolation: isolate;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
.chart-canvas::before {
|
|
461
|
-
content: "";
|
|
462
|
-
position: absolute;
|
|
463
|
-
inset: 0;
|
|
464
|
-
z-index: -1;
|
|
465
|
-
pointer-events: none;
|
|
466
|
-
background-image:
|
|
467
|
-
linear-gradient(var(--color-sidebar-border) 1px, transparent 1px),
|
|
468
|
-
linear-gradient(90deg, var(--color-sidebar-border) 1px, transparent 1px);
|
|
469
|
-
background-size:
|
|
470
|
-
46px 46px,
|
|
471
|
-
46px 46px;
|
|
472
|
-
opacity: 0.65;
|
|
473
|
-
-webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 35%, transparent 100%);
|
|
474
|
-
mask-image: radial-gradient(120% 90% at 50% 0%, #000 35%, transparent 100%);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
.dark .chart-canvas::before {
|
|
478
|
-
background-image:
|
|
479
|
-
radial-gradient(
|
|
480
|
-
900px 480px at 78% -8%,
|
|
481
|
-
color-mix(in oklab, var(--color-cta-accent) 12%, transparent),
|
|
482
|
-
transparent 60%
|
|
483
|
-
),
|
|
484
|
-
radial-gradient(820px 520px at 8% 4%, color-mix(in oklab, var(--color-primary) 12%, transparent), transparent 60%),
|
|
485
|
-
linear-gradient(var(--color-sidebar-border) 1px, transparent 1px),
|
|
486
|
-
linear-gradient(90deg, var(--color-sidebar-border) 1px, transparent 1px);
|
|
487
|
-
background-size:
|
|
488
|
-
100% 100%,
|
|
489
|
-
100% 100%,
|
|
490
|
-
46px 46px,
|
|
491
|
-
46px 46px;
|
|
492
|
-
opacity: 0.6;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
450
|
@media (prefers-reduced-motion: no-preference) {
|
|
496
451
|
.chart-rise {
|
|
497
452
|
animation: chart-rise 0.65s cubic-bezier(0.2, 0.7, 0.2, 1) both;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import * as React$1 from "react";
|
|
2
|
-
|
|
3
|
-
//#region src/components/grid-fx.d.ts
|
|
4
|
-
declare function GridFx({
|
|
5
|
-
className,
|
|
6
|
-
cellSize,
|
|
7
|
-
style,
|
|
8
|
-
...props
|
|
9
|
-
}: React$1.ComponentProps<"canvas"> & {
|
|
10
|
-
cellSize?: number;
|
|
11
|
-
}): React$1.JSX.Element | null;
|
|
12
|
-
//#endregion
|
|
13
|
-
export { GridFx };
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { cn } from "../lib/cn.mjs";
|
|
3
|
-
import { useReducedMotion } from "../hooks/use-reduced-motion.mjs";
|
|
4
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
|
-
import * as React$1 from "react";
|
|
6
|
-
//#region src/components/grid-fx.tsx
|
|
7
|
-
const CELL_SIZE = 46;
|
|
8
|
-
const TTL_MIN = 42;
|
|
9
|
-
const TTL_MAX = 78;
|
|
10
|
-
const SPAWN_MIN = 180;
|
|
11
|
-
const SPAWN_MAX = 480;
|
|
12
|
-
const rand = (min, max) => min + Math.random() * (max - min);
|
|
13
|
-
function resolveColors(element) {
|
|
14
|
-
const styles = getComputedStyle(element);
|
|
15
|
-
return {
|
|
16
|
-
color: styles.getPropertyValue("--color-primary").trim() || "#e90060",
|
|
17
|
-
colorHi: styles.getPropertyValue("--color-primary-hover").trim() || "#f22b79"
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function strokeFull(ctx, horiz, at, width, height) {
|
|
21
|
-
ctx.beginPath();
|
|
22
|
-
if (horiz) {
|
|
23
|
-
ctx.moveTo(0, at);
|
|
24
|
-
ctx.lineTo(width, at);
|
|
25
|
-
} else {
|
|
26
|
-
ctx.moveTo(at, 0);
|
|
27
|
-
ctx.lineTo(at, height);
|
|
28
|
-
}
|
|
29
|
-
ctx.stroke();
|
|
30
|
-
}
|
|
31
|
-
function drawGlitchLine(ctx, line, width, height) {
|
|
32
|
-
const { horiz, at, color, colorHi } = line;
|
|
33
|
-
const span = horiz ? width : height;
|
|
34
|
-
const progress = line.life / line.ttl;
|
|
35
|
-
const envelope = progress < .08 ? progress / .08 : 1 - (progress - .08) / .92;
|
|
36
|
-
const base = Math.max(0, envelope);
|
|
37
|
-
ctx.lineCap = "round";
|
|
38
|
-
const ghosts = [
|
|
39
|
-
{
|
|
40
|
-
offset: 0,
|
|
41
|
-
alpha: .85,
|
|
42
|
-
blur: 12
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
offset: rand(-3, 3),
|
|
46
|
-
alpha: .35,
|
|
47
|
-
blur: 0
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
offset: rand(-7, 7),
|
|
51
|
-
alpha: .2,
|
|
52
|
-
blur: 0
|
|
53
|
-
}
|
|
54
|
-
];
|
|
55
|
-
for (const ghost of ghosts) {
|
|
56
|
-
ctx.globalAlpha = base * ghost.alpha * (Math.random() < .1 ? .3 : 1);
|
|
57
|
-
ctx.strokeStyle = color;
|
|
58
|
-
ctx.lineWidth = 1.5;
|
|
59
|
-
ctx.shadowBlur = ghost.blur;
|
|
60
|
-
ctx.shadowColor = color;
|
|
61
|
-
strokeFull(ctx, horiz, at + ghost.offset, width, height);
|
|
62
|
-
}
|
|
63
|
-
if (Math.random() < .5) {
|
|
64
|
-
ctx.globalAlpha = base * .6;
|
|
65
|
-
ctx.lineWidth = 2;
|
|
66
|
-
ctx.shadowBlur = 0;
|
|
67
|
-
ctx.strokeStyle = colorHi;
|
|
68
|
-
for (let segment = 0; segment < 3; segment++) {
|
|
69
|
-
const start = rand(0, span * .85);
|
|
70
|
-
const end = start + rand(20, 80);
|
|
71
|
-
const jitter = rand(-4, 4);
|
|
72
|
-
ctx.beginPath();
|
|
73
|
-
if (horiz) {
|
|
74
|
-
ctx.moveTo(start, at + jitter);
|
|
75
|
-
ctx.lineTo(end, at + jitter);
|
|
76
|
-
} else {
|
|
77
|
-
ctx.moveTo(at + jitter, start);
|
|
78
|
-
ctx.lineTo(at + jitter, end);
|
|
79
|
-
}
|
|
80
|
-
ctx.stroke();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function GridFx({ className, cellSize = CELL_SIZE, style, ...props }) {
|
|
85
|
-
const reduced = useReducedMotion();
|
|
86
|
-
const canvasRef = React$1.useRef(null);
|
|
87
|
-
React$1.useEffect(() => {
|
|
88
|
-
if (reduced) return;
|
|
89
|
-
const canvas = canvasRef.current;
|
|
90
|
-
const parent = canvas?.parentElement;
|
|
91
|
-
const ctx = canvas?.getContext("2d");
|
|
92
|
-
if (!canvas || !parent || !ctx) return;
|
|
93
|
-
let width = 0;
|
|
94
|
-
let height = 0;
|
|
95
|
-
let dpr = 1;
|
|
96
|
-
let frame = 0;
|
|
97
|
-
let nextIn = rand(SPAWN_MIN, SPAWN_MAX);
|
|
98
|
-
let onScreen = true;
|
|
99
|
-
const lines = [];
|
|
100
|
-
const resize = () => {
|
|
101
|
-
dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
102
|
-
width = parent.clientWidth;
|
|
103
|
-
height = parent.clientHeight;
|
|
104
|
-
canvas.width = width * dpr;
|
|
105
|
-
canvas.height = height * dpr;
|
|
106
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
107
|
-
};
|
|
108
|
-
const spawn = () => {
|
|
109
|
-
const horiz = Math.random() < .5;
|
|
110
|
-
const tracks = Math.ceil((horiz ? height : width) / cellSize);
|
|
111
|
-
const { color, colorHi } = resolveColors(canvas);
|
|
112
|
-
lines.push({
|
|
113
|
-
horiz,
|
|
114
|
-
at: Math.floor(Math.random() * tracks) * cellSize + .5,
|
|
115
|
-
life: 0,
|
|
116
|
-
ttl: rand(TTL_MIN, TTL_MAX),
|
|
117
|
-
color,
|
|
118
|
-
colorHi
|
|
119
|
-
});
|
|
120
|
-
};
|
|
121
|
-
const tick = () => {
|
|
122
|
-
ctx.globalAlpha = 1;
|
|
123
|
-
ctx.shadowBlur = 0;
|
|
124
|
-
ctx.clearRect(0, 0, width, height);
|
|
125
|
-
if (--nextIn <= 0) {
|
|
126
|
-
spawn();
|
|
127
|
-
nextIn = rand(SPAWN_MIN, SPAWN_MAX);
|
|
128
|
-
}
|
|
129
|
-
for (let index = lines.length - 1; index >= 0; index--) {
|
|
130
|
-
const line = lines[index];
|
|
131
|
-
if (!line) continue;
|
|
132
|
-
line.life++;
|
|
133
|
-
drawGlitchLine(ctx, line, width, height);
|
|
134
|
-
if (line.life >= line.ttl) lines.splice(index, 1);
|
|
135
|
-
}
|
|
136
|
-
ctx.globalAlpha = 1;
|
|
137
|
-
ctx.shadowBlur = 0;
|
|
138
|
-
frame = requestAnimationFrame(tick);
|
|
139
|
-
};
|
|
140
|
-
const running = () => onScreen && document.visibilityState === "visible";
|
|
141
|
-
const start = () => {
|
|
142
|
-
if (!frame && running()) frame = requestAnimationFrame(tick);
|
|
143
|
-
};
|
|
144
|
-
const stop = () => {
|
|
145
|
-
if (frame) cancelAnimationFrame(frame);
|
|
146
|
-
frame = 0;
|
|
147
|
-
ctx.clearRect(0, 0, width, height);
|
|
148
|
-
};
|
|
149
|
-
resize();
|
|
150
|
-
start();
|
|
151
|
-
const resizeObserver = new ResizeObserver(() => resize());
|
|
152
|
-
resizeObserver.observe(parent);
|
|
153
|
-
const intersectionObserver = new IntersectionObserver(([entry]) => {
|
|
154
|
-
if (!entry) return;
|
|
155
|
-
onScreen = entry.isIntersecting;
|
|
156
|
-
if (onScreen) start();
|
|
157
|
-
else stop();
|
|
158
|
-
});
|
|
159
|
-
intersectionObserver.observe(canvas);
|
|
160
|
-
const onVisibility = () => {
|
|
161
|
-
if (running()) start();
|
|
162
|
-
else stop();
|
|
163
|
-
};
|
|
164
|
-
document.addEventListener("visibilitychange", onVisibility);
|
|
165
|
-
return () => {
|
|
166
|
-
stop();
|
|
167
|
-
resizeObserver.disconnect();
|
|
168
|
-
intersectionObserver.disconnect();
|
|
169
|
-
document.removeEventListener("visibilitychange", onVisibility);
|
|
170
|
-
};
|
|
171
|
-
}, [reduced, cellSize]);
|
|
172
|
-
if (reduced) return null;
|
|
173
|
-
return /* @__PURE__ */ jsx("canvas", {
|
|
174
|
-
ref: canvasRef,
|
|
175
|
-
"aria-hidden": true,
|
|
176
|
-
"data-slot": "grid-fx",
|
|
177
|
-
className: cn("pointer-events-none absolute inset-0 h-full w-full", className),
|
|
178
|
-
style: {
|
|
179
|
-
zIndex: -1,
|
|
180
|
-
WebkitMaskImage: "radial-gradient(120% 95% at 50% 0%, #000 30%, transparent 100%)",
|
|
181
|
-
maskImage: "radial-gradient(120% 95% at 50% 0%, #000 30%, transparent 100%)",
|
|
182
|
-
...style
|
|
183
|
-
},
|
|
184
|
-
...props
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
//#endregion
|
|
188
|
-
export { GridFx };
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
import { useReducedMotion } from "../hooks/use-reduced-motion";
|
|
6
|
-
import { cn } from "../lib/cn";
|
|
7
|
-
|
|
8
|
-
const CELL_SIZE = 46;
|
|
9
|
-
const TTL_MIN = 42;
|
|
10
|
-
const TTL_MAX = 78;
|
|
11
|
-
const SPAWN_MIN = 180;
|
|
12
|
-
const SPAWN_MAX = 480;
|
|
13
|
-
|
|
14
|
-
interface GlitchLine {
|
|
15
|
-
horiz: boolean;
|
|
16
|
-
at: number;
|
|
17
|
-
life: number;
|
|
18
|
-
ttl: number;
|
|
19
|
-
color: string;
|
|
20
|
-
colorHi: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const rand = (min: number, max: number) => min + Math.random() * (max - min);
|
|
24
|
-
|
|
25
|
-
function resolveColors(element: HTMLElement) {
|
|
26
|
-
const styles = getComputedStyle(element);
|
|
27
|
-
return {
|
|
28
|
-
color: styles.getPropertyValue("--color-primary").trim() || "#e90060" /* --color-primary */,
|
|
29
|
-
colorHi: styles.getPropertyValue("--color-primary-hover").trim() || "#f22b79" /* --color-primary-hover */,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function strokeFull(ctx: CanvasRenderingContext2D, horiz: boolean, at: number, width: number, height: number) {
|
|
34
|
-
ctx.beginPath();
|
|
35
|
-
if (horiz) {
|
|
36
|
-
ctx.moveTo(0, at);
|
|
37
|
-
ctx.lineTo(width, at);
|
|
38
|
-
} else {
|
|
39
|
-
ctx.moveTo(at, 0);
|
|
40
|
-
ctx.lineTo(at, height);
|
|
41
|
-
}
|
|
42
|
-
ctx.stroke();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function drawGlitchLine(ctx: CanvasRenderingContext2D, line: GlitchLine, width: number, height: number) {
|
|
46
|
-
const { horiz, at, color, colorHi } = line;
|
|
47
|
-
const span = horiz ? width : height;
|
|
48
|
-
const progress = line.life / line.ttl;
|
|
49
|
-
const envelope = progress < 0.08 ? progress / 0.08 : 1 - (progress - 0.08) / 0.92;
|
|
50
|
-
const base = Math.max(0, envelope);
|
|
51
|
-
|
|
52
|
-
ctx.lineCap = "round";
|
|
53
|
-
|
|
54
|
-
const ghosts = [
|
|
55
|
-
{ offset: 0, alpha: 0.85, blur: 12 },
|
|
56
|
-
{ offset: rand(-3, 3), alpha: 0.35, blur: 0 },
|
|
57
|
-
{ offset: rand(-7, 7), alpha: 0.2, blur: 0 },
|
|
58
|
-
];
|
|
59
|
-
for (const ghost of ghosts) {
|
|
60
|
-
ctx.globalAlpha = base * ghost.alpha * (Math.random() < 0.1 ? 0.3 : 1);
|
|
61
|
-
ctx.strokeStyle = color;
|
|
62
|
-
ctx.lineWidth = 1.5;
|
|
63
|
-
ctx.shadowBlur = ghost.blur;
|
|
64
|
-
ctx.shadowColor = color;
|
|
65
|
-
strokeFull(ctx, horiz, at + ghost.offset, width, height);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (Math.random() < 0.5) {
|
|
69
|
-
ctx.globalAlpha = base * 0.6;
|
|
70
|
-
ctx.lineWidth = 2;
|
|
71
|
-
ctx.shadowBlur = 0;
|
|
72
|
-
ctx.strokeStyle = colorHi;
|
|
73
|
-
for (let segment = 0; segment < 3; segment++) {
|
|
74
|
-
const start = rand(0, span * 0.85);
|
|
75
|
-
const end = start + rand(20, 80);
|
|
76
|
-
const jitter = rand(-4, 4);
|
|
77
|
-
ctx.beginPath();
|
|
78
|
-
if (horiz) {
|
|
79
|
-
ctx.moveTo(start, at + jitter);
|
|
80
|
-
ctx.lineTo(end, at + jitter);
|
|
81
|
-
} else {
|
|
82
|
-
ctx.moveTo(at + jitter, start);
|
|
83
|
-
ctx.lineTo(at + jitter, end);
|
|
84
|
-
}
|
|
85
|
-
ctx.stroke();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function GridFx({
|
|
91
|
-
className,
|
|
92
|
-
cellSize = CELL_SIZE,
|
|
93
|
-
style,
|
|
94
|
-
...props
|
|
95
|
-
}: React.ComponentProps<"canvas"> & { cellSize?: number }) {
|
|
96
|
-
const reduced = useReducedMotion();
|
|
97
|
-
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
|
98
|
-
|
|
99
|
-
React.useEffect(() => {
|
|
100
|
-
if (reduced) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const canvas = canvasRef.current;
|
|
104
|
-
const parent = canvas?.parentElement;
|
|
105
|
-
const ctx = canvas?.getContext("2d");
|
|
106
|
-
if (!canvas || !parent || !ctx) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
let width = 0;
|
|
111
|
-
let height = 0;
|
|
112
|
-
let dpr = 1;
|
|
113
|
-
let frame = 0;
|
|
114
|
-
let nextIn = rand(SPAWN_MIN, SPAWN_MAX);
|
|
115
|
-
let onScreen = true;
|
|
116
|
-
const lines: GlitchLine[] = [];
|
|
117
|
-
|
|
118
|
-
const resize = () => {
|
|
119
|
-
dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
120
|
-
width = parent.clientWidth;
|
|
121
|
-
height = parent.clientHeight;
|
|
122
|
-
canvas.width = width * dpr;
|
|
123
|
-
canvas.height = height * dpr;
|
|
124
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const spawn = () => {
|
|
128
|
-
const horiz = Math.random() < 0.5;
|
|
129
|
-
const tracks = Math.ceil((horiz ? height : width) / cellSize);
|
|
130
|
-
const { color, colorHi } = resolveColors(canvas);
|
|
131
|
-
lines.push({
|
|
132
|
-
horiz,
|
|
133
|
-
at: Math.floor(Math.random() * tracks) * cellSize + 0.5,
|
|
134
|
-
life: 0,
|
|
135
|
-
ttl: rand(TTL_MIN, TTL_MAX),
|
|
136
|
-
color,
|
|
137
|
-
colorHi,
|
|
138
|
-
});
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const tick = () => {
|
|
142
|
-
ctx.globalAlpha = 1;
|
|
143
|
-
ctx.shadowBlur = 0;
|
|
144
|
-
ctx.clearRect(0, 0, width, height);
|
|
145
|
-
|
|
146
|
-
if (--nextIn <= 0) {
|
|
147
|
-
spawn();
|
|
148
|
-
nextIn = rand(SPAWN_MIN, SPAWN_MAX);
|
|
149
|
-
}
|
|
150
|
-
for (let index = lines.length - 1; index >= 0; index--) {
|
|
151
|
-
const line = lines[index];
|
|
152
|
-
if (!line) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
line.life++;
|
|
156
|
-
drawGlitchLine(ctx, line, width, height);
|
|
157
|
-
if (line.life >= line.ttl) {
|
|
158
|
-
lines.splice(index, 1);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
ctx.globalAlpha = 1;
|
|
163
|
-
ctx.shadowBlur = 0;
|
|
164
|
-
frame = requestAnimationFrame(tick);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const running = () => onScreen && document.visibilityState === "visible";
|
|
168
|
-
const start = () => {
|
|
169
|
-
if (!frame && running()) {
|
|
170
|
-
frame = requestAnimationFrame(tick);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
const stop = () => {
|
|
174
|
-
if (frame) {
|
|
175
|
-
cancelAnimationFrame(frame);
|
|
176
|
-
}
|
|
177
|
-
frame = 0;
|
|
178
|
-
ctx.clearRect(0, 0, width, height);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
resize();
|
|
182
|
-
start();
|
|
183
|
-
|
|
184
|
-
const resizeObserver = new ResizeObserver(() => resize());
|
|
185
|
-
resizeObserver.observe(parent);
|
|
186
|
-
|
|
187
|
-
const intersectionObserver = new IntersectionObserver(([entry]) => {
|
|
188
|
-
if (!entry) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
onScreen = entry.isIntersecting;
|
|
192
|
-
if (onScreen) {
|
|
193
|
-
start();
|
|
194
|
-
} else {
|
|
195
|
-
stop();
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
intersectionObserver.observe(canvas);
|
|
199
|
-
|
|
200
|
-
const onVisibility = () => {
|
|
201
|
-
if (running()) {
|
|
202
|
-
start();
|
|
203
|
-
} else {
|
|
204
|
-
stop();
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
document.addEventListener("visibilitychange", onVisibility);
|
|
208
|
-
|
|
209
|
-
return () => {
|
|
210
|
-
stop();
|
|
211
|
-
resizeObserver.disconnect();
|
|
212
|
-
intersectionObserver.disconnect();
|
|
213
|
-
document.removeEventListener("visibilitychange", onVisibility);
|
|
214
|
-
};
|
|
215
|
-
}, [reduced, cellSize]);
|
|
216
|
-
|
|
217
|
-
if (reduced) {
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
<canvas
|
|
223
|
-
ref={canvasRef}
|
|
224
|
-
aria-hidden
|
|
225
|
-
data-slot="grid-fx"
|
|
226
|
-
className={cn("pointer-events-none absolute inset-0 h-full w-full", className)}
|
|
227
|
-
style={{
|
|
228
|
-
zIndex: -1,
|
|
229
|
-
WebkitMaskImage: "radial-gradient(120% 95% at 50% 0%, #000 30%, transparent 100%)",
|
|
230
|
-
maskImage: "radial-gradient(120% 95% at 50% 0%, #000 30%, transparent 100%)",
|
|
231
|
-
...style,
|
|
232
|
-
}}
|
|
233
|
-
{...props}
|
|
234
|
-
/>
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export { GridFx };
|