@alpic-ai/ui 0.0.0-dev.g05467b7 → 0.0.0-dev.g05c89ce
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/area-chart.d.mts +2 -0
- package/dist/components/area-chart.mjs +9 -3
- package/dist/components/bar-chart.d.mts +2 -0
- package/dist/components/bar-chart.mjs +9 -3
- package/dist/components/bar-list.d.mts +3 -0
- package/dist/components/bar-list.mjs +25 -12
- package/dist/components/chart-card.d.mts +1 -1
- package/dist/components/chart-card.mjs +1 -1
- package/dist/components/chart-container.d.mts +1 -1
- package/dist/components/chart-legend.d.mts +5 -0
- package/dist/components/chart-legend.mjs +11 -2
- package/dist/components/donut-chart.mjs +9 -5
- package/dist/components/form.mjs +1 -1
- package/dist/components/heatmap-chart.d.mts +8 -0
- package/dist/components/heatmap-chart.mjs +39 -8
- package/dist/components/line-chart.d.mts +2 -0
- package/dist/components/line-chart.mjs +10 -3
- package/dist/components/stat.d.mts +3 -1
- package/dist/components/stat.mjs +14 -4
- package/dist/components/wizard.d.mts +1 -19
- package/dist/components/wizard.mjs +1 -19
- package/dist/lib/chart.mjs +16 -1
- package/package.json +23 -23
- package/src/components/area-chart.tsx +12 -4
- package/src/components/bar-chart.tsx +12 -4
- package/src/components/bar-list.tsx +26 -10
- package/src/components/chart-card.tsx +8 -6
- package/src/components/chart-container.tsx +2 -0
- package/src/components/chart-legend.tsx +10 -2
- package/src/components/donut-chart.tsx +6 -6
- package/src/components/form.tsx +1 -1
- package/src/components/heatmap-chart.tsx +62 -18
- package/src/components/line-chart.tsx +18 -5
- package/src/components/stat.tsx +10 -6
- package/src/components/wizard.tsx +1 -35
- package/src/lib/chart.ts +34 -0
- 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 +23 -5
- 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
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "recharts";
|
|
17
17
|
|
|
18
18
|
import { useReducedMotion } from "../hooks/use-reduced-motion";
|
|
19
|
-
import { type ChartSeries, resolveSeries } from "../lib/chart";
|
|
19
|
+
import { type ChartSeries, makeXAxisTick, resolveSeries } from "../lib/chart";
|
|
20
20
|
import type { ChartPaletteName } from "../lib/chart-palette";
|
|
21
21
|
import { cn } from "../lib/cn";
|
|
22
22
|
import type { ChartMarker } from "./area-chart";
|
|
@@ -32,6 +32,7 @@ export interface LineChartProps {
|
|
|
32
32
|
series: ChartSeries[];
|
|
33
33
|
curve?: keyof typeof CURVE_TYPE;
|
|
34
34
|
legend?: boolean;
|
|
35
|
+
legendAlign?: "left" | "center" | "right";
|
|
35
36
|
valueFlags?: boolean;
|
|
36
37
|
dots?: boolean;
|
|
37
38
|
height?: number;
|
|
@@ -52,6 +53,7 @@ function LineChart({
|
|
|
52
53
|
series,
|
|
53
54
|
curve = "monotone",
|
|
54
55
|
legend = false,
|
|
56
|
+
legendAlign = "left",
|
|
55
57
|
valueFlags = false,
|
|
56
58
|
dots = false,
|
|
57
59
|
height = 200,
|
|
@@ -170,11 +172,22 @@ function LineChart({
|
|
|
170
172
|
no data in range
|
|
171
173
|
</div>
|
|
172
174
|
) : (
|
|
173
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
175
|
+
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 0, height }}>
|
|
174
176
|
<RechartsLineChart data={data as Record<string, string | number>[]} margin={margin}>
|
|
175
177
|
<CartesianGrid vertical={false} stroke={theme.grid} strokeDasharray="2 4" />
|
|
176
|
-
<XAxis
|
|
177
|
-
|
|
178
|
+
<XAxis
|
|
179
|
+
dataKey={index}
|
|
180
|
+
{...axis}
|
|
181
|
+
tick={makeXAxisTick(theme)}
|
|
182
|
+
interval="preserveStartEnd"
|
|
183
|
+
minTickGap={44}
|
|
184
|
+
/>
|
|
185
|
+
<YAxis
|
|
186
|
+
{...axis}
|
|
187
|
+
width={yAxisWidth}
|
|
188
|
+
domain={["auto", "auto"]}
|
|
189
|
+
tickFormatter={(value: number) => valueFormatter(value)}
|
|
190
|
+
/>
|
|
178
191
|
<Tooltip
|
|
179
192
|
offset={12}
|
|
180
193
|
allowEscapeViewBox={{ x: false, y: false }}
|
|
@@ -256,7 +269,7 @@ function LineChart({
|
|
|
256
269
|
)}
|
|
257
270
|
</div>
|
|
258
271
|
|
|
259
|
-
{legend && !isEmpty && <ChartLegend items={legendItems}
|
|
272
|
+
{legend && !isEmpty && <ChartLegend items={legendItems} align={legendAlign} insetLeft={yAxisWidth} />}
|
|
260
273
|
</div>
|
|
261
274
|
);
|
|
262
275
|
}
|
package/src/components/stat.tsx
CHANGED
|
@@ -31,8 +31,9 @@ export interface StatDelta {
|
|
|
31
31
|
export interface StatProps extends React.ComponentProps<"div"> {
|
|
32
32
|
value: React.ReactNode;
|
|
33
33
|
unit?: string;
|
|
34
|
-
delta?: StatDelta;
|
|
34
|
+
delta?: StatDelta | null;
|
|
35
35
|
sparkline?: number[] | Array<{ value: number }>;
|
|
36
|
+
semantic?: "error" | "warning" | "success";
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const toSparkData = (sparkline: StatProps["sparkline"]) =>
|
|
@@ -40,12 +41,15 @@ const toSparkData = (sparkline: StatProps["sparkline"]) =>
|
|
|
40
41
|
typeof point === "number" ? { index, value: point } : { index, value: point.value },
|
|
41
42
|
);
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const SEMANTIC_KEY = { error: "destructive", warning: "warning", success: "success" } as const;
|
|
45
|
+
|
|
46
|
+
function Stat({ value, unit, delta, sparkline, semantic, className, ...props }: StatProps) {
|
|
47
|
+
const { palette, theme } = useChartContext();
|
|
45
48
|
const gradientId = React.useId().replace(/:/g, "");
|
|
46
49
|
const sparkData = React.useMemo(() => toSparkData(sparkline), [sparkline]);
|
|
50
|
+
const hasSpark = sparkData.some((point) => point.value > 0);
|
|
47
51
|
// biome-ignore lint/style/noNonNullAssertion: palettes are never empty
|
|
48
|
-
const sparkColor = palette[0]!;
|
|
52
|
+
const sparkColor = semantic ? theme[SEMANTIC_KEY[semantic]] : palette[0]!;
|
|
49
53
|
|
|
50
54
|
const sentiment =
|
|
51
55
|
delta && (delta.invert ? delta.direction === "down" : delta.direction === "up") ? "positive" : "negative";
|
|
@@ -64,9 +68,9 @@ function Stat({ value, unit, delta, sparkline, className, ...props }: StatProps)
|
|
|
64
68
|
</DeltaPill>
|
|
65
69
|
)}
|
|
66
70
|
</div>
|
|
67
|
-
{
|
|
71
|
+
{hasSpark && (
|
|
68
72
|
<div className="h-9 w-full">
|
|
69
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
73
|
+
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 0, height: 36 }}>
|
|
70
74
|
<AreaChart data={sparkData} margin={{ top: 4, right: 2, bottom: 0, left: 2 }}>
|
|
71
75
|
<defs>
|
|
72
76
|
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
|
@@ -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 };
|
package/src/lib/chart.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
import type { ChartTheme } from "../hooks/use-chart-theme";
|
|
2
3
|
import { luminance, paletteColor } from "./chart-palette";
|
|
3
4
|
|
|
@@ -54,3 +55,36 @@ export const resolveSeries = (
|
|
|
54
55
|
*/
|
|
55
56
|
export const orderByLuminance = (series: ResolvedSeries[]) =>
|
|
56
57
|
[...series].sort((lower, upper) => luminance(lower.color) - luminance(upper.color));
|
|
58
|
+
|
|
59
|
+
export const makeXAxisTick =
|
|
60
|
+
(theme: ChartTheme) =>
|
|
61
|
+
({
|
|
62
|
+
x,
|
|
63
|
+
y,
|
|
64
|
+
payload,
|
|
65
|
+
index,
|
|
66
|
+
visibleTicksCount,
|
|
67
|
+
}: {
|
|
68
|
+
x?: string | number;
|
|
69
|
+
y?: string | number;
|
|
70
|
+
payload?: { value?: string | number };
|
|
71
|
+
index?: number;
|
|
72
|
+
visibleTicksCount?: number;
|
|
73
|
+
}) => {
|
|
74
|
+
const isFirst = index === 0;
|
|
75
|
+
const isLast = visibleTicksCount != null && index === visibleTicksCount - 1;
|
|
76
|
+
const anchor = isFirst ? "start" : isLast ? "end" : "middle";
|
|
77
|
+
return React.createElement(
|
|
78
|
+
"text",
|
|
79
|
+
{
|
|
80
|
+
x: Number(x ?? 0),
|
|
81
|
+
y: Number(y ?? 0),
|
|
82
|
+
dy: 12,
|
|
83
|
+
textAnchor: anchor,
|
|
84
|
+
fill: theme.axisForeground,
|
|
85
|
+
fontFamily: theme.fontMono,
|
|
86
|
+
fontSize: 10,
|
|
87
|
+
},
|
|
88
|
+
String(payload?.value ?? ""),
|
|
89
|
+
);
|
|
90
|
+
};
|
|
@@ -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,18 +1,36 @@
|
|
|
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" },
|
|
12
13
|
{ id: "tools", label: "Tools & test cases" },
|
|
13
|
-
{ id: "review", label: "Review &
|
|
14
|
+
{ id: "review", label: "Review & export" },
|
|
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 };
|