@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.
@@ -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" | "primary" | "secondary" | "error" | null | undefined;
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 gap-2", className),
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 motion-safe:transition-[width,opacity] motion-safe:duration-700 motion-safe:ease-out",
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-16 shrink-0 text-right font-mono text-text-xs tabular-nums motion-safe:transition-colors",
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" | "primary" | "secondary" | "tertiary" | "link" | "link-muted" | "cta" | null | undefined;
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-[72%] truncate font-mono text-[10px] text-quaternary-foreground uppercase tracking-[0.18em]",
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-3",
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-3 font-mono tabular-nums",
158
+ className: "flex shrink-0 items-center gap-2 font-mono tabular-nums",
159
159
  children: [/* @__PURE__ */ jsx("span", {
160
- className: "min-w-[3.5rem] text-right font-semibold text-foreground",
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-10 text-right text-quaternary-foreground",
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?: "primary" | "secondary" | null | undefined;
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, type WizardStep, WizardSteps };
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, WizardSteps };
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.g172fb9f",
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.18.0",
26
+ "lucide-react": "^1.21.0",
27
27
  "react": "^19.2.7",
28
28
  "react-dom": "^19.2.7",
29
- "react-hook-form": "^7.79.0",
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.13",
36
- "@radix-ui/react-avatar": "^1.1.12",
37
- "@radix-ui/react-checkbox": "^1.3.4",
38
- "@radix-ui/react-collapsible": "^1.1.13",
39
- "@radix-ui/react-dialog": "^1.1.16",
40
- "@radix-ui/react-dropdown-menu": "^2.1.17",
41
- "@radix-ui/react-label": "^2.1.9",
42
- "@radix-ui/react-popover": "^1.1.16",
43
- "@radix-ui/react-radio-group": "^1.4.0",
44
- "@radix-ui/react-scroll-area": "^1.2.11",
45
- "@radix-ui/react-select": "^2.3.0",
46
- "@radix-ui/react-separator": "^1.1.9",
47
- "@radix-ui/react-slot": "^1.2.5",
48
- "@radix-ui/react-switch": "^1.3.0",
49
- "@radix-ui/react-tabs": "^1.1.14",
50
- "@radix-ui/react-toggle-group": "^1.1.12",
51
- "@radix-ui/react-tooltip": "^1.2.9",
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.18.0",
64
- "react-hook-form": "^7.79.0",
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.2",
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 gap-2", className)}>
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 motion-safe:transition-[width,opacity] motion-safe:duration-700 motion-safe:ease-out"
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-16 shrink-0 text-right font-mono text-text-xs tabular-nums motion-safe:transition-colors"
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-[72%] truncate font-mono text-[10px] text-quaternary-foreground uppercase tracking-[0.18em]">
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-3">
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-3 font-mono tabular-nums">
189
- <span className="min-w-[3.5rem] text-right font-semibold text-foreground">
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-10 text-right text-quaternary-foreground">
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 type { WizardStep };
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="chart-canvas mx-auto max-w-[1600px] p-8">
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="chart-canvas mx-auto max-w-[1600px] p-8">
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="chart-canvas mx-auto max-w-[1600px] p-8">
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="chart-canvas mx-auto max-w-[1600px] p-8">
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="chart-canvas mx-auto max-w-[1600px] p-8">
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="chart-canvas mx-auto max-w-[1600px] p-8">
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 { WizardProgress, type WizardStep, WizardSteps } from "../components/wizard";
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: WizardStep[] = [
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
- <WizardSteps steps={steps} activeIdx={activeIdx} onSelect={setActiveIdx} ariaLabel="Submission steps" />
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
- <WizardSteps steps={steps} activeIdx={activeIdx} onSelect={setActiveIdx} />
57
+ <StepRail activeIdx={activeIdx} onSelect={setActiveIdx} />
40
58
  </div>
41
59
  </div>
42
60
 
@@ -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 };