@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.
- package/dist/components/ui/accordion-container/accordion-container.js +14 -14
- package/dist/components/ui/accordion-container/accordion-container.js.map +1 -1
- package/dist/components/ui/app-shell-chat/app-shell-chat.js.map +1 -1
- package/dist/components/ui/area-chart/area-chart.js +141 -139
- package/dist/components/ui/area-chart/area-chart.js.map +1 -1
- package/dist/components/ui/bar-chart/bar-chart.js +295 -293
- package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
- package/dist/components/ui/category-bar/category-bar.js +109 -93
- package/dist/components/ui/category-bar/category-bar.js.map +1 -1
- package/dist/components/ui/chart/chart-palette.js +113 -0
- package/dist/components/ui/chart/chart-palette.js.map +1 -0
- package/dist/components/ui/chart/chart.js +176 -129
- package/dist/components/ui/chart/chart.js.map +1 -1
- package/dist/components/ui/chart-state/chart-state-illustrations.js +310 -0
- package/dist/components/ui/chart-state/chart-state-illustrations.js.map +1 -0
- package/dist/components/ui/chart-state/chart-state.js +48 -34
- package/dist/components/ui/chart-state/chart-state.js.map +1 -1
- package/dist/components/ui/chart-widget/chart-widget.js +87 -0
- package/dist/components/ui/chart-widget/chart-widget.js.map +1 -0
- package/dist/components/ui/composed-chart/composed-chart.js +117 -115
- package/dist/components/ui/composed-chart/composed-chart.js.map +1 -1
- package/dist/components/ui/confidence-cone/confidence-cone.js +166 -158
- package/dist/components/ui/confidence-cone/confidence-cone.js.map +1 -1
- package/dist/components/ui/funnel-chart/funnel-chart.js +162 -161
- package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
- package/dist/components/ui/histogram/histogram.js +94 -92
- package/dist/components/ui/histogram/histogram.js.map +1 -1
- package/dist/components/ui/input/input.js.map +1 -1
- package/dist/components/ui/label/label.js +2 -9
- package/dist/components/ui/label/label.js.map +1 -1
- package/dist/components/ui/line-chart/line-chart.js +143 -141
- package/dist/components/ui/line-chart/line-chart.js.map +1 -1
- package/dist/components/ui/metric/metric.js +74 -185
- package/dist/components/ui/metric/metric.js.map +1 -1
- package/dist/components/ui/page-content/page-content.js.map +1 -1
- package/dist/components/ui/pie-chart/pie-chart.js +201 -195
- package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
- package/dist/components/ui/radar-chart/radar-chart.js +133 -131
- package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
- package/dist/components/ui/radial-bar-chart/radial-bar-chart.js +297 -265
- package/dist/components/ui/radial-bar-chart/radial-bar-chart.js.map +1 -1
- package/dist/components/ui/sankey-chart/sankey-chart.js +123 -101
- package/dist/components/ui/sankey-chart/sankey-chart.js.map +1 -1
- package/dist/components/ui/scatter-chart/scatter-chart.js +76 -74
- package/dist/components/ui/scatter-chart/scatter-chart.js.map +1 -1
- package/dist/components/ui/section/section.js +157 -31
- package/dist/components/ui/section/section.js.map +1 -1
- package/dist/components/ui/stepper/stepper.js +24 -24
- package/dist/components/ui/stepper/stepper.js.map +1 -1
- package/dist/components/ui/stepper-item/stepper-item.js +43 -24
- package/dist/components/ui/stepper-item/stepper-item.js.map +1 -1
- package/dist/components/ui/treemap/treemap.js +133 -124
- package/dist/components/ui/treemap/treemap.js.map +1 -1
- package/dist/components/ui/trend-indicator/trend-indicator.js +34 -69
- package/dist/components/ui/trend-indicator/trend-indicator.js.map +1 -1
- package/dist/components/ui/wizard/wizard.js +59 -0
- package/dist/components/ui/wizard/wizard.js.map +1 -0
- package/dist/hooks/use-wizard.js +75 -0
- package/dist/hooks/use-wizard.js.map +1 -0
- package/dist/index.js +502 -497
- package/dist/index.js.map +1 -1
- package/dist/llms/accordion-container.md +181 -0
- package/dist/llms/accordion.md +124 -0
- package/dist/llms/alert.md +225 -0
- package/dist/llms/app-shell-chat.md +415 -0
- package/dist/llms/area-chart.md +355 -0
- package/dist/llms/avatar.md +208 -0
- package/dist/llms/bar-chart.md +406 -0
- package/dist/llms/breadcrumb.md +326 -0
- package/dist/llms/button-group.md +298 -0
- package/dist/llms/button-icon-input.md +142 -0
- package/dist/llms/button-icon-menu.md +209 -0
- package/dist/llms/button-icon.md +128 -0
- package/dist/llms/button-menu.md +172 -0
- package/dist/llms/button.md +185 -0
- package/dist/llms/calendar-panel.md +733 -0
- package/dist/llms/calendar.md +188 -0
- package/dist/llms/card-filter.md +214 -0
- package/dist/llms/card-section.md +293 -0
- package/dist/llms/card.md +447 -0
- package/dist/llms/carousel-dialog.md +156 -0
- package/dist/llms/category-bar.md +193 -0
- package/dist/llms/chart-state.md +190 -0
- package/dist/llms/chart-widget.md +209 -0
- package/dist/llms/chart.md +182 -0
- package/dist/llms/checkbox.md +257 -0
- package/dist/llms/chip.md +195 -0
- package/dist/llms/combobox.md +164 -0
- package/dist/llms/composed-chart.md +508 -0
- package/dist/llms/confidence-cone.md +277 -0
- package/dist/llms/data-table.md +474 -0
- package/dist/llms/date-range-picker.md +272 -0
- package/dist/llms/description-list.md +131 -0
- package/dist/llms/dialog-footer-carousel.md +147 -0
- package/dist/llms/dialog-footer-default.md +142 -0
- package/dist/llms/dialog-root.md +246 -0
- package/dist/llms/dialog-welcome.md +223 -0
- package/dist/llms/dialog.md +403 -0
- package/dist/llms/dropdown-menu.md +193 -0
- package/dist/llms/empty-overlay.md +146 -0
- package/dist/llms/empty.md +156 -0
- package/dist/llms/field.md +185 -0
- package/dist/llms/filter-cards.md +157 -0
- package/dist/llms/filter-chips.md +323 -0
- package/dist/llms/filter-search.md +512 -0
- package/dist/llms/form.md +154 -0
- package/dist/llms/funnel-chart.md +333 -0
- package/dist/llms/grid.md +80 -0
- package/dist/llms/histogram.md +190 -0
- package/dist/llms/input-date-picker.md +203 -0
- package/dist/llms/input-otp.md +173 -0
- package/dist/llms/input-password.md +197 -0
- package/dist/llms/input-search.md +170 -0
- package/dist/llms/input-select.md +278 -0
- package/dist/llms/input-text-area.md +271 -0
- package/dist/llms/input-text.md +192 -0
- package/dist/llms/line-chart.md +369 -0
- package/dist/llms/link.md +209 -0
- package/dist/llms/loading.md +130 -0
- package/dist/llms/meter.md +177 -0
- package/dist/llms/metric.md +204 -0
- package/dist/llms/number-field.md +122 -0
- package/dist/llms/page-content.md +69 -0
- package/dist/llms/page-header.md +182 -0
- package/dist/llms/pagination.md +133 -0
- package/dist/llms/pie-chart.md +307 -0
- package/dist/llms/popover.md +252 -0
- package/dist/llms/progress-circle.md +126 -0
- package/dist/llms/progress.md +111 -0
- package/dist/llms/radar-chart.md +316 -0
- package/dist/llms/radial-bar-chart.md +319 -0
- package/dist/llms/radio.md +255 -0
- package/dist/llms/resizable.md +195 -0
- package/dist/llms/sankey-chart.md +208 -0
- package/dist/llms/scatter-chart.md +239 -0
- package/dist/llms/scroll-area.md +183 -0
- package/dist/llms/section.md +401 -0
- package/dist/llms/select.md +210 -0
- package/dist/llms/separator.md +87 -0
- package/dist/llms/sheet.md +202 -0
- package/dist/llms/sidebar-primary.md +360 -0
- package/dist/llms/sidebar-secondary.md +417 -0
- package/dist/llms/skeleton.md +81 -0
- package/dist/llms/slider.md +97 -0
- package/dist/llms/spinner.md +100 -0
- package/dist/llms/stack.md +80 -0
- package/dist/llms/stepper-item.md +309 -0
- package/dist/llms/stepper.md +251 -0
- package/dist/llms/switch.md +140 -0
- package/dist/llms/table.md +436 -0
- package/dist/llms/tabs.md +197 -0
- package/dist/llms/tag.md +139 -0
- package/dist/llms/timeline.md +361 -0
- package/dist/llms/timer.md +259 -0
- package/dist/llms/toast.md +241 -0
- package/dist/llms/toggle-group.md +121 -0
- package/dist/llms/toolbar.md +453 -0
- package/dist/llms/tooltip.md +164 -0
- package/dist/llms/treemap.md +254 -0
- package/dist/llms/trend-indicator.md +132 -0
- package/dist/llms/widget-placeholder.md +169 -0
- package/dist/llms/wizard.md +412 -0
- package/dist/llms.txt +142 -0
- package/dist/react.js +502 -497
- package/dist/react.js.map +1 -1
- package/dist/src/components/ui/area-chart/area-chart.d.ts +9 -4
- package/dist/src/components/ui/avatar/avatar.d.ts +1 -1
- package/dist/src/components/ui/bar-chart/bar-chart.d.ts +9 -4
- package/dist/src/components/ui/category-bar/category-bar.d.ts +10 -4
- package/dist/src/components/ui/chart/__stories__/palette-control.d.ts +48 -0
- package/dist/src/components/ui/chart/__tests__/chart-palette.test.d.ts +1 -0
- package/dist/src/components/ui/chart/chart-palette.d.ts +128 -0
- package/dist/src/components/ui/chart/chart.d.ts +66 -15
- package/dist/src/components/ui/chart/index.d.ts +4 -2
- package/dist/src/components/ui/chart-state/chart-state-illustrations.d.ts +6 -0
- package/dist/src/components/ui/chart-state/chart-state.d.ts +13 -2
- package/dist/src/components/ui/chart-state/index.d.ts +1 -0
- package/dist/src/components/ui/chart-widget/chart-widget.d.ts +64 -0
- package/dist/src/components/ui/chart-widget/index.d.ts +1 -0
- package/dist/src/components/ui/composed-chart/composed-chart.d.ts +12 -7
- package/dist/src/components/ui/confidence-cone/confidence-cone.d.ts +10 -5
- package/dist/src/components/ui/empty-overlay/empty-overlay.d.ts +1 -1
- package/dist/src/components/ui/funnel-chart/funnel-chart.d.ts +11 -5
- package/dist/src/components/ui/histogram/histogram.d.ts +10 -4
- package/dist/src/components/ui/label/index.d.ts +1 -1
- package/dist/src/components/ui/label/label.d.ts +1 -5
- package/dist/src/components/ui/line-chart/line-chart.d.ts +9 -4
- package/dist/src/components/ui/metric/metric.d.ts +13 -14
- package/dist/src/components/ui/pie-chart/pie-chart.d.ts +17 -8
- package/dist/src/components/ui/radar-chart/radar-chart.d.ts +10 -5
- package/dist/src/components/ui/radial-bar-chart/radial-bar-chart.d.ts +17 -6
- package/dist/src/components/ui/sankey-chart/sankey-chart.d.ts +10 -5
- package/dist/src/components/ui/scatter-chart/scatter-chart.d.ts +10 -5
- package/dist/src/components/ui/section/index.d.ts +1 -1
- package/dist/src/components/ui/section/section.d.ts +75 -6
- package/dist/src/components/ui/stepper-item/stepper-item.d.ts +6 -5
- package/dist/src/components/ui/tag/tag.d.ts +1 -1
- package/dist/src/components/ui/treemap/treemap.d.ts +11 -5
- package/dist/src/components/ui/trend-indicator/trend-indicator.d.ts +3 -7
- package/dist/src/components/ui/wizard/index.d.ts +1 -0
- package/dist/src/components/ui/wizard/wizard.d.ts +6 -0
- package/dist/src/hooks/index.d.ts +1 -0
- package/dist/src/hooks/use-wizard.d.ts +80 -0
- package/dist/src/index.d.ts +2 -3
- package/dist/ui-react.css +1 -1
- package/package.json +9 -3
- package/dist/components/ui/app-shell/app-shell.js +0 -75
- package/dist/components/ui/app-shell/app-shell.js.map +0 -1
- package/dist/components/ui/auth-layout/auth-layout.js +0 -56
- package/dist/components/ui/auth-layout/auth-layout.js.map +0 -1
- package/dist/components/ui/collapsible/collapsible.js +0 -22
- package/dist/components/ui/collapsible/collapsible.js.map +0 -1
- package/dist/src/components/ui/app-shell/app-shell.d.ts +0 -8
- package/dist/src/components/ui/app-shell/index.d.ts +0 -1
- package/dist/src/components/ui/auth-layout/auth-layout.d.ts +0 -6
- package/dist/src/components/ui/auth-layout/index.d.ts +0 -1
- package/dist/src/components/ui/collapsible/collapsible.d.ts +0 -5
- package/dist/src/components/ui/collapsible/index.d.ts +0 -1
- package/dist/src/components/ui/label/label.docs.d.ts +0 -10
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Timer
|
|
2
|
+
|
|
3
|
+
> An elapsed-time readout paired with a hairline-separated cluster of icon-only actions, in a single bordered 32px box. Displays a caller-formatted value; it does not tick.
|
|
4
|
+
|
|
5
|
+
**Status:** ready · **Category:** data-display · **Since:** v0.63.0
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Timer, ButtonGroupItem } from '@acronis-platform/ui-react'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick example
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
const [elapsed, setElapsed] = useState(0);
|
|
17
|
+
|
|
18
|
+
<Timer value={formatDuration(elapsed)} actionsLabel="Time tracking">
|
|
19
|
+
<ButtonGroupItem aria-label="Pause" onClick={pause}>
|
|
20
|
+
<CirclePauseIcon size={16} />
|
|
21
|
+
</ButtonGroupItem>
|
|
22
|
+
<ButtonGroupItem aria-label="Rename" onClick={rename}>
|
|
23
|
+
<PencilIcon size={16} />
|
|
24
|
+
</ButtonGroupItem>
|
|
25
|
+
<ButtonGroupItem aria-label="Add entry" onClick={addEntry}>
|
|
26
|
+
<PlusIcon size={16} />
|
|
27
|
+
</ButtonGroupItem>
|
|
28
|
+
</Timer>
|
|
29
|
+
|
|
30
|
+
{/* Read-only: no actions, so no toolbar and no divider */}
|
|
31
|
+
<Timer value="00:12:30" />
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
An elapsed-time readout paired with a hairline-separated cluster of icon-only
|
|
35
|
+
actions, in a single bordered 32px box. The readout displays a value the caller
|
|
36
|
+
formats; the actions are ButtonGroup items the caller supplies.
|
|
37
|
+
|
|
38
|
+
## When to use
|
|
39
|
+
|
|
40
|
+
- A **time-tracking control** in a toolbar, table row, or card header: show the
|
|
41
|
+
elapsed time on a task and offer the two or three actions that operate on it
|
|
42
|
+
(pause, rename, add an entry).
|
|
43
|
+
- A **stopwatch or session counter** the user can act on — pause, resume, stop —
|
|
44
|
+
where the count and its controls should read as one compact unit.
|
|
45
|
+
- A **read-only duration** that belongs in the same visual family as the
|
|
46
|
+
interactive ones. Omit the actions and the box becomes just the readout.
|
|
47
|
+
|
|
48
|
+
## When not to use
|
|
49
|
+
|
|
50
|
+
- **A duration inside prose or a data table cell.** A bordered 32px box next to
|
|
51
|
+
plain text is chrome for nothing; render the formatted string directly.
|
|
52
|
+
- **A countdown that must announce itself.** `role="timer"` is deliberately
|
|
53
|
+
silent (`aria-live="off"`) so it doesn't flood a screen reader once a second.
|
|
54
|
+
If a threshold matters, announce that event from a separate status region.
|
|
55
|
+
- **Actions unrelated to the time shown.** The shared box claims the readout and
|
|
56
|
+
the actions are one control; unrelated actions belong outside it.
|
|
57
|
+
- **Labelled actions.** The cluster is icon-only by design. For text actions
|
|
58
|
+
next to a duration, compose a `Toolbar` with plain `Button`s.
|
|
59
|
+
- **As a clock.** The component holds no timer of its own (see Notes).
|
|
60
|
+
|
|
61
|
+
## Example (React — implemented)
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { Timer, ButtonGroupItem } from '@acronis-platform/ui-react';
|
|
65
|
+
import {
|
|
66
|
+
CirclePauseIcon,
|
|
67
|
+
PencilIcon,
|
|
68
|
+
PlusIcon,
|
|
69
|
+
} from '@acronis-platform/icons-react/stroke-mono';
|
|
70
|
+
|
|
71
|
+
<Timer value={formatDuration(elapsed)} actionsLabel="Time tracking">
|
|
72
|
+
<ButtonGroupItem aria-label="Pause" onClick={pause}>
|
|
73
|
+
<CirclePauseIcon size={16} />
|
|
74
|
+
</ButtonGroupItem>
|
|
75
|
+
<ButtonGroupItem aria-label="Rename" onClick={rename}>
|
|
76
|
+
<PencilIcon size={16} />
|
|
77
|
+
</ButtonGroupItem>
|
|
78
|
+
<ButtonGroupItem aria-label="Add entry" onClick={addEntry}>
|
|
79
|
+
<PlusIcon size={16} />
|
|
80
|
+
</ButtonGroupItem>
|
|
81
|
+
</Timer>;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Read-only — no actions, so no toolbar and no divider:
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<Timer value="00:12:30" />
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Vue and Web Component implementations are planned and will target the same
|
|
91
|
+
contract — see `api.yaml` `adapters`.
|
|
92
|
+
|
|
93
|
+
## Parts
|
|
94
|
+
|
|
95
|
+
| Part | Element | Notes |
|
|
96
|
+
| --------- | ---------- | --------------------------------------------------------------------------------------------------- |
|
|
97
|
+
| container | `div` | The bordered box. No role — it owns the border, radius, background, height, and the clipping. |
|
|
98
|
+
| `readout` | `div` | `role="timer"`. The formatted value, in tabular figures. |
|
|
99
|
+
| `divider` | _(border)_ | The readout's inline-end border, dropped when there are no actions. Not an element, so not in a11y. |
|
|
100
|
+
| `actions` | `div` | `role="toolbar"` — a `ButtonGroup` in its `inlined` style, holding the caller's `ButtonGroupItem`s. |
|
|
101
|
+
|
|
102
|
+
## Notes
|
|
103
|
+
|
|
104
|
+
- **It does not tick.** The component holds no clock and starts no interval: it
|
|
105
|
+
renders whatever `value` it is handed. The caller owns the interval, the
|
|
106
|
+
format, and the state the actions mutate — which is what lets the same
|
|
107
|
+
component serve a stopwatch, a countdown, and a static duration.
|
|
108
|
+
- **Formatting is yours too**, including whether hours appear and how the locale
|
|
109
|
+
renders the separators. The readout only guarantees that whatever you pass
|
|
110
|
+
keeps its width as digits change, via tabular figures.
|
|
111
|
+
- **Actions are ButtonGroup items**, imported from `ButtonGroup` rather than
|
|
112
|
+
re-exported under a Timer name. The Timer renders the group itself so it can
|
|
113
|
+
always pick the `inlined` style — its own box already draws the border and
|
|
114
|
+
radius an `outlined` group would duplicate — but everything about an item
|
|
115
|
+
(separators, one Tab stop, arrow-key roving, hover/active/focus/disabled) is
|
|
116
|
+
ButtonGroup's contract, unchanged.
|
|
117
|
+
- **Name the actions.** Each icon-only item needs its own `aria-label`, and a
|
|
118
|
+
play/pause control's name must track its current function rather than its
|
|
119
|
+
icon. The toolbar's own name comes from `actionsLabel`, whose default is an
|
|
120
|
+
untranslated English string. See `accessibility.md`.
|
|
121
|
+
- **The box hugs its content.** Figma's 224px frame is the measured width of the
|
|
122
|
+
sample value plus three actions, not a fixed size — a longer value simply
|
|
123
|
+
makes the box wider.
|
|
124
|
+
- **RTL-safe.** The divider is an inline-end border, so the whole layout mirrors
|
|
125
|
+
under `dir="rtl"` with no extra work.
|
|
126
|
+
- **No variants, no states.** Figma models this as a single component, not a
|
|
127
|
+
component set: there is no size or style axis, and every interaction state
|
|
128
|
+
lives on the individual actions.
|
|
129
|
+
|
|
130
|
+
## Props
|
|
131
|
+
|
|
132
|
+
| Prop | Type | Default | Required | Description |
|
|
133
|
+
|------|------|---------|----------|-------------|
|
|
134
|
+
| `value` | `string | Node` | — | yes | The formatted time to display, e.g. `12:01:45`. The component holds no clock: it neither ticks nor formats, so the caller owns the interval and the format (including whether hours are shown and how a locale renders the separators). Rendered with tabular figures so the readout keeps its width as the digits change. |
|
|
135
|
+
| `actions-label` | `string` | `'Timer actions'` | no | Accessible name for the action cluster's toolbar. Exists because the caller cannot reach that element directly — the Timer renders it — and the default is an unlocalized English string, so translate it. |
|
|
136
|
+
|
|
137
|
+
## Content slots
|
|
138
|
+
|
|
139
|
+
| Slot | Description |
|
|
140
|
+
|------|-------------|
|
|
141
|
+
| `default` | The actions, as ButtonGroup items in visual order. Each is icon-only and needs its own accessible name. Omit them for a read-only readout: the toolbar is then not rendered at all and the divider goes with it. |
|
|
142
|
+
|
|
143
|
+
## Behavior
|
|
144
|
+
|
|
145
|
+
### The readout
|
|
146
|
+
|
|
147
|
+
**Given** a timer
|
|
148
|
+
**When** it renders
|
|
149
|
+
**Then** the `value` it was given is displayed verbatim inside the readout, with
|
|
150
|
+
tabular figures so every digit occupies the same width.
|
|
151
|
+
|
|
152
|
+
**Given** a running timer whose `value` changes from `12:01:45` to `12:01:46`
|
|
153
|
+
**When** the new value renders
|
|
154
|
+
**Then** the readout's width is unchanged and nothing to its inline-end shifts,
|
|
155
|
+
because the figures are tabular and the box hugs its content rather than
|
|
156
|
+
re-measuring per frame.
|
|
157
|
+
|
|
158
|
+
**Given** a timer
|
|
159
|
+
**When** time passes without the caller supplying a new `value`
|
|
160
|
+
**Then** the readout does not change. The component owns no clock and starts no
|
|
161
|
+
interval — ticking, formatting, pausing, and resetting all belong to the caller,
|
|
162
|
+
who also owns whatever the actions operate on.
|
|
163
|
+
|
|
164
|
+
**Given** a `value` longer than the design's sample (e.g. `128:59:59`)
|
|
165
|
+
**When** it renders
|
|
166
|
+
**Then** the box grows to fit it. The readout hugs its content plus the
|
|
167
|
+
horizontal padding; the Figma frame's 224px width is the measured result of the
|
|
168
|
+
sample content, not a fixed width.
|
|
169
|
+
|
|
170
|
+
### The action cluster
|
|
171
|
+
|
|
172
|
+
**Given** a timer with one or more actions
|
|
173
|
+
**When** it renders
|
|
174
|
+
**Then** they appear inline-end of the readout as a single toolbar, in the order
|
|
175
|
+
given, separated from the readout by a hairline divider and from each other by
|
|
176
|
+
the ButtonGroup's own separators.
|
|
177
|
+
|
|
178
|
+
**Given** a timer with no actions
|
|
179
|
+
**When** it renders
|
|
180
|
+
**Then** no toolbar element is rendered at all, and the divider is dropped too —
|
|
181
|
+
the readout is the last child, and the divider is its own `:last-child`-reset
|
|
182
|
+
inline-end border.
|
|
183
|
+
|
|
184
|
+
**Given** an action
|
|
185
|
+
**When** it is hovered, activated, focused by keyboard, or disabled
|
|
186
|
+
**Then** it behaves exactly as a ButtonGroup item does; the timer adds nothing
|
|
187
|
+
and overrides nothing. See the ButtonGroup spec.
|
|
188
|
+
|
|
189
|
+
**Given** the action cluster
|
|
190
|
+
**When** it renders
|
|
191
|
+
**Then** it is always the `inlined` ButtonGroup style — never `outlined`. The
|
|
192
|
+
timer's own box already draws the border and radius an outlined group would
|
|
193
|
+
duplicate, and this is how the design instantiates it. It is not configurable.
|
|
194
|
+
|
|
195
|
+
### Container
|
|
196
|
+
|
|
197
|
+
**Given** a timer whose trailing action is hovered or focused
|
|
198
|
+
**When** the fill or the inset focus ring paints
|
|
199
|
+
**Then** it is clipped to the container's radius, so no square corner escapes
|
|
200
|
+
the rounded box.
|
|
201
|
+
|
|
202
|
+
**Given** a timer under `dir="rtl"`
|
|
203
|
+
**When** it renders
|
|
204
|
+
**Then** the readout sits at the inline start and the actions at the inline end,
|
|
205
|
+
mirrored — the divider is an inline-end border, not a right-hand one. The value
|
|
206
|
+
itself is not reordered; formatting the time for the locale is the caller's job.
|
|
207
|
+
|
|
208
|
+
## Accessibility
|
|
209
|
+
|
|
210
|
+
- **Roles:** the readout is `role="timer"` — a live-region role for a numeric
|
|
211
|
+
counter of elapsed or remaining time. The action cluster is the ButtonGroup's
|
|
212
|
+
`role="toolbar"`. The container itself carries no role; it is a box, not a
|
|
213
|
+
widget. The divider is a CSS border rather than an element, so it correctly
|
|
214
|
+
contributes nothing to the accessibility tree.
|
|
215
|
+
|
|
216
|
+
- **Live-region politeness:** `role="timer"` is implicitly `aria-live="off"`,
|
|
217
|
+
which is the right default: a per-second announcement would flood a screen
|
|
218
|
+
reader and drown out everything else on the page. Do **not** raise it to
|
|
219
|
+
`polite` for a ticking clock. If a milestone matters (a limit reached, a
|
|
220
|
+
session about to expire), announce that event from a separate status region
|
|
221
|
+
rather than making the timer itself chatty.
|
|
222
|
+
|
|
223
|
+
- **Accessible name — readout:** optional. The value speaks for itself in most
|
|
224
|
+
contexts; where several timers appear together, name each one by putting
|
|
225
|
+
`aria-labelledby` on the container's own visible label, or pass `aria-label`
|
|
226
|
+
through to the container.
|
|
227
|
+
|
|
228
|
+
- **Accessible name — action cluster:** supplied by `actions-label`, which
|
|
229
|
+
defaults to the English string `Timer actions`. Translate it. The property
|
|
230
|
+
exists precisely because the caller cannot reach the toolbar element directly.
|
|
231
|
+
|
|
232
|
+
- **Accessible name — actions:** each action is icon-only, so it has no text to
|
|
233
|
+
name it. Every one MUST be given an `aria-label` (or `aria-labelledby`);
|
|
234
|
+
decorative glyphs inside stay `aria-hidden`. A play/pause action's name must
|
|
235
|
+
track its current function ("Pause" while running, "Resume" while paused), not
|
|
236
|
+
its icon.
|
|
237
|
+
|
|
238
|
+
- **Keyboard:** the timer adds no key handling of its own. The action cluster is
|
|
239
|
+
a **single Tab stop** with a roving tabindex — Tab enters it and the next Tab
|
|
240
|
+
leaves; the arrow keys move between actions; Enter and Space activate. See the
|
|
241
|
+
ButtonGroup spec for the full pattern, including how a disabled action stays
|
|
242
|
+
focusable.
|
|
243
|
+
|
|
244
|
+
- **Focus visibility:** an action's focus ring is drawn inset, so the
|
|
245
|
+
container's clipping cannot shave it off at the rounded corners.
|
|
246
|
+
|
|
247
|
+
- **Contrast:** the readout uses `--ui-timer-value-color` on
|
|
248
|
+
`--ui-timer-container-color` and the action glyphs use
|
|
249
|
+
`--ui-glyph-on-surface-primary`; both pairs meet WCAG AA in the light and dark
|
|
250
|
+
themes of every shipped brand. The divider and the container border are
|
|
251
|
+
decorative hairlines and are not held to a text-contrast ratio.
|
|
252
|
+
|
|
253
|
+
- **Text sizing:** the readout is 18px with a 24px line-height inside a 32px
|
|
254
|
+
box. It scales with the user's font size; the container's height token is the
|
|
255
|
+
design's fixed 32px, so a large browser zoom scales the whole box rather than
|
|
256
|
+
clipping the digits.
|
|
257
|
+
|
|
258
|
+
- **Motion:** none. The value updates are plain text changes with no transition,
|
|
259
|
+
so nothing here is affected by `prefers-reduced-motion`.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Toast
|
|
2
|
+
|
|
3
|
+
> A transient notification in a corner stack: a neutral card carrying its severity through a status-colored border and a status line down the leading edge, with a fixed status icon per severity, an optional description, and optional action buttons. Triggered imperatively via the `toast(...)` API and rendered by a single `<Toaster>` region.
|
|
4
|
+
|
|
5
|
+
**Status:** stable · **Category:** feedback · **Since:** v0.41.0
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Toaster, toast } from '@acronis-platform/ui-react'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick example
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
// once, near the app root:
|
|
17
|
+
<Toaster />
|
|
18
|
+
|
|
19
|
+
// anywhere:
|
|
20
|
+
toast.success('Profile saved', { description: 'Your changes were saved.' });
|
|
21
|
+
toast.danger('Delete failed', {
|
|
22
|
+
description: 'Please try again or contact support.',
|
|
23
|
+
actions: [{ label: 'Retry', onClick: retry }],
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A transient notification shown in a corner stack — triggered imperatively with
|
|
28
|
+
the `toast(...)` API and rendered by a single `<Toaster>` region.
|
|
29
|
+
|
|
30
|
+
Visually it is `Alert` plus a drop shadow: a neutral card whose severity is
|
|
31
|
+
carried by a 1px status-colored border, a 6px status line down the leading edge,
|
|
32
|
+
and a fixed status icon. Every value comes from the `--ui-toast-*` tier, mirroring
|
|
33
|
+
the Figma "Toast" component set (node `7421:126262`). Built on the Base UI toast
|
|
34
|
+
manager — no Sonner dependency.
|
|
35
|
+
|
|
36
|
+
## When to use
|
|
37
|
+
|
|
38
|
+
- Brief, low-priority feedback for an action (saved, copied, undo).
|
|
39
|
+
- Background operation status (loading → success/danger via `toast.promise`).
|
|
40
|
+
|
|
41
|
+
## When not to use
|
|
42
|
+
|
|
43
|
+
- For information the user must act on or must not miss — use a `Dialog` or an
|
|
44
|
+
inline message; toasts auto-dismiss.
|
|
45
|
+
- For persistent, in-page status — use `Alert`, which is the same card without the
|
|
46
|
+
shadow and without the auto-dismiss.
|
|
47
|
+
|
|
48
|
+
## Setup
|
|
49
|
+
|
|
50
|
+
Render one `<Toaster>` near the app root, then call `toast` anywhere. **Exactly
|
|
51
|
+
one** region per page: the queue is a module-level singleton scoped to the package
|
|
52
|
+
instance, so a micro-frontend host must share a single `@acronis-platform/ui-react`
|
|
53
|
+
instance. With two copies loaded, `toast(...)` from one is invisible to the other's
|
|
54
|
+
region (queued, never rendered) and two regions overlap in the same corner.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { Toaster, toast } from '@acronis-platform/ui-react';
|
|
58
|
+
|
|
59
|
+
// app root
|
|
60
|
+
<Toaster />;
|
|
61
|
+
|
|
62
|
+
// anywhere
|
|
63
|
+
toast.success('Profile saved', { description: 'Your changes were saved.' });
|
|
64
|
+
toast.danger('Delete failed', {
|
|
65
|
+
description: 'Please try again or contact support.',
|
|
66
|
+
actions: [
|
|
67
|
+
{ label: 'Retry', onClick: retry },
|
|
68
|
+
{ label: 'Get help', onClick: openSupport },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// tie to a promise
|
|
73
|
+
toast.promise(save(), {
|
|
74
|
+
loading: 'Saving…',
|
|
75
|
+
success: 'Saved',
|
|
76
|
+
error: 'Could not save',
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The severity comes from the method: `toast.info` / `success` / `warning` /
|
|
81
|
+
`critical` / `danger` match the Figma variants, a bare `toast(...)` is `info`, and
|
|
82
|
+
`toast.loading` shows a persistent spinner (no Figma variant of its own).
|
|
83
|
+
|
|
84
|
+
## Parts
|
|
85
|
+
|
|
86
|
+
| Part | Element | Purpose |
|
|
87
|
+
| ------------------- | ---------- | ------------------------------------------- |
|
|
88
|
+
| `root` | div | The `role="region"` viewport / toast stack. |
|
|
89
|
+
| `toast` | div | A single notification card. |
|
|
90
|
+
| `toast-status-line` | `::before` | The 6px status bar on the leading edge. |
|
|
91
|
+
| `toast-icon` | div | Status glyph, or a spinner while loading. |
|
|
92
|
+
| `toast-content` | div | The column beside the icon. |
|
|
93
|
+
| `toast-text` | div | Title + description block. |
|
|
94
|
+
| `toast-title` | h5 | The notification's heading. |
|
|
95
|
+
| `toast-description` | div | Optional supporting text (3-line clamp). |
|
|
96
|
+
| `toast-actions` | div | Optional wrapping row of action buttons. |
|
|
97
|
+
| `toast-close` | button | Optional dismiss (✕) button. |
|
|
98
|
+
|
|
99
|
+
## Props
|
|
100
|
+
|
|
101
|
+
| Prop | Type | Default | Required | Description |
|
|
102
|
+
|------|------|---------|----------|-------------|
|
|
103
|
+
| `timeout` | `number` | `5000` | no | Default auto-dismiss delay (ms) for toasts that don't set their own. `0` keeps a toast until dismissed. |
|
|
104
|
+
| `limit` | `number` | `3` | no | Maximum toasts shown at once; the oldest is dropped past it. |
|
|
105
|
+
| `label` | `string` | `'Notifications'` | no | Accessible name for the toast region. |
|
|
106
|
+
| `closeAriaLabel` | `string` | `'Close'` | no | Accessible name for each toast's dismiss control. |
|
|
107
|
+
|
|
108
|
+
## Content slots
|
|
109
|
+
|
|
110
|
+
| Slot | Description |
|
|
111
|
+
|------|-------------|
|
|
112
|
+
| `options.dismissable` | Whether the toast renders its dismiss control and can be swiped away. Defaults to true, matching the Figma's `dismissable` boolean. Setting it false also revokes swipe-to-dismiss, so the control and the capability never disagree; combined with `timeout: 0` the toast can then only be removed with `toast.dismiss(id)`. |
|
|
113
|
+
| `options.actions` | Action-button descriptors — `{ label, onClick?, variant? }` — rendered in a wrapping row under the text. The first defaults to the `secondary` button style and the rest to `ghost`, matching the Figma. Clicking one runs its handler; it does not dismiss the toast. |
|
|
114
|
+
|
|
115
|
+
## Behavior
|
|
116
|
+
|
|
117
|
+
Toast is imperative: render one `<Toaster>` region, then push notifications with
|
|
118
|
+
the `toast(...)` API from anywhere (including outside React — the manager is
|
|
119
|
+
module-level). The severity is chosen by which method is called, not by a prop.
|
|
120
|
+
|
|
121
|
+
```gherkin
|
|
122
|
+
Scenario: Show a toast
|
|
123
|
+
Given a mounted <Toaster>
|
|
124
|
+
When toast('Event created', { description: 'Monday at 6:00 PM' }) is called
|
|
125
|
+
Then a card with the title and description appears in the bottom-end stack
|
|
126
|
+
And it uses the info severity (the Figma's default variant)
|
|
127
|
+
And it auto-dismisses after the timeout (default 5000ms)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```gherkin
|
|
131
|
+
Scenario: Severities
|
|
132
|
+
Given toast.info / success / warning / critical / danger is called
|
|
133
|
+
Then the card keeps its neutral surface
|
|
134
|
+
And its border and 6px leading status line take that severity's color
|
|
135
|
+
And it shows that severity's multicolor status icon
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```gherkin
|
|
139
|
+
Scenario: No description
|
|
140
|
+
Given a toast created without a description
|
|
141
|
+
Then only the title renders, and the icon stays aligned with its first line
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```gherkin
|
|
145
|
+
Scenario: Long description
|
|
146
|
+
Given a description longer than three lines
|
|
147
|
+
Then it is clamped to three lines with a trailing ellipsis
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```gherkin
|
|
151
|
+
Scenario: Loading
|
|
152
|
+
Given toast.loading('Processing…') is called
|
|
153
|
+
Then the card shows a spinner in place of the status glyph
|
|
154
|
+
And it borrows the info border and status line (there is no Figma loading variant)
|
|
155
|
+
And it does NOT auto-dismiss
|
|
156
|
+
And toast.dismiss(id) or toast.promise resolution removes it
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```gherkin
|
|
160
|
+
Scenario: Actions
|
|
161
|
+
Given a toast created with actions: [{ label: 'View' }, { label: 'Undo' }]
|
|
162
|
+
Then a wrapping row renders under the text
|
|
163
|
+
And the first action uses the secondary button style, the rest ghost
|
|
164
|
+
When an action is clicked
|
|
165
|
+
Then its onClick runs and the toast stays open
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```gherkin
|
|
169
|
+
Scenario: Promise lifecycle
|
|
170
|
+
Given toast.promise(p, { loading, success, error }) is called
|
|
171
|
+
Then a persistent loading toast appears
|
|
172
|
+
And on fulfilment it becomes a success toast
|
|
173
|
+
And on rejection it becomes a danger toast
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```gherkin
|
|
177
|
+
Scenario: Manual dismiss
|
|
178
|
+
Given a visible toast
|
|
179
|
+
When the close (✕) button is clicked
|
|
180
|
+
Then the toast animates out and is removed
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```gherkin
|
|
184
|
+
Scenario: Not dismissable
|
|
185
|
+
Given a toast created with dismissable: false
|
|
186
|
+
Then no close (✕) button renders
|
|
187
|
+
And swiping it does not dismiss it either
|
|
188
|
+
And it leaves only on its timeout, or via toast.dismiss(id)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```gherkin
|
|
192
|
+
Scenario: Direction
|
|
193
|
+
Given dir="rtl" on an ancestor
|
|
194
|
+
Then the stack pins to the bottom-left corner
|
|
195
|
+
And the status line sits on the right (leading) edge
|
|
196
|
+
And toasts slide in from the left
|
|
197
|
+
And the status icons do NOT mirror
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```gherkin
|
|
201
|
+
Scenario: Limit
|
|
202
|
+
Given more than `limit` toasts (default 3) are active
|
|
203
|
+
Then the oldest is dropped to make room for the newest
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```gherkin
|
|
207
|
+
Scenario: Update in place
|
|
208
|
+
Given toast(..., { id: 'x' }) is called twice with the same id
|
|
209
|
+
Then the existing toast updates and its auto-dismiss timer resets
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Accessibility
|
|
213
|
+
|
|
214
|
+
- The `<Toaster>` region is a live region (`role="region"`, `aria-live="polite"`)
|
|
215
|
+
named by the `label` prop (default `"Notifications"`), so screen readers
|
|
216
|
+
announce new toasts without moving focus. Pass `label` to localize it.
|
|
217
|
+
- Base UI manages the announcement: it exposes an off-screen copy of each toast's
|
|
218
|
+
text to assistive tech and marks the visible card `aria-hidden`, so the message
|
|
219
|
+
is announced once and not duplicated. The card's accessible name comes from its
|
|
220
|
+
title.
|
|
221
|
+
- The dismiss and action buttons are reachable by keyboard via the viewport's
|
|
222
|
+
focus management (F6 / Tab into the region). The dismiss control's accessible
|
|
223
|
+
name comes from `closeAriaLabel` (default `"Close"`) — pass it to localize —
|
|
224
|
+
and it shows a visible focus ring (`--ui-focus-primary`) via ButtonIcon.
|
|
225
|
+
- Severity is never conveyed by color alone: each severity has its own status
|
|
226
|
+
icon, and the title/description carry the message in words.
|
|
227
|
+
- Don't put essential, time-critical information only in a toast — it
|
|
228
|
+
auto-dismisses. Pair destructive confirmations with a persistent surface, and
|
|
229
|
+
prefer `Alert` (the same visual, inline and persistent) when the message must
|
|
230
|
+
stay on screen.
|
|
231
|
+
|
|
232
|
+
## Contrast
|
|
233
|
+
|
|
234
|
+
The card uses `--ui-toast-global-content-text-container-title-color` (title) and
|
|
235
|
+
`--ui-toast-global-content-text-container-description-color` (description) over
|
|
236
|
+
the neutral `--ui-toast-global-container-background`, meeting text contrast in
|
|
237
|
+
light and dark. The severity border and status line
|
|
238
|
+
(`--ui-toast-<severity>-border-color` / `-left-line`) are decorative — they
|
|
239
|
+
reinforce the status icon rather than carrying meaning on their own — so they are
|
|
240
|
+
not held to text contrast. Action buttons and the dismiss control inherit the
|
|
241
|
+
contrast-checked `--ui-button-*` / `--ui-button-icon-*` tiers.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# ToggleGroup
|
|
2
|
+
|
|
3
|
+
> A set of pressable toggle buttons (single or multiple selection) — plus a standalone Toggle. Built on Base UI's Toggle / ToggleGroup. Ported from the legacy shadcn-uikit `toggle-group`.
|
|
4
|
+
|
|
5
|
+
**Status:** draft · **Category:** forms · **Since:** v0.51.0
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Toggle, ToggleGroup, ToggleGroupItem } from '@acronis-platform/ui-react'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick example
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<ToggleGroup defaultValue={['grid']}>
|
|
17
|
+
<ToggleGroupItem value="grid" aria-label="Grid">▦</ToggleGroupItem>
|
|
18
|
+
<ToggleGroupItem value="list" aria-label="List">≣</ToggleGroupItem>
|
|
19
|
+
</ToggleGroup>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A set of pressable toggle buttons (single or multiple selection), plus a
|
|
23
|
+
standalone `Toggle`. Built on Base UI's Toggle / ToggleGroup.
|
|
24
|
+
|
|
25
|
+
> Design-pending v1, ported from the legacy shadcn-uikit `toggle-group`.
|
|
26
|
+
|
|
27
|
+
## When to use
|
|
28
|
+
|
|
29
|
+
- A small set of mutually-exclusive (or multi-select) view/format options —
|
|
30
|
+
view-mode switchers, text-formatting toolbars.
|
|
31
|
+
|
|
32
|
+
## When not to use
|
|
33
|
+
|
|
34
|
+
- Choosing one option from a list / form value — use `RadioGroup` or `InputSelect`.
|
|
35
|
+
- A single on/off setting with a label — use `Switch`.
|
|
36
|
+
|
|
37
|
+
## Parts
|
|
38
|
+
|
|
39
|
+
| Export | Purpose |
|
|
40
|
+
| ----------------- | -------------------------------------------------- |
|
|
41
|
+
| `ToggleGroup` | The group — tracks the pressed value(s). |
|
|
42
|
+
| `ToggleGroupItem` | A pressable button in the group (needs a `value`). |
|
|
43
|
+
| `Toggle` | A standalone pressable button (same styling). |
|
|
44
|
+
|
|
45
|
+
## Example
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { ToggleGroup, ToggleGroupItem } from '@acronis-platform/ui-react';
|
|
49
|
+
|
|
50
|
+
<ToggleGroup defaultValue={['grid']} aria-label="View mode">
|
|
51
|
+
<ToggleGroupItem value="grid" aria-label="Grid view">
|
|
52
|
+
<GridIcon />
|
|
53
|
+
</ToggleGroupItem>
|
|
54
|
+
<ToggleGroupItem value="list" aria-label="List view">
|
|
55
|
+
<ListIcon />
|
|
56
|
+
</ToggleGroupItem>
|
|
57
|
+
</ToggleGroup>;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Props
|
|
61
|
+
|
|
62
|
+
| Prop | Type | Default | Required | Description |
|
|
63
|
+
|------|------|---------|----------|-------------|
|
|
64
|
+
| `value` | `string[]` | — | no | The pressed item value(s) (controlled, on ToggleGroup). |
|
|
65
|
+
| `defaultValue` | `string[]` | — | no | The initially pressed value(s) (uncontrolled, on ToggleGroup). |
|
|
66
|
+
| `pressed` | `boolean` | — | no | Whether a standalone Toggle is pressed (controlled). |
|
|
67
|
+
| `defaultPressed` | `boolean` | `false` | no | Whether a standalone Toggle is initially pressed (uncontrolled). |
|
|
68
|
+
| `disabled` | `boolean` | `false` | no | Disables the group / toggle. |
|
|
69
|
+
|
|
70
|
+
## Events
|
|
71
|
+
|
|
72
|
+
| Event | Payload | Description |
|
|
73
|
+
|-------|---------|-------------|
|
|
74
|
+
| `onValueChange` | `(value: string[], eventDetails) => void` | Fired when the pressed set changes (ToggleGroup). |
|
|
75
|
+
| `onPressedChange` | `(pressed: boolean, eventDetails) => void` | Fired when a standalone Toggle's pressed state changes. |
|
|
76
|
+
|
|
77
|
+
## Behavior
|
|
78
|
+
|
|
79
|
+
```gherkin
|
|
80
|
+
Scenario: Press an item
|
|
81
|
+
Given a ToggleGroup with items
|
|
82
|
+
When the user clicks an item
|
|
83
|
+
Then it becomes pressed (tinted) and its value is added to the group value
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```gherkin
|
|
87
|
+
Scenario: Single vs. multiple
|
|
88
|
+
Given a single-selection group
|
|
89
|
+
When the user presses another item
|
|
90
|
+
Then the previously pressed item is released
|
|
91
|
+
(a multiple-selection group keeps both pressed)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```gherkin
|
|
95
|
+
Scenario: Standalone toggle
|
|
96
|
+
Given a standalone Toggle
|
|
97
|
+
When the user clicks it
|
|
98
|
+
Then it flips its pressed state (onPressedChange fires)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```gherkin
|
|
102
|
+
Scenario: Disabled
|
|
103
|
+
Given disabled is set
|
|
104
|
+
Then the toggle(s) cannot be pressed or focused
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Accessibility
|
|
108
|
+
|
|
109
|
+
- Built on Base UI's Toggle / ToggleGroup: each toggle is a `<button>` with
|
|
110
|
+
`aria-pressed`, operable with Enter/Space; the group supports arrow-key roving
|
|
111
|
+
focus. Give each toggle an accessible name (`aria-label` for icon-only toggles)
|
|
112
|
+
and the group an `aria-label`.
|
|
113
|
+
- Don't rely on the pressed tint alone — for icon-only toggles the `aria-pressed`
|
|
114
|
+
state conveys selection to assistive tech.
|
|
115
|
+
|
|
116
|
+
## Contrast
|
|
117
|
+
|
|
118
|
+
Idle is transparent (text/icon use the foreground); hover uses
|
|
119
|
+
`--ui-background-surface-hover` and pressed uses `--ui-background-surface-active`,
|
|
120
|
+
both meeting contrast against the page in light and dark; the focus ring uses
|
|
121
|
+
`--ui-focus-primary`.
|