@acronis-platform/ui-react 3.0.0 → 4.0.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 (219) hide show
  1. package/dist/components/ui/accordion-container/accordion-container.js +14 -14
  2. package/dist/components/ui/accordion-container/accordion-container.js.map +1 -1
  3. package/dist/components/ui/app-shell-chat/app-shell-chat.js.map +1 -1
  4. package/dist/components/ui/area-chart/area-chart.js +141 -139
  5. package/dist/components/ui/area-chart/area-chart.js.map +1 -1
  6. package/dist/components/ui/bar-chart/bar-chart.js +295 -293
  7. package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
  8. package/dist/components/ui/category-bar/category-bar.js +109 -93
  9. package/dist/components/ui/category-bar/category-bar.js.map +1 -1
  10. package/dist/components/ui/chart/chart-palette.js +113 -0
  11. package/dist/components/ui/chart/chart-palette.js.map +1 -0
  12. package/dist/components/ui/chart/chart.js +176 -129
  13. package/dist/components/ui/chart/chart.js.map +1 -1
  14. package/dist/components/ui/chart-state/chart-state-illustrations.js +310 -0
  15. package/dist/components/ui/chart-state/chart-state-illustrations.js.map +1 -0
  16. package/dist/components/ui/chart-state/chart-state.js +48 -34
  17. package/dist/components/ui/chart-state/chart-state.js.map +1 -1
  18. package/dist/components/ui/chart-widget/chart-widget.js +87 -0
  19. package/dist/components/ui/chart-widget/chart-widget.js.map +1 -0
  20. package/dist/components/ui/composed-chart/composed-chart.js +117 -115
  21. package/dist/components/ui/composed-chart/composed-chart.js.map +1 -1
  22. package/dist/components/ui/confidence-cone/confidence-cone.js +166 -158
  23. package/dist/components/ui/confidence-cone/confidence-cone.js.map +1 -1
  24. package/dist/components/ui/funnel-chart/funnel-chart.js +162 -161
  25. package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
  26. package/dist/components/ui/histogram/histogram.js +94 -92
  27. package/dist/components/ui/histogram/histogram.js.map +1 -1
  28. package/dist/components/ui/input/input.js.map +1 -1
  29. package/dist/components/ui/label/label.js +2 -9
  30. package/dist/components/ui/label/label.js.map +1 -1
  31. package/dist/components/ui/line-chart/line-chart.js +143 -141
  32. package/dist/components/ui/line-chart/line-chart.js.map +1 -1
  33. package/dist/components/ui/metric/metric.js +74 -185
  34. package/dist/components/ui/metric/metric.js.map +1 -1
  35. package/dist/components/ui/page-content/page-content.js.map +1 -1
  36. package/dist/components/ui/pie-chart/pie-chart.js +201 -195
  37. package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
  38. package/dist/components/ui/radar-chart/radar-chart.js +133 -131
  39. package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
  40. package/dist/components/ui/radial-bar-chart/radial-bar-chart.js +297 -265
  41. package/dist/components/ui/radial-bar-chart/radial-bar-chart.js.map +1 -1
  42. package/dist/components/ui/sankey-chart/sankey-chart.js +123 -101
  43. package/dist/components/ui/sankey-chart/sankey-chart.js.map +1 -1
  44. package/dist/components/ui/scatter-chart/scatter-chart.js +76 -74
  45. package/dist/components/ui/scatter-chart/scatter-chart.js.map +1 -1
  46. package/dist/components/ui/section/section.js +157 -31
  47. package/dist/components/ui/section/section.js.map +1 -1
  48. package/dist/components/ui/stepper/stepper.js +24 -24
  49. package/dist/components/ui/stepper/stepper.js.map +1 -1
  50. package/dist/components/ui/stepper-item/stepper-item.js +43 -24
  51. package/dist/components/ui/stepper-item/stepper-item.js.map +1 -1
  52. package/dist/components/ui/treemap/treemap.js +133 -124
  53. package/dist/components/ui/treemap/treemap.js.map +1 -1
  54. package/dist/components/ui/trend-indicator/trend-indicator.js +34 -69
  55. package/dist/components/ui/trend-indicator/trend-indicator.js.map +1 -1
  56. package/dist/components/ui/wizard/wizard.js +59 -0
  57. package/dist/components/ui/wizard/wizard.js.map +1 -0
  58. package/dist/hooks/use-wizard.js +75 -0
  59. package/dist/hooks/use-wizard.js.map +1 -0
  60. package/dist/index.js +502 -497
  61. package/dist/index.js.map +1 -1
  62. package/dist/llms/accordion-container.md +181 -0
  63. package/dist/llms/accordion.md +124 -0
  64. package/dist/llms/alert.md +225 -0
  65. package/dist/llms/app-shell-chat.md +415 -0
  66. package/dist/llms/area-chart.md +355 -0
  67. package/dist/llms/avatar.md +208 -0
  68. package/dist/llms/bar-chart.md +406 -0
  69. package/dist/llms/breadcrumb.md +326 -0
  70. package/dist/llms/button-group.md +298 -0
  71. package/dist/llms/button-icon-input.md +142 -0
  72. package/dist/llms/button-icon-menu.md +209 -0
  73. package/dist/llms/button-icon.md +128 -0
  74. package/dist/llms/button-menu.md +172 -0
  75. package/dist/llms/button.md +185 -0
  76. package/dist/llms/calendar-panel.md +733 -0
  77. package/dist/llms/calendar.md +188 -0
  78. package/dist/llms/card-filter.md +214 -0
  79. package/dist/llms/card-section.md +293 -0
  80. package/dist/llms/card.md +447 -0
  81. package/dist/llms/carousel-dialog.md +156 -0
  82. package/dist/llms/category-bar.md +193 -0
  83. package/dist/llms/chart-state.md +190 -0
  84. package/dist/llms/chart-widget.md +209 -0
  85. package/dist/llms/chart.md +182 -0
  86. package/dist/llms/checkbox.md +257 -0
  87. package/dist/llms/chip.md +195 -0
  88. package/dist/llms/combobox.md +164 -0
  89. package/dist/llms/composed-chart.md +508 -0
  90. package/dist/llms/confidence-cone.md +277 -0
  91. package/dist/llms/data-table.md +474 -0
  92. package/dist/llms/date-range-picker.md +272 -0
  93. package/dist/llms/description-list.md +131 -0
  94. package/dist/llms/dialog-footer-carousel.md +147 -0
  95. package/dist/llms/dialog-footer-default.md +142 -0
  96. package/dist/llms/dialog-root.md +246 -0
  97. package/dist/llms/dialog-welcome.md +223 -0
  98. package/dist/llms/dialog.md +403 -0
  99. package/dist/llms/dropdown-menu.md +193 -0
  100. package/dist/llms/empty-overlay.md +146 -0
  101. package/dist/llms/empty.md +156 -0
  102. package/dist/llms/field.md +185 -0
  103. package/dist/llms/filter-cards.md +157 -0
  104. package/dist/llms/filter-chips.md +323 -0
  105. package/dist/llms/filter-search.md +512 -0
  106. package/dist/llms/form.md +154 -0
  107. package/dist/llms/funnel-chart.md +333 -0
  108. package/dist/llms/grid.md +80 -0
  109. package/dist/llms/histogram.md +190 -0
  110. package/dist/llms/input-date-picker.md +203 -0
  111. package/dist/llms/input-otp.md +173 -0
  112. package/dist/llms/input-password.md +197 -0
  113. package/dist/llms/input-search.md +170 -0
  114. package/dist/llms/input-select.md +278 -0
  115. package/dist/llms/input-text-area.md +271 -0
  116. package/dist/llms/input-text.md +192 -0
  117. package/dist/llms/line-chart.md +369 -0
  118. package/dist/llms/link.md +209 -0
  119. package/dist/llms/loading.md +130 -0
  120. package/dist/llms/meter.md +177 -0
  121. package/dist/llms/metric.md +204 -0
  122. package/dist/llms/number-field.md +122 -0
  123. package/dist/llms/page-content.md +69 -0
  124. package/dist/llms/page-header.md +182 -0
  125. package/dist/llms/pagination.md +133 -0
  126. package/dist/llms/pie-chart.md +307 -0
  127. package/dist/llms/popover.md +252 -0
  128. package/dist/llms/progress-circle.md +126 -0
  129. package/dist/llms/progress.md +111 -0
  130. package/dist/llms/radar-chart.md +316 -0
  131. package/dist/llms/radial-bar-chart.md +319 -0
  132. package/dist/llms/radio.md +255 -0
  133. package/dist/llms/resizable.md +195 -0
  134. package/dist/llms/sankey-chart.md +208 -0
  135. package/dist/llms/scatter-chart.md +239 -0
  136. package/dist/llms/scroll-area.md +183 -0
  137. package/dist/llms/section.md +401 -0
  138. package/dist/llms/select.md +210 -0
  139. package/dist/llms/separator.md +87 -0
  140. package/dist/llms/sheet.md +202 -0
  141. package/dist/llms/sidebar-primary.md +360 -0
  142. package/dist/llms/sidebar-secondary.md +417 -0
  143. package/dist/llms/skeleton.md +81 -0
  144. package/dist/llms/slider.md +97 -0
  145. package/dist/llms/spinner.md +100 -0
  146. package/dist/llms/stack.md +80 -0
  147. package/dist/llms/stepper-item.md +309 -0
  148. package/dist/llms/stepper.md +251 -0
  149. package/dist/llms/switch.md +140 -0
  150. package/dist/llms/table.md +436 -0
  151. package/dist/llms/tabs.md +197 -0
  152. package/dist/llms/tag.md +139 -0
  153. package/dist/llms/timeline.md +361 -0
  154. package/dist/llms/timer.md +259 -0
  155. package/dist/llms/toast.md +241 -0
  156. package/dist/llms/toggle-group.md +121 -0
  157. package/dist/llms/toolbar.md +453 -0
  158. package/dist/llms/tooltip.md +164 -0
  159. package/dist/llms/treemap.md +254 -0
  160. package/dist/llms/trend-indicator.md +132 -0
  161. package/dist/llms/widget-placeholder.md +169 -0
  162. package/dist/llms/wizard.md +412 -0
  163. package/dist/llms.txt +142 -0
  164. package/dist/react.js +502 -497
  165. package/dist/react.js.map +1 -1
  166. package/dist/src/components/ui/area-chart/area-chart.d.ts +9 -4
  167. package/dist/src/components/ui/avatar/avatar.d.ts +1 -1
  168. package/dist/src/components/ui/bar-chart/bar-chart.d.ts +9 -4
  169. package/dist/src/components/ui/category-bar/category-bar.d.ts +10 -4
  170. package/dist/src/components/ui/chart/__stories__/palette-control.d.ts +48 -0
  171. package/dist/src/components/ui/chart/__tests__/chart-palette.test.d.ts +1 -0
  172. package/dist/src/components/ui/chart/chart-palette.d.ts +128 -0
  173. package/dist/src/components/ui/chart/chart.d.ts +66 -15
  174. package/dist/src/components/ui/chart/index.d.ts +4 -2
  175. package/dist/src/components/ui/chart-state/chart-state-illustrations.d.ts +6 -0
  176. package/dist/src/components/ui/chart-state/chart-state.d.ts +13 -2
  177. package/dist/src/components/ui/chart-state/index.d.ts +1 -0
  178. package/dist/src/components/ui/chart-widget/chart-widget.d.ts +64 -0
  179. package/dist/src/components/ui/chart-widget/index.d.ts +1 -0
  180. package/dist/src/components/ui/composed-chart/composed-chart.d.ts +12 -7
  181. package/dist/src/components/ui/confidence-cone/confidence-cone.d.ts +10 -5
  182. package/dist/src/components/ui/empty-overlay/empty-overlay.d.ts +1 -1
  183. package/dist/src/components/ui/funnel-chart/funnel-chart.d.ts +11 -5
  184. package/dist/src/components/ui/histogram/histogram.d.ts +10 -4
  185. package/dist/src/components/ui/label/index.d.ts +1 -1
  186. package/dist/src/components/ui/label/label.d.ts +1 -5
  187. package/dist/src/components/ui/line-chart/line-chart.d.ts +9 -4
  188. package/dist/src/components/ui/metric/metric.d.ts +13 -14
  189. package/dist/src/components/ui/pie-chart/pie-chart.d.ts +17 -8
  190. package/dist/src/components/ui/radar-chart/radar-chart.d.ts +10 -5
  191. package/dist/src/components/ui/radial-bar-chart/radial-bar-chart.d.ts +17 -6
  192. package/dist/src/components/ui/sankey-chart/sankey-chart.d.ts +10 -5
  193. package/dist/src/components/ui/scatter-chart/scatter-chart.d.ts +10 -5
  194. package/dist/src/components/ui/section/index.d.ts +1 -1
  195. package/dist/src/components/ui/section/section.d.ts +75 -6
  196. package/dist/src/components/ui/stepper-item/stepper-item.d.ts +6 -5
  197. package/dist/src/components/ui/tag/tag.d.ts +1 -1
  198. package/dist/src/components/ui/treemap/treemap.d.ts +11 -5
  199. package/dist/src/components/ui/trend-indicator/trend-indicator.d.ts +3 -7
  200. package/dist/src/components/ui/wizard/index.d.ts +1 -0
  201. package/dist/src/components/ui/wizard/wizard.d.ts +6 -0
  202. package/dist/src/hooks/index.d.ts +1 -0
  203. package/dist/src/hooks/use-wizard.d.ts +80 -0
  204. package/dist/src/index.d.ts +2 -3
  205. package/dist/ui-react.css +1 -1
  206. package/package.json +9 -3
  207. package/dist/components/ui/app-shell/app-shell.js +0 -75
  208. package/dist/components/ui/app-shell/app-shell.js.map +0 -1
  209. package/dist/components/ui/auth-layout/auth-layout.js +0 -56
  210. package/dist/components/ui/auth-layout/auth-layout.js.map +0 -1
  211. package/dist/components/ui/collapsible/collapsible.js +0 -22
  212. package/dist/components/ui/collapsible/collapsible.js.map +0 -1
  213. package/dist/src/components/ui/app-shell/app-shell.d.ts +0 -8
  214. package/dist/src/components/ui/app-shell/index.d.ts +0 -1
  215. package/dist/src/components/ui/auth-layout/auth-layout.d.ts +0 -6
  216. package/dist/src/components/ui/auth-layout/index.d.ts +0 -1
  217. package/dist/src/components/ui/collapsible/collapsible.d.ts +0 -5
  218. package/dist/src/components/ui/collapsible/index.d.ts +0 -1
  219. package/dist/src/components/ui/label/label.docs.d.ts +0 -10
@@ -0,0 +1,307 @@
1
+ # PieChart
2
+
3
+ > A typed pie/donut-chart composition over the shared `Chart` primitives. Takes `data` + `config` + a `dataKey` (slice value) and `nameKey` (slice label) and renders a themed recharts `PieChart` inside a `ChartContainer`, with tooltip and legend chrome. One variant axis: `shape` (filled pie / hollow-centre donut). Ported from the apps/demo `PieChartPlayground`.
4
+
5
+ **Status:** draft · **Category:** data-display · **Since:** v0.58.0
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import { PieChart } from '@acronis-platform/ui-react'
11
+ ```
12
+
13
+ ## Quick example
14
+
15
+ ```tsx
16
+ <PieChart
17
+ config={config}
18
+ data={data}
19
+ dataKey="value"
20
+ nameKey="browser"
21
+ shape="donut"
22
+ className="h-[360px] w-[360px]"
23
+ />
24
+ ```
25
+
26
+ A typed pie/donut-chart built on the shared `Chart` primitives. Give it `data`, a
27
+ per-slice `config`, the value field (`dataKey`), and the label field (`nameKey`);
28
+ it renders a themed recharts `PieChart` — tooltip and legend included — so you
29
+ don't hand-compose recharts children.
30
+
31
+ Slice colors come from the `palette` prop — a dataviz palette resolving to the
32
+ `--ui-dataviz-*` tokens, `categorical` by default — not from `config`.
33
+
34
+ > **Design-pending v1.** Ported from the apps/demo `PieChartPlayground`. The
35
+ > chrome is reconciled with Figma later; Code Connect is deferred.
36
+
37
+ ## When to use
38
+
39
+ - Showing part-to-whole proportions across a handful of categories.
40
+ - A compact, at-a-glance distribution (device share, channel split).
41
+ - Use `shape="donut"` when you want a lighter footprint or a centre for a total.
42
+
43
+ ## When not to use
44
+
45
+ - More than ~6 categories, or many similar-sized slices — use a bar chart, which
46
+ compares magnitudes far more precisely.
47
+ - Trends over time — use a line or area chart.
48
+ - Comparing exact values — angles are hard to read; prefer bars or a table.
49
+
50
+ ## Variants
51
+
52
+ | Axis | Values | Effect |
53
+ | ------- | --------------- | ----------------------------------------- |
54
+ | `shape` | `pie` · `donut` | Filled arc vs a hollow-centre donut ring. |
55
+
56
+ ## Example
57
+
58
+ ```tsx
59
+ import { PieChart } from '@acronis-platform/ui-react';
60
+ import type { ChartConfig } from '@acronis-platform/ui-react';
61
+
62
+ const data = [
63
+ { browser: 'Chrome', value: 275 },
64
+ { browser: 'Safari', value: 200 },
65
+ { browser: 'Firefox', value: 187 },
66
+ { browser: 'Edge', value: 173 },
67
+ ];
68
+
69
+ const config = {
70
+ Chrome: { label: 'Chrome', color: 'var(--ui-background-brand-secondary)' },
71
+ Safari: {
72
+ label: 'Safari',
73
+ color: 'var(--ui-background-status-strong-danger)',
74
+ },
75
+ Firefox: {
76
+ label: 'Firefox',
77
+ color: 'var(--ui-background-status-strong-success)',
78
+ },
79
+ Edge: { label: 'Edge', color: 'var(--ui-background-status-strong-warning)' },
80
+ } satisfies ChartConfig;
81
+
82
+ <PieChart
83
+ config={config}
84
+ data={data}
85
+ dataKey="value"
86
+ nameKey="browser"
87
+ shape="donut"
88
+ className="h-[360px] w-[360px]"
89
+ />;
90
+ ```
91
+
92
+ Slice colors reference existing semantic `--ui-*` tokens, keyed by each slice's
93
+ `nameKey` value. `--ui-background-status-strong-*` is chromatic in every brand;
94
+ `--ui-background-brand-secondary` is brand-dependent (blue in `default`, neutral
95
+ in some white-label brands), so it is not color-stable across brands until the
96
+ real data-viz palette lands.
97
+
98
+ ## Props
99
+
100
+ | Prop | Type | Default | Required | Description |
101
+ |------|------|---------|----------|-------------|
102
+ | `shape` | `'pie' | 'donut'` | `'donut'` | no | Arc shape. `donut` leaves a hollow centre (`innerRadius`); `pie` fills to the centre. |
103
+ | `data` | `ReadonlyArray<Record<string, string | number>>` | — | yes | Row-per-slice data — each row holds a `nameKey` label and a `dataKey` numeric value. |
104
+ | `config` | `ChartConfig` | — | yes | Slices take their colour from the container's `palette`; each entry, keyed by the slice's `nameKey` value, maps that slice to a `label` and an optional `tone`. Resolved into the `--color-<name>` custom properties the slices fill from. |
105
+ | `dataKey` | `string` | — | yes | The numeric field that sizes each slice. |
106
+ | `nameKey` | `string` | — | yes | The label field that names each slice (drives legend, tooltip, and the `--color-<name>` lookup). Values should be unique per chart — rows sharing a name share one config/color entry. |
107
+ | `innerRadius` | `number` | `48` | no | Inner radius of the arc when `shape="donut"` (ignored for `shape="pie"`). |
108
+ | `centerLabel` | `{ value?: string | number; label?: string | number }` | — | no | Custom content for the donut hole — a headline `value` and/or a `label` caption, stacked and centered. Rendered only for `shape="donut"`. |
109
+ | `outerRadius` | `number` | `60` | no | Outer radius of the arc. |
110
+ | `paddingAngle` | `number` | `2` | no | Gap between slices, in degrees. |
111
+ | `cornerRadius` | `number` | — | no | Corner radius of each slice. |
112
+ | `startAngle` | `number` | `90` | no | Angle the sweep starts at, in degrees (90 is 12 o'clock). Defaults to the top of the clock face. Pair with endAngle for a semicircle (180 → 0) or an arc. A partial sweep still centres on the full plot area, so a half turn fills only half its box — give it a short, wide one. |
113
+ | `endAngle` | `number` | `-270` | no | Angle the sweep ends at, in degrees. Defaults to a full clockwise circle from the startAngle (startAngle − 360). |
114
+ | `minAngle` | `number` | — | no | Smallest angle a non-zero slice may occupy, in degrees — keeps a tiny slice visible (and hoverable) at the cost of being drawn out of proportion. |
115
+ | `sliceSettings` | `Record<string, { color?: string; hideLabel?: boolean; labelFormat?: PieChartLabelFormat }>` | — | no | Per-slice overrides keyed by the slice's nameKey value: its color, and whether/how its data label reads. A name with no entry keeps the chart-level behavior. |
116
+ | `showTooltip` | `boolean` | `true` | no | Render the hover tooltip. |
117
+ | `showLegend` | `boolean` | `true` | no | Render the legend. |
118
+ | `legendValueFormatter` | `(value: string | number) => string` | — | no | Format each slice's value in the list legend. Omit to stringify the raw value. |
119
+ | `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | — | no | Plot-area margin, in px. Omit to use the renderer's default (5 on every side). |
120
+ | `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. Takes precedence over tooltipFormat. Ignored when showTooltip is false. |
121
+ | `tooltipFormat` | `'value' | 'value-percent'` | `'value'` | no | How the default tooltip reads a slice: its value alone, or the value followed by its share of the total (formatted like a data label's share, so the two always agree). Pass tooltipContent to format it differently. |
122
+ | `animate` | `boolean` | `false` | no | Enable entrance animation. Off by default so output/baselines are unchanged. |
123
+ | `animationDuration` | `number` | — | no | Animation duration in ms. |
124
+ | `animationBegin` | `number` | — | no | Delay before the animation starts, in ms. |
125
+ | `animationEasing` | `'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'` | — | no | Easing curve. |
126
+ | `showLabels` | `boolean` | `false` | no | Render a value label on each slice. |
127
+ | `labelPosition` | `'outside' | 'center' | 'centerTop' | 'centerBottom' | 'insideStart' | 'insideEnd' | 'end'` | `'outside'` | no | Where the value labels sit. Defaults to outside the arc, on the chart surface — the only placement guaranteed to have contrast against any palette's slice colors. Inside placements switch the label to the on-fill token. Ignored when labelLine is on — a leader line only ever ends outside the arc. |
128
+ | `labelFormat` | `'value' | 'name-value' | 'name-percent' | 'percent'` | `'value'` | no | What each data label reads — the value alone, or the slice name and/or its share of the total (to one decimal, with a "." decimal separator and a bare "%", not locale-aware). Overridable per slice via sliceSettings. |
129
+ | `labelLine` | `boolean` | `false` | no | Draw a leader line from each slice to its data label, in the slice's own color. Moves the labels outside the arc; a slice whose label is hidden by sliceSettings gets no line either. |
130
+ | `labelFormatter` | `(value) => string` | — | no | Format each label value (reuses the tick formatters). Formats the slice's value, so it reaches the value and name-value formats only — a share is always rendered as NN.N% and never passes through it. |
131
+
132
+ ## Behavior
133
+
134
+ `PieChart` is a typed [recharts](https://recharts.org) composition over the
135
+ shared `Chart` primitives. It takes `data`, a per-slice `config`, the value field
136
+ (`dataKey`), and the label field (`nameKey`), and renders a themed recharts
137
+ `PieChart` inside a `ChartContainer`.
138
+
139
+ ```gherkin
140
+ Scenario: Render slices from data and config
141
+ Given data rows and a config mapping each slice name to a label and color
142
+ And dataKey "value" and nameKey "browser"
143
+ Then one arc slice renders per row, sized by its dataKey value
144
+ And each slice fills from its injected --color-<name> custom property
145
+ ```
146
+
147
+ ```gherkin
148
+ Scenario: Pie shape (default)
149
+ Given shape is "pie"
150
+ Then the arc fills to the centre (innerRadius 0)
151
+ ```
152
+
153
+ ```gherkin
154
+ Scenario: Donut shape
155
+ Given shape is "donut"
156
+ Then the arc has a hollow centre at innerRadius
157
+ ```
158
+
159
+ ```gherkin
160
+ Scenario: Padding between slices
161
+ Given paddingAngle is greater than 0
162
+ Then a gap of that many degrees separates adjacent slices
163
+ ```
164
+
165
+ ```gherkin
166
+ Scenario: Partial sweep
167
+ Given a startAngle of 180 and an endAngle of 0
168
+ Then the slices are laid out over that half turn instead of a full circle
169
+ And their relative sizes are unchanged
170
+ ```
171
+
172
+ ```gherkin
173
+ Scenario: Rounded slices
174
+ Given cornerRadius is greater than 0
175
+ Then each slice's corners are rounded by that radius
176
+ ```
177
+
178
+ ```gherkin
179
+ Scenario: Minimum slice angle
180
+ Given minAngle is greater than 0
181
+ Then every non-zero slice occupies at least that angle
182
+ And a slice smaller than it is drawn out of proportion so it stays hoverable
183
+ ```
184
+
185
+ ```gherkin
186
+ Scenario: Data-label format
187
+ Given showLabels is true
188
+ And labelFormat is one of value, name-value, name-percent or percent
189
+ Then each slice's label reads its value, its name, and/or its share of the
190
+ sum of every slice value, to one decimal
191
+ And a labelFormatter formats the slice's value, so it reaches the value and
192
+ name-value formats only — a share is always rendered as NN.N%
193
+ ```
194
+
195
+ ```gherkin
196
+ Scenario: Percent with nothing to divide by
197
+ Given a percent-bearing labelFormat
198
+ And the slice values sum to zero, or this slice's value is non-numeric
199
+ Then name-percent falls back to the slice name and percent renders no label
200
+ ```
201
+
202
+ ```gherkin
203
+ Scenario: Slice with no value at all
204
+ Given a data row that carries no dataKey field
205
+ Then the name-bearing formats fall back to the bare slice name
206
+ And the value-only formats render no label, rather than an empty one
207
+ ```
208
+
209
+ ```gherkin
210
+ Scenario: Leader lines
211
+ Given showLabels and labelLine are both true
212
+ Then a line runs from each slice to its label, in that slice's colour
213
+ And the labels sit outside the arc regardless of labelPosition
214
+ ```
215
+
216
+ ```gherkin
217
+ Scenario: Per-slice overrides
218
+ Given a sliceSettings entry keyed by a slice's nameKey value
219
+ Then that slice takes the entry's colour and labelFormat in place of the
220
+ chart-level ones
221
+ And an entry with hideLabel drops that slice's label — and its leader line —
222
+ while every other slice keeps its own
223
+ ```
224
+
225
+ ```gherkin
226
+ Scenario: Tooltip on hover
227
+ Given showTooltip is true
228
+ When the user hovers a slice
229
+ Then a card shows that slice's name and value
230
+ ```
231
+
232
+ ```gherkin
233
+ Scenario: Tooltip value format
234
+ Given tooltipFormat is "value-percent"
235
+ When the user hovers a slice
236
+ Then the card shows that slice's value followed by its share of the total
237
+ And a tooltipContent of the caller's own takes precedence over the preset
238
+ ```
239
+
240
+ ```gherkin
241
+ Scenario: Legend
242
+ Given showLegend is true
243
+ Then a swatch + label renders for each slice (from nameKey / config)
244
+ And it sits on the edge given by legendPosition (bottom by default)
245
+ ```
246
+
247
+ ```gherkin
248
+ Scenario: Donut center label
249
+ Given shape is "donut" and a centerLabel { value, label }
250
+ Then the value renders large and the label smaller beneath it, centered in the hole
251
+ And the block stays centered on the donut whether or not a legend is shown,
252
+ and on whichever edge legendPosition puts it
253
+ ```
254
+
255
+ ```gherkin
256
+ Scenario: Center label ignored for a pie
257
+ Given shape is "pie" and a centerLabel
258
+ Then no center content renders (a filled pie has no hole)
259
+ ```
260
+
261
+ ```gherkin
262
+ Scenario: Empty data
263
+ Given data is an empty array
264
+ Then the chart renders with no slices and does not throw
265
+ ```
266
+
267
+ ```gherkin
268
+ Scenario: Entrance animation is opt-in
269
+ Given animate is unset
270
+ Then the series render at their final geometry with no entrance animation
271
+ And the committed visual baselines are unaffected
272
+ ```
273
+
274
+ ```gherkin
275
+ Scenario: Reduced motion
276
+ Given animate is true
277
+ And a user with prefers-reduced-motion
278
+ Then the entrance animation does not play and the series render at their final geometry
279
+ And the same applies when rendering on the server
280
+ ```
281
+
282
+ ## Accessibility
283
+
284
+ - recharts' `accessibilityLayer` is **on by default** (recharts v3), giving the
285
+ chart keyboard focus and an accessible description of the plotted slices.
286
+ - A pie chart is inherently visual. **Pair it with a text alternative** — a
287
+ caption, a summary sentence, or an adjacent data table carrying the same
288
+ numbers — and give the chart an accessible name (`aria-label` /
289
+ `aria-labelledby` referencing a visible heading) when it is meaningful on its
290
+ own. The wrapper forwards native `div` attributes, so `aria-*` pass through.
291
+ - Do **not** rely on color alone to distinguish slices. Keep `showLegend` (or the
292
+ tooltip) visible so each color is paired with a text label. Pie charts read
293
+ poorly with many similar-sized slices — prefer a handful of slices and combine
294
+ the tail into an "Other" slice.
295
+ - The chrome (tooltip, legend) resolves to semantic `--ui-*` tokens that meet
296
+ contrast in light and dark. **Slice colors are caller-supplied** via `config` —
297
+ pick values that meet 3:1 against the surface and are distinguishable for
298
+ color-vision deficiencies (adjacent slices especially). The borrowed semantic
299
+ tokens are a design-pending stopgap until the `--ui-chart-*` palette lands.
300
+ - Watch recharts issue [#4809](https://github.com/recharts/recharts/issues/4809)
301
+ on the a11y layer for heavily-customized charts.
302
+
303
+ ## Contrast
304
+
305
+ Chart chrome meets contrast in both themes via the semantic tokens. Slice fills
306
+ come from `config` and are the caller's responsibility — keep adjacent slices
307
+ distinguishable, not just against the surface but from each other.
@@ -0,0 +1,252 @@
1
+ # Popover
2
+
3
+ > A floating panel anchored to a trigger, shown on demand for secondary content or quick actions. Built on the Base UI Popover primitive (positioning, focus management, outside-press / Esc dismissal). Ported from the legacy shadcn-uikit `Popover`; container chrome themed by the `--ui-popover-*` tier, with optional `PopoverBody`/`PopoverFooter` parts for the body-rhythm and default action-row recipe shown in Figma.
4
+
5
+ **Status:** ready · **Category:** overlays · **Since:** v0.12.0
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import { Popover, PopoverTrigger, PopoverContent, PopoverBody, PopoverFooter } from '@acronis-platform/ui-react'
11
+ ```
12
+
13
+ ## Quick example
14
+
15
+ ```tsx
16
+ <Popover>
17
+ <PopoverTrigger render={<Button variant="secondary">Open</Button>} />
18
+ <PopoverContent side="bottom" align="center">
19
+ <PopoverBody>
20
+ <h4>Dimensions</h4>
21
+ <p>Set the dimensions for the layer.</p>
22
+ </PopoverBody>
23
+ <PopoverFooter>
24
+ <Button variant="secondary">Cancel</Button>
25
+ <Button>Apply</Button>
26
+ </PopoverFooter>
27
+ </PopoverContent>
28
+ </Popover>
29
+ ```
30
+
31
+ A floating panel anchored to a trigger, shown on demand for secondary content or
32
+ quick actions. Built on the Base UI Popover primitive.
33
+
34
+ > Ported from the legacy `@acronis-platform/shadcn-uikit` `Popover`. Container
35
+ > chrome is themed by the `--ui-popover-*` tier; the optional `PopoverBody` /
36
+ > `PopoverFooter` parts (body rhythm, default action-row footer) are themed by
37
+ > `--ui-popover-body-*` and the shared `--ui-footer-*` tier. Enter/exit
38
+ > animations use `tw-animate-css`.
39
+
40
+ ## When to use
41
+
42
+ - Showing secondary content or a small set of controls anchored to a trigger
43
+ (a filter form, a quick edit, extra detail) without leaving the page.
44
+
45
+ ## When not to use
46
+
47
+ - For a blocking decision or a focused task — use a **Dialog** (modal).
48
+ - For a short, non-interactive hint on hover/focus — use a **Tooltip**.
49
+ - For a list of actions/commands — use a menu.
50
+
51
+ ## Parts
52
+
53
+ | Part | Element | Purpose |
54
+ | ---------------- | ------------------ | ---------------------------------------------- |
55
+ | `Popover` | — (Root) | Owns the open state. |
56
+ | `PopoverTrigger` | `button` | Toggles and anchors the popover. |
57
+ | `PopoverContent` | `div[role=dialog]` | The portaled, positioned popup panel. |
58
+ | `PopoverPortal` | — | Lower-level portal (`PopoverContent` uses it). |
59
+ | `PopoverBody` | `div` | Optional vertical-rhythm wrapper for content. |
60
+ | `PopoverFooter` | `div` | Optional default action-row footer. |
61
+
62
+ ## Example
63
+
64
+ ```tsx
65
+ import {
66
+ Popover,
67
+ PopoverTrigger,
68
+ PopoverContent,
69
+ PopoverBody,
70
+ Button,
71
+ } from '@acronis-platform/ui-react';
72
+
73
+ <Popover>
74
+ <PopoverTrigger render={<Button variant="secondary">Open</Button>} />
75
+ <PopoverContent side="bottom" align="center">
76
+ <PopoverBody>
77
+ <h4 className="font-medium leading-none">Dimensions</h4>
78
+ <p className="text-sm text-muted-foreground">
79
+ Set the dimensions for the layer.
80
+ </p>
81
+ </PopoverBody>
82
+ </PopoverContent>
83
+ </Popover>;
84
+ ```
85
+
86
+ With the default action-row footer (the Figma "Popover" recipe — Cancel + Apply):
87
+
88
+ ```tsx
89
+ import { PopoverFooter } from '@acronis-platform/ui-react';
90
+
91
+ <PopoverContent>
92
+ <PopoverBody>Drop any content into this slot.</PopoverBody>
93
+ <PopoverFooter>
94
+ <Button variant="secondary">Cancel</Button>
95
+ <Button>Apply</Button>
96
+ </PopoverFooter>
97
+ </PopoverContent>;
98
+ ```
99
+
100
+ For an isolated container (e.g. a shadow root), pass `portalContainer` so the
101
+ popup inherits that scope's styles. When portaled into a resolved
102
+ `portalContainer`, `PopoverContent` also defaults `positionMethod` to `'fixed'`
103
+ so the popup escapes a plain overflow-clipping ancestor (e.g. a Shadow DOM host
104
+ with `overflow: hidden`) instead of being clipped at the container's edge.
105
+
106
+ ## Props
107
+
108
+ | Prop | Type | Default | Required | Description |
109
+ |------|------|---------|----------|-------------|
110
+ | `open` | `boolean` | — | no | Controlled open state. Pair with the change event. |
111
+ | `default-open` | `boolean` | `false` | no | Initial open state when uncontrolled. |
112
+ | `side` | `'top' | 'bottom' | 'left' | 'right' | 'inline-start' | 'inline-end'` | `'bottom'` | no | On the content — which side of the trigger to render on. |
113
+ | `align` | `'start' | 'center' | 'end'` | `'center'` | no | On the content — alignment along the chosen side. |
114
+ | `side-offset` | `number` | `4` | no | On the content — distance in px from the trigger. |
115
+ | `portal` | `boolean` | `true` | no | On the content — render inside a portal. Set to false for inline usage; requires a consumer-supplied ancestor Portal. When false, none of portal-container/collision-boundary/position-method's portal-dependent defaults apply — even if that ancestor Portal targets a constrained container, so pass position-method explicitly in that case. |
116
+ | `portal-container` | `Element | ShadowRoot | null | RefObject<Element | ShadowRoot | null>` | `nearest PortalContainerProvider, or document.body` | no | On the content — portal mount point. Pass a shadow-root mount for isolated-style previews, or a constrained MFE/Shadow DOM mount point. |
117
+ | `collision-boundary` | `Element | Element[] | Rect | 'clipping-ancestors'` | `the platform default (clipping ancestors)` | no | On the content — element/rect the popup is confined to when avoiding collisions. Left at the platform default — paired with position-method's default, that default already avoids clipping inside a constrained portal-container without needing an explicit override (see position-method). |
118
+ | `position-method` | `'absolute' | 'fixed'` | `'fixed' whenever the popup is portaled into a resolved portal-container; otherwise the platform default ('absolute')` | no | On the content — which CSS position the popup uses. An 'absolute'-positioned popup that IS portaled into a custom portal-container is still clipped by it when it constrains overflow (e.g. a Shadow DOM host with overflow: hidden) regardless of the collision boundary, since it shares that ancestor's containing block. 'fixed' escapes a plain overflow-clipping ancestor: collision detection drops an overflow ancestor that doesn't establish its own containing block from a fixed element's clipping chain, resolving to the real, scroll-independent viewport — not one whose chain also has a transform/filter/will-change/etc. that establishes its own containing block for fixed descendants. Has no effect when not portaled. |
119
+ | `keep-mounted` | `boolean` | `false` | no | On the content — keep the popup mounted in the DOM while closed. |
120
+
121
+ ## Events
122
+
123
+ | Event | Payload | Description |
124
+ |-------|---------|-------------|
125
+ | `open-change` | `boolean` | Fired when the popover opens or closes (React: `onOpenChange`). Payload is the new open value. |
126
+
127
+ ## Content slots
128
+
129
+ | Slot | Description |
130
+ |------|-------------|
131
+ | `default` | A `trigger` plus the popover `content` (the root popup), which holds arbitrary content — text, fields, actions. |
132
+ | `body` | Optional vertical-rhythm wrapper for the popup's main content (`PopoverBody`); a themed gap + vertical padding around whatever is passed in. |
133
+ | `footer` | Optional default action-row footer (`PopoverFooter`) holding one or more actions (e.g. Cancel / Apply buttons), end-aligned. |
134
+
135
+ ## Behavior
136
+
137
+ Popover shows a floating panel anchored to a trigger. It owns the open state (or
138
+ the consumer controls it via `open`).
139
+
140
+ ### Opening & closing
141
+
142
+ ```gherkin
143
+ Scenario: Toggling from the trigger
144
+ Given a closed popover
145
+ When the user activates the trigger
146
+ Then the popup opens, positioned per side/align, and receives focus
147
+ And open-change(true) fires
148
+ ```
149
+
150
+ ```gherkin
151
+ Scenario: Dismissing
152
+ Given an open popover
153
+ When the user presses Escape, or presses outside the popup
154
+ Then the popover closes and open-change(false) fires
155
+ And focus returns to the trigger
156
+ ```
157
+
158
+ ### Controlled vs uncontrolled
159
+
160
+ ```gherkin
161
+ Scenario: Uncontrolled with default-open
162
+ Given a Popover with default-open = true and no open prop
163
+ When it mounts
164
+ Then it renders open and the user can dismiss it without consumer code
165
+ ```
166
+
167
+ ```gherkin
168
+ Scenario: Controlled
169
+ Given a Popover with a fixed open prop
170
+ When the user attempts to toggle it
171
+ Then internal state does NOT change on its own
172
+ And open-change fires so the consumer can update open
173
+ ```
174
+
175
+ ### Positioning
176
+
177
+ ```gherkin
178
+ Scenario: Side and alignment
179
+ Given a PopoverContent with side="right" and align="start"
180
+ When it opens
181
+ Then the popup renders to the right of the trigger, aligned to its start edge
182
+ And it flips/shifts to stay within the viewport when space is tight
183
+ ```
184
+
185
+ ### Positioning inside a constrained portal container
186
+
187
+ ```gherkin
188
+ Scenario: Escaping a constrained portal container's clipping
189
+ Given a PopoverContent portaled (default) into a resolved portal-container
190
+ that constrains overflow (e.g. a small or offset MFE/Shadow DOM mount)
191
+ When it opens
192
+ Then it uses 'fixed' positioning by default, with collision-boundary left at
193
+ the platform default ('clipping-ancestors')
194
+ And it is not clipped at the portal container's own edge, since 'fixed'
195
+ already drops that overflow-clipping ancestor from its clipping chain
196
+ ```
197
+
198
+ ```gherkin
199
+ Scenario: Overriding the computed defaults
200
+ Given the same constrained-container setup
201
+ When a consumer passes an explicit collision-boundary or position-method
202
+ Then that value overrides the computed default
203
+ ```
204
+
205
+ ```gherkin
206
+ Scenario: Non-portaled content is unaffected
207
+ Given a PopoverContent rendered without a portal (inline usage), even when
208
+ a portal-container is resolved from context
209
+ When it opens
210
+ Then it keeps the platform's default positioning and collision boundary —
211
+ the computed defaults only apply when this component's own `portal` prop
212
+ is true. If a consumer supplies their own ancestor Portal targeting a
213
+ constrained container, `portal={false}` content can still end up inside
214
+ that container's containing block and clip; pass `position-method`
215
+ explicitly in that case.
216
+ ```
217
+
218
+ ## Accessibility
219
+
220
+ Popover leans on the Base UI Popover primitive for focus management, dismissal,
221
+ and ARIA wiring.
222
+
223
+ ## Roles & semantics
224
+
225
+ - The popup has `role="dialog"`; the trigger is associated with it via
226
+ `aria-haspopup` / `aria-expanded` and `aria-controls` (managed by Base UI).
227
+ - **Name the popup** when it isn't self-evident: render a heading inside and
228
+ reference it (or pass `aria-label`/`aria-labelledby` on the content), so screen
229
+ readers announce what the popover is.
230
+ - Unlike a modal dialog, a popover is **non-modal** by default — focus is placed
231
+ inside on open but background content remains available.
232
+
233
+ ## Keyboard
234
+
235
+ - The trigger opens the popover on **Enter / Space** (it's a button).
236
+ - On open, focus moves into the popup (first focusable, or the popup itself).
237
+ - **Escape** closes the popover and returns focus to the trigger.
238
+ - **Tab** moves through the popup's focusable content; focus is not trapped
239
+ (non-modal), so tabbing past the end returns to the page.
240
+
241
+ ## Screen reader
242
+
243
+ - Opening announces the dialog role and its name (heading / label). Closing
244
+ returns the user to the trigger.
245
+
246
+ ## Contrast
247
+
248
+ - The popup uses `--ui-text-on-surface-primary` text on `--ui-popover-container-color`
249
+ with a `--ui-popover-container-border-color` border — meeting WCAG AA in light
250
+ and dark. The default footer (`PopoverFooter`) uses `--ui-footer-default-color`
251
+ with a `--ui-footer-default-border-color` top border; its action buttons carry
252
+ their own contrast guarantees (see the Button spec).
@@ -0,0 +1,126 @@
1
+ # ProgressCircle
2
+
3
+ > A circular (radial) progress indicator — an SVG ring whose arc fills with the value and whose color tracks the level (danger / critical / warning / success), with an optional numeric or icon center. Built from the Cyber-Compliance "Compliance %" design; the sibling of the linear Progress. Design reconciliation against the Figma component master is pending.
4
+
5
+ **Status:** draft · **Category:** feedback · **Since:** v0.43.0
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import { ProgressCircle } from '@acronis-platform/ui-react'
11
+ ```
12
+
13
+ ## Quick example
14
+
15
+ ```tsx
16
+ <ProgressCircle value={75} max={100} size="sm" showValue />
17
+ ```
18
+
19
+ A circular (radial) progress indicator — an SVG ring whose arc fills with the
20
+ value and whose color tracks the level.
21
+
22
+ > **Status: draft (design-pending v1).** Built from the Cyber-Compliance
23
+ > "Compliance %" design and the Vue kit's `AvProgressRadial`. Reuses the shared
24
+ > status colors — no `--ui-progress-circle-*` tier. Reconcile with
25
+ > `/figma-component ProgressCircle <url> --update` once the component master node
26
+ > is confirmed.
27
+
28
+ ## When to use
29
+
30
+ - A compact, at-a-glance completion/score in a table cell, card, or widget
31
+ (e.g. compliance %, usage, health).
32
+
33
+ ## When not to use
34
+
35
+ - A full-width determinate bar — use the linear `Progress`.
36
+ - An indeterminate "busy" state — use `Loading`.
37
+
38
+ ## Example
39
+
40
+ ```tsx
41
+ import { ProgressCircle } from '@acronis-platform/ui-react';
42
+
43
+ <ProgressCircle value={75} size="md" showValue />
44
+ <ProgressCircle value={25} size="sm" /> {/* arc auto-colors to danger */}
45
+ <ProgressCircle value={100} size="lg" showIcon /> {/* success check in center */}
46
+ ```
47
+
48
+ In a table cell, compose the ring with an external label:
49
+
50
+ ```tsx
51
+ <div className="flex items-center gap-2">
52
+ <ProgressCircle value={81} size="sm" />
53
+ <span className="text-sm">81%</span>
54
+ </div>
55
+ ```
56
+
57
+ ## Props
58
+
59
+ | Prop | Type | Default | Required | Description |
60
+ |------|------|---------|----------|-------------|
61
+ | `value` | `number` | `0` | no | Current progress, clamped to [0, max]. |
62
+ | `max` | `number` | `100` | no | Value representing full completion. |
63
+ | `size` | `'tiny' | 'sm' | 'md' | 'lg'` | `'sm'` | no | Ring diameter + stroke width. |
64
+ | `status` | `'brand' | 'danger' | 'critical' | 'warning' | 'success'` | — | no | Arc color level. `brand` is a single neutral color; the rest are score levels. Omit to derive the level from value/max by thresholds (≈ <40% danger, <60% critical, <80% warning, else success — confirm with design). |
65
+ | `showValue` | `boolean` | `false` | no | Show the rounded percentage in the center. |
66
+ | `showIcon` | `boolean` | `false` | no | Show a status icon in the center (priority over showValue). |
67
+
68
+ ## Content slots
69
+
70
+ | Slot | Description |
71
+ |------|-------------|
72
+ | `children` | Custom center content (priority over icon / value). |
73
+
74
+ ## Behavior
75
+
76
+ A circular progress indicator. State is driven by `value`; there is no internal
77
+ state.
78
+
79
+ ```gherkin
80
+ Scenario: Fill
81
+ Given a ProgressCircle with value={75}
82
+ Then the arc fills 75% of the ring
83
+ And aria-valuenow is 75
84
+ ```
85
+
86
+ ```gherkin
87
+ Scenario: Status by level
88
+ Given no explicit status
89
+ Then the arc color is derived from value/max (danger → critical → warning → success)
90
+ ```
91
+
92
+ ```gherkin
93
+ Scenario: Status override
94
+ Given status="danger" with value={90}
95
+ Then the arc is the danger color regardless of value
96
+ ```
97
+
98
+ ```gherkin
99
+ Scenario: Center content
100
+ Given showValue, the center shows the rounded percentage
101
+ Given showIcon, the center shows a status icon (priority over the percentage)
102
+ Given children, they render in the center (priority over icon / percentage)
103
+ ```
104
+
105
+ ```gherkin
106
+ Scenario: Custom range
107
+ Given value={5} and max={10}
108
+ Then the arc fills 50% and aria-valuemax is 10
109
+ ```
110
+
111
+ ## Accessibility
112
+
113
+ - The root is `role="progressbar"` (Base UI Progress.Root) with
114
+ `aria-valuenow`/`aria-valuemin`/`aria-valuemax`. The SVG is `aria-hidden`; the
115
+ value semantics come from the root.
116
+ - Give it an accessible name via `aria-label`/`aria-labelledby` (e.g. "Compliance")
117
+ so assistive tech announces what is progressing.
118
+ - Status is conveyed by the arc color **and** the value/icon — never color alone.
119
+ Keep the meaningful value visible (`showValue`) or labelled when color encodes
120
+ status.
121
+
122
+ ## Contrast
123
+
124
+ The arc uses `--ui-text-on-status-*` over the `--ui-border-on-surface-border`
125
+ track, and the center label uses `--ui-text-on-surface-primary` — all meeting the
126
+ relevant contrast in light and dark.