@alpic-ai/ui 0.0.0-dev.g19fc228 → 0.0.0-dev.g1a5d5ed

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 (42) hide show
  1. package/dist/components/area-chart.d.mts +2 -0
  2. package/dist/components/area-chart.mjs +9 -3
  3. package/dist/components/bar-chart.d.mts +2 -0
  4. package/dist/components/bar-chart.mjs +9 -3
  5. package/dist/components/bar-list.d.mts +3 -0
  6. package/dist/components/bar-list.mjs +19 -7
  7. package/dist/components/chart-card.d.mts +1 -1
  8. package/dist/components/chart-card.mjs +1 -1
  9. package/dist/components/chart-container.d.mts +1 -1
  10. package/dist/components/chart-legend.d.mts +5 -0
  11. package/dist/components/chart-legend.mjs +11 -2
  12. package/dist/components/donut-chart.mjs +4 -0
  13. package/dist/components/heatmap-chart.d.mts +8 -0
  14. package/dist/components/heatmap-chart.mjs +39 -8
  15. package/dist/components/line-chart.d.mts +2 -0
  16. package/dist/components/line-chart.mjs +10 -3
  17. package/dist/components/stat.d.mts +3 -1
  18. package/dist/components/stat.mjs +14 -4
  19. package/dist/components/textarea.mjs +1 -1
  20. package/dist/lib/chart.mjs +16 -1
  21. package/package.json +23 -23
  22. package/src/components/area-chart.tsx +12 -4
  23. package/src/components/bar-chart.tsx +12 -4
  24. package/src/components/bar-list.tsx +21 -6
  25. package/src/components/chart-card.tsx +8 -6
  26. package/src/components/chart-container.tsx +2 -0
  27. package/src/components/chart-legend.tsx +10 -2
  28. package/src/components/donut-chart.tsx +1 -1
  29. package/src/components/heatmap-chart.tsx +62 -18
  30. package/src/components/line-chart.tsx +18 -5
  31. package/src/components/stat.tsx +10 -6
  32. package/src/components/textarea.tsx +1 -1
  33. package/src/lib/chart.ts +34 -0
  34. package/src/stories/area-chart.stories.tsx +1 -1
  35. package/src/stories/bar-chart.stories.tsx +1 -1
  36. package/src/stories/bar-list.stories.tsx +1 -1
  37. package/src/stories/donut-chart.stories.tsx +1 -1
  38. package/src/stories/heatmap-chart.stories.tsx +1 -1
  39. package/src/stories/line-chart.stories.tsx +1 -1
  40. package/src/stories/textarea.stories.tsx +7 -0
  41. package/src/stories/wizard.stories.tsx +1 -1
  42. package/src/styles/tokens.css +0 -45
@@ -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,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.mjs";
3
3
  import { useReducedMotion } from "../hooks/use-reduced-motion.mjs";
4
- import { orderByLuminance, resolveSeries } from "../lib/chart.mjs";
4
+ import { makeXAxisTick, orderByLuminance, resolveSeries } from "../lib/chart.mjs";
5
5
  import { useChartContext } from "./chart-container.mjs";
6
6
  import { ChartLegend } from "./chart-legend.mjs";
7
7
  import { ChartTooltipContent } from "./chart-tooltip.mjs";
@@ -14,7 +14,7 @@ const CURVE_TYPE = {
14
14
  linear: "linear",
15
15
  step: "stepAfter"
16
16
  };
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 }) {
17
+ 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
18
  const { palette: paletteColors, theme } = useChartContext(palette);
19
19
  const reactId = React$1.useId().replace(/:/g, "");
20
20
  const animated = !useReducedMotion();
@@ -128,6 +128,10 @@ function AreaChart({ data, index, series, variant = "stacked", curve = "monotone
128
128
  }) : /* @__PURE__ */ jsx(ResponsiveContainer, {
129
129
  width: "100%",
130
130
  height: "100%",
131
+ initialDimension: {
132
+ width: 0,
133
+ height
134
+ },
131
135
  children: /* @__PURE__ */ jsxs(AreaChart$1, {
132
136
  data,
133
137
  stackOffset: variant === "expand" ? "expand" : "none",
@@ -177,6 +181,7 @@ function AreaChart({ data, index, series, variant = "stacked", curve = "monotone
177
181
  /* @__PURE__ */ jsx(XAxis, {
178
182
  dataKey: index,
179
183
  ...axis,
184
+ tick: makeXAxisTick(theme),
180
185
  interval: "preserveStartEnd",
181
186
  minTickGap: 44
182
187
  }),
@@ -261,7 +266,8 @@ function AreaChart({ data, index, series, variant = "stacked", curve = "monotone
261
266
  })
262
267
  }), legend && !isEmpty && /* @__PURE__ */ jsx(ChartLegend, {
263
268
  items: legendItems,
264
- style: { paddingLeft: yAxisWidth }
269
+ align: legendAlign,
270
+ insetLeft: yAxisWidth
265
271
  })]
266
272
  });
267
273
  }
@@ -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,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.mjs";
3
3
  import { useReducedMotion } from "../hooks/use-reduced-motion.mjs";
4
- import { orderByLuminance, resolveSeries } from "../lib/chart.mjs";
4
+ import { makeXAxisTick, orderByLuminance, resolveSeries } from "../lib/chart.mjs";
5
5
  import { useChartContext } from "./chart-container.mjs";
6
6
  import { ChartLegend } from "./chart-legend.mjs";
7
7
  import { ChartTooltipContent } from "./chart-tooltip.mjs";
@@ -11,7 +11,7 @@ import { Bar, BarChart as BarChart$1, CartesianGrid, LabelList, ReferenceArea, R
11
11
  //#region src/components/bar-chart.tsx
12
12
  const BAR_RADIUS = 4;
13
13
  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 }) {
14
+ 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
15
  const { palette: paletteColors, theme } = useChartContext(palette);
16
16
  const reactId = React$1.useId().replace(/:/g, "");
17
17
  const animated = !useReducedMotion();
@@ -111,6 +111,10 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, va
111
111
  }) : /* @__PURE__ */ jsx(ResponsiveContainer, {
112
112
  width: "100%",
113
113
  height: "100%",
114
+ initialDimension: {
115
+ width: 0,
116
+ height
117
+ },
114
118
  children: /* @__PURE__ */ jsxs(BarChart$1, {
115
119
  data,
116
120
  stackOffset: variant === "expand" ? "expand" : "none",
@@ -161,6 +165,7 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, va
161
165
  /* @__PURE__ */ jsx(XAxis, {
162
166
  dataKey: index,
163
167
  ...axis,
168
+ tick: makeXAxisTick(theme),
164
169
  interval: "preserveStartEnd",
165
170
  minTickGap: 44
166
171
  }),
@@ -248,7 +253,8 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, va
248
253
  })
249
254
  }), legend && !isEmpty && /* @__PURE__ */ jsx(ChartLegend, {
250
255
  items: legendItems,
251
- style: { paddingLeft: yAxisWidth }
256
+ align: legendAlign,
257
+ insetLeft: yAxisWidth
252
258
  })]
253
259
  });
254
260
  }
@@ -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,
@@ -7,11 +7,18 @@ import { useChartContext } from "./chart-container.mjs";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import * as React$1 from "react";
9
9
  //#region src/components/bar-list.tsx
10
+ const SEMANTIC_KEY = {
11
+ error: "destructive",
12
+ warning: "warning",
13
+ success: "success"
14
+ };
10
15
  const PLACEHOLDER_HEIGHT = 168;
11
16
  const RAMP_CEILING = .8;
17
+ const SEMANTIC_FLOOR = .62;
12
18
  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);
19
+ function BarList({ data, index, dataKey = "value", maxItems, palette, semantic, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
20
+ const { paletteName, theme } = useChartContext(palette);
21
+ const accent = semantic ? theme[SEMANTIC_KEY[semantic]] : null;
15
22
  const reducedMotion = useReducedMotion();
16
23
  const [mounted, setMounted] = React$1.useState(false);
17
24
  const [active, setActive] = React$1.useState(null);
@@ -26,16 +33,21 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, loading =
26
33
  }));
27
34
  mapped.sort((lower, upper) => upper.value - lower.value);
28
35
  const capped = maxItems ? mapped.slice(0, maxItems) : mapped;
29
- return capped.map((row, rank) => ({
30
- ...row,
31
- color: rampColor(paletteName, (capped.length > 1 ? rank / (capped.length - 1) : 0) * RAMP_CEILING)
32
- }));
36
+ return capped.map((row, rank) => {
37
+ const rankFraction = capped.length > 1 ? rank / (capped.length - 1) : 0;
38
+ const accentWeight = Math.round((1 - rankFraction * (1 - SEMANTIC_FLOOR)) * 100);
39
+ return {
40
+ ...row,
41
+ color: accent ? `color-mix(in oklab, ${accent} ${accentWeight}%, white)` : rampColor(paletteName, rankFraction * RAMP_CEILING)
42
+ };
43
+ });
33
44
  }, [
34
45
  data,
35
46
  index,
36
47
  dataKey,
37
48
  maxItems,
38
- paletteName
49
+ paletteName,
50
+ accent
39
51
  ]);
40
52
  const maxValue = rows.reduce((max, row) => row.value > max ? row.value : max, 0);
41
53
  const isEmpty = rows.length === 0;
@@ -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]",
@@ -73,6 +73,10 @@ function DonutChart({ data, index, dataKey = "value", variant = "donut", legend
73
73
  children: [/* @__PURE__ */ jsx(ResponsiveContainer, {
74
74
  width: "100%",
75
75
  height: "100%",
76
+ initialDimension: {
77
+ width: 0,
78
+ height
79
+ },
76
80
  children: /* @__PURE__ */ jsxs(PieChart, { children: [/* @__PURE__ */ jsx("defs", { children: slices.map((slice, slot) => /* @__PURE__ */ jsxs("linearGradient", {
77
81
  id: `donut-${reactId}-${slot}`,
78
82
  x1: "0",
@@ -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
- for (const row of data) valueAt.set(pairKey(String(row[yKey] ?? ""), String(row[xKey] ?? "")), Number(row[dataKey]) || 0);
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 value = valueAt.get(pairKey(rowLabel, colLabel)) ?? 0;
57
+ const key = pairKey(rowLabel, colLabel);
58
+ const value = valueAt.get(key) ?? 0;
53
59
  if (value > maxValue) {
54
60
  maxValue = value;
55
- peakKey = pairKey(rowLabel, colLabel);
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__ */ jsxs("div", {
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,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.mjs";
3
3
  import { useReducedMotion } from "../hooks/use-reduced-motion.mjs";
4
- import { resolveSeries } from "../lib/chart.mjs";
4
+ import { makeXAxisTick, resolveSeries } from "../lib/chart.mjs";
5
5
  import { useChartContext } from "./chart-container.mjs";
6
6
  import { ChartLegend } from "./chart-legend.mjs";
7
7
  import { ChartTooltipContent } from "./chart-tooltip.mjs";
@@ -14,7 +14,7 @@ const CURVE_TYPE = {
14
14
  linear: "linear",
15
15
  step: "stepAfter"
16
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 }) {
17
+ function LineChart({ data, index, series, curve = "monotone", legend = false, legendAlign = "left", valueFlags = false, dots = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, lastValueLabel = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
18
18
  const { palette: paletteColors, theme } = useChartContext(palette);
19
19
  const animated = !useReducedMotion();
20
20
  const resolved = resolveSeries(series, paletteColors, theme);
@@ -106,6 +106,10 @@ function LineChart({ data, index, series, curve = "monotone", legend = false, va
106
106
  }) : /* @__PURE__ */ jsx(ResponsiveContainer, {
107
107
  width: "100%",
108
108
  height: "100%",
109
+ initialDimension: {
110
+ width: 0,
111
+ height
112
+ },
109
113
  children: /* @__PURE__ */ jsxs(LineChart$1, {
110
114
  data,
111
115
  margin,
@@ -118,12 +122,14 @@ function LineChart({ data, index, series, curve = "monotone", legend = false, va
118
122
  /* @__PURE__ */ jsx(XAxis, {
119
123
  dataKey: index,
120
124
  ...axis,
125
+ tick: makeXAxisTick(theme),
121
126
  interval: "preserveStartEnd",
122
127
  minTickGap: 44
123
128
  }),
124
129
  /* @__PURE__ */ jsx(YAxis, {
125
130
  ...axis,
126
131
  width: yAxisWidth,
132
+ domain: ["auto", "auto"],
127
133
  tickFormatter: (value) => valueFormatter(value)
128
134
  }),
129
135
  /* @__PURE__ */ jsx(Tooltip, {
@@ -203,7 +209,8 @@ function LineChart({ data, index, series, curve = "monotone", legend = false, va
203
209
  })
204
210
  }), legend && !isEmpty && /* @__PURE__ */ jsx(ChartLegend, {
205
211
  items: legendItems,
206
- style: { paddingLeft: yAxisWidth }
212
+ align: legendAlign,
213
+ insetLeft: yAxisWidth
207
214
  })]
208
215
  });
209
216
  }
@@ -13,16 +13,18 @@ interface StatDelta {
13
13
  interface StatProps extends React$1.ComponentProps<"div"> {
14
14
  value: React$1.ReactNode;
15
15
  unit?: string;
16
- delta?: StatDelta;
16
+ delta?: StatDelta | null;
17
17
  sparkline?: number[] | Array<{
18
18
  value: number;
19
19
  }>;
20
+ semantic?: "error" | "warning" | "success";
20
21
  }
21
22
  declare function Stat({
22
23
  value,
23
24
  unit,
24
25
  delta,
25
26
  sparkline,
27
+ semantic,
26
28
  className,
27
29
  ...props
28
30
  }: StatProps): React$1.JSX.Element;
@@ -21,11 +21,17 @@ const toSparkData = (sparkline) => (sparkline ?? []).map((point, index) => typeo
21
21
  index,
22
22
  value: point.value
23
23
  });
24
- function Stat({ value, unit, delta, sparkline, className, ...props }) {
25
- const { palette } = useChartContext();
24
+ const SEMANTIC_KEY = {
25
+ error: "destructive",
26
+ warning: "warning",
27
+ success: "success"
28
+ };
29
+ function Stat({ value, unit, delta, sparkline, semantic, className, ...props }) {
30
+ const { palette, theme } = useChartContext();
26
31
  const gradientId = React$1.useId().replace(/:/g, "");
27
32
  const sparkData = React$1.useMemo(() => toSparkData(sparkline), [sparkline]);
28
- const sparkColor = palette[0];
33
+ const hasSpark = sparkData.some((point) => point.value > 0);
34
+ const sparkColor = semantic ? theme[SEMANTIC_KEY[semantic]] : palette[0];
29
35
  const sentiment = delta && (delta.invert ? delta.direction === "down" : delta.direction === "up") ? "positive" : "negative";
30
36
  return /* @__PURE__ */ jsxs("div", {
31
37
  "data-slot": "stat",
@@ -48,11 +54,15 @@ function Stat({ value, unit, delta, sparkline, className, ...props }) {
48
54
  children: [delta.direction === "up" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "size-3" }), delta.label ?? `${delta.value}%`]
49
55
  })
50
56
  ]
51
- }), sparkData.length > 0 && /* @__PURE__ */ jsx("div", {
57
+ }), hasSpark && /* @__PURE__ */ jsx("div", {
52
58
  className: "h-9 w-full",
53
59
  children: /* @__PURE__ */ jsx(ResponsiveContainer, {
54
60
  width: "100%",
55
61
  height: "100%",
62
+ initialDimension: {
63
+ width: 0,
64
+ height: 36
65
+ },
56
66
  children: /* @__PURE__ */ jsxs(AreaChart, {
57
67
  data: sparkData,
58
68
  margin: {
@@ -12,7 +12,7 @@ function Textarea({ className, id, label, required, hint, error, tooltip, ...pro
12
12
  const textarea = /* @__PURE__ */ jsx("textarea", {
13
13
  id: fieldId,
14
14
  "data-slot": "textarea",
15
- className: cn("block w-full min-h-[120px] resize-none", "px-3.5 py-3", "type-text-md text-foreground placeholder:text-placeholder", "bg-background border border-border rounded-md", "transition-colors", "outline-none focus-visible:border-ring focus-visible:border-2", "disabled:bg-disabled disabled:text-disabled-foreground disabled:cursor-not-allowed", "aria-invalid:border-border-error [@media(hover:hover)]:aria-invalid:hover:border-border-error", error && "border-border-error [@media(hover:hover)]:hover:border-border-error", className),
15
+ className: cn("block w-full min-h-[120px] max-h-[480px] resize-y [field-sizing:content]", "px-3.5 py-3", "type-text-md text-foreground placeholder:text-placeholder", "bg-background border border-border rounded-md", "transition-colors", "outline-none focus-visible:border-ring focus-visible:border-2", "disabled:bg-disabled disabled:text-disabled-foreground disabled:cursor-not-allowed", "aria-invalid:border-border-error [@media(hover:hover)]:aria-invalid:hover:border-border-error", error && "border-border-error [@media(hover:hover)]:hover:border-border-error", className),
16
16
  required,
17
17
  "aria-invalid": error ? true : void 0,
18
18
  "aria-describedby": fieldId && (hint || error) ? `${fieldId}-description` : void 0,
@@ -1,4 +1,5 @@
1
1
  import { luminance, paletteColor } from "./chart-palette.mjs";
2
+ import * as React$1 from "react";
2
3
  //#region src/lib/chart.ts
3
4
  const formatShare = (fraction) => `${(fraction * 100).toFixed(fraction >= .1 ? 0 : 1)}%`;
4
5
  const semanticColor = (theme, semantic) => {
@@ -23,5 +24,19 @@ const resolveSeries = (series, palette, theme) => series.map((entry, index) => (
23
24
  * stack order in Recharts — first entry sits at the bottom.
24
25
  */
25
26
  const orderByLuminance = (series) => [...series].sort((lower, upper) => luminance(lower.color) - luminance(upper.color));
27
+ const makeXAxisTick = (theme) => ({ x, y, payload, index, visibleTicksCount }) => {
28
+ const isFirst = index === 0;
29
+ const isLast = visibleTicksCount != null && index === visibleTicksCount - 1;
30
+ const anchor = isFirst ? "start" : isLast ? "end" : "middle";
31
+ return React$1.createElement("text", {
32
+ x: Number(x ?? 0),
33
+ y: Number(y ?? 0),
34
+ dy: 12,
35
+ textAnchor: anchor,
36
+ fill: theme.axisForeground,
37
+ fontFamily: theme.fontMono,
38
+ fontSize: 10
39
+ }, String(payload?.value ?? ""));
40
+ };
26
41
  //#endregion
27
- export { formatShare, orderByLuminance, resolveSeries };
42
+ export { formatShare, makeXAxisTick, orderByLuminance, resolveSeries };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpic-ai/ui",
3
- "version": "0.0.0-dev.g19fc228",
3
+ "version": "0.0.0-dev.g1a5d5ed",
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
  },