@alpic-ai/ui 0.0.0-dev.g16d80c2 → 0.0.0-dev.g172fb9f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/components/area-chart.d.mts +62 -0
  2. package/dist/components/area-chart.mjs +269 -0
  3. package/dist/components/badge.d.mts +1 -1
  4. package/dist/components/bar-chart.d.mts +48 -0
  5. package/dist/components/bar-chart.mjs +256 -0
  6. package/dist/components/bar-list.d.mts +28 -0
  7. package/dist/components/bar-list.mjs +98 -0
  8. package/dist/components/button.d.mts +1 -1
  9. package/dist/components/chart-card.d.mts +25 -0
  10. package/dist/components/chart-card.mjs +48 -0
  11. package/dist/components/chart-container.d.mts +20 -0
  12. package/dist/components/chart-container.mjs +37 -0
  13. package/dist/components/chart-legend.d.mts +16 -0
  14. package/dist/components/chart-legend.mjs +26 -0
  15. package/dist/components/chart-tooltip.d.mts +33 -0
  16. package/dist/components/chart-tooltip.mjs +52 -0
  17. package/dist/components/donut-chart.d.mts +46 -0
  18. package/dist/components/donut-chart.mjs +185 -0
  19. package/dist/components/grid-fx.d.mts +13 -0
  20. package/dist/components/grid-fx.mjs +188 -0
  21. package/dist/components/heatmap-chart.d.mts +40 -0
  22. package/dist/components/heatmap-chart.mjs +198 -0
  23. package/dist/components/line-chart.d.mts +55 -0
  24. package/dist/components/line-chart.mjs +211 -0
  25. package/dist/components/spinner.d.mts +1 -1
  26. package/dist/components/stat.d.mts +30 -0
  27. package/dist/components/stat.mjs +107 -0
  28. package/dist/hooks/use-chart-theme.d.mts +18 -0
  29. package/dist/hooks/use-chart-theme.mjs +57 -0
  30. package/dist/hooks/use-reduced-motion.d.mts +4 -0
  31. package/dist/hooks/use-reduced-motion.mjs +16 -0
  32. package/dist/lib/chart-palette.d.mts +4 -0
  33. package/dist/lib/chart-palette.mjs +95 -0
  34. package/dist/lib/chart.d.mts +14 -0
  35. package/dist/lib/chart.mjs +27 -0
  36. package/package.json +2 -1
  37. package/src/components/area-chart.tsx +339 -0
  38. package/src/components/bar-chart.tsx +309 -0
  39. package/src/components/bar-list.tsx +150 -0
  40. package/src/components/chart-card.tsx +63 -0
  41. package/src/components/chart-container.tsx +49 -0
  42. package/src/components/chart-legend.tsx +41 -0
  43. package/src/components/chart-tooltip.tsx +93 -0
  44. package/src/components/donut-chart.tsx +217 -0
  45. package/src/components/grid-fx.tsx +238 -0
  46. package/src/components/heatmap-chart.tsx +287 -0
  47. package/src/components/line-chart.tsx +264 -0
  48. package/src/components/stat.tsx +109 -0
  49. package/src/hooks/use-chart-theme.ts +75 -0
  50. package/src/hooks/use-reduced-motion.ts +17 -0
  51. package/src/lib/chart-palette.ts +110 -0
  52. package/src/lib/chart.ts +56 -0
  53. package/src/stories/area-chart.stories.tsx +200 -0
  54. package/src/stories/bar-chart.stories.tsx +169 -0
  55. package/src/stories/bar-list.stories.tsx +85 -0
  56. package/src/stories/donut-chart.stories.tsx +112 -0
  57. package/src/stories/heatmap-chart.stories.tsx +107 -0
  58. package/src/stories/line-chart.stories.tsx +146 -0
  59. package/src/stories/stat.stories.tsx +64 -0
  60. package/src/styles/tokens.css +63 -0
@@ -0,0 +1,188 @@
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 };
@@ -0,0 +1,40 @@
1
+ import { ChartPaletteName } from "../lib/chart-palette.mjs";
2
+ import * as React$1 from "react";
3
+
4
+ //#region src/components/heatmap-chart.d.ts
5
+ interface HeatmapChartProps {
6
+ data: ReadonlyArray<Record<string, string | number | null | undefined>>;
7
+ xKey: string;
8
+ yKey: string;
9
+ dataKey?: string;
10
+ variant?: "square" | "dot";
11
+ palette?: ChartPaletteName;
12
+ xLabels?: readonly string[];
13
+ yLabels?: readonly string[];
14
+ highlightPeak?: boolean;
15
+ loading?: boolean;
16
+ valueFormatter?: (value: number) => string;
17
+ xTickFormatter?: (label: string) => string;
18
+ yTickFormatter?: (label: string) => string;
19
+ ariaLabel?: string;
20
+ className?: string;
21
+ }
22
+ declare function HeatmapChart({
23
+ data,
24
+ xKey,
25
+ yKey,
26
+ dataKey,
27
+ variant,
28
+ palette,
29
+ xLabels,
30
+ yLabels,
31
+ highlightPeak,
32
+ loading,
33
+ valueFormatter,
34
+ xTickFormatter,
35
+ yTickFormatter,
36
+ ariaLabel,
37
+ className
38
+ }: HeatmapChartProps): React$1.JSX.Element;
39
+ //#endregion
40
+ export { HeatmapChart, HeatmapChartProps };
@@ -0,0 +1,198 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.mjs";
3
+ import { HEAT_EMPTY, heatColor, heatRamp } from "../lib/chart-palette.mjs";
4
+ import { useChartContext } from "./chart-container.mjs";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import * as React$1 from "react";
7
+ import { createPortal } from "react-dom";
8
+ //#region src/components/heatmap-chart.tsx
9
+ const PLACEHOLDER_HEIGHT = 168;
10
+ const CELL = 22;
11
+ const GAP = 3;
12
+ const PAD = {
13
+ left: 36,
14
+ top: 18,
15
+ right: 6,
16
+ bottom: 6
17
+ };
18
+ const MAX_X_TICKS = 13;
19
+ const DOT_MIN = .42;
20
+ const DOT_RANGE = .52;
21
+ const TOOLTIP_OFFSET = 14;
22
+ const TOOLTIP_WIDTH = 150;
23
+ const TOOLTIP_HEIGHT = 64;
24
+ const pairKey = (rowLabel, colLabel) => JSON.stringify([rowLabel, colLabel]);
25
+ const uniqueInOrder = (values) => {
26
+ const seen = /* @__PURE__ */ new Set();
27
+ const ordered = [];
28
+ for (const value of values) if (!seen.has(value)) {
29
+ seen.add(value);
30
+ ordered.push(value);
31
+ }
32
+ return ordered;
33
+ };
34
+ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square", palette, xLabels, yLabels, highlightPeak = true, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), xTickFormatter, yTickFormatter, ariaLabel = "Activity heatmap", className }) {
35
+ const { paletteName, theme } = useChartContext(palette);
36
+ const [hovered, setHovered] = React$1.useState(null);
37
+ const [mounted, setMounted] = React$1.useState(false);
38
+ React$1.useEffect(() => {
39
+ setMounted(true);
40
+ }, []);
41
+ const { columns, rows, cells, maxValue, peakKey } = React$1.useMemo(() => {
42
+ const columns = xLabels ? [...xLabels] : uniqueInOrder(data.map((row) => String(row[xKey] ?? "")));
43
+ const rows = yLabels ? [...yLabels] : uniqueInOrder(data.map((row) => String(row[yKey] ?? "")));
44
+ const valueAt = /* @__PURE__ */ new Map();
45
+ for (const row of data) valueAt.set(pairKey(String(row[yKey] ?? ""), String(row[xKey] ?? "")), Number(row[dataKey]) || 0);
46
+ let maxValue = 0;
47
+ let peakKey = "";
48
+ return {
49
+ columns,
50
+ rows,
51
+ cells: rows.flatMap((rowLabel, rowIndex) => columns.map((colLabel, colIndex) => {
52
+ const value = valueAt.get(pairKey(rowLabel, colLabel)) ?? 0;
53
+ if (value > maxValue) {
54
+ maxValue = value;
55
+ peakKey = pairKey(rowLabel, colLabel);
56
+ }
57
+ return {
58
+ rowLabel,
59
+ colLabel,
60
+ rowIndex,
61
+ colIndex,
62
+ value
63
+ };
64
+ })),
65
+ maxValue,
66
+ peakKey
67
+ };
68
+ }, [
69
+ data,
70
+ xKey,
71
+ yKey,
72
+ dataKey,
73
+ xLabels,
74
+ yLabels
75
+ ]);
76
+ const isEmpty = columns.length === 0 || rows.length === 0 || maxValue <= 0;
77
+ if (loading) return /* @__PURE__ */ jsxs("div", {
78
+ className: cn("flex items-center justify-center gap-2.5 font-mono text-quaternary-foreground text-text-xs", className),
79
+ style: { minHeight: PLACEHOLDER_HEIGHT },
80
+ children: [/* @__PURE__ */ jsx("span", { className: "size-4 animate-spin rounded-full border-2 border-border border-t-foreground" }), "loading…"]
81
+ });
82
+ if (isEmpty) return /* @__PURE__ */ jsx("div", {
83
+ className: cn("flex items-center justify-center rounded-lg border border-border border-dashed font-mono text-quaternary-foreground text-text-xs", className),
84
+ style: { minHeight: PLACEHOLDER_HEIGHT },
85
+ children: "no data in range"
86
+ });
87
+ const ramp = heatRamp(paletteName, theme.isDark ? HEAT_EMPTY.dark : HEAT_EMPTY.light);
88
+ const totalWidth = PAD.left + columns.length * CELL + (columns.length - 1) * GAP + PAD.right;
89
+ const totalHeight = PAD.top + rows.length * CELL + (rows.length - 1) * GAP + PAD.bottom;
90
+ const xStride = Math.ceil(columns.length / MAX_X_TICKS);
91
+ const cellX = (col) => PAD.left + col * 25;
92
+ const cellY = (row) => PAD.top + row * 25;
93
+ const formatX = (label) => xTickFormatter ? xTickFormatter(label) : label;
94
+ const formatY = (label) => yTickFormatter ? yTickFormatter(label) : label;
95
+ const tooltipLeft = hovered && hovered.x + TOOLTIP_OFFSET + TOOLTIP_WIDTH > window.innerWidth ? hovered.x - TOOLTIP_OFFSET - TOOLTIP_WIDTH : (hovered?.x ?? 0) + TOOLTIP_OFFSET;
96
+ const tooltipTop = hovered && hovered.y + TOOLTIP_OFFSET + TOOLTIP_HEIGHT > window.innerHeight ? hovered.y - TOOLTIP_OFFSET - TOOLTIP_HEIGHT : (hovered?.y ?? 0) + TOOLTIP_OFFSET;
97
+ return /* @__PURE__ */ jsxs("div", {
98
+ "data-slot": "heatmap-chart",
99
+ className: cn("w-full", className),
100
+ children: [/* @__PURE__ */ jsx("div", {
101
+ className: "overflow-x-auto",
102
+ children: /* @__PURE__ */ jsxs("svg", {
103
+ viewBox: `0 0 ${totalWidth} ${totalHeight}`,
104
+ className: "block min-w-[480px]",
105
+ style: {
106
+ width: "100%",
107
+ height: "auto"
108
+ },
109
+ role: "img",
110
+ "aria-label": ariaLabel,
111
+ onMouseLeave: () => setHovered(null),
112
+ children: [
113
+ columns.map((label, col) => col % xStride === 0 ? /* @__PURE__ */ jsx("text", {
114
+ x: cellX(col) + CELL / 2,
115
+ y: PAD.top - 7,
116
+ textAnchor: "middle",
117
+ className: "fill-quaternary-foreground font-mono text-[8.5px]",
118
+ children: formatX(label)
119
+ }, `x-${label}`) : null),
120
+ rows.map((label, row) => /* @__PURE__ */ jsx("text", {
121
+ x: PAD.left - 8,
122
+ y: cellY(row) + CELL / 2 + 3,
123
+ textAnchor: "end",
124
+ className: "fill-quaternary-foreground font-mono text-[8.5px]",
125
+ children: formatY(label)
126
+ }, `y-${label}`)),
127
+ cells.map((cell) => {
128
+ const fraction = cell.value / maxValue;
129
+ const fill = heatColor(ramp, fraction);
130
+ const key = pairKey(cell.rowLabel, cell.colLabel);
131
+ const isPeak = highlightPeak && key === peakKey;
132
+ if (variant === "dot") return /* @__PURE__ */ jsx("circle", {
133
+ cx: cellX(cell.colIndex) + CELL / 2,
134
+ cy: cellY(cell.rowIndex) + CELL / 2,
135
+ r: CELL / 2 * (DOT_MIN + DOT_RANGE * fraction),
136
+ fill,
137
+ stroke: isPeak ? theme.foreground : void 0,
138
+ strokeWidth: isPeak ? 1.4 : void 0
139
+ }, key);
140
+ return /* @__PURE__ */ jsx("rect", {
141
+ x: cellX(cell.colIndex),
142
+ y: cellY(cell.rowIndex),
143
+ width: CELL,
144
+ height: CELL,
145
+ rx: 2.5,
146
+ fill,
147
+ stroke: isPeak ? theme.foreground : void 0,
148
+ strokeWidth: isPeak ? 1.4 : void 0
149
+ }, key);
150
+ }),
151
+ cells.map((cell) => /* @__PURE__ */ jsx("rect", {
152
+ x: cellX(cell.colIndex),
153
+ y: cellY(cell.rowIndex),
154
+ width: 25,
155
+ height: 25,
156
+ fill: "transparent",
157
+ onMouseMove: (event) => setHovered({
158
+ x: event.clientX,
159
+ y: event.clientY,
160
+ rowLabel: cell.rowLabel,
161
+ colLabel: cell.colLabel,
162
+ value: cell.value
163
+ })
164
+ }, `hit-${pairKey(cell.rowLabel, cell.colLabel)}`))
165
+ ]
166
+ })
167
+ }), mounted && hovered && createPortal(/* @__PURE__ */ jsxs("div", {
168
+ className: "pointer-events-none fixed z-50 min-w-[130px] rounded-lg border border-border bg-popover px-3 py-2.5 text-popover-foreground shadow-lg",
169
+ style: {
170
+ left: tooltipLeft,
171
+ top: tooltipTop
172
+ },
173
+ children: [/* @__PURE__ */ jsxs("p", {
174
+ className: "mb-1.5 font-mono text-[10px] text-quaternary-foreground uppercase tracking-wider",
175
+ children: [
176
+ formatY(hovered.rowLabel),
177
+ " · ",
178
+ formatX(hovered.colLabel)
179
+ ]
180
+ }), /* @__PURE__ */ jsxs("div", {
181
+ className: "flex items-center justify-between gap-4 text-text-xs",
182
+ children: [/* @__PURE__ */ jsxs("span", {
183
+ className: "inline-flex items-center gap-2 text-muted-foreground",
184
+ children: [/* @__PURE__ */ jsx("span", {
185
+ "aria-hidden": true,
186
+ className: "size-2 shrink-0 rounded-[2px]",
187
+ style: { background: heatColor(ramp, hovered.value / maxValue) }
188
+ }), "activity"]
189
+ }), /* @__PURE__ */ jsx("span", {
190
+ className: "font-mono font-semibold tabular-nums text-foreground",
191
+ children: valueFormatter(hovered.value)
192
+ })]
193
+ })]
194
+ }), document.body)]
195
+ });
196
+ }
197
+ //#endregion
198
+ export { HeatmapChart };
@@ -0,0 +1,55 @@
1
+ import { ChartSeries } from "../lib/chart.mjs";
2
+ import { ChartPaletteName } from "../lib/chart-palette.mjs";
3
+ import { ChartMarker } from "./area-chart.mjs";
4
+ import * as React$1 from "react";
5
+
6
+ //#region src/components/line-chart.d.ts
7
+ declare const CURVE_TYPE: {
8
+ readonly monotone: "monotone";
9
+ readonly linear: "linear";
10
+ readonly step: "stepAfter";
11
+ };
12
+ interface LineChartProps {
13
+ data: ReadonlyArray<Record<string, string | number | null | undefined>>;
14
+ index: string;
15
+ series: ChartSeries[];
16
+ curve?: keyof typeof CURVE_TYPE;
17
+ legend?: boolean;
18
+ valueFlags?: boolean;
19
+ dots?: boolean;
20
+ height?: number;
21
+ yAxisWidth?: number;
22
+ palette?: ChartPaletteName;
23
+ referenceLine?: {
24
+ y: number;
25
+ label?: string;
26
+ band?: boolean;
27
+ };
28
+ markers?: ChartMarker[];
29
+ lastValueLabel?: boolean;
30
+ loading?: boolean;
31
+ valueFormatter?: (value: number) => string;
32
+ labelFormatter?: (label: string | number) => string;
33
+ className?: string;
34
+ }
35
+ declare function LineChart({
36
+ data,
37
+ index,
38
+ series,
39
+ curve,
40
+ legend,
41
+ valueFlags,
42
+ dots,
43
+ height,
44
+ yAxisWidth,
45
+ palette,
46
+ referenceLine,
47
+ markers,
48
+ lastValueLabel,
49
+ loading,
50
+ valueFormatter,
51
+ labelFormatter,
52
+ className
53
+ }: LineChartProps): React$1.JSX.Element;
54
+ //#endregion
55
+ export { LineChart, LineChartProps };
@@ -0,0 +1,211 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.mjs";
3
+ import { useReducedMotion } from "../hooks/use-reduced-motion.mjs";
4
+ import { resolveSeries } from "../lib/chart.mjs";
5
+ import { useChartContext } from "./chart-container.mjs";
6
+ import { ChartLegend } from "./chart-legend.mjs";
7
+ import { ChartTooltipContent } from "./chart-tooltip.mjs";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ import * as React$1 from "react";
10
+ import { CartesianGrid, LabelList, Line, LineChart as LineChart$1, ReferenceArea, ReferenceDot, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
11
+ //#region src/components/line-chart.tsx
12
+ const CURVE_TYPE = {
13
+ monotone: "monotone",
14
+ linear: "linear",
15
+ step: "stepAfter"
16
+ };
17
+ function LineChart({ data, index, series, curve = "monotone", legend = false, valueFlags = false, dots = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, lastValueLabel = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
18
+ const { palette: paletteColors, theme } = useChartContext(palette);
19
+ const animated = !useReducedMotion();
20
+ const resolved = resolveSeries(series, paletteColors, theme);
21
+ const numericMax = React$1.useMemo(() => {
22
+ let max = 0;
23
+ for (const row of data) for (const entry of series) {
24
+ const value = Number(row[entry.key]);
25
+ if (Number.isFinite(value) && value > max) max = value;
26
+ }
27
+ return max;
28
+ }, [data, series]);
29
+ const curveType = CURVE_TYPE[curve];
30
+ const margin = {
31
+ top: markers?.length ? 18 : 8,
32
+ right: lastValueLabel ? 56 : 8,
33
+ bottom: 2,
34
+ left: 0
35
+ };
36
+ const axis = {
37
+ stroke: theme.border,
38
+ tick: {
39
+ fill: theme.axisForeground,
40
+ fontSize: 10,
41
+ fontFamily: theme.fontMono
42
+ },
43
+ tickLine: false,
44
+ axisLine: {
45
+ stroke: theme.border,
46
+ strokeOpacity: .6
47
+ }
48
+ };
49
+ const legendItems = resolved.map((entry) => ({
50
+ name: entry.name,
51
+ color: entry.color,
52
+ dashed: entry.dashed
53
+ }));
54
+ const activeDotFor = (entry) => valueFlags ? (dotProps) => {
55
+ if (dotProps.cx == null || dotProps.cy == null) return /* @__PURE__ */ jsx("g", {});
56
+ return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("circle", {
57
+ cx: dotProps.cx,
58
+ cy: dotProps.cy,
59
+ r: 3.5,
60
+ fill: entry.color,
61
+ stroke: theme.card,
62
+ strokeWidth: 2
63
+ }), /* @__PURE__ */ jsx("text", {
64
+ x: dotProps.cx,
65
+ y: dotProps.cy - 8,
66
+ textAnchor: "middle",
67
+ fill: entry.color,
68
+ fontFamily: theme.fontMono,
69
+ fontSize: 10,
70
+ style: { fontVariantNumeric: "tabular-nums" },
71
+ children: valueFormatter(Number(dotProps.value ?? 0))
72
+ })] });
73
+ } : {
74
+ r: 3.5,
75
+ fill: entry.color,
76
+ stroke: theme.card,
77
+ strokeWidth: 2
78
+ };
79
+ const renderLastLabel = (color) => (props) => {
80
+ if (props.index !== data.length - 1 || props.x == null || props.y == null) return null;
81
+ return /* @__PURE__ */ jsx("text", {
82
+ x: Number(props.x) + 6,
83
+ y: Number(props.y),
84
+ dy: 3,
85
+ fill: color,
86
+ fontFamily: theme.fontMono,
87
+ fontSize: 10,
88
+ textAnchor: "start",
89
+ style: { fontVariantNumeric: "tabular-nums" },
90
+ children: valueFormatter(Number(props.value ?? 0))
91
+ });
92
+ };
93
+ const isEmpty = data.length === 0 || resolved.length === 0;
94
+ return /* @__PURE__ */ jsxs("div", {
95
+ "data-slot": "line-chart",
96
+ className: cn("flex w-full flex-col gap-3", className),
97
+ children: [/* @__PURE__ */ jsx("div", {
98
+ className: "w-full",
99
+ style: { height },
100
+ children: loading ? /* @__PURE__ */ jsxs("div", {
101
+ className: "flex h-full items-center justify-center gap-2.5 font-mono text-quaternary-foreground text-text-xs",
102
+ children: [/* @__PURE__ */ jsx("span", { className: "size-4 animate-spin rounded-full border-2 border-border border-t-foreground" }), "loading…"]
103
+ }) : isEmpty ? /* @__PURE__ */ jsx("div", {
104
+ className: "flex h-full items-center justify-center rounded-lg border border-border border-dashed font-mono text-quaternary-foreground text-text-xs",
105
+ children: "no data in range"
106
+ }) : /* @__PURE__ */ jsx(ResponsiveContainer, {
107
+ width: "100%",
108
+ height: "100%",
109
+ children: /* @__PURE__ */ jsxs(LineChart$1, {
110
+ data,
111
+ margin,
112
+ children: [
113
+ /* @__PURE__ */ jsx(CartesianGrid, {
114
+ vertical: false,
115
+ stroke: theme.grid,
116
+ strokeDasharray: "2 4"
117
+ }),
118
+ /* @__PURE__ */ jsx(XAxis, {
119
+ dataKey: index,
120
+ ...axis,
121
+ interval: "preserveStartEnd",
122
+ minTickGap: 44
123
+ }),
124
+ /* @__PURE__ */ jsx(YAxis, {
125
+ ...axis,
126
+ width: yAxisWidth,
127
+ tickFormatter: (value) => valueFormatter(value)
128
+ }),
129
+ /* @__PURE__ */ jsx(Tooltip, {
130
+ offset: 12,
131
+ allowEscapeViewBox: {
132
+ x: false,
133
+ y: false
134
+ },
135
+ cursor: {
136
+ stroke: theme.axisForeground,
137
+ strokeWidth: 1,
138
+ strokeDasharray: "3 3"
139
+ },
140
+ content: /* @__PURE__ */ jsx(ChartTooltipContent, {
141
+ valueFormatter,
142
+ labelFormatter
143
+ })
144
+ }),
145
+ referenceLine?.band && /* @__PURE__ */ jsx(ReferenceArea, {
146
+ y1: referenceLine.y,
147
+ y2: numericMax,
148
+ fill: theme.warning,
149
+ fillOpacity: .06,
150
+ ifOverflow: "extendDomain"
151
+ }),
152
+ referenceLine && /* @__PURE__ */ jsx(ReferenceLine, {
153
+ y: referenceLine.y,
154
+ stroke: theme.warning,
155
+ strokeDasharray: "4 4",
156
+ strokeOpacity: .6,
157
+ label: referenceLine.label ? {
158
+ value: referenceLine.label,
159
+ fill: theme.warning,
160
+ fontSize: 9,
161
+ fontFamily: theme.fontMono,
162
+ position: "insideBottomRight"
163
+ } : void 0
164
+ }),
165
+ resolved.map((entry) => /* @__PURE__ */ jsx(Line, {
166
+ type: curveType,
167
+ dataKey: entry.key,
168
+ name: entry.name,
169
+ stroke: entry.color,
170
+ strokeWidth: entry.dashed ? 2 : 1.8,
171
+ strokeDasharray: entry.dashed ? "5 3" : void 0,
172
+ dot: dots ? {
173
+ r: 2.5,
174
+ fill: entry.color,
175
+ strokeWidth: 0
176
+ } : false,
177
+ activeDot: activeDotFor(entry),
178
+ isAnimationActive: animated,
179
+ animationDuration: 650,
180
+ animationEasing: "ease-out",
181
+ children: lastValueLabel && /* @__PURE__ */ jsx(LabelList, {
182
+ dataKey: entry.key,
183
+ content: renderLastLabel(entry.color)
184
+ })
185
+ }, entry.key)),
186
+ markers?.map((marker) => /* @__PURE__ */ jsx(ReferenceDot, {
187
+ x: marker.x,
188
+ y: marker.y,
189
+ r: 3.5,
190
+ fill: marker.color ?? theme.foreground,
191
+ stroke: theme.card,
192
+ strokeWidth: 2,
193
+ label: marker.label ? {
194
+ value: marker.label,
195
+ fill: marker.color ?? theme.foreground,
196
+ fontSize: 9,
197
+ fontFamily: theme.fontMono,
198
+ position: "top"
199
+ } : void 0
200
+ }, `${marker.x}-${marker.y}`))
201
+ ]
202
+ })
203
+ })
204
+ }), legend && !isEmpty && /* @__PURE__ */ jsx(ChartLegend, {
205
+ items: legendItems,
206
+ style: { paddingLeft: yAxisWidth }
207
+ })]
208
+ });
209
+ }
210
+ //#endregion
211
+ export { LineChart };
@@ -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?: "secondary" | "primary" | null | undefined;
5
+ variant?: "primary" | "secondary" | 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> {}