@alpic-ai/ui 0.0.0-staging.g154e447 → 0.0.0-staging.g15a7fed
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 +10 -6
- package/dist/components/bar-chart.d.mts +2 -0
- package/dist/components/bar-chart.mjs +10 -6
- package/dist/components/bar-list.d.mts +3 -0
- package/dist/components/bar-list.mjs +24 -18
- 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/chart-tooltip.mjs +5 -3
- package/dist/components/donut-chart.mjs +10 -8
- package/dist/components/form.mjs +3 -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 +11 -6
- package/dist/components/stat.d.mts +3 -1
- package/dist/components/stat.mjs +14 -4
- package/dist/components/textarea.mjs +1 -1
- package/dist/components/wizard.d.mts +1 -19
- package/dist/components/wizard.mjs +1 -19
- package/dist/lib/chart-palette.mjs +16 -16
- package/dist/lib/chart.mjs +16 -1
- package/package.json +23 -23
- package/src/components/area-chart.tsx +13 -8
- package/src/components/bar-chart.tsx +13 -8
- package/src/components/bar-list.tsx +26 -18
- 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/chart-tooltip.tsx +6 -6
- package/src/components/donut-chart.tsx +7 -10
- package/src/components/form.tsx +4 -2
- package/src/components/heatmap-chart.tsx +62 -18
- package/src/components/line-chart.tsx +19 -9
- package/src/components/stat.tsx +10 -6
- package/src/components/textarea.tsx +1 -1
- package/src/components/wizard.tsx +1 -35
- package/src/lib/chart-palette.ts +16 -16
- 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/textarea.stories.tsx +7 -0
- 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/dist/hooks/use-reduced-motion.d.mts +0 -4
- package/dist/hooks/use-reduced-motion.mjs +0 -16
- package/src/components/grid-fx.tsx +0 -238
- package/src/hooks/use-reduced-motion.ts +0 -17
|
@@ -21,6 +21,7 @@ interface AreaChartProps {
|
|
|
21
21
|
variant?: "stacked" | "grouped" | "expand";
|
|
22
22
|
curve?: keyof typeof CURVE_TYPE;
|
|
23
23
|
legend?: boolean;
|
|
24
|
+
legendAlign?: "left" | "center" | "right";
|
|
24
25
|
valueFlags?: boolean;
|
|
25
26
|
height?: number;
|
|
26
27
|
yAxisWidth?: number;
|
|
@@ -45,6 +46,7 @@ declare function AreaChart({
|
|
|
45
46
|
variant,
|
|
46
47
|
curve,
|
|
47
48
|
legend,
|
|
49
|
+
legendAlign,
|
|
48
50
|
valueFlags,
|
|
49
51
|
height,
|
|
50
52
|
yAxisWidth,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { orderByLuminance, resolveSeries } from "../lib/chart.mjs";
|
|
3
|
+
import { makeXAxisTick, orderByLuminance, resolveSeries } from "../lib/chart.mjs";
|
|
5
4
|
import { useChartContext } from "./chart-container.mjs";
|
|
6
5
|
import { ChartLegend } from "./chart-legend.mjs";
|
|
7
6
|
import { ChartTooltipContent } from "./chart-tooltip.mjs";
|
|
@@ -14,10 +13,9 @@ const CURVE_TYPE = {
|
|
|
14
13
|
linear: "linear",
|
|
15
14
|
step: "stepAfter"
|
|
16
15
|
};
|
|
17
|
-
function AreaChart({ data, index, series, variant = "stacked", curve = "monotone", legend = false, valueFlags = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, lastValueLabel = false, texture = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
16
|
+
function AreaChart({ data, index, series, variant = "stacked", curve = "monotone", legend = false, legendAlign = "left", valueFlags = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, lastValueLabel = false, texture = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
18
17
|
const { palette: paletteColors, theme } = useChartContext(palette);
|
|
19
18
|
const reactId = React$1.useId().replace(/:/g, "");
|
|
20
|
-
const animated = !useReducedMotion();
|
|
21
19
|
const resolved = resolveSeries(series, paletteColors, theme);
|
|
22
20
|
const stacked = variant === "stacked" || variant === "expand";
|
|
23
21
|
const rendered = stacked ? orderByLuminance(resolved) : resolved;
|
|
@@ -128,6 +126,10 @@ function AreaChart({ data, index, series, variant = "stacked", curve = "monotone
|
|
|
128
126
|
}) : /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
129
127
|
width: "100%",
|
|
130
128
|
height: "100%",
|
|
129
|
+
initialDimension: {
|
|
130
|
+
width: 0,
|
|
131
|
+
height
|
|
132
|
+
},
|
|
131
133
|
children: /* @__PURE__ */ jsxs(AreaChart$1, {
|
|
132
134
|
data,
|
|
133
135
|
stackOffset: variant === "expand" ? "expand" : "none",
|
|
@@ -177,6 +179,7 @@ function AreaChart({ data, index, series, variant = "stacked", curve = "monotone
|
|
|
177
179
|
/* @__PURE__ */ jsx(XAxis, {
|
|
178
180
|
dataKey: index,
|
|
179
181
|
...axis,
|
|
182
|
+
tick: makeXAxisTick(theme),
|
|
180
183
|
interval: "preserveStartEnd",
|
|
181
184
|
minTickGap: 44
|
|
182
185
|
}),
|
|
@@ -233,7 +236,7 @@ function AreaChart({ data, index, series, variant = "stacked", curve = "monotone
|
|
|
233
236
|
fill: fillFor(entry, slot),
|
|
234
237
|
dot: false,
|
|
235
238
|
activeDot: activeDotFor(entry),
|
|
236
|
-
isAnimationActive:
|
|
239
|
+
isAnimationActive: false,
|
|
237
240
|
animationDuration: 650,
|
|
238
241
|
animationEasing: "ease-out",
|
|
239
242
|
children: lastValueLabel && /* @__PURE__ */ jsx(LabelList, {
|
|
@@ -261,7 +264,8 @@ function AreaChart({ data, index, series, variant = "stacked", curve = "monotone
|
|
|
261
264
|
})
|
|
262
265
|
}), legend && !isEmpty && /* @__PURE__ */ jsx(ChartLegend, {
|
|
263
266
|
items: legendItems,
|
|
264
|
-
|
|
267
|
+
align: legendAlign,
|
|
268
|
+
insetLeft: yAxisWidth
|
|
265
269
|
})]
|
|
266
270
|
});
|
|
267
271
|
}
|
|
@@ -10,6 +10,7 @@ interface BarChartProps {
|
|
|
10
10
|
series: ChartSeries[];
|
|
11
11
|
variant?: "stacked" | "grouped" | "expand";
|
|
12
12
|
legend?: boolean;
|
|
13
|
+
legendAlign?: "left" | "center" | "right";
|
|
13
14
|
valueLabels?: boolean;
|
|
14
15
|
height?: number;
|
|
15
16
|
yAxisWidth?: number;
|
|
@@ -32,6 +33,7 @@ declare function BarChart({
|
|
|
32
33
|
series,
|
|
33
34
|
variant,
|
|
34
35
|
legend,
|
|
36
|
+
legendAlign,
|
|
35
37
|
valueLabels,
|
|
36
38
|
height,
|
|
37
39
|
yAxisWidth,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { orderByLuminance, resolveSeries } from "../lib/chart.mjs";
|
|
3
|
+
import { makeXAxisTick, orderByLuminance, resolveSeries } from "../lib/chart.mjs";
|
|
5
4
|
import { useChartContext } from "./chart-container.mjs";
|
|
6
5
|
import { ChartLegend } from "./chart-legend.mjs";
|
|
7
6
|
import { ChartTooltipContent } from "./chart-tooltip.mjs";
|
|
@@ -11,10 +10,9 @@ import { Bar, BarChart as BarChart$1, CartesianGrid, LabelList, ReferenceArea, R
|
|
|
11
10
|
//#region src/components/bar-chart.tsx
|
|
12
11
|
const BAR_RADIUS = 4;
|
|
13
12
|
const MAX_BAR_SIZE = 48;
|
|
14
|
-
function BarChart({ data, index, series, variant = "stacked", legend = false, valueLabels = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, texture = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
13
|
+
function BarChart({ data, index, series, variant = "stacked", legend = false, legendAlign = "left", valueLabels = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, texture = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
15
14
|
const { palette: paletteColors, theme } = useChartContext(palette);
|
|
16
15
|
const reactId = React$1.useId().replace(/:/g, "");
|
|
17
|
-
const animated = !useReducedMotion();
|
|
18
16
|
const resolved = resolveSeries(series, paletteColors, theme);
|
|
19
17
|
const stacked = variant === "stacked" || variant === "expand";
|
|
20
18
|
const rendered = stacked ? orderByLuminance(resolved) : resolved;
|
|
@@ -111,6 +109,10 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, va
|
|
|
111
109
|
}) : /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
112
110
|
width: "100%",
|
|
113
111
|
height: "100%",
|
|
112
|
+
initialDimension: {
|
|
113
|
+
width: 0,
|
|
114
|
+
height
|
|
115
|
+
},
|
|
114
116
|
children: /* @__PURE__ */ jsxs(BarChart$1, {
|
|
115
117
|
data,
|
|
116
118
|
stackOffset: variant === "expand" ? "expand" : "none",
|
|
@@ -161,6 +163,7 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, va
|
|
|
161
163
|
/* @__PURE__ */ jsx(XAxis, {
|
|
162
164
|
dataKey: index,
|
|
163
165
|
...axis,
|
|
166
|
+
tick: makeXAxisTick(theme),
|
|
164
167
|
interval: "preserveStartEnd",
|
|
165
168
|
minTickGap: 44
|
|
166
169
|
}),
|
|
@@ -220,7 +223,7 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, va
|
|
|
220
223
|
stroke: theme.card,
|
|
221
224
|
strokeWidth: 1
|
|
222
225
|
},
|
|
223
|
-
isAnimationActive:
|
|
226
|
+
isAnimationActive: false,
|
|
224
227
|
animationDuration: 650,
|
|
225
228
|
animationEasing: "ease-out",
|
|
226
229
|
children: valueLabels && (!stacked || rendered.length === 1) && /* @__PURE__ */ jsx(LabelList, {
|
|
@@ -248,7 +251,8 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, va
|
|
|
248
251
|
})
|
|
249
252
|
}), legend && !isEmpty && /* @__PURE__ */ jsx(ChartLegend, {
|
|
250
253
|
items: legendItems,
|
|
251
|
-
|
|
254
|
+
align: legendAlign,
|
|
255
|
+
insetLeft: yAxisWidth
|
|
252
256
|
})]
|
|
253
257
|
});
|
|
254
258
|
}
|
|
@@ -8,6 +8,8 @@ interface BarListProps {
|
|
|
8
8
|
dataKey?: string;
|
|
9
9
|
maxItems?: number;
|
|
10
10
|
palette?: ChartPaletteName;
|
|
11
|
+
/** Renders bars in a single semantic hue (e.g. red for errors) rather than the palette ramp. */
|
|
12
|
+
semantic?: "error" | "warning" | "success";
|
|
11
13
|
loading?: boolean;
|
|
12
14
|
valueFormatter?: (value: number) => string;
|
|
13
15
|
labelFormatter?: (label: string | number) => string;
|
|
@@ -19,6 +21,7 @@ declare function BarList({
|
|
|
19
21
|
dataKey,
|
|
20
22
|
maxItems,
|
|
21
23
|
palette,
|
|
24
|
+
semantic,
|
|
22
25
|
loading,
|
|
23
26
|
valueFormatter,
|
|
24
27
|
labelFormatter,
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
|
-
import { useReducedMotion } from "../hooks/use-reduced-motion.mjs";
|
|
4
3
|
import { rampColor } from "../lib/chart-palette.mjs";
|
|
5
4
|
import { formatShare } from "../lib/chart.mjs";
|
|
6
5
|
import { useChartContext } from "./chart-container.mjs";
|
|
7
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
7
|
import * as React$1 from "react";
|
|
9
8
|
//#region src/components/bar-list.tsx
|
|
9
|
+
const SEMANTIC_KEY = {
|
|
10
|
+
error: "destructive",
|
|
11
|
+
warning: "warning",
|
|
12
|
+
success: "success"
|
|
13
|
+
};
|
|
10
14
|
const PLACEHOLDER_HEIGHT = 168;
|
|
11
15
|
const RAMP_CEILING = .8;
|
|
16
|
+
const SEMANTIC_FLOOR = .62;
|
|
12
17
|
const IN_FILL_SHADOW = "0 1px 2px rgb(0 0 0 / 0.28)";
|
|
13
|
-
function BarList({ data, index, dataKey = "value", maxItems, palette, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
14
|
-
const { paletteName } = useChartContext(palette);
|
|
15
|
-
const
|
|
16
|
-
const [mounted, setMounted] = React$1.useState(false);
|
|
18
|
+
function BarList({ data, index, dataKey = "value", maxItems, palette, semantic, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
19
|
+
const { paletteName, theme } = useChartContext(palette);
|
|
20
|
+
const accent = semantic ? theme[SEMANTIC_KEY[semantic]] : null;
|
|
17
21
|
const [active, setActive] = React$1.useState(null);
|
|
18
|
-
React$1.useEffect(() => {
|
|
19
|
-
setMounted(true);
|
|
20
|
-
}, []);
|
|
21
22
|
const total = React$1.useMemo(() => data.reduce((sum, row) => sum + (Number(row[dataKey]) || 0), 0), [data, dataKey]);
|
|
22
23
|
const rows = React$1.useMemo(() => {
|
|
23
24
|
const mapped = data.map((row) => ({
|
|
@@ -26,16 +27,21 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, loading =
|
|
|
26
27
|
}));
|
|
27
28
|
mapped.sort((lower, upper) => upper.value - lower.value);
|
|
28
29
|
const capped = maxItems ? mapped.slice(0, maxItems) : mapped;
|
|
29
|
-
return capped.map((row, rank) =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
return capped.map((row, rank) => {
|
|
31
|
+
const rankFraction = capped.length > 1 ? rank / (capped.length - 1) : 0;
|
|
32
|
+
const accentWeight = Math.round((1 - rankFraction * (1 - SEMANTIC_FLOOR)) * 100);
|
|
33
|
+
return {
|
|
34
|
+
...row,
|
|
35
|
+
color: accent ? `color-mix(in oklab, ${accent} ${accentWeight}%, white)` : rampColor(paletteName, rankFraction * RAMP_CEILING)
|
|
36
|
+
};
|
|
37
|
+
});
|
|
33
38
|
}, [
|
|
34
39
|
data,
|
|
35
40
|
index,
|
|
36
41
|
dataKey,
|
|
37
42
|
maxItems,
|
|
38
|
-
paletteName
|
|
43
|
+
paletteName,
|
|
44
|
+
accent
|
|
39
45
|
]);
|
|
40
46
|
const maxValue = rows.reduce((max, row) => row.value > max ? row.value : max, 0);
|
|
41
47
|
const isEmpty = rows.length === 0;
|
|
@@ -52,23 +58,23 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, loading =
|
|
|
52
58
|
});
|
|
53
59
|
return /* @__PURE__ */ jsx("div", {
|
|
54
60
|
"data-slot": "bar-list",
|
|
55
|
-
className: cn("flex w-full flex-col
|
|
61
|
+
className: cn("flex w-full flex-col", className),
|
|
56
62
|
children: rows.map((row, slot) => {
|
|
57
63
|
const fraction = maxValue > 0 ? row.value / maxValue : 0;
|
|
58
|
-
const fillWidth =
|
|
64
|
+
const fillWidth = `${fraction * 100}%`;
|
|
59
65
|
const dimmed = active !== null && active !== slot;
|
|
60
66
|
const isActive = active === slot;
|
|
61
67
|
return /* @__PURE__ */ jsxs("div", {
|
|
62
68
|
onMouseEnter: () => setActive(slot),
|
|
63
69
|
onMouseLeave: () => setActive(null),
|
|
64
|
-
className: "flex items-center gap-3",
|
|
70
|
+
className: "flex items-center gap-3 py-1",
|
|
65
71
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
66
72
|
className: "relative h-[30px] flex-1 overflow-hidden rounded-md bg-muted",
|
|
67
73
|
children: [/* @__PURE__ */ jsx("span", {
|
|
68
74
|
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
75
|
children: formatName(row.name)
|
|
70
76
|
}), /* @__PURE__ */ jsx("div", {
|
|
71
|
-
className: "absolute inset-y-0 left-0 z-10 overflow-hidden rounded-md
|
|
77
|
+
className: "absolute inset-y-0 left-0 z-10 overflow-hidden rounded-md",
|
|
72
78
|
style: {
|
|
73
79
|
width: fillWidth,
|
|
74
80
|
background: `linear-gradient(90deg, ${row.color}, color-mix(in oklab, ${row.color} 82%, transparent))`,
|
|
@@ -86,7 +92,7 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, loading =
|
|
|
86
92
|
})
|
|
87
93
|
})]
|
|
88
94
|
}), /* @__PURE__ */ jsx("span", {
|
|
89
|
-
className: "w-
|
|
95
|
+
className: "min-w-[4rem] shrink-0 whitespace-nowrap text-right font-mono text-text-xs tabular-nums motion-safe:transition-colors",
|
|
90
96
|
style: { color: isActive ? row.color : void 0 },
|
|
91
97
|
children: isActive ? formatShare(total > 0 ? row.value / total : 0) : valueFormatter(row.value)
|
|
92
98
|
})]
|
|
@@ -8,7 +8,7 @@ interface ChartCardProps extends Omit<React$1.ComponentProps<"section">, "title"
|
|
|
8
8
|
title?: React$1.ReactNode;
|
|
9
9
|
description?: React$1.ReactNode;
|
|
10
10
|
action?: React$1.ReactNode;
|
|
11
|
-
accent?: "top" | "left";
|
|
11
|
+
accent?: "top" | "left" | "none";
|
|
12
12
|
}
|
|
13
13
|
declare function ChartCard({
|
|
14
14
|
palette,
|
|
@@ -12,7 +12,7 @@ function ChartCard({ palette = "magenta", kicker, title, description, action, ac
|
|
|
12
12
|
className: cn("chart-rise relative overflow-hidden rounded-xl border bg-card p-5 text-card-foreground shadow-shadow", isLeft && "pl-6", className),
|
|
13
13
|
...props,
|
|
14
14
|
children: [
|
|
15
|
-
/* @__PURE__ */ jsx("span", {
|
|
15
|
+
accent !== "none" && /* @__PURE__ */ jsx("span", {
|
|
16
16
|
"aria-hidden": true,
|
|
17
17
|
className: cn("absolute", isLeft ? "inset-y-0 left-0 w-[3px]" : "inset-x-0 top-0 h-[3px]"),
|
|
18
18
|
style: { background: lead }
|
|
@@ -17,4 +17,4 @@ declare function ChartContainer({
|
|
|
17
17
|
}): React$1.JSX.Element;
|
|
18
18
|
declare function useChartContext(paletteOverride?: ChartPaletteName): ChartContextValue;
|
|
19
19
|
//#endregion
|
|
20
|
-
export { ChartContainer, type ChartContextValue, useChartContext };
|
|
20
|
+
export { ChartContainer, type ChartContextValue, type ChartPaletteName, useChartContext };
|
|
@@ -6,10 +6,15 @@ interface ChartLegendItem {
|
|
|
6
6
|
}
|
|
7
7
|
interface ChartLegendProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
8
8
|
items: ChartLegendItem[];
|
|
9
|
+
align?: "left" | "center" | "right";
|
|
10
|
+
insetLeft?: number;
|
|
9
11
|
}
|
|
10
12
|
declare function ChartLegend({
|
|
11
13
|
items,
|
|
14
|
+
align,
|
|
15
|
+
insetLeft,
|
|
12
16
|
className,
|
|
17
|
+
style,
|
|
13
18
|
...props
|
|
14
19
|
}: ChartLegendProps): import("react").JSX.Element;
|
|
15
20
|
//#endregion
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/components/chart-legend.tsx
|
|
5
|
+
const ALIGN_CLASS = {
|
|
6
|
+
left: "justify-start",
|
|
7
|
+
center: "justify-center",
|
|
8
|
+
right: "justify-end"
|
|
9
|
+
};
|
|
5
10
|
function Swatch({ color, dashed }) {
|
|
6
11
|
return /* @__PURE__ */ jsx("span", {
|
|
7
12
|
"aria-hidden": true,
|
|
@@ -9,9 +14,13 @@ function Swatch({ color, dashed }) {
|
|
|
9
14
|
style: dashed ? { border: `1.5px solid ${color}` } : { background: color }
|
|
10
15
|
});
|
|
11
16
|
}
|
|
12
|
-
function ChartLegend({ items, className, ...props }) {
|
|
17
|
+
function ChartLegend({ items, align = "left", insetLeft, className, style, ...props }) {
|
|
13
18
|
return /* @__PURE__ */ jsx("div", {
|
|
14
|
-
className: cn("flex flex-wrap gap-x-4 gap-y-1.5", className),
|
|
19
|
+
className: cn("flex flex-wrap gap-x-4 gap-y-1.5", ALIGN_CLASS[align], className),
|
|
20
|
+
style: {
|
|
21
|
+
paddingLeft: align === "left" ? insetLeft : void 0,
|
|
22
|
+
...style
|
|
23
|
+
},
|
|
15
24
|
...props,
|
|
16
25
|
children: items.map((item) => /* @__PURE__ */ jsxs("span", {
|
|
17
26
|
className: "inline-flex items-center gap-1.5 font-mono text-[10px] text-muted-foreground uppercase tracking-[0.12em]",
|
|
@@ -6,7 +6,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
function ChartTooltipContent({ active, payload, label, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, hideLabel, showTotal, totalLabel = "Total", className }) {
|
|
7
7
|
const { theme } = useChartContext();
|
|
8
8
|
if (!active || !payload?.length) return null;
|
|
9
|
-
const
|
|
9
|
+
const toNumber = (value) => typeof value === "number" ? value : Number(value ?? 0);
|
|
10
|
+
const total = payload.reduce((sum, item) => sum + toNumber(item.value), 0);
|
|
11
|
+
const rows = [...payload].sort((lower, upper) => toNumber(upper.value) - toNumber(lower.value));
|
|
10
12
|
return /* @__PURE__ */ jsxs("div", {
|
|
11
13
|
className: cn("min-w-[130px] rounded-lg border px-3 py-2.5 shadow-lg", "border-border bg-popover text-popover-foreground", className),
|
|
12
14
|
children: [!hideLabel && label !== void 0 && /* @__PURE__ */ jsx("p", {
|
|
@@ -14,13 +16,13 @@ function ChartTooltipContent({ active, payload, label, valueFormatter = (value)
|
|
|
14
16
|
children: labelFormatter ? labelFormatter(label) : label
|
|
15
17
|
}), /* @__PURE__ */ jsxs("div", {
|
|
16
18
|
className: "flex flex-col gap-1",
|
|
17
|
-
children: [
|
|
19
|
+
children: [rows.map((item, index) => {
|
|
18
20
|
const swatch = [
|
|
19
21
|
item.color,
|
|
20
22
|
item.stroke,
|
|
21
23
|
item.fill
|
|
22
24
|
].find((candidate) => typeof candidate === "string" && candidate.length > 0 && !candidate.startsWith("url(")) ?? theme.mutedForeground;
|
|
23
|
-
const numeric =
|
|
25
|
+
const numeric = toNumber(item.value);
|
|
24
26
|
return /* @__PURE__ */ jsxs("div", {
|
|
25
27
|
className: "flex items-center justify-between gap-4 text-text-xs",
|
|
26
28
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
|
-
import { useReducedMotion } from "../hooks/use-reduced-motion.mjs";
|
|
4
3
|
import { paletteColor } from "../lib/chart-palette.mjs";
|
|
5
4
|
import { formatShare } from "../lib/chart.mjs";
|
|
6
5
|
import { useChartContext } from "./chart-container.mjs";
|
|
@@ -21,7 +20,6 @@ const GEOMETRY = {
|
|
|
21
20
|
function DonutChart({ data, index, dataKey = "value", variant = "donut", legend = false, paddingAngle = 1, height = 220, palette, centerLabel = "total", loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
22
21
|
const { palette: paletteColors, theme } = useChartContext(palette);
|
|
23
22
|
const reactId = React$1.useId().replace(/:/g, "");
|
|
24
|
-
const animated = !useReducedMotion();
|
|
25
23
|
const [active, setActive] = React$1.useState(null);
|
|
26
24
|
const rowRefs = React$1.useRef([]);
|
|
27
25
|
const slices = React$1.useMemo(() => {
|
|
@@ -73,6 +71,10 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
|
|
|
73
71
|
children: [/* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
74
72
|
width: "100%",
|
|
75
73
|
height: "100%",
|
|
74
|
+
initialDimension: {
|
|
75
|
+
width: 0,
|
|
76
|
+
height
|
|
77
|
+
},
|
|
76
78
|
children: /* @__PURE__ */ jsxs(PieChart, { children: [/* @__PURE__ */ jsx("defs", { children: slices.map((slice, slot) => /* @__PURE__ */ jsxs("linearGradient", {
|
|
77
79
|
id: `donut-${reactId}-${slot}`,
|
|
78
80
|
x1: "0",
|
|
@@ -100,7 +102,7 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
|
|
|
100
102
|
cornerRadius: 2,
|
|
101
103
|
stroke: theme.card,
|
|
102
104
|
strokeWidth: 1.5,
|
|
103
|
-
isAnimationActive:
|
|
105
|
+
isAnimationActive: false,
|
|
104
106
|
animationDuration: 650,
|
|
105
107
|
animationEasing: "ease-out",
|
|
106
108
|
onMouseEnter: (_entry, sliceIndex) => setActive(sliceIndex),
|
|
@@ -117,7 +119,7 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
|
|
|
117
119
|
className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-1 text-center",
|
|
118
120
|
children: [
|
|
119
121
|
/* @__PURE__ */ jsx("span", {
|
|
120
|
-
className: "max-w-[
|
|
122
|
+
className: "max-w-[52%] truncate font-mono text-[10px] text-quaternary-foreground uppercase tracking-[0.18em]",
|
|
121
123
|
children: centerTitle
|
|
122
124
|
}),
|
|
123
125
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -143,7 +145,7 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
|
|
|
143
145
|
onMouseLeave: () => setActive(null),
|
|
144
146
|
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
147
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
146
|
-
className: "flex items-center justify-between gap-
|
|
148
|
+
className: "flex items-center justify-between gap-2",
|
|
147
149
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
148
150
|
className: "inline-flex min-w-0 items-center gap-2 text-muted-foreground",
|
|
149
151
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -155,12 +157,12 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
|
|
|
155
157
|
children: formatName(slice.name)
|
|
156
158
|
})]
|
|
157
159
|
}), /* @__PURE__ */ jsxs("span", {
|
|
158
|
-
className: "flex shrink-0 items-center gap-
|
|
160
|
+
className: "flex shrink-0 items-center gap-2 font-mono tabular-nums",
|
|
159
161
|
children: [/* @__PURE__ */ jsx("span", {
|
|
160
|
-
className: "min-w-[
|
|
162
|
+
className: "min-w-[2.25rem] text-right font-semibold text-foreground",
|
|
161
163
|
children: valueFormatter(slice.value)
|
|
162
164
|
}), /* @__PURE__ */ jsx("span", {
|
|
163
|
-
className: "w-
|
|
165
|
+
className: "w-9 text-right text-quaternary-foreground",
|
|
164
166
|
children: formatShare(slice.value / total)
|
|
165
167
|
})]
|
|
166
168
|
})]
|
package/dist/components/form.mjs
CHANGED
|
@@ -41,10 +41,12 @@ const useFormField = () => {
|
|
|
41
41
|
const FormItemContext = React$1.createContext({});
|
|
42
42
|
function FormItem({ className, ...props }) {
|
|
43
43
|
const id = React$1.useId();
|
|
44
|
+
const { name } = React$1.useContext(FormFieldContext);
|
|
44
45
|
return /* @__PURE__ */ jsx(FormItemContext.Provider, {
|
|
45
46
|
value: { id },
|
|
46
47
|
children: /* @__PURE__ */ jsx("div", {
|
|
47
48
|
"data-slot": "form-item",
|
|
49
|
+
"data-field-name": name,
|
|
48
50
|
className: cn("grid gap-2", className),
|
|
49
51
|
...props
|
|
50
52
|
})
|
|
@@ -65,7 +67,7 @@ function FormLabel({ className, required, tooltip, children, ...props }) {
|
|
|
65
67
|
}),
|
|
66
68
|
required && /* @__PURE__ */ jsx("span", {
|
|
67
69
|
"aria-hidden": true,
|
|
68
|
-
className: "type-text-sm font-medium text-required",
|
|
70
|
+
className: "type-text-sm font-medium text-required leading-none",
|
|
69
71
|
children: "*"
|
|
70
72
|
}),
|
|
71
73
|
tooltip && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
@@ -11,11 +11,17 @@ interface HeatmapChartProps {
|
|
|
11
11
|
palette?: ChartPaletteName;
|
|
12
12
|
xLabels?: readonly string[];
|
|
13
13
|
yLabels?: readonly string[];
|
|
14
|
+
showAllXLabels?: boolean;
|
|
14
15
|
highlightPeak?: boolean;
|
|
15
16
|
loading?: boolean;
|
|
16
17
|
valueFormatter?: (value: number) => string;
|
|
17
18
|
xTickFormatter?: (label: string) => string;
|
|
18
19
|
yTickFormatter?: (label: string) => string;
|
|
20
|
+
tooltipMetrics?: ReadonlyArray<{
|
|
21
|
+
key: string;
|
|
22
|
+
label: string;
|
|
23
|
+
format?: (value: number) => string;
|
|
24
|
+
}>;
|
|
19
25
|
ariaLabel?: string;
|
|
20
26
|
className?: string;
|
|
21
27
|
}
|
|
@@ -28,11 +34,13 @@ declare function HeatmapChart({
|
|
|
28
34
|
palette,
|
|
29
35
|
xLabels,
|
|
30
36
|
yLabels,
|
|
37
|
+
showAllXLabels,
|
|
31
38
|
highlightPeak,
|
|
32
39
|
loading,
|
|
33
40
|
valueFormatter,
|
|
34
41
|
xTickFormatter,
|
|
35
42
|
yTickFormatter,
|
|
43
|
+
tooltipMetrics,
|
|
36
44
|
ariaLabel,
|
|
37
45
|
className
|
|
38
46
|
}: HeatmapChartProps): React$1.JSX.Element;
|
|
@@ -31,7 +31,7 @@ const uniqueInOrder = (values) => {
|
|
|
31
31
|
}
|
|
32
32
|
return ordered;
|
|
33
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 }) {
|
|
34
|
+
function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square", palette, xLabels, yLabels, showAllXLabels = false, highlightPeak = true, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), xTickFormatter, yTickFormatter, tooltipMetrics, ariaLabel = "Activity heatmap", className }) {
|
|
35
35
|
const { paletteName, theme } = useChartContext(palette);
|
|
36
36
|
const [hovered, setHovered] = React$1.useState(null);
|
|
37
37
|
const [mounted, setMounted] = React$1.useState(false);
|
|
@@ -42,24 +42,31 @@ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square",
|
|
|
42
42
|
const columns = xLabels ? [...xLabels] : uniqueInOrder(data.map((row) => String(row[xKey] ?? "")));
|
|
43
43
|
const rows = yLabels ? [...yLabels] : uniqueInOrder(data.map((row) => String(row[yKey] ?? "")));
|
|
44
44
|
const valueAt = /* @__PURE__ */ new Map();
|
|
45
|
-
|
|
45
|
+
const recordAt = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const row of data) {
|
|
47
|
+
const key = pairKey(String(row[yKey] ?? ""), String(row[xKey] ?? ""));
|
|
48
|
+
valueAt.set(key, Number(row[dataKey]) || 0);
|
|
49
|
+
recordAt.set(key, row);
|
|
50
|
+
}
|
|
46
51
|
let maxValue = 0;
|
|
47
52
|
let peakKey = "";
|
|
48
53
|
return {
|
|
49
54
|
columns,
|
|
50
55
|
rows,
|
|
51
56
|
cells: rows.flatMap((rowLabel, rowIndex) => columns.map((colLabel, colIndex) => {
|
|
52
|
-
const
|
|
57
|
+
const key = pairKey(rowLabel, colLabel);
|
|
58
|
+
const value = valueAt.get(key) ?? 0;
|
|
53
59
|
if (value > maxValue) {
|
|
54
60
|
maxValue = value;
|
|
55
|
-
peakKey =
|
|
61
|
+
peakKey = key;
|
|
56
62
|
}
|
|
57
63
|
return {
|
|
58
64
|
rowLabel,
|
|
59
65
|
colLabel,
|
|
60
66
|
rowIndex,
|
|
61
67
|
colIndex,
|
|
62
|
-
value
|
|
68
|
+
value,
|
|
69
|
+
record: recordAt.get(key) ?? null
|
|
63
70
|
};
|
|
64
71
|
})),
|
|
65
72
|
maxValue,
|
|
@@ -87,7 +94,7 @@ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square",
|
|
|
87
94
|
const ramp = heatRamp(paletteName, theme.isDark ? HEAT_EMPTY.dark : HEAT_EMPTY.light);
|
|
88
95
|
const totalWidth = PAD.left + columns.length * CELL + (columns.length - 1) * GAP + PAD.right;
|
|
89
96
|
const totalHeight = PAD.top + rows.length * CELL + (rows.length - 1) * GAP + PAD.bottom;
|
|
90
|
-
const xStride = Math.ceil(columns.length / MAX_X_TICKS);
|
|
97
|
+
const xStride = showAllXLabels ? 1 : Math.ceil(columns.length / MAX_X_TICKS);
|
|
91
98
|
const cellX = (col) => PAD.left + col * 25;
|
|
92
99
|
const cellY = (row) => PAD.top + row * 25;
|
|
93
100
|
const formatX = (label) => xTickFormatter ? xTickFormatter(label) : label;
|
|
@@ -159,7 +166,8 @@ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square",
|
|
|
159
166
|
y: event.clientY,
|
|
160
167
|
rowLabel: cell.rowLabel,
|
|
161
168
|
colLabel: cell.colLabel,
|
|
162
|
-
value: cell.value
|
|
169
|
+
value: cell.value,
|
|
170
|
+
record: cell.record
|
|
163
171
|
})
|
|
164
172
|
}, `hit-${pairKey(cell.rowLabel, cell.colLabel)}`))
|
|
165
173
|
]
|
|
@@ -177,7 +185,30 @@ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square",
|
|
|
177
185
|
" · ",
|
|
178
186
|
formatX(hovered.colLabel)
|
|
179
187
|
]
|
|
180
|
-
}), /* @__PURE__ */
|
|
188
|
+
}), tooltipMetrics && tooltipMetrics.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
189
|
+
className: "flex flex-col gap-1",
|
|
190
|
+
children: tooltipMetrics.map((metric) => {
|
|
191
|
+
const raw = hovered.record ? Number(hovered.record[metric.key]) || 0 : 0;
|
|
192
|
+
const isActive = metric.key === dataKey;
|
|
193
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
194
|
+
className: "flex items-center justify-between gap-4 text-text-xs",
|
|
195
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
196
|
+
className: "inline-flex items-center gap-2 text-muted-foreground",
|
|
197
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
198
|
+
"aria-hidden": true,
|
|
199
|
+
className: "size-2 shrink-0 rounded-[2px]",
|
|
200
|
+
style: {
|
|
201
|
+
background: isActive ? heatColor(ramp, hovered.value / maxValue) : theme.mutedForeground,
|
|
202
|
+
opacity: isActive ? 1 : .35
|
|
203
|
+
}
|
|
204
|
+
}), metric.label]
|
|
205
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
206
|
+
className: cn("font-mono tabular-nums", isActive ? "font-semibold text-foreground" : "text-muted-foreground"),
|
|
207
|
+
children: (metric.format ?? valueFormatter)(raw)
|
|
208
|
+
})]
|
|
209
|
+
}, metric.key);
|
|
210
|
+
})
|
|
211
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
181
212
|
className: "flex items-center justify-between gap-4 text-text-xs",
|
|
182
213
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
183
214
|
className: "inline-flex items-center gap-2 text-muted-foreground",
|
|
@@ -15,6 +15,7 @@ interface LineChartProps {
|
|
|
15
15
|
series: ChartSeries[];
|
|
16
16
|
curve?: keyof typeof CURVE_TYPE;
|
|
17
17
|
legend?: boolean;
|
|
18
|
+
legendAlign?: "left" | "center" | "right";
|
|
18
19
|
valueFlags?: boolean;
|
|
19
20
|
dots?: boolean;
|
|
20
21
|
height?: number;
|
|
@@ -38,6 +39,7 @@ declare function LineChart({
|
|
|
38
39
|
series,
|
|
39
40
|
curve,
|
|
40
41
|
legend,
|
|
42
|
+
legendAlign,
|
|
41
43
|
valueFlags,
|
|
42
44
|
dots,
|
|
43
45
|
height,
|