@charcuterie/ui 4.15.2 → 4.17.0

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/README.md CHANGED
@@ -328,4 +328,4 @@ rename breaks only the rendered page).
328
328
  board. `sourceRules.test.ts` now derives the container-declaring components from
329
329
  source and fails on this, so the component M4 adds joins the rule automatically.
330
330
 
331
- **`Chart`** provides responsive bar and line plots, negative values, gaps for missing data, and an optional semantic values table. Its escaped SVG renderer lives in `@charcuterie/logic/core` for Preact and image consumers. Apps supply their own series, labels, and semantic colors.
331
+ **`Chart`** provides responsive bar and line plots, negative values, gaps for missing data, and an optional semantic values table. TanStack provides hover/touch tooltips, keyboard navigation, and transitions with reduced-motion support. Set `renderer="portable"` for the lightweight fallback. The `@charcuterie/logic/charts`, `/charts/preact`, and `/charts/react` subpaths expose static and framework-specific surfaces; `/core` remains dependency-free. Apps supply their own series, labels, and semantic colors.
@@ -4,7 +4,10 @@ export type ChartProps = ChartOptions & {
4
4
  className?: string;
5
5
  /** A text table exposes every value without relying on color, hover, or SVG. */
6
6
  isTableVisible?: boolean;
7
+ /** The portable renderer is an explicit fallback for constrained environments. */
8
+ renderer?: "tanstack" | "portable";
9
+ isAnimated?: boolean;
7
10
  };
8
11
  /** The same escaped SVG renderer is available without React for kiosk/image consumers. */
9
- export declare const Chart: ({ className, isTableVisible, ...options }: ChartProps) => ReactNode;
12
+ export declare const Chart: ({ className, isTableVisible, renderer, isAnimated, ...options }: ChartProps) => ReactNode;
10
13
  //# sourceMappingURL=Chart.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Chart.d.ts","sourceRoot":"","sources":["../../src/Chart/Chart.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,YAAY,EAElB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,OAAO,CAAA;AAGd,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gFAAgF;IAChF,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAED,0FAA0F;AAC1F,eAAO,MAAM,KAAK,GAAI,2CAInB,UAAU,KAAG,SAwGf,CAAA"}
1
+ {"version":3,"file":"Chart.d.ts","sourceRoot":"","sources":["../../src/Chart/Chart.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,YAAY,EAElB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAEL,KAAK,SAAS,EAIf,MAAM,OAAO,CAAA;AAGd,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gFAAgF;IAChF,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,UAAU,GAAG,UAAU,CAAA;IAClC,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,0FAA0F;AAC1F,eAAO,MAAM,KAAK,GAAI,iEAMnB,UAAU,KAAG,SA+Hf,CAAA"}
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // biome-ignore-all lint/security/noDangerouslySetInnerHtml: The portable renderer escapes text and restricts color references; security tests cover this boundary.
3
+ import { ChartPlot } from "@charcuterie/logic/charts/react";
3
4
  import { renderChartSvg, } from "@charcuterie/logic/core";
4
5
  import { useEffect, useRef, useState, } from "react";
5
6
  import { toClassName } from "../toClassName.js";
6
7
  /** The same escaped SVG renderer is available without React for kiosk/image consumers. */
7
- export const Chart = ({ className, isTableVisible = false, ...options }) => {
8
+ export const Chart = ({ className, isTableVisible = false, renderer = "tanstack", isAnimated = true, ...options }) => {
8
9
  const container = useRef(null);
9
10
  const [measuredWidth, setMeasuredWidth] = useState(options.width ?? 640);
10
11
  useEffect(() => {
@@ -26,12 +27,17 @@ export const Chart = ({ className, isTableVisible = false, ...options }) => {
26
27
  cancelAnimationFrame(frame.id);
27
28
  };
28
29
  }, [options.width]);
29
- return (_jsxs("figure", { className: toClassName("min-w-0 text-content-primary", className), children: [_jsx("figcaption", { className: "mb-2 text-sm font-semibold", children: options.title }), _jsx("div", { ref: container, style: { height: options.height ?? 240 }, dangerouslySetInnerHTML: {
30
- __html: renderChartSvg({
31
- ...options,
32
- width: options.width ?? measuredWidth,
33
- }),
34
- } }), _jsx("ul", { "aria-label": `${options.title} legend`, className: "mt-2 flex flex-wrap gap-3 text-xs", children: options.series.map((entry, index) => (_jsxs("li", { className: "min-w-0 wrap-anywhere", children: [_jsx("span", { "aria-hidden": "true", style: {
30
+ return (_jsxs("figure", { style: {
31
+ "--ts-chart-tooltip-background": "var(--color-surface-raised)",
32
+ "--ts-chart-tooltip-color": "var(--color-content-primary)",
33
+ "--ts-chart-tooltip-border": "1px solid var(--color-border-subtle)",
34
+ "--ts-chart-tooltip-font": "inherit",
35
+ }, className: toClassName("min-w-0 text-content-primary", className), children: [_jsx("figcaption", { className: "mb-2 text-sm font-semibold", children: options.title }), _jsx("div", { ref: container, style: { height: options.height ?? 240 }, children: renderer === "portable" ? (_jsx("div", { style: { height: "100%" }, dangerouslySetInnerHTML: {
36
+ __html: renderChartSvg({
37
+ ...options,
38
+ width: options.width ?? measuredWidth,
39
+ }),
40
+ } })) : (_jsx(ChartPlot, { ...options, width: options.width ?? measuredWidth, isAnimated: isAnimated })) }), _jsx("ul", { "aria-label": `${options.title} legend`, className: "mt-2 flex flex-wrap gap-3 text-xs", children: options.series.map((entry, index) => (_jsxs("li", { className: "min-w-0 wrap-anywhere", children: [_jsx("span", { "aria-hidden": "true", style: {
35
41
  color: entry.color ?? "currentColor",
36
42
  borderTop: `3px ${index % 3 === 0 ? "solid" : index % 3 === 1 ? "dashed" : "dotted"} currentColor`,
37
43
  display: "inline-block",
@@ -1 +1 @@
1
- {"version":3,"file":"Chart.js","sourceRoot":"","sources":["../../src/Chart/Chart.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Chart.js","sourceRoot":"","sources":["../../src/Chart/Chart.tsx"],"names":[],"mappings":"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"}
@@ -60,6 +60,8 @@ export type StepperProps = {
60
60
  * vertical — a column cannot hold a list.
61
61
  */
62
62
  readonly orientation?: StepperOrientation;
63
+ /** Completed stages become buttons; the caller resets dependent answers. */
64
+ readonly onStepSelect?: (key: string) => void;
63
65
  readonly steps: readonly Step[];
64
66
  };
65
67
  /**
@@ -94,5 +96,5 @@ export type StepperProps = {
94
96
  * step drawing a connector into empty space, which is a
95
97
  * `:not(:last-child)` selector nobody remembers to write.
96
98
  */
97
- export declare const Stepper: ({ appearance, className, headingLevel, isResponsive, label, orientation, steps, }: StepperProps) => import("react").JSX.Element;
99
+ export declare const Stepper: ({ appearance, className, headingLevel, isResponsive, label, orientation, onStepSelect, steps, }: StepperProps) => import("react").JSX.Element;
98
100
  //# sourceMappingURL=Stepper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Stepper.d.ts","sourceRoot":"","sources":["../../src/Stepper/Stepper.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,SAAS,GACT,MAAM,GACN,UAAU,CAAA;AAEd,MAAM,MAAM,IAAI,GAAG;IACjB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB;uCACmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAA;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,UAAU,CAAA;AAE1D,MAAM,MAAM,YAAY,GAAG;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACrC;2CACuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;CAChC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,OAAO,GAAI,mFAQrB,YAAY,gCAuKd,CAAA"}
1
+ {"version":3,"file":"Stepper.d.ts","sourceRoot":"","sources":["../../src/Stepper/Stepper.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,SAAS,GACT,MAAM,GACN,UAAU,CAAA;AAEd,MAAM,MAAM,IAAI,GAAG;IACjB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB;uCACmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAA;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,UAAU,CAAA;AAE1D,MAAM,MAAM,YAAY,GAAG;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACrC;2CACuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IACzC,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;CAChC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,OAAO,GAAI,iGASrB,YAAY,gCAmLd,CAAA"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { FLIP_KEY_ATTRIBUTE } from "@charcuterie/logic";
3
+ import { Button } from "../Button/Button.js";
3
4
  import { toClassName } from "../toClassName.js";
4
5
  import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
5
6
  /**
@@ -34,7 +35,7 @@ import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
34
35
  * step drawing a connector into empty space, which is a
35
36
  * `:not(:last-child)` selector nobody remembers to write.
36
37
  */
37
- export const Stepper = ({ appearance = "default", className, headingLevel = 3, isResponsive = true, label, orientation = "vertical", steps, }) => {
38
+ export const Stepper = ({ appearance = "default", className, headingLevel = 3, isResponsive = true, label, orientation = "vertical", onStepSelect, steps, }) => {
38
39
  const Heading = `h${headingLevel}`;
39
40
  const isHorizontal = orientation === "horizontal";
40
41
  const isCompact = appearance === "compact";
@@ -50,7 +51,7 @@ export const Stepper = ({ appearance = "default", className, headingLevel = 3, i
50
51
  : "flex-row gap-0")), children: steps.map((step, index) => {
51
52
  const status = step.status ?? "upcoming";
52
53
  const isLast = index === steps.length - 1;
53
- return (_jsxs("li", { [FLIP_KEY_ATTRIBUTE]: step.key, className: toClassName("relative grid grid-cols-[auto_minmax(0,1fr)] grid-rows-[auto_minmax(0,1fr)] gap-x-3", isHorizontal &&
54
+ return (_jsxs("li", { "aria-current": status === "current" ? "step" : undefined, [FLIP_KEY_ATTRIBUTE]: step.key, className: toClassName("relative grid grid-cols-[auto_minmax(0,1fr)] grid-rows-[auto_minmax(0,1fr)] gap-x-3", isHorizontal &&
54
55
  (isResponsive
55
56
  ? "cq-md:flex cq-md:min-w-0 cq-md:flex-1 cq-md:flex-col cq-md:gap-y-2"
56
57
  : "flex min-w-0 flex-1 flex-col gap-y-2"), isCompact && "gap-y-1",
@@ -82,7 +83,7 @@ export const Stepper = ({ appearance = "default", className, headingLevel = 3, i
82
83
  : "absolute top-3.5 start-7 my-0 h-0.5 w-[calc(100%-1.75rem)]")) })), _jsxs("div", { className: toClassName("col-start-2 row-span-2 row-start-1 min-w-0", isHorizontal &&
83
84
  (isResponsive
84
85
  ? "cq-md:col-start-1"
85
- : "col-start-1")), children: [_jsxs(Heading, { className: toClassName("m-0 flex flex-wrap items-center gap-2 font-medium text-content-primary", isCompact ? "text-xs" : "text-md"), children: [step.label, _jsx(VisuallyHidden, { children: STATUS_LABEL[status] })] }), step.description == null ? null : (_jsx("p", { className: "mt-0.5 mb-0 text-sm text-content-secondary", children: step.description })), step.content == null ? null : (_jsx("div", { className: "mt-2", children: step.content }))] })] }, step.key));
86
+ : "col-start-1")), children: [_jsxs(Heading, { className: toClassName("m-0 flex flex-wrap items-center gap-2 font-medium text-content-primary", isCompact ? "text-xs" : "text-md"), children: [onStepSelect && status === "done" ? (_jsx(Button, { appearance: "ghost", onClick: () => onStepSelect(step.key), children: step.label })) : (step.label), _jsx(VisuallyHidden, { children: STATUS_LABEL[status] })] }), step.description == null ? null : (_jsx("p", { className: "mt-0.5 mb-0 text-sm text-content-secondary", children: step.description })), step.content == null ? null : (_jsx("div", { className: "mt-2", children: step.content }))] })] }, step.key));
86
87
  }) }) }));
87
88
  };
88
89
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Stepper.js","sourceRoot":"","sources":["../../src/Stepper/Stepper.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Stepper.js","sourceRoot":"","sources":["../../src/Stepper/Stepper.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcuterie/ui",
3
- "version": "4.15.2",
3
+ "version": "4.17.0",
4
4
  "description": "The Charcuterie components. Tokens for look, @charcuterie/logic for state, ARIA that agents can actually drive.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -53,7 +53,7 @@
53
53
  "typecheck": "tsc --noEmit"
54
54
  },
55
55
  "dependencies": {
56
- "@charcuterie/logic": "2.7.0",
56
+ "@charcuterie/logic": "2.9.0",
57
57
  "@charcuterie/tokens": "1.10.2",
58
58
  "@floating-ui/react": "^0.27.20",
59
59
  "@tanstack/react-virtual": "^3.13.12",
@@ -7,10 +7,19 @@ import * as stories from "./Chart.stories.tsx"
7
7
 
8
8
  Bar and line plots with a zero baseline, negative values, explicit missing samples, and an optional values table. The app owns the data and its meaning. Null means unavailable, never zero. Color is optional; mark titles, labels, and the table preserve the information without it.
9
9
 
10
- The framework-free `renderChartSvg` export from `@charcuterie/logic/core` draws exactly the same plot for Preact kiosks and image renderers. It escapes all producer text and rejects external color references. Use `Chart` in React. Use the portable renderer where a React tree is not available.
10
+ TanStack Charts 0.18.0 is the primary renderer. Hover, tap, or focus the plot and use arrow keys, Home, and End to inspect values. Tooltips show every series for the selected label, including unavailable samples. Positive and negative contributions stack independently with `barLayout="stacked"`; grouped bars remain the default. `isAnimated={false}` disables motion, and changes to `prefers-reduced-motion` are honored while mounted.
11
+
12
+ Use `renderer="portable"` for the existing lightweight SVG fallback. Its static output and the optional semantic values table remain available. Neither renderer fetches data.
13
+
14
+ `@charcuterie/logic/charts` exports `createChartDefinition` and a standalone `renderChartSvg` with explicit image dimensions. `@charcuterie/logic/charts/preact` exports native `ChartPlot` without React or a compatibility layer. `@charcuterie/logic/core` keeps the dependency-free portable renderer for constrained consumers. Static consumers supply resolved colors rather than browser CSS variables.
15
+
16
+ TanStack remains alpha and is pinned exactly. Upgrades require the interaction and export checks recorded in the decision and evaluation.
11
17
 
12
18
  <Canvas of={stories.Playground} />
13
19
  <Canvas of={stories.AllVariants} />
14
20
  <Canvas of={stories.AllStates} />
15
21
  <Canvas of={stories.Responsive} />
16
22
  <Canvas of={stories.Interactive} />
23
+
24
+ <Canvas of={stories.StackedDaily} />
25
+ <Canvas of={stories.PortableFallback} />
@@ -78,15 +78,65 @@ export const Interactive: Story = {
78
78
  render: (props) => {
79
79
  const [isTableVisible, setIsTableVisible] =
80
80
  useState(false)
81
+ const [isUpdated, setIsUpdated] = useState(false)
81
82
  return (
82
83
  <>
84
+ <Button onClick={() => setIsUpdated(!isUpdated)}>
85
+ Update values
86
+ </Button>
83
87
  <Button
84
88
  onClick={() => setIsTableVisible(!isTableVisible)}
85
89
  >
86
90
  {isTableVisible ? "Hide values" : "Show values"}
87
91
  </Button>
88
- <Chart {...props} isTableVisible={isTableVisible} />
92
+ <Chart
93
+ {...props}
94
+ series={props.series.map((entry) => ({
95
+ ...entry,
96
+ values: entry.values.map((value) =>
97
+ value === null
98
+ ? null
99
+ : value + (isUpdated ? 100 : 0),
100
+ ),
101
+ }))}
102
+ isTableVisible={isTableVisible}
103
+ />
89
104
  </>
90
105
  )
91
106
  },
92
107
  }
108
+
109
+ export const StackedDaily: Story = {
110
+ args: {
111
+ barLayout: "stacked",
112
+ series: [
113
+ {
114
+ id: "tasks",
115
+ label: "Tasks",
116
+ values: [200, 280, 180, 260, 300],
117
+ color: "var(--color-intent-accent-solid)",
118
+ },
119
+ {
120
+ id: "bonuses",
121
+ label: "Bonuses",
122
+ values: [10, 20, 0, 10, 0],
123
+ color: "var(--color-intent-success-solid)",
124
+ },
125
+ {
126
+ id: "penalties",
127
+ label: "Penalties",
128
+ values: [0, -50, -50, 0, 0],
129
+ color: "var(--color-intent-danger-solid)",
130
+ },
131
+ {
132
+ id: "reversals",
133
+ label: "Reversals",
134
+ values: [0, -10, 0, 0, 0],
135
+ color: "var(--color-content-muted)",
136
+ },
137
+ ],
138
+ },
139
+ }
140
+ export const PortableFallback: Story = {
141
+ args: { renderer: "portable" },
142
+ }
@@ -5,7 +5,8 @@ import { mountStory } from "../mountStory.testHelpers.ts"
5
5
  import { expectAgentDrivable } from "../testing/index.ts"
6
6
  import * as stories from "./Chart.stories.tsx"
7
7
 
8
- const { Playground, Interactive } = composeStories(stories)
8
+ const { Playground, Interactive, StackedDaily } =
9
+ composeStories(stories)
9
10
  test("the plot has an accessible name and a named legend", async () => {
10
11
  const { canvas } = await mountStory(Playground)
11
12
  expectAgentDrivable(canvas, {
@@ -35,3 +36,31 @@ test("the keyboard exposes all values, including missing data, in a real table",
35
36
  ).toBeVisible()
36
37
  await expect(within(table).getByText("-50")).toBeVisible()
37
38
  })
39
+
40
+ test("keyboard point navigation exposes the day's original category amounts", async () => {
41
+ const { canvas } = await mountStory(StackedDaily)
42
+ const plot = expectAgentDrivable(canvas, {
43
+ role: "img",
44
+ name: "Daily progress",
45
+ })
46
+ plot.focus()
47
+ await userEvent.keyboard("{Home}")
48
+ const body = within(document.body)
49
+ await expect(body.getByRole("status")).toBeVisible()
50
+ await expect(
51
+ within(body.getByRole("status")).getByText("Tasks"),
52
+ ).toBeVisible()
53
+ await expect(
54
+ within(body.getByRole("status")).getByText("200"),
55
+ ).toBeVisible()
56
+ await expect(
57
+ within(body.getByRole("status")).getByText("10"),
58
+ ).toBeVisible()
59
+ await userEvent.keyboard("{End}")
60
+ await expect(
61
+ within(body.getByRole("status")).getByText("Fri"),
62
+ ).toBeVisible()
63
+ await expect(
64
+ within(body.getByRole("status")).getByText("300"),
65
+ ).toBeVisible()
66
+ })
@@ -1,9 +1,12 @@
1
1
  // biome-ignore-all lint/security/noDangerouslySetInnerHtml: The portable renderer escapes text and restricts color references; security tests cover this boundary.
2
+
3
+ import { ChartPlot } from "@charcuterie/logic/charts/react"
2
4
  import {
3
5
  type ChartOptions,
4
6
  renderChartSvg,
5
7
  } from "@charcuterie/logic/core"
6
8
  import {
9
+ type CSSProperties,
7
10
  type ReactNode,
8
11
  useEffect,
9
12
  useRef,
@@ -15,12 +18,17 @@ export type ChartProps = ChartOptions & {
15
18
  className?: string
16
19
  /** A text table exposes every value without relying on color, hover, or SVG. */
17
20
  isTableVisible?: boolean
21
+ /** The portable renderer is an explicit fallback for constrained environments. */
22
+ renderer?: "tanstack" | "portable"
23
+ isAnimated?: boolean
18
24
  }
19
25
 
20
26
  /** The same escaped SVG renderer is available without React for kiosk/image consumers. */
21
27
  export const Chart = ({
22
28
  className,
23
29
  isTableVisible = false,
30
+ renderer = "tanstack",
31
+ isAnimated = true,
24
32
  ...options
25
33
  }: ChartProps): ReactNode => {
26
34
  const container = useRef<HTMLDivElement>(null)
@@ -47,6 +55,17 @@ export const Chart = ({
47
55
  }, [options.width])
48
56
  return (
49
57
  <figure
58
+ style={
59
+ {
60
+ "--ts-chart-tooltip-background":
61
+ "var(--color-surface-raised)",
62
+ "--ts-chart-tooltip-color":
63
+ "var(--color-content-primary)",
64
+ "--ts-chart-tooltip-border":
65
+ "1px solid var(--color-border-subtle)",
66
+ "--ts-chart-tooltip-font": "inherit",
67
+ } as CSSProperties
68
+ }
50
69
  className={toClassName(
51
70
  "min-w-0 text-content-primary",
52
71
  className,
@@ -58,13 +77,25 @@ export const Chart = ({
58
77
  <div
59
78
  ref={container}
60
79
  style={{ height: options.height ?? 240 }}
61
- dangerouslySetInnerHTML={{
62
- __html: renderChartSvg({
63
- ...options,
64
- width: options.width ?? measuredWidth,
65
- }),
66
- }}
67
- />
80
+ >
81
+ {renderer === "portable" ? (
82
+ <div
83
+ style={{ height: "100%" }}
84
+ dangerouslySetInnerHTML={{
85
+ __html: renderChartSvg({
86
+ ...options,
87
+ width: options.width ?? measuredWidth,
88
+ }),
89
+ }}
90
+ />
91
+ ) : (
92
+ <ChartPlot
93
+ {...options}
94
+ width={options.width ?? measuredWidth}
95
+ isAnimated={isAnimated}
96
+ />
97
+ )}
98
+ </div>
68
99
  <ul
69
100
  aria-label={`${options.title} legend`}
70
101
  className="mt-2 flex flex-wrap gap-3 text-xs"
@@ -84,3 +84,15 @@ A stepper is a **document outline**, not a widget: an `<ol>` of headings. Advanc
84
84
  app's operation and the component only reports where the sequence got to, so there is no
85
85
  roving focus, no `aria-current` on a control, and nothing to arrow between. If your steps
86
86
  are *navigable* — a checkout you can click back into — you want `Tabs`.
87
+
88
+
89
+ ## Wizard navigation
90
+
91
+ Provide `onStepSelect` to make completed stages keyboard-accessible buttons.
92
+ Current and upcoming stages remain progress indicators. The caller owns the answers:
93
+ returning to an earlier stage should clear choices that depend on it.
94
+
95
+ <Canvas of={StepperStories.WizardNavigation} />
96
+
97
+ Preact displays can use `getWizardSteps` from `@charcuterie/logic/core` to derive the
98
+ same numbered stages and backward navigation without importing React components.
@@ -252,3 +252,27 @@ export const Interactive: Story = {
252
252
  )
253
253
  },
254
254
  }
255
+
256
+ /** A wizard permits returning to completed stages without skipping answers. */
257
+ export const WizardNavigation: Story = {
258
+ render: (stepperProps) => {
259
+ const [currentKey, setCurrentKey] = useState("tag")
260
+ return (
261
+ <Stepper
262
+ {...stepperProps}
263
+ label="Assignment wizard"
264
+ orientation="horizontal"
265
+ onStepSelect={setCurrentKey}
266
+ steps={RIP.map((step) => ({
267
+ ...step,
268
+ status:
269
+ step.key === currentKey
270
+ ? "current"
271
+ : step.key === "rip"
272
+ ? "done"
273
+ : "upcoming",
274
+ }))}
275
+ />
276
+ )
277
+ },
278
+ }
@@ -12,6 +12,7 @@ const {
12
12
  Playground,
13
13
  Interactive,
14
14
  Responsive,
15
+ WizardNavigation,
15
16
  } = composeStories(stories)
16
17
 
17
18
  test("the sequence is an ordered list with a name", async () => {
@@ -142,3 +143,21 @@ test("a compact card can keep the horizontal ladder below cq-md", async () => {
142
143
  ).flexDirection,
143
144
  ).toBe("row")
144
145
  })
146
+
147
+ test("wizard stages can be revisited, while upcoming stages cannot be skipped", async () => {
148
+ const { canvas, canvasElement } = await mountStory(
149
+ WizardNavigation,
150
+ )
151
+ await expect(
152
+ canvas.queryByRole("button", { name: "File" }),
153
+ ).toBeNull()
154
+ await userEvent.click(
155
+ canvas.getByRole("button", { name: "Rip" }),
156
+ )
157
+ await expect(
158
+ canvas.getByRole("heading", {
159
+ name: /Rip In progress/,
160
+ }),
161
+ ).toBeVisible()
162
+ await expectNoAxeViolations(canvasElement)
163
+ })
@@ -1,6 +1,7 @@
1
1
  import { FLIP_KEY_ATTRIBUTE } from "@charcuterie/logic"
2
2
  import type { ReactNode } from "react"
3
3
 
4
+ import { Button } from "../Button/Button.tsx"
4
5
  import { toClassName } from "../toClassName.ts"
5
6
  import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.tsx"
6
7
 
@@ -72,6 +73,8 @@ export type StepperProps = {
72
73
  * vertical — a column cannot hold a list.
73
74
  */
74
75
  readonly orientation?: StepperOrientation
76
+ /** Completed stages become buttons; the caller resets dependent answers. */
77
+ readonly onStepSelect?: (key: string) => void
75
78
  readonly steps: readonly Step[]
76
79
  }
77
80
 
@@ -114,6 +117,7 @@ export const Stepper = ({
114
117
  isResponsive = true,
115
118
  label,
116
119
  orientation = "vertical",
120
+ onStepSelect,
117
121
  steps,
118
122
  }: StepperProps) => {
119
123
  const Heading = `h${headingLevel}` as "h3"
@@ -174,6 +178,9 @@ export const Stepper = ({
174
178
  * nobody reads it, which is why it is not opt-in.
175
179
  */
176
180
  <li
181
+ aria-current={
182
+ status === "current" ? "step" : undefined
183
+ }
177
184
  {...{ [FLIP_KEY_ATTRIBUTE]: step.key }}
178
185
  className={toClassName(
179
186
  "relative grid grid-cols-[auto_minmax(0,1fr)] grid-rows-[auto_minmax(0,1fr)] gap-x-3",
@@ -254,7 +261,16 @@ export const Stepper = ({
254
261
  isCompact ? "text-xs" : "text-md",
255
262
  )}
256
263
  >
257
- {step.label}
264
+ {onStepSelect && status === "done" ? (
265
+ <Button
266
+ appearance="ghost"
267
+ onClick={() => onStepSelect(step.key)}
268
+ >
269
+ {step.label}
270
+ </Button>
271
+ ) : (
272
+ step.label
273
+ )}
258
274
 
259
275
  {/* The status as a WORD. Four marker colours is
260
276
  four colours as far as assistive technology is
@@ -700,6 +700,33 @@ const ENTRY_POINT_RUNTIMES: Record<
700
700
  // into `core` — which is why `ColorSchemeApplier` lives in the
701
701
  // core rather than beside the hook, so this entry never pulls
702
702
  // `react`.
703
+ "./charts": [
704
+ "@tanstack/charts",
705
+ "@tanstack/charts/scales/band",
706
+ "@tanstack/charts/scales/linear",
707
+ "@tanstack/charts/svg",
708
+ ],
709
+ "./charts/preact": [
710
+ "@tanstack/charts",
711
+ "@tanstack/charts/adapter/renderer",
712
+ "@tanstack/charts/motion",
713
+ "@tanstack/charts/scales/band",
714
+ "@tanstack/charts/scales/linear",
715
+ "@tanstack/charts/svg/renderer",
716
+ "@tanstack/charts/tooltip",
717
+ "preact",
718
+ "preact/hooks",
719
+ ],
720
+ "./charts/react": [
721
+ "@tanstack/charts",
722
+ "@tanstack/charts/motion",
723
+ "@tanstack/charts/react/core",
724
+ "@tanstack/charts/scales/band",
725
+ "@tanstack/charts/scales/linear",
726
+ "@tanstack/charts/svg/renderer",
727
+ "@tanstack/charts/tooltip",
728
+ "react",
729
+ ],
703
730
  "./browser": [],
704
731
  "./core": [],
705
732
  "./jotai": ["jotai"],
@@ -733,6 +760,7 @@ const ENTRY_POINT_RUNTIMES: Record<
733
760
  // browser, and it does so through this subpath so the coupling
734
761
  // is named rather than smuggled in through the main entry.
735
762
  "@charcuterie/logic/browser",
763
+ "@charcuterie/logic/charts/react",
736
764
  "@charcuterie/logic/core",
737
765
  "@charcuterie/tokens",
738
766
  "@floating-ui/react",