@acronis-platform/ui-react 4.1.0 → 5.1.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.
Files changed (79) hide show
  1. package/dist/components/ui/area-chart/area-chart.js +191 -85
  2. package/dist/components/ui/area-chart/area-chart.js.map +1 -1
  3. package/dist/components/ui/bar-chart/bar-chart.js +288 -225
  4. package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
  5. package/dist/components/ui/card-widget-carousel/card-widget-carousel.js +85 -0
  6. package/dist/components/ui/card-widget-carousel/card-widget-carousel.js.map +1 -0
  7. package/dist/components/ui/card-widget-carousel/card-widget.js +82 -0
  8. package/dist/components/ui/card-widget-carousel/card-widget.js.map +1 -0
  9. package/dist/components/ui/category-bar/category-bar.js.map +1 -1
  10. package/dist/components/ui/chart/chart-format.js +25 -22
  11. package/dist/components/ui/chart/chart-format.js.map +1 -1
  12. package/dist/components/ui/chart/chart-palette.js +9 -8
  13. package/dist/components/ui/chart/chart-palette.js.map +1 -1
  14. package/dist/components/ui/chart/chart.js +127 -89
  15. package/dist/components/ui/chart/chart.js.map +1 -1
  16. package/dist/components/ui/funnel-chart/funnel-chart.js +189 -113
  17. package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
  18. package/dist/components/ui/input-select/input-select.js +18 -9
  19. package/dist/components/ui/input-select/input-select.js.map +1 -1
  20. package/dist/components/ui/line-chart/line-chart.js +194 -86
  21. package/dist/components/ui/line-chart/line-chart.js.map +1 -1
  22. package/dist/components/ui/radar-chart/radar-chart.js +76 -66
  23. package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
  24. package/dist/components/ui/scatter-chart/scatter-chart.js +5 -2
  25. package/dist/components/ui/scatter-chart/scatter-chart.js.map +1 -1
  26. package/dist/components/ui/separator/separator.js +52 -10
  27. package/dist/components/ui/separator/separator.js.map +1 -1
  28. package/dist/components/ui/sidebar-secondary/sidebar-secondary.js +144 -121
  29. package/dist/components/ui/sidebar-secondary/sidebar-secondary.js.map +1 -1
  30. package/dist/components/ui/tree-item/tree-item.js +67 -0
  31. package/dist/components/ui/tree-item/tree-item.js.map +1 -0
  32. package/dist/components/ui/treemap/treemap.js +73 -66
  33. package/dist/components/ui/treemap/treemap.js.map +1 -1
  34. package/dist/index.js +109 -107
  35. package/dist/llms/area-chart.md +2 -1
  36. package/dist/llms/bar-chart.md +132 -33
  37. package/dist/llms/card-widget-carousel.md +252 -0
  38. package/dist/llms/category-bar.md +2 -2
  39. package/dist/llms/funnel-chart.md +269 -101
  40. package/dist/llms/input-select.md +175 -1
  41. package/dist/llms/line-chart.md +2 -1
  42. package/dist/llms/metric.md +10 -5
  43. package/dist/llms/radar-chart.md +15 -5
  44. package/dist/llms/scatter-chart.md +2 -1
  45. package/dist/llms/separator.md +27 -8
  46. package/dist/llms/sidebar-secondary.md +235 -4
  47. package/dist/llms/tree-item.md +359 -0
  48. package/dist/llms/treemap.md +47 -36
  49. package/dist/llms.txt +5 -4
  50. package/dist/react.js +109 -107
  51. package/dist/src/components/ui/area-chart/area-chart.d.ts +10 -0
  52. package/dist/src/components/ui/bar-chart/bar-chart.d.ts +75 -10
  53. package/dist/src/components/ui/bar-chart/bar-chart.docs.d.ts +146 -0
  54. package/dist/src/components/ui/bar-chart/index.d.ts +1 -1
  55. package/dist/src/components/ui/card-widget-carousel/card-widget-carousel.d.ts +9 -0
  56. package/dist/src/components/ui/card-widget-carousel/card-widget.d.ts +43 -0
  57. package/dist/src/components/ui/card-widget-carousel/index.d.ts +2 -0
  58. package/dist/src/components/ui/chart/chart-format.d.ts +9 -1
  59. package/dist/src/components/ui/chart/chart-palette.d.ts +38 -10
  60. package/dist/src/components/ui/chart/chart.d.ts +8 -1
  61. package/dist/src/components/ui/chart/index.d.ts +2 -2
  62. package/dist/src/components/ui/funnel-chart/funnel-chart.d.ts +92 -26
  63. package/dist/src/components/ui/funnel-chart/index.d.ts +1 -1
  64. package/dist/src/components/ui/input-select/input-select.d.ts +34 -2
  65. package/dist/src/components/ui/line-chart/line-chart.d.ts +11 -1
  66. package/dist/src/components/ui/radar-chart/radar-chart.d.ts +42 -9
  67. package/dist/src/components/ui/separator/separator.d.ts +9 -1
  68. package/dist/src/components/ui/sidebar-secondary/sidebar-secondary.d.ts +23 -3
  69. package/dist/src/components/ui/tree-item/index.d.ts +1 -0
  70. package/dist/src/components/ui/tree-item/tree-item.d.ts +64 -0
  71. package/dist/src/components/ui/treemap/treemap.d.ts +16 -5
  72. package/dist/src/index.d.ts +2 -1
  73. package/dist/ui-react.css +1 -1
  74. package/package.json +1 -1
  75. package/dist/components/ui/meter/meter.js +0 -79
  76. package/dist/components/ui/meter/meter.js.map +0 -1
  77. package/dist/llms/meter.md +0 -177
  78. package/dist/src/components/ui/meter/index.d.ts +0 -1
  79. package/dist/src/components/ui/meter/meter.d.ts +0 -25
@@ -1,6 +1,6 @@
1
1
  # FunnelChart
2
2
 
3
- > A typed funnel-chart composition over the shared `Chart` primitives. Plots a single series of stages (rows) as stacked, narrowing segments inside a `ChartContainer`, with a tooltip and on-chart stage labels (no legend — the inline labels name every stage). One variant axis: `lastShape` (the funnel narrows to a `triangle` point or ends `rectangle` flat). Ported from the apps/demo `FunnelChartPlayground`.
3
+ > A typed funnel-chart composition over the shared `Chart` primitives. The root is a row: a 120×120 square plot, a 16px gutter, then a stage-list legend taking every remaining pixel — the same composition `PieChart` and `RadialBarChart` use. The plot stacks one series of stages (`data` rows) as narrowing, 2px-gapped, 2px-rounded segments inside a `ChartContainer`, with a tooltip and optional on-plot labels. One variant axis: `lastShape` (the funnel narrows to a `triangle` point or ends `rectangle` flat). Stages are painted from the sequential blue ramp by default, because a funnel's stages are an ordered series rather than unrelated categories. Card-less by design: Figma's card, header and metric row are `ChartWidget`'s.
4
4
 
5
5
  **Status:** draft · **Category:** data-display · **Since:** v0.58.0
6
6
 
@@ -19,23 +19,78 @@ import { FunnelChart } from '@acronis-platform/ui-react'
19
19
  dataKey="value"
20
20
  nameKey="stage"
21
21
  lastShape="triangle"
22
- className="h-[380px] w-[460px]"
23
22
  />
24
23
  ```
25
24
 
26
25
  A typed funnel-chart built on the shared `Chart` primitives. Give it `data`, a
27
26
  per-stage `config`, the value field (`dataKey`), and the label field (`nameKey`);
28
- it renders a themed recharts `FunnelChart` — on-chart stage labels and a tooltip
29
- included — so you don't hand-compose recharts children. (There's no legend: the
30
- inline labels already name every stage.)
31
-
32
- Stage colors come from the `palette` prop — a dataviz palette resolving to the
33
- `--ui-dataviz-*` tokens — not from `config`. Stages are an ordered progression,
34
- so `{ type: 'sequential', ramp }` usually reads better than the default
35
- `categorical`.
36
-
37
- > **Design-pending v1.** Ported from the apps/demo `FunnelChartPlayground`. The
38
- > chrome is reconciled with Figma later; Code Connect is deferred.
27
+ it renders a themed recharts funnel with a tooltip, plus the two-column stage
28
+ list beside it — so you don't hand-compose recharts children.
29
+
30
+ ## Layout
31
+
32
+ The root is a **row**: a 120×120 square plot, a 16px gutter, then the legend
33
+ column taking every remaining pixel. That is the same composition `PieChart` and
34
+ `RadialBarChart` use, and the legend is a **sibling of the plot** rendered
35
+ outside the SVG — never a recharts legend, and never a row below the funnel.
36
+
37
+ The component declares **no width and no height of its own**. It fills the width
38
+ its parent gives it and the legend column absorbs the surplus; the 120px figure
39
+ is the plot square Figma specifies, not a widget width. With `showLegend={false}`
40
+ the row centres its lone plot.
41
+
42
+ > The card, header and metric row Figma draws around the funnel belong to
43
+ > [`ChartWidget`](../chart-widget/README.md). `FunnelChart` is card-less by
44
+ > design.
45
+
46
+ ## Colour
47
+
48
+ Stage colours come from the `palette` prop — a dataviz palette resolving to the
49
+ `--ui-dataviz-*` tokens — not from `config`, which supplies each stage's label.
50
+ The default is `{ type: 'sequential', ramp: 'blue' }`, exported as
51
+ `FUNNEL_CHART_DEFAULT_PALETTE`. Every other chart takes the shared **categorical**
52
+ default; a funnel differs on purpose, because its stages are an ordered series
53
+ rather than unrelated categories, which is exactly what a ramp reads as.
54
+
55
+ `stageSettings[name].color` is the per-stage escape hatch, and applies to the
56
+ segment and its legend marker alike.
57
+
58
+ ### Known deviation from Figma
59
+
60
+ Figma paints the four stages with sequential blue stops **2 / 4 / 6 / 8**, while
61
+ the shared palette resolver walks the ramp in order and assigns the **consecutive**
62
+ stops 1 / 2 / 3 / 4. The funnel is therefore paler than the mockup. This is not a
63
+ funnel-local fix: how N series spread across a ramp's stops is the shared
64
+ resolver's decision, applied identically to every ramp-painted chart, so it is
65
+ left as a known deviation pending a decision there.
66
+
67
+ ## Geometry
68
+
69
+ - **2px of surface between stages.** n stages produce exactly n-1 gaps; the
70
+ topmost stage stays flush with the top of the plot area.
71
+ - **2px rounded corners** on every stage, the triangle's apex included.
72
+ - **Plot-area inset** when nothing sits beside the funnel: 4 top, 8 right, 4
73
+ bottom, 8 left. Whichever side a label list sits on widens.
74
+ - `lastShape="triangle"` (the default) narrows the final stage to a point;
75
+ `"rectangle"` keeps a flat lower edge.
76
+ - `reversed` flips the stack so the funnel widens downward — the gap-free stage
77
+ becomes the last row.
78
+
79
+ The gap and the radius are drawn by the component's own shape renderer, because
80
+ recharts' `Funnel`/`Trapezoid` support neither.
81
+
82
+ ## Legend
83
+
84
+ One two-column row per visible stage: the round marker and the `config` label on
85
+ the inline start, the stage's `dataKey` value — formatted by
86
+ `legendValueFormatter` when given — on the inline end. Both columns use
87
+ `--ui-text-on-surface-primary`, the value in semibold; Figma deliberately differs
88
+ from the donut and radial list legends here, which colour their values with the
89
+ link token.
90
+
91
+ Markers carry each stage's **resolved palette token** rather than a
92
+ `--color-<name>` custom property: those are scoped to the chart container, and the
93
+ legend sits outside it.
39
94
 
40
95
  ## When to use
41
96
 
@@ -52,9 +107,9 @@ so `{ type: 'sequential', ramp }` usually reads better than the default
52
107
 
53
108
  ## Variants
54
109
 
55
- | Axis | Values | Effect |
56
- | ----------- | ------------------------ | ---------------------------------------------- |
57
- | `lastShape` | `triangle` · `rectangle` | Final segment narrows to a point vs ends flat. |
110
+ | Axis | Values | Effect |
111
+ | ----------- | ------------------------ | -------------------------------------------- |
112
+ | `lastShape` | `triangle` · `rectangle` | Final stage narrows to a point vs ends flat. |
58
113
 
59
114
  ## Example
60
115
 
@@ -70,47 +125,33 @@ const data = [
70
125
  ];
71
126
 
72
127
  const config = {
73
- Visits: { label: 'Visits', color: 'var(--ui-background-brand-secondary)' },
74
- Signups: {
75
- label: 'Signups',
76
- color: 'var(--ui-background-status-strong-success)',
77
- },
78
- Trials: {
79
- label: 'Trials',
80
- color: 'var(--ui-background-status-strong-warning)',
81
- },
82
- Purchases: {
83
- label: 'Purchases',
84
- color: 'var(--ui-background-status-strong-danger)',
85
- },
128
+ Visits: { label: 'Visits' },
129
+ Signups: { label: 'Signups' },
130
+ Trials: { label: 'Trials' },
131
+ Purchases: { label: 'Purchases' },
86
132
  } satisfies ChartConfig;
87
133
 
88
- <FunnelChart
89
- config={config}
90
- data={data}
91
- dataKey="value"
92
- nameKey="stage"
93
- className="h-[380px] w-[460px]"
94
- />;
134
+ <FunnelChart config={config} data={data} dataKey="value" nameKey="stage" />;
95
135
  ```
96
136
 
97
- Stage colors reference existing semantic `--ui-*` tokens, keyed by each stage's
98
- `nameKey` value. `--ui-background-status-strong-*` is chromatic in every brand;
99
- `--ui-background-brand-secondary` is brand-dependent (blue in `default`, neutral
100
- in some white-label brands), so it is not color-stable across brands until the
101
- real data-viz palette lands.
137
+ The `config` keys are each stage's `nameKey` value. No colour is declared there —
138
+ the stages take the sequential blue ramp; pass `palette` to change it.
139
+
140
+ On-plot labels are **off** by default (`showLabels`), because the legend already
141
+ names every stage. Turn them on when a funnel is shown without its legend.
102
142
 
103
143
  ## Props
104
144
 
105
145
  | Prop | Type | Default | Required | Description |
106
146
  |------|------|---------|----------|-------------|
107
- | `lastShape` | `'triangle' | 'rectangle'` | `'triangle'` | no | How the funnel ends. `triangle` narrows the final segment to a point; `rectangle` ends it flat (a stack of trapezoids). |
147
+ | `lastShape` | `'triangle' | 'rectangle'` | `'triangle'` | no | How the funnel ends. `triangle` narrows the final stage to a point; `rectangle` keeps a flat lower edge (a stack of trapezoids). |
108
148
  | `data` | `ReadonlyArray<Record<string, string | number>>` | — | yes | Row-per-stage data — each row holds a `nameKey` label and a `dataKey` numeric value. |
109
- | `config` | `ChartConfig` | — | yes | Stages take their colour from the container's `palette`; each entry, keyed by the stage's `nameKey` value, maps that stage to a `label` and an optional `tone`. Resolved into the `--color-<name>` custom properties the segments fill from. |
110
- | `dataKey` | `string` | — | yes | The numeric field that sizes each stage. |
111
- | `nameKey` | `string` | — | yes | The label field that names each stage (drives the tooltip, on-chart labels, and the `--color-<name>` lookup). Values should be unique per chart — stages sharing a name share one config/color entry. |
112
- | `reversed` | `boolean` | `false` | no | Flip the funnel so it widens toward the bottom instead of narrowing. |
113
- | `showLabels` | `boolean` | `true` | no | Render a label for each stage — see labelFormat and labelPosition. |
149
+ | `config` | `ChartConfig` | — | yes | Stages take their colour from `palette`; each entry, keyed by the stage's `nameKey` value, maps that stage to a `label` and an optional `tone`. The labels are what the legend and the tooltip read. Resolved into the `--color-<name>` custom properties the segments fill from. |
150
+ | `dataKey` | `string` | — | yes | The numeric field that sizes each stage — and the value each legend row shows on its inline end. |
151
+ | `nameKey` | `string` | — | yes | The label field that names each stage (drives the legend, the tooltip, the on-plot labels, and the `--color-<name>` lookup). Values should be unique per chart — stages sharing a name share one config/color entry and one legend row. |
152
+ | `palette` | `ChartPalette` | `{ type: 'sequential', ramp: 'blue' }` | no | The dataviz palette the stages are painted from — the only source of a stage's colour, bar a per-stage `stageSettings.color`. Unlike every other chart, which takes the shared categorical default, a funnel defaults to the sequential blue ramp: its stages are an ordered series, which is what a ramp is for. Exported as FUNNEL_CHART_DEFAULT_PALETTE. |
153
+ | `reversed` | `boolean` | `false` | no | Flip the stack so the funnel widens toward the bottom instead of narrowing. The gap-free stage — the one flush with the top of the plot area — becomes the last row rather than the first. |
154
+ | `showLabels` | `boolean` | `false` | no | Render a label for each stage — see labelFormat and labelPosition. Off by default: the design's funnel carries no on-plot labels, because the legend beside it already names every stage. |
114
155
  | `labelFormat` | `'name' | 'value' | 'percent' | 'name-value' | 'name-percent' | 'value-percent'` | `'name'` | no | What each stage's label says. A percentage is the stage's share of the widest stage — its conversion from the top of the funnel, not a share of the sum, because a funnel's stages are nested subsets rather than parts of a whole. The value is formatted by labelFormatter and the share by percentFormatter. A composite format (a pair) narrows the funnel to leave itself room, unless funnelWidth says otherwise. |
115
156
  | `labelPosition` | `'right' | 'left' | 'inside'` | `'right'` | no | Where each stage's label sits. `right`/`left` place it beside the segment on the chart surface; `inside` centres it on the segment, which switches the label to the on-fill color so it keeps contrast over a saturated fill. `inside` only works while every stage is wide enough to hold the text — a funnel narrows, so its last stages often are not; keep the text short there (`percent`) or leave the labels beside the funnel. |
116
157
  | `showValueLabels` | `boolean` | `false` | no | Render a second label carrying the stage's value, so the name and the number can sit on opposite sides of the funnel. |
@@ -118,16 +159,14 @@ real data-viz palette lands.
118
159
  | `labelFill` | `string` | — | no | Color for both label lists, overriding the contrast-matched default. Pass a --ui-* token; only needed when the default doesn't work on the surface. |
119
160
  | `labelFormatter` | `(value: number | string, index?: number) => string` | — | no | Format the numeric part of a label. |
120
161
  | `percentFormatter` | `(value: number | string, index?: number) => string` | — | no | Format the conversion share behind the percent formats. Receives the share as a fraction (0.52); defaults to a one-decimal bare percentage ("52.0%"). Replace it for a locale that doesn't write a bare percent sign. |
121
- | `showLegend` | `boolean` | `false` | no | Render the legend — one entry per visible stage. Off by default because a funnel labels its stages on the chart. |
122
- | `legendPos` | `'top' | 'bottom'` | `'bottom'` | no | Which edge the legend sits on. |
123
- | `colorMode` | `'palette' | 'gradient'` | `'palette'` | no | How the stages are colored. `palette` gives each stage its own config color; `gradient` ramps one hue from the widest stage down to the narrowest, mixing it toward the surface so every segment stays opaque. |
124
- | `gradientColor` | `string` | — | no | The hue gradient mode ramps. Defaults to the first visible stage's own color — including a stageSettings color set on it, since that is what the stage actually paints with. Ignored when colorMode is palette. |
125
- | `stageSettings` | `Record<string, { color?: string; hidden?: boolean }>` | — | no | Per-stage overrides keyed by the stage's nameKey value. `color` wins over both config and colorMode; `hidden` drops the stage from the funnel, its labels, the legend, and the conversion percentages. |
162
+ | `showLegend` | `boolean` | `true` | no | Render the stage list beside the plot — one two-column row per visible stage, a sibling of the plot rather than a renderer legend. On by default, because it is where the design's funnel names its stages. With it off the row centres its lone plot. |
163
+ | `legendValueFormatter` | `(value: string | number) => string` | — | no | Format the value in each legend row — the stage's dataKey number. Omit to stringify the raw value. |
164
+ | `stageSettings` | `Record<string, { color?: string; hidden?: boolean }>` | — | no | Per-stage overrides keyed by the stage's nameKey value. `color` replaces that stage's palette stop, in the funnel and in its legend marker; `hidden` drops the stage from the funnel, its labels, the legend, and the conversion percentages. |
126
165
  | `showActiveShape` | `boolean` | `false` | no | Outline the hovered segment, rather than changing its fill, so it keeps the color that ties it to its legend entry and label. |
127
- | `stroke` | `string` | — | no | Segment border color — reference an existing --ui-* token. Defaults to the border token when strokeWidth is set on its own, because the renderer's own default is a hardcoded white the container neutralizes. |
166
+ | `stroke` | `string` | — | no | Segment border color — reference an existing --ui-* token. Defaults to the border token when strokeWidth is set on its own, because the renderer's own default there is a hardcoded white the container neutralizes. |
128
167
  | `strokeWidth` | `number` | — | no | Segment border width, in px. Implies a default stroke when given alone. |
129
168
  | `funnelWidth` | `number | string` | — | no | Funnel width — px, or a percentage of the plot area ('70%'). Defaults to the full plot area, except that a composite labelFormat beside the funnel narrows it: the renderer word-wraps a label against the gap between its segment and the plot-area edge, so narrowing the funnel is what creates label room. A margin cannot — it moves that edge inward with the funnel. |
130
- | `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | — | no | Plot-area margin, in px, merged over the defaults per side so passing one side keeps the others. It reserves the side a label list sits on so unwrappable text isn't clipped at the SVG edge — see funnelWidth for the label's wrap room. |
169
+ | `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | `{ top: 4, right: 8, bottom: 4, left: 8 }` | no | Plot-area inset, in px, merged over the defaults per side so passing one side keeps the others. The default is the design's own tight inset — the room the rounded corners and the apex need — and applies whenever nothing sits beside the funnel. As soon as a label list does, the side it sits on widens so unwrappable text isn't clipped at the SVG edge; see funnelWidth for the label's wrap room. |
131
170
  | `showTooltip` | `boolean` | `true` | no | Render the hover tooltip. |
132
171
  | `tooltipContent` | `ReactElement | ((props) => ReactNode)` | — | no | Replace the default tooltip. Pass a configured ChartTooltipContent (from this library) — e.g. with a formatter / labelFormatter — to customize the tooltip content without composing recharts. Ignored when showTooltip is false. |
133
172
  | `animate` | `boolean` | `false` | no | Enable entrance animation. Off by default so output/baselines are unchanged. |
@@ -138,39 +177,133 @@ real data-viz palette lands.
138
177
  ## Behavior
139
178
 
140
179
  `FunnelChart` is a typed [recharts](https://recharts.org) composition over the
141
- shared `Chart` primitives. It plots a single series of stages (`data` rows) as
142
- stacked, narrowing segments inside a `ChartContainer`.
180
+ shared `Chart` primitives. It lays out a square plot and a stage-list legend in a
181
+ row: the plot stacks one series of stages (`data` rows) as narrowing segments
182
+ inside a `ChartContainer`, and the legend beside it names them.
143
183
 
144
184
  ```gherkin
145
185
  Scenario: Render stages from data and config
146
- Given data rows and a config mapping each stage name to a label and color
186
+ Given data rows and a config mapping each stage name to a label
147
187
  And dataKey "value" and nameKey "stage"
148
188
  Then one segment renders per row, sized by its dataKey value
149
- And each segment fills from its injected --color-<name> custom property
189
+ And each segment is painted from its palette stop
190
+ ```
191
+
192
+ ### Layout
193
+
194
+ ```gherkin
195
+ Scenario: The plot and the legend sit side by side
196
+ Given a FunnelChart with its default chrome
197
+ Then the root is a row holding a 120×120 square plot, a 16px gutter, and the
198
+ legend column
199
+ And the legend column takes every pixel the plot does not
200
+ ```
201
+
202
+ ```gherkin
203
+ Scenario: The component has no size of its own
204
+ Given a parent wider than the plot
205
+ And no label list sits beside the funnel
206
+ Then the component fills that width and the legend column absorbs the surplus
207
+ And the plot stays a 120×120 square rather than stretching into a tall wedge
208
+ ```
209
+
210
+ ```gherkin
211
+ Scenario: The plot grows when labels sit beside the funnel
212
+ Given showLabels is true and labelPosition is "right" or "left"
213
+ Or showValueLabels is true and valuePosition is "right" or "left"
214
+ Then the plot-frame switches to h-[120px] flex-1 and grows with available width
215
+ Because the label margins would otherwise consume the entire 120px fixed width
216
+ ```
217
+
218
+ ```gherkin
219
+ Scenario: The lone plot centres
220
+ Given showLegend is false
221
+ Then the plot is the row's only child and is centred in it
222
+ ```
223
+
224
+ ```gherkin
225
+ Scenario: No card of its own
226
+ Given a FunnelChart rendered directly
227
+ Then it draws no card, header or metric row — those belong to ChartWidget
228
+ ```
229
+
230
+ ### Geometry
231
+
232
+ ```gherkin
233
+ Scenario: Surface shows between the stages
234
+ Given a funnel of n stages
235
+ Then 2px of surface separates each stage from the one above it, n-1 times over
236
+ And the topmost stage stays flush with the top of the plot area
237
+ ```
238
+
239
+ ```gherkin
240
+ Scenario: Every stage is rounded
241
+ Given any funnel
242
+ Then each stage's corners are rounded by 2px, the triangle's apex included
243
+ ```
244
+
245
+ ```gherkin
246
+ Scenario: The gap and the radius are the component's own drawing
247
+ Given the renderer's funnel shape supports neither a gap nor a radius
248
+ Then the component draws each stage itself, so both survive on hover too
249
+ ```
250
+
251
+ ```gherkin
252
+ Scenario: Plot-area inset with nothing beside the funnel
253
+ Given no label list sits beside the funnel
254
+ Then the plot area is inset by 4 top, 8 right, 4 bottom and 8 left — the room
255
+ the rounded corners and the apex need, and nothing more
150
256
  ```
151
257
 
152
258
  ```gherkin
153
259
  Scenario: Triangle last shape (default)
154
260
  Given lastShape is "triangle"
155
- Then the final segment narrows to a point
261
+ Then the final stage narrows to a point
156
262
  ```
157
263
 
158
264
  ```gherkin
159
265
  Scenario: Rectangle last shape
160
266
  Given lastShape is "rectangle"
161
- Then the final segment ends flat (a stack of trapezoids)
267
+ Then the final stage keeps a flat lower edge (a stack of trapezoids)
162
268
  ```
163
269
 
164
270
  ```gherkin
165
271
  Scenario: Reversed
166
272
  Given reversed is true
167
273
  Then the funnel widens toward the bottom instead of narrowing
274
+ And the gap-free stage is the last row rather than the first
168
275
  ```
169
276
 
277
+ ### Colour
278
+
170
279
  ```gherkin
171
- Scenario: Stage labels
172
- Given showLabels is true
173
- Then each stage's name renders beside its segment
280
+ Scenario: The default palette is a ramp, not a categorical set
281
+ Given no palette is given
282
+ Then the stages are painted from the sequential blue ramp, because a funnel's
283
+ stages are an ordered series
284
+ And every other chart type still defaults to the shared categorical palette
285
+ ```
286
+
287
+ ```gherkin
288
+ Scenario: Palette is the only source of a stage's colour
289
+ Given a palette and a config
290
+ Then each stage takes a palette stop; config supplies its label, not its colour
291
+ ```
292
+
293
+ ```gherkin
294
+ Scenario: Per-stage colour override
295
+ Given a stageSettings entry keyed by a stage's nameKey value carries a color
296
+ Then that stage paints with it in the funnel and in its legend marker,
297
+ whatever the palette says
298
+ ```
299
+
300
+ ### Labels
301
+
302
+ ```gherkin
303
+ Scenario: On-plot labels are off by default
304
+ Given showLabels is unset
305
+ Then no stage labels render on the plot, because the legend already names them
306
+ And showLabels renders them
174
307
  ```
175
308
 
176
309
  ```gherkin
@@ -227,53 +360,74 @@ Scenario: A composite label gets room by narrowing the funnel
227
360
  And a caller-supplied funnelWidth replaces the reserve
228
361
  ```
229
362
 
363
+ ```gherkin
364
+ Scenario: A label list widens the inset on its own side
365
+ Given a label list sits beside the funnel
366
+ Then the plot area's inset on that side widens, so text that cannot wrap is not
367
+ clipped at the SVG edge
368
+ ```
369
+
230
370
  ```gherkin
231
371
  Scenario: A partial margin keeps the other sides
232
372
  Given a margin that sets only one side
233
373
  Then the remaining sides keep their default insets rather than collapsing to zero
234
374
  ```
235
375
 
376
+ ### Legend
377
+
236
378
  ```gherkin
237
- Scenario: Legend is opt-in
379
+ Scenario: The legend is visible by default
238
380
  Given showLegend is unset
239
- Then no legend renders
381
+ Then the stage list renders beside the plot
382
+ And showLegend=false hides it
383
+ ```
384
+
385
+ ```gherkin
386
+ Scenario: The legend is a sibling of the plot, not a chart legend
387
+ Given showLegend is true
388
+ Then the list is ordinary HTML outside the plot's SVG, never a renderer legend
389
+ and never a row below the funnel
240
390
  ```
241
391
 
242
392
  ```gherkin
243
393
  Scenario: Legend entries come from the visible stages
244
394
  Given showLegend is true
245
- Then one entry renders per distinct visible stage name, labelled and colored
246
- from config
247
- And two stages sharing a name share the single entry they share a color with
248
- And it sits on the edge given by legendPos (bottom by default)
395
+ Then one two-column row renders per distinct visible stage name
396
+ And the inline start holds the stage's round marker and its config label
397
+ And the inline end holds the stage's dataKey value, formatted by
398
+ legendValueFormatter when one is given
399
+ And two stages sharing a name share the single row they share a colour with
249
400
  ```
250
401
 
251
402
  ```gherkin
252
- Scenario: Gradient color mode
253
- Given colorMode is "gradient"
254
- Then every stage fills from one hue — gradientColor, or the first visible stage's
255
- own color, including a stageSettings color set on it
256
- And the hue is mixed further toward the surface at each stage down the funnel
403
+ Scenario: Legend text colour
404
+ Given a legend row
405
+ Then both its label and its value use the primary on-surface text token, the
406
+ value in semibold — not the link colour the donut and radial legends give
407
+ their values
257
408
  ```
258
409
 
259
410
  ```gherkin
260
- Scenario: Per-stage color override
261
- Given a stageSettings entry keyed by a stage's nameKey value carries a color
262
- Then that stage fills with it, whatever config and colorMode say
411
+ Scenario: Legend markers resolve their own colour
412
+ Given the legend sits outside the chart container
413
+ Then each marker carries its stage's resolved palette token, because the
414
+ container-scoped --color-<name> custom properties do not reach outside it
263
415
  ```
264
416
 
265
417
  ```gherkin
266
418
  Scenario: Hidden stage
267
419
  Given a stageSettings entry hides its stage
268
- Then the stage has no segment, no label and no legend entry
420
+ Then the stage has no segment, no label and no legend row
269
421
  And the remaining conversions are measured over the visible stages only
270
422
  ```
271
423
 
424
+ ### Interaction
425
+
272
426
  ```gherkin
273
427
  Scenario: Active segment
274
428
  Given showActiveShape is true
275
429
  When the user hovers a stage
276
- Then that segment is outlined and keeps its fill
430
+ Then that segment is outlined and keeps its fill, gap and rounded corners
277
431
  ```
278
432
 
279
433
  ```gherkin
@@ -306,28 +460,42 @@ Scenario: Reduced motion
306
460
 
307
461
  ## Accessibility
308
462
 
309
- - recharts' `accessibilityLayer` is **on by default** (recharts v3), giving the
310
- chart keyboard focus and an accessible description of the plotted stages.
311
- - A funnel chart is inherently visual. **Pair it with a text alternative** — a
312
- caption, a summary sentence, or an adjacent data table carrying the same
313
- numbers (including the drop-off between stages, which the funnel only implies) —
314
- and give the chart an accessible name (`aria-label` / `aria-labelledby`
315
- referencing a visible heading). The wrapper forwards native `div` attributes,
316
- so `aria-*` pass through.
317
- - Do **not** rely on color alone to distinguish stages. Keep `showLabels` (or the
318
- tooltip) visible so each color is paired with a text label — the on-chart labels
319
- are the funnel's naming channel (there is no legend).
320
- - The tooltip chrome resolves to semantic `--ui-*` tokens, and on-chart labels use
321
- the foreground text token — both meet contrast in light and dark.
322
- **Stage colors are caller-supplied** via `config` — pick values that meet 3:1
323
- against the surface and are distinguishable for color-vision deficiencies. The
324
- borrowed semantic tokens are a design-pending stopgap until the `--ui-chart-*`
325
- palette lands.
326
- - Watch recharts issue [#4809](https://github.com/recharts/recharts/issues/4809)
327
- on the a11y layer for heavily-customized charts.
463
+ - **No name of its own.** `FunnelChart` sets no `role` and no `aria-*`, and it
464
+ passes nothing down to the plot, so the `<title>`/`<desc>` pair the renderer
465
+ emits stays empty. Put the name on the root, which spreads the props you give
466
+ it: `aria-label` or `aria-labelledby` referencing a visible heading. Choose the
467
+ role deliberately — `role="img"` names the chart but makes its subtree
468
+ presentational, so a `<figure>` with a `<figcaption>` is often the better fit.
469
+ - recharts' `accessibilityLayer` is **on by default** (recharts v3) and is not
470
+ switched off here, so the plot is a tab stop carrying `role="application"`.
471
+ Until the chart is named, that focus stop announces neither a name nor any
472
+ data. Watch recharts issue
473
+ [#4809](https://github.com/recharts/recharts/issues/4809) on the a11y layer for
474
+ heavily-customized charts.
475
+ - **A name is not a text alternative.** A funnel encodes numbers as geometry, so
476
+ the numbers themselves have to be reachable as text — a caption, a summary
477
+ sentence, or an adjacent table carrying the same values (including the
478
+ drop-off between stages, which the funnel only implies).
479
+ - **The legend is the reliable text.** It is on by default and is ordinary HTML
480
+ laid out beside the plot, so it reads in the accessibility tree: one row per
481
+ stage, the marker and label on the inline start, the value on the inline end.
482
+ The on-plot labels (`showLabels`, off by default) and value labels
483
+ (`showValueLabels`) are real `<text>` rather than pixels, but they sit inside
484
+ the SVG with no structural role, and a stage's name and its value are two
485
+ separate lists on opposite sides with nothing tying them together.
486
+ - Do **not** rely on colour alone to distinguish stages. Keep the legend, the
487
+ on-plot labels, or the tooltip visible so every colour is paired with a text
488
+ label. A sequential ramp — the funnel's default palette — makes this stricter
489
+ than a categorical one: adjacent stops are neighbouring shades of one hue, not
490
+ distinct colours.
328
491
 
329
492
  ## Contrast
330
493
 
331
- Chart chrome and the on-chart stage labels meet contrast in both themes via the
332
- semantic tokens. Segment fills come from `config` and are the caller's
333
- responsibility — keep adjacent segments distinguishable from each other.
494
+ Chart chrome, the legend and the on-plot labels resolve to semantic `--ui-*`
495
+ tokens and meet contrast in light and dark: a label beside the funnel uses the
496
+ primary on-surface token, a label on a segment switches to the on-fill token
497
+ (white in both themes) so it survives a saturated stage fill. Stage fills come
498
+ from the `--ui-dataviz-*` palettes rather than from caller-supplied colours, so
499
+ their contrast against the surface is the design data's responsibility — but
500
+ picking a ramp still trades stage-to-stage distinguishability for its ordered
501
+ reading, so keep the stage count low.