gridsmith-ui 0.9.0 → 0.9.1

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/charts.d.ts CHANGED
@@ -2,6 +2,8 @@ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
 
5
+ type XAxisInterval = number | "preserveStart" | "preserveEnd" | "preserveStartEnd" | "equidistantPreserveStart";
6
+
5
7
  interface BarDef {
6
8
  dataKey: string;
7
9
  label?: string;
@@ -9,10 +11,12 @@ interface BarDef {
9
11
  }
10
12
  interface BarChartProps {
11
13
  /**
12
- * Which category ticks show. "preserveStartEnd" (default) keeps the first and last and drops the rest as they
13
- * would collide; 0 shows every tick (labels stay on one line and truncate). Recharts measures the full label.
14
+ * Which category ticks show. Leave it out: the chart measures its widest label with the chart's font and labels every
15
+ * k-th category from the first, the last only when it falls on the step (Sprint 25.4 C-11, decision 7). A number
16
+ * labels every Nth category instead. The Recharts strings ("preserveStartEnd") are accepted for compatibility and
17
+ * mean the same measured labelling — they never hand the ticks back to Recharts' thinning (round 13).
14
18
  */
15
- xAxisInterval?: number | "preserveStart" | "preserveEnd" | "preserveStartEnd" | "equidistantPreserveStart";
19
+ xAxisInterval?: XAxisInterval;
16
20
  data: Record<string, unknown>[];
17
21
  bars: BarDef[];
18
22
  xAxisKey: string;
@@ -37,10 +41,12 @@ interface LineDef {
37
41
  }
38
42
  interface LineChartProps {
39
43
  /**
40
- * Which category ticks show. "preserveStartEnd" (default) keeps the first and last and drops the rest as they
41
- * would collide; 0 shows every tick (labels stay on one line and truncate). Recharts measures the full label.
44
+ * Which category ticks show. Leave it out: the chart measures its widest label with the chart's font and labels every
45
+ * k-th category from the first, the last only when it falls on the step (Sprint 25.4 C-11, decision 7). A number
46
+ * labels every Nth category instead. The Recharts strings ("preserveStartEnd") are accepted for compatibility and
47
+ * mean the same measured labelling — they never hand the ticks back to Recharts' thinning (round 13).
42
48
  */
43
- xAxisInterval?: number | "preserveStart" | "preserveEnd" | "preserveStartEnd" | "equidistantPreserveStart";
49
+ xAxisInterval?: XAxisInterval;
44
50
  data: Record<string, unknown>[];
45
51
  lines: LineDef[];
46
52
  xAxisKey: string;
@@ -64,10 +70,12 @@ interface AreaDef {
64
70
  }
65
71
  interface AreaChartProps {
66
72
  /**
67
- * Which category ticks show. "preserveStartEnd" (default) keeps the first and last and drops the rest as they
68
- * would collide; 0 shows every tick (labels stay on one line and truncate). Recharts measures the full label.
73
+ * Which category ticks show. Leave it out: the chart measures its widest label with the chart's font and labels every
74
+ * k-th category from the first, the last only when it falls on the step (Sprint 25.4 C-11, decision 7). A number
75
+ * labels every Nth category instead. The Recharts strings ("preserveStartEnd") are accepted for compatibility and
76
+ * mean the same measured labelling — they never hand the ticks back to Recharts' thinning (round 13).
69
77
  */
70
- xAxisInterval?: number | "preserveStart" | "preserveEnd" | "preserveStartEnd" | "equidistantPreserveStart";
78
+ xAxisInterval?: XAxisInterval;
71
79
  data: Record<string, unknown>[];
72
80
  areas: AreaDef[];
73
81
  xAxisKey: string;
@@ -105,10 +113,12 @@ declare const PieChart: react.ForwardRefExoticComponent<PieChartProps & react.Re
105
113
 
106
114
  interface CompositeChartProps {
107
115
  /**
108
- * Which category ticks show. "preserveStartEnd" (default) keeps the first and last and drops the rest as they
109
- * would collide; 0 shows every tick (labels stay on one line and truncate). Recharts measures the full label.
116
+ * Which category ticks show. Leave it out: the chart measures its widest label with the chart's font and labels every
117
+ * k-th category from the first, the last only when it falls on the step (Sprint 25.4 C-11, decision 7). A number
118
+ * labels every Nth category instead. The Recharts strings ("preserveStartEnd") are accepted for compatibility and
119
+ * mean the same measured labelling — they never hand the ticks back to Recharts' thinning (round 13).
110
120
  */
111
- xAxisInterval?: number | "preserveStart" | "preserveEnd" | "preserveStartEnd" | "equidistantPreserveStart";
121
+ xAxisInterval?: XAxisInterval;
112
122
  data: Record<string, unknown>[];
113
123
  bars: BarDef[];
114
124
  lines: LineDef[];
package/dist/charts.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Skeleton } from './chunk-M3AARGLI.js';
2
+ import { mergeRefs } from './chunk-PKLVP5ZB.js';
2
3
  import { cn, GLASS_SURFACE } from './chunk-4KVFH5KC.js';
3
- import { createContext, forwardRef, useId, useContext, useSyncExternalStore, useState, useLayoutEffect } from 'react';
4
+ import { createContext, forwardRef, useRef, useId, useContext, useMemo, useState, useEffect, useSyncExternalStore, useLayoutEffect } from 'react';
4
5
  import { ResponsiveContainer, BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LineChart as LineChart$1, Line, AreaChart as AreaChart$1, Area, PieChart as PieChart$1, Pie, Cell, ComposedChart, ScatterChart as ScatterChart$1, ZAxis, Scatter, RadarChart as RadarChart$1, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, RadialBarChart as RadialBarChart$1, RadialBar, FunnelChart as FunnelChart$1, Funnel, LabelList, Treemap, Sankey, Text } from 'recharts';
5
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
7
 
@@ -208,6 +209,62 @@ function yAxisTick(props) {
208
209
  const { x, y, payload, ...rest } = props;
209
210
  return /* @__PURE__ */ jsx(Text, { ...rest, x, y, textAnchor: "end", verticalAnchor: "middle", width: 200, maxLines: 2, children: payload.value });
210
211
  }
212
+ function lengthPx(value, el) {
213
+ const v = value.trim();
214
+ if (v.endsWith("rem")) return parseFloat(v) * (parseFloat(getComputedStyle(el.ownerDocument.documentElement).fontSize) || 16);
215
+ if (v.endsWith("px")) return parseFloat(v);
216
+ const n = parseFloat(v);
217
+ return Number.isFinite(n) ? n : NaN;
218
+ }
219
+ function widestLabel(el, labels) {
220
+ if (typeof document === "undefined") return null;
221
+ const ctx = document.createElement("canvas").getContext?.("2d");
222
+ if (!ctx) return null;
223
+ const cs = getComputedStyle(el);
224
+ const size = lengthPx(cs.getPropertyValue("--ds-font-size-xs"), el) || 12;
225
+ const family = cs.getPropertyValue("--ds-font-sans").trim() || cs.fontFamily || "sans-serif";
226
+ ctx.font = `${size}px ${family}`;
227
+ return labels.reduce((w, l) => Math.max(w, ctx.measureText(l).width), 0);
228
+ }
229
+ var TICK_GAP = 12;
230
+ var AXIS_RESERVE = 48;
231
+ function useCategoryTicks(data, xAxisKey, ref, override) {
232
+ const labels = useMemo(() => (data ?? []).map((d) => String(d[xAxisKey] ?? "")), [data, xAxisKey]);
233
+ const labelsKey = labels.join("");
234
+ const [ticks, setTicks] = useState(void 0);
235
+ const [endMargin, setEndMargin] = useState(4);
236
+ const numeric = typeof override === "number" ? override : void 0;
237
+ useEffect(() => {
238
+ const el = ref.current;
239
+ if (numeric !== void 0 || !el || labels.length === 0) return;
240
+ let live = true;
241
+ const compute = () => {
242
+ if (!live) return;
243
+ const widest = widestLabel(el, labels);
244
+ const plot = el.clientWidth - AXIS_RESERVE;
245
+ if (widest == null || plot <= 0) {
246
+ setTicks(void 0);
247
+ return;
248
+ }
249
+ setEndMargin(Math.max(4, Math.ceil(widest / 2)));
250
+ const fits = Math.max(1, Math.floor(plot / (widest + TICK_GAP)));
251
+ const step = Math.ceil(labels.length / fits);
252
+ setTicks(labels.filter((_, i) => i % step === 0));
253
+ };
254
+ if (typeof ResizeObserver === "undefined") return;
255
+ const ro = new ResizeObserver(compute);
256
+ ro.observe(el);
257
+ const fonts = document.fonts;
258
+ fonts?.ready.then(compute, () => {
259
+ });
260
+ return () => {
261
+ live = false;
262
+ ro.disconnect();
263
+ };
264
+ }, [labelsKey, numeric, ref]);
265
+ if (numeric !== void 0) return { interval: numeric, endMargin: 4 };
266
+ return ticks ? { ticks, interval: 0, endMargin } : { interval: "preserveStartEnd", endMargin: 4 };
267
+ }
211
268
  var ChartContainerContext = createContext(false);
212
269
  function useInsideChartContainer() {
213
270
  return useContext(ChartContainerContext);
@@ -229,7 +286,7 @@ var BarChart = forwardRef(function BarChart2({
229
286
  data,
230
287
  bars,
231
288
  xAxisKey,
232
- xAxisInterval = "preserveStartEnd",
289
+ xAxisInterval,
233
290
  height = "md",
234
291
  stacked = false,
235
292
  horizontal = false,
@@ -242,6 +299,8 @@ var BarChart = forwardRef(function BarChart2({
242
299
  "aria-label": ariaLabel,
243
300
  className
244
301
  }, ref) {
302
+ const wrapperRef = useRef(null);
303
+ const xTicks = useCategoryTicks(data, xAxisKey, wrapperRef, horizontal ? xAxisInterval ?? "preserveStartEnd" : xAxisInterval);
245
304
  const solidColors = useChartColors(bars.length, false);
246
305
  const softColors = useChartColors(bars.length, true);
247
306
  const gridStroke = useGridStroke();
@@ -260,12 +319,12 @@ var BarChart = forwardRef(function BarChart2({
260
319
  }
261
320
  const axisLabelStyle = { fontSize: "var(--ds-font-size-xs)", fontFamily: "var(--ds-font-sans)", fill: axisStyle.tick.fill };
262
321
  if (!data?.length) return null;
263
- return /* @__PURE__ */ jsx(ChartWrapper, { ref, height, className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
322
+ return /* @__PURE__ */ jsx(ChartWrapper, { ref: mergeRefs(ref, wrapperRef), height, className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
264
323
  BarChart$1,
265
324
  {
266
325
  data,
267
326
  layout,
268
- margin: { top: 4, right: 4, bottom: 4, left: 4 },
327
+ margin: { top: 4, right: xTicks.endMargin, bottom: 4, left: 4 },
269
328
  accessibilityLayer: true,
270
329
  children: [
271
330
  showGrid && /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke, vertical: !horizontal, horizontal: true }),
@@ -301,7 +360,8 @@ var BarChart = forwardRef(function BarChart2({
301
360
  axisLine: false,
302
361
  tickLine: false,
303
362
  tick: xAxisTick,
304
- interval: xAxisInterval,
363
+ interval: xTicks.interval,
364
+ ticks: xTicks.ticks,
305
365
  height: "auto",
306
366
  label: xAxisLabel ? { value: xAxisLabel, position: "insideBottom", offset: -2, style: axisLabelStyle } : void 0
307
367
  }
@@ -339,7 +399,7 @@ var LineChart = forwardRef(function LineChart2({
339
399
  data,
340
400
  lines,
341
401
  xAxisKey,
342
- xAxisInterval = "preserveStartEnd",
402
+ xAxisInterval,
343
403
  height = "md",
344
404
  curved = true,
345
405
  showDots = false,
@@ -352,6 +412,8 @@ var LineChart = forwardRef(function LineChart2({
352
412
  "aria-label": ariaLabel,
353
413
  className
354
414
  }, ref) {
415
+ const wrapperRef = useRef(null);
416
+ const xTicks = useCategoryTicks(data, xAxisKey, wrapperRef, xAxisInterval);
355
417
  const colors = useChartColors(lines.length, false);
356
418
  const gridStroke = useGridStroke();
357
419
  const axisStyle = useAxisStyle();
@@ -359,7 +421,7 @@ var LineChart = forwardRef(function LineChart2({
359
421
  const legendVisible = showLegend ?? lines.length >= 2;
360
422
  const axisLabelStyle = { fontSize: "var(--ds-font-size-xs)", fontFamily: "var(--ds-font-sans)", fill: axisStyle.tick.fill };
361
423
  if (!data?.length) return null;
362
- return /* @__PURE__ */ jsx(ChartWrapper, { ref, height, className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(LineChart$1, { data, margin: { top: 4, right: 4, bottom: 4, left: 4 }, accessibilityLayer: true, children: [
424
+ return /* @__PURE__ */ jsx(ChartWrapper, { ref: mergeRefs(ref, wrapperRef), height, className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(LineChart$1, { data, margin: { top: 4, right: xTicks.endMargin, bottom: 4, left: 4 }, accessibilityLayer: true, children: [
363
425
  showGrid && /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }),
364
426
  /* @__PURE__ */ jsx(
365
427
  XAxis,
@@ -369,7 +431,8 @@ var LineChart = forwardRef(function LineChart2({
369
431
  axisLine: false,
370
432
  tickLine: false,
371
433
  tick: xAxisTick,
372
- interval: xAxisInterval,
434
+ interval: xTicks.interval,
435
+ ticks: xTicks.ticks,
373
436
  height: "auto",
374
437
  label: xAxisLabel ? { value: xAxisLabel, position: "insideBottom", offset: -2, style: axisLabelStyle } : void 0
375
438
  }
@@ -407,7 +470,7 @@ var AreaChart = forwardRef(function AreaChart2({
407
470
  data,
408
471
  areas,
409
472
  xAxisKey,
410
- xAxisInterval = "preserveStartEnd",
473
+ xAxisInterval,
411
474
  height = "md",
412
475
  stacked = false,
413
476
  curved = true,
@@ -421,6 +484,8 @@ var AreaChart = forwardRef(function AreaChart2({
421
484
  "aria-label": ariaLabel,
422
485
  className
423
486
  }, ref) {
487
+ const wrapperRef = useRef(null);
488
+ const xTicks = useCategoryTicks(data, xAxisKey, wrapperRef, xAxisInterval);
424
489
  const instanceId = useId();
425
490
  const solidColors = useChartColors(areas.length, false);
426
491
  const softColors = useChartColors(areas.length, true);
@@ -430,7 +495,7 @@ var AreaChart = forwardRef(function AreaChart2({
430
495
  const legendVisible = showLegend ?? areas.length >= 2;
431
496
  const axisLabelStyle = { fontSize: "var(--ds-font-size-xs)", fontFamily: "var(--ds-font-sans)", fill: axisStyle.tick.fill };
432
497
  if (!data?.length) return null;
433
- return /* @__PURE__ */ jsx(ChartWrapper, { ref, height, className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(AreaChart$1, { data, margin: { top: 4, right: 4, bottom: 4, left: 4 }, accessibilityLayer: true, children: [
498
+ return /* @__PURE__ */ jsx(ChartWrapper, { ref: mergeRefs(ref, wrapperRef), height, className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(AreaChart$1, { data, margin: { top: 4, right: xTicks.endMargin, bottom: 4, left: 4 }, accessibilityLayer: true, children: [
434
499
  /* @__PURE__ */ jsx("defs", { children: areas.map((area, i) => /* @__PURE__ */ jsxs("linearGradient", { id: `gradient-${instanceId}-${area.dataKey}`, x1: "0", y1: "0", x2: "0", y2: "1", children: [
435
500
  /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: softColors[i], stopOpacity: 1 }),
436
501
  /* @__PURE__ */ jsx("stop", { offset: "95%", stopColor: softColors[i], stopOpacity: 0.1 })
@@ -444,7 +509,8 @@ var AreaChart = forwardRef(function AreaChart2({
444
509
  axisLine: false,
445
510
  tickLine: false,
446
511
  tick: xAxisTick,
447
- interval: xAxisInterval,
512
+ interval: xTicks.interval,
513
+ ticks: xTicks.ticks,
448
514
  height: "auto",
449
515
  label: xAxisLabel ? { value: xAxisLabel, position: "insideBottom", offset: -2, style: axisLabelStyle } : void 0
450
516
  }
@@ -520,7 +586,7 @@ var CompositeChart = forwardRef(function CompositeChart2({
520
586
  bars,
521
587
  lines,
522
588
  xAxisKey,
523
- xAxisInterval = "preserveStartEnd",
589
+ xAxisInterval,
524
590
  height = "md",
525
591
  dualAxis = true,
526
592
  showGrid = true,
@@ -532,6 +598,8 @@ var CompositeChart = forwardRef(function CompositeChart2({
532
598
  "aria-label": ariaLabel,
533
599
  className
534
600
  }, ref) {
601
+ const wrapperRef = useRef(null);
602
+ const xTicks = useCategoryTicks(data, xAxisKey, wrapperRef, xAxisInterval);
535
603
  const barSoftColors = useChartColors(bars.length, true);
536
604
  const lineColors = useChartColors(lines.length, false);
537
605
  const gridStroke = useGridStroke();
@@ -540,7 +608,7 @@ var CompositeChart = forwardRef(function CompositeChart2({
540
608
  const radius = useRadius();
541
609
  const axisLabelStyle = { fontSize: "var(--ds-font-size-xs)", fontFamily: "var(--ds-font-sans)", fill: axisStyle.tick.fill };
542
610
  if (!data?.length) return null;
543
- return /* @__PURE__ */ jsx(ChartWrapper, { ref, height, className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(ComposedChart, { data, margin: { top: 4, right: 4, bottom: 4, left: 4 }, accessibilityLayer: true, children: [
611
+ return /* @__PURE__ */ jsx(ChartWrapper, { ref: mergeRefs(ref, wrapperRef), height, className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(ComposedChart, { data, margin: { top: 4, right: xTicks.endMargin, bottom: 4, left: 4 }, accessibilityLayer: true, children: [
544
612
  showGrid && /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }),
545
613
  /* @__PURE__ */ jsx(
546
614
  XAxis,
@@ -550,7 +618,8 @@ var CompositeChart = forwardRef(function CompositeChart2({
550
618
  axisLine: false,
551
619
  tickLine: false,
552
620
  tick: xAxisTick,
553
- interval: xAxisInterval,
621
+ interval: xTicks.interval,
622
+ ticks: xTicks.ticks,
554
623
  height: "auto",
555
624
  label: xAxisLabel ? { value: xAxisLabel, position: "insideBottom", offset: -2, style: axisLabelStyle } : void 0
556
625
  }
@@ -1005,6 +1074,7 @@ function ChartContainer({
1005
1074
  return /* @__PURE__ */ jsxs(
1006
1075
  "div",
1007
1076
  {
1077
+ "data-card": "",
1008
1078
  className: cn(
1009
1079
  variant === "ghost" ? "bg-transparent" : cn(
1010
1080
  "rounded-[var(--ds-radius-md)] p-ds-4",