@stealthscale/component-feedback 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +514 -37
- package/dist/cyclonedx/bom.json +1 -1
- package/dist/index.d.ts +716 -60
- package/dist/index.js +1741 -92
- package/dist/recipe-CX1gSsN7.js +1290 -0
- package/dist/theme.d.ts +3 -4
- package/dist/theme.js +14 -7
- package/package.json +36 -23
- package/dist/recipe-0Xo90j3-.js +0 -162
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @stealthscale/component-feedback
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
extending the recipe. The preset under `./theme` registers the
|
|
6
|
-
compiler.
|
|
3
|
+
React components that report state: `Alert`, `Skeleton`, `SkeletonText`, `Spinner`, `Loader`,
|
|
4
|
+
`Progress`, `ProgressCircle`, `Meter`, `EmptyState` and `Toast`. Each component renders through a
|
|
5
|
+
recipe, so a theme restyles it by extending the recipe. The preset under `./theme` registers the
|
|
6
|
+
recipes with an application's compiler.
|
|
7
7
|
|
|
8
|
-
Every value a theme can change
|
|
9
|
-
|
|
8
|
+
Every value a theme can change is a recipe axis, and a caller sets it as a prop. A caller changes
|
|
9
|
+
the rendered element with `as`.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -14,15 +14,83 @@ prop and writes no style. A caller changes the element a component draws with `a
|
|
|
14
14
|
pnpm add @stealthscale/component-feedback
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
The package peers on `react` and `@stealthscale/
|
|
18
|
-
|
|
17
|
+
The package peers on `react`, `@stealthscale/theme` and `@stealthscale/hooks`. It depends on
|
|
18
|
+
`@zag-js/progress`, which tracks the value of a progress bar and a meter, and `@zag-js/toast`, which
|
|
19
|
+
runs the toasts. Add the preset under `./theme` to the presets the application's compiler installs.
|
|
20
|
+
|
|
21
|
+
## Alert
|
|
22
|
+
|
|
23
|
+
Renders a notice the reader needs to act on or know about. Compose it as `Alert.Root` around an
|
|
24
|
+
icon, the text, optional trailing controls and a close trigger.
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { Alert } from "@stealthscale/component-feedback";
|
|
28
|
+
|
|
29
|
+
<Alert.Root live="assertive" status="error">
|
|
30
|
+
<Alert.Indicator>
|
|
31
|
+
<TriangleAlertIcon />
|
|
32
|
+
</Alert.Indicator>
|
|
33
|
+
<Alert.Content>
|
|
34
|
+
<Alert.Title>Payment failed</Alert.Title>
|
|
35
|
+
<Alert.Description>The card was declined.</Alert.Description>
|
|
36
|
+
</Alert.Content>
|
|
37
|
+
<Alert.CloseTrigger label="Dismiss payment failed" onClick={dismiss}>
|
|
38
|
+
<XIcon />
|
|
39
|
+
</Alert.CloseTrigger>
|
|
40
|
+
</Alert.Root>;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Axis | Values | Default |
|
|
44
|
+
| --------- | ------------------------------------------------ | --------- |
|
|
45
|
+
| `status` | `info`, `success`, `warning`, `error`, `neutral` | `info` |
|
|
46
|
+
| `variant` | `solid`, `subtle`, `surface`, `outline`, `plain` | `subtle` |
|
|
47
|
+
| `size` | `sm`, `md`, `lg` | `md` |
|
|
48
|
+
| `layout` | `stacked`, `inline` | `stacked` |
|
|
49
|
+
| `radius` | `l1`, `l2`, `l3`, `full` | `l3` |
|
|
50
|
+
| `edge` | `top`, `bottom`, `end` | none |
|
|
51
|
+
| `motion` | `fade`, `rise`, `reveal` | none |
|
|
52
|
+
|
|
53
|
+
| Part | Element | What it renders |
|
|
54
|
+
| -------------- | -------- | ------------------------------------------------- |
|
|
55
|
+
| `Root` | `div` | The container, with the role of its `live` level |
|
|
56
|
+
| `Indicator` | `div` | The icon, sized by the recipe, `aria-hidden` |
|
|
57
|
+
| `Content` | `div` | The title and the description |
|
|
58
|
+
| `Title` | `span` | The headline, which states the severity |
|
|
59
|
+
| `Description` | `span` | The supporting text |
|
|
60
|
+
| `Aside` | `div` | Trailing controls such as a retry button |
|
|
61
|
+
| `CloseTrigger` | `button` | The dismiss control, named by `label` (`Dismiss`) |
|
|
62
|
+
|
|
63
|
+
`live` sets the role that announces the alert:
|
|
64
|
+
|
|
65
|
+
| `live` | Role | Use it for |
|
|
66
|
+
| ----------- | -------- | ---------------------------------------- |
|
|
67
|
+
| `assertive` | `alert` | An alert raised by a user action |
|
|
68
|
+
| `polite` | `status` | Progress that can wait for a pause |
|
|
69
|
+
| `off` | none | A notice present from the initial render |
|
|
70
|
+
|
|
71
|
+
The default is `polite`. Keep a live region mounted and empty and write into it, because screen
|
|
72
|
+
readers do not reliably announce a region mounted with its text.
|
|
73
|
+
|
|
74
|
+
State the severity in the title. The indicator is `aria-hidden`, and an alert that conveys its
|
|
75
|
+
status through color and icon alone fails WCAG 1.4.1.
|
|
76
|
+
|
|
77
|
+
`Alert.CloseTrigger` takes the alert's ink, so it matches every look and status. On the solid look
|
|
78
|
+
it hovers to a tint of the contrast ink and draws its focus ring in the contrast ink. The box is
|
|
79
|
+
`max(24px, 1.5em)` square, and its glyph is aligned with the padding edge. Pass a `label` that
|
|
80
|
+
includes the notice's title. Label each control in `Alert.Aside` with its target, such as
|
|
81
|
+
`Retry the payment`.
|
|
82
|
+
|
|
83
|
+
`edge` draws a border in the palette's `solid` along one edge, so the rule follows the rounded
|
|
84
|
+
corners. `end` is the right edge in a left-to-right document and the left edge in a right-to-left
|
|
85
|
+
one.
|
|
86
|
+
|
|
87
|
+
`Alert.Title` is a `span`. Pass `as="h2"` for a notice that stays on the page and belongs in the
|
|
88
|
+
outline. Under forced colors the root draws a hairline `CanvasText` outline.
|
|
19
89
|
|
|
20
90
|
## Skeleton
|
|
21
91
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
in for without anybody stating a width. While it waits it hides everything inside it. Once the
|
|
25
|
-
content arrives the stand-in fades out and the content is shown.
|
|
92
|
+
Renders a placeholder over content that is still loading. Wrap the content in the skeleton, so the
|
|
93
|
+
placeholder takes the content's size and the layout does not shift when `loading` turns off.
|
|
26
94
|
|
|
27
95
|
```tsx
|
|
28
96
|
import { Skeleton } from "@stealthscale/component-feedback";
|
|
@@ -39,15 +107,14 @@ import { Skeleton } from "@stealthscale/component-feedback";
|
|
|
39
107
|
| `motion` | `none`, `pulse`, `shimmer` | `pulse` |
|
|
40
108
|
| `radius` | `l1`, `l2`, `l3`, `full` | `l2` |
|
|
41
109
|
|
|
42
|
-
The element
|
|
43
|
-
|
|
44
|
-
|
|
110
|
+
The element has no role. Set `aria-busy` on the region that is loading. The animations stop under
|
|
111
|
+
reduced motion. Under forced colors a loading skeleton draws a hairline `GrayText` outline, because
|
|
112
|
+
the browser replaces its fill.
|
|
45
113
|
|
|
46
114
|
## SkeletonText
|
|
47
115
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
the size of that text, so the page does not jump when the real words replace it.
|
|
116
|
+
Renders a placeholder paragraph with one bar per line. Each bar is one line height tall and the bars
|
|
117
|
+
are half a line height apart, both in `lh` units of the surrounding text.
|
|
51
118
|
|
|
52
119
|
```tsx
|
|
53
120
|
import { SkeletonText } from "@stealthscale/component-feedback";
|
|
@@ -56,22 +123,364 @@ import { SkeletonText } from "@stealthscale/component-feedback";
|
|
|
56
123
|
<SkeletonText lines={5} motion="shimmer" />;
|
|
57
124
|
```
|
|
58
125
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
126
|
+
With two or more lines the last bar is 80% wide. `lines` defaults to 3 and renders at least one bar.
|
|
127
|
+
Render the placeholder while the text loads and the text once it arrives. The component has no
|
|
128
|
+
`loading` prop.
|
|
129
|
+
|
|
130
|
+
## Spinner
|
|
131
|
+
|
|
132
|
+
Shows that work is running when there is no progress to report. Where the work reports how far along
|
|
133
|
+
it is, show `Progress` instead.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { Button } from "@stealthscale/component-actions";
|
|
137
|
+
import { Spinner } from "@stealthscale/component-feedback";
|
|
138
|
+
|
|
139
|
+
<Spinner />;
|
|
140
|
+
<Spinner palette="primary" size="lg" stroke="heavy" track />;
|
|
141
|
+
<Spinner effect="glow" palette="accent" />;
|
|
142
|
+
<Button disabled>
|
|
143
|
+
<Spinner size="inherit" />
|
|
144
|
+
Saving
|
|
145
|
+
</Button>;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Axis | Values | Default |
|
|
149
|
+
| --------- | ----------------------------------------------------------------------------- | ----------- |
|
|
150
|
+
| `size` | `inherit`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `4xl` | `md` |
|
|
151
|
+
| `palette` | `current`, `primary`, `secondary`, `accent`, `neutral`, and the four statuses | `current` |
|
|
152
|
+
| `stroke` | `hairline`, `control`, `indicator`, `heavy` | `indicator` |
|
|
153
|
+
| `track` | `true` | off |
|
|
154
|
+
| `effect` | `glow`, `pulse` | none |
|
|
155
|
+
|
|
156
|
+
The element is an empty `span` with no role, so a screen reader skips it. Write the words the wait
|
|
157
|
+
needs beside it, and set `aria-busy` on the region that is waiting.
|
|
158
|
+
|
|
159
|
+
`palette` draws the arc in the palette's `solid` color. The theme engine keeps every `solid` at 3:1
|
|
160
|
+
or more against the page. `current` draws it in the surrounding ink, which is the label ink inside a
|
|
161
|
+
button. `inherit` sizes the spinner to the surrounding font size, so it keeps a line of text or a
|
|
162
|
+
button at its height.
|
|
163
|
+
|
|
164
|
+
`stroke` reads the theme's semantic widths, so a theme with heavier controls draws a heavier ring.
|
|
165
|
+
`heavy` is the 4px step, heavier than the `indicator` stroke of every published theme.
|
|
166
|
+
|
|
167
|
+
The arc turns on the theme's `spin` animation style, so it stops when the reader prefers reduced
|
|
168
|
+
motion. The words beside it still say what is happening. In forced colors mode the arc is drawn in
|
|
169
|
+
`CanvasText` and the track in `GrayText`.
|
|
170
|
+
|
|
171
|
+
## Loader
|
|
172
|
+
|
|
173
|
+
Shows that something is loading, beside words or over the content it replaces. `LoaderOverlay`
|
|
174
|
+
covers a positioned container while its content loads.
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { Button } from "@stealthscale/component-actions";
|
|
178
|
+
import { Loader, LoaderOverlay } from "@stealthscale/component-feedback";
|
|
179
|
+
import { Card } from "@stealthscale/component-surfaces";
|
|
180
|
+
|
|
181
|
+
<Loader text="Matching" />;
|
|
182
|
+
<Loader placement="end" palette="primary" text="Matching" />;
|
|
183
|
+
<Button disabled={saving}>
|
|
184
|
+
<Loader label="Saving changes" loading={saving}>
|
|
185
|
+
Save changes
|
|
186
|
+
</Loader>
|
|
187
|
+
</Button>;
|
|
188
|
+
<Card.Root>
|
|
189
|
+
…
|
|
190
|
+
<LoaderOverlay scrim="glass">
|
|
191
|
+
<Loader text="Matching" />
|
|
192
|
+
</LoaderOverlay>
|
|
193
|
+
</Card.Root>;
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| Axis | Values | Default |
|
|
197
|
+
| --------- | ------------------------------------------------------------------ | --------- |
|
|
198
|
+
| `palette` | `primary`, `secondary`, `accent`, `neutral`, and the four statuses | inherited |
|
|
199
|
+
| `scrim` | `veil`, `glass`, `none`, on `LoaderOverlay` | `veil` |
|
|
200
|
+
|
|
201
|
+
| Prop | Type | Default | Effect |
|
|
202
|
+
| ----------- | -------------- | --------- | -------------------------------------------------- |
|
|
203
|
+
| `loading` | `boolean` | `true` | With `false`, the children render unchanged |
|
|
204
|
+
| `text` | `ReactNode` | none | Renders the words beside the spinner, not children |
|
|
205
|
+
| `placement` | `start`, `end` | `start` | The side of the words the spinner is rendered on |
|
|
206
|
+
| `label` | `string` | `Loading` | Read by screen readers in place of hidden children |
|
|
207
|
+
| `spinner` | `ReactNode` | a spinner | Replaces the default spinner |
|
|
208
|
+
|
|
209
|
+
With `text`, the loader renders the spinner and the words in one inline row, so they stay on one
|
|
210
|
+
line in any container. Without `text`, the loader hides its children with `visibility: hidden` and
|
|
211
|
+
centres the spinner over them in the same grid cell. The children keep their box, so a button keeps
|
|
212
|
+
its width and a row does not reflow when the content returns. The loader needs no positioned
|
|
213
|
+
ancestor.
|
|
214
|
+
|
|
215
|
+
Hidden children leave the accessibility tree, so the loader renders `label` in their place for
|
|
216
|
+
screen readers. Set `aria-busy` on the region that is loading.
|
|
217
|
+
|
|
218
|
+
The default spinner is at `inherit` and `current`, so it takes the size and the ink of the
|
|
219
|
+
surrounding text. `palette` draws it in the palette's `solid` color instead. Pass `spinner` to
|
|
220
|
+
change its stroke, track or effect.
|
|
221
|
+
|
|
222
|
+
`LoaderOverlay` is absolutely positioned at `inset: 0` and takes its container's corner radius, so
|
|
223
|
+
the container must be positioned, as `Card.Root` is. `veil` fills it with the panel at 80% opacity
|
|
224
|
+
and `glass` blurs the content behind it.
|
|
225
|
+
|
|
226
|
+
## Progress
|
|
227
|
+
|
|
228
|
+
Reports how far a task has come. The root runs the Zag progress machine. The track is the progress
|
|
229
|
+
bar a screen reader reports, with `role="progressbar"`, the value range and the formatted value.
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
import { Progress } from "@stealthscale/component-feedback";
|
|
233
|
+
|
|
234
|
+
<Progress.Root value={62}>
|
|
235
|
+
<Progress.Label>Reconciling payouts</Progress.Label>
|
|
236
|
+
<Progress.ValueText />
|
|
237
|
+
<Progress.Track>
|
|
238
|
+
<Progress.Range />
|
|
239
|
+
</Progress.Track>
|
|
240
|
+
</Progress.Root>;
|
|
241
|
+
<Progress.Root value={null}>
|
|
242
|
+
<Progress.Label>Preparing the export</Progress.Label>
|
|
243
|
+
<Progress.Track>
|
|
244
|
+
<Progress.Range />
|
|
245
|
+
</Progress.Track>
|
|
246
|
+
</Progress.Root>;
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
| Part | Element | What it renders |
|
|
250
|
+
| ----------- | ------- | --------------------------------------------------------------------------- |
|
|
251
|
+
| `Root` | `div` | A grid of the label, the value and the track, with the machine and variants |
|
|
252
|
+
| `Label` | `span` | The words that name the bar |
|
|
253
|
+
| `ValueText` | `span` | Its children, or the formatted value, `62%` by default |
|
|
254
|
+
| `Track` | `div` | The progress bar, with the machine's role and value range |
|
|
255
|
+
| `Range` | `div` | The fill, as wide as the value's share of the track |
|
|
256
|
+
| `Segment` | `span` | One part of the fill, as wide as its value's share of the range |
|
|
257
|
+
| `Marker` | `div` | A tick across the track at a value, such as a target |
|
|
258
|
+
|
|
259
|
+
| Axis | Values | Default |
|
|
260
|
+
| ---------- | ------------------------------------------------------------------ | --------- |
|
|
261
|
+
| `size` | `xs`, `sm`, `md`, `lg`, `xl` | `md` |
|
|
262
|
+
| `variant` | `subtle`, `outline` | `outline` |
|
|
263
|
+
| `shape` | `square`, `rounded`, `full` | `full` |
|
|
264
|
+
| `palette` | `primary`, `secondary`, `accent`, `neutral`, and the four statuses | `primary` |
|
|
265
|
+
| `layout` | `stacked`, `inline` | `stacked` |
|
|
266
|
+
| `striped` | `true` | off |
|
|
267
|
+
| `animated` | `true` | off |
|
|
268
|
+
| `effect` | `glow`, `pulse` | none |
|
|
269
|
+
|
|
270
|
+
`value` sets the value between `min` and `max`, 0 and 100 by default. Without `value` or
|
|
271
|
+
`defaultValue` the machine starts at the midpoint. `null` is a value the machine does not know. The
|
|
272
|
+
track then leaves out `aria-valuenow`, the value text is empty, and a segment of the range crosses
|
|
273
|
+
the track once every 1.2 seconds. `formatOptions` and `locale` format the value with
|
|
274
|
+
`Intl.NumberFormat`, as a percentage by default.
|
|
275
|
+
|
|
276
|
+
The size is the track's thickness: 4, 6, 8, 12 and 16px. The label and the value keep the label
|
|
277
|
+
style at every size. `inline` puts the label, the track and the value on one row, and the track
|
|
278
|
+
takes the room the words leave. `animated` moves the stripes at 40px a second, adding them where
|
|
279
|
+
`striped` is not set. Both motions stop for a reader who asked for less motion, and the segment then
|
|
280
|
+
spreads over the whole track.
|
|
281
|
+
|
|
282
|
+
### Markers and segments
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
<Progress.Root max={1200} value={864}>
|
|
286
|
+
<Progress.Label>Test run</Progress.Label>
|
|
287
|
+
<Progress.ValueText>864 of 1,200</Progress.ValueText>
|
|
288
|
+
<Progress.Track aria-valuetext="864 of 1,200">
|
|
289
|
+
<Progress.Segment color="success" value={812} />
|
|
290
|
+
<Progress.Segment color="error" value={12} />
|
|
291
|
+
<Progress.Segment color="neutral" value={40} />
|
|
292
|
+
<Progress.Marker value={900} />
|
|
293
|
+
</Progress.Track>
|
|
294
|
+
</Progress.Root>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
- `Marker` places a tick at its `value`'s share of the range, from `min` to `max`. A value past
|
|
298
|
+
either end is placed at that end, inside the track.
|
|
299
|
+
- The tick is as wide as the theme's `indicator` border, 2px unless the theme states another, and
|
|
300
|
+
150% of the track's thickness. It is painted in `fg` with a hairline edge in `bg.panel`, so the
|
|
301
|
+
tick or its edge contrasts with the range and with the track.
|
|
302
|
+
- `Segment` fills the track from its start in the order the segments render, in place of `Range`.
|
|
303
|
+
Each segment is as wide as its `value`'s share of `max - min`.
|
|
304
|
+
- The first eight segments take `series.1` to `series.8` in order, the colors a chart gives its
|
|
305
|
+
series, and the ninth starts again at `series.1`. `color` picks one series color, or a palette
|
|
306
|
+
whose `solid` fills the segment.
|
|
307
|
+
- The first segment takes the track's start corners and the last segment takes its end corners.
|
|
308
|
+
Segments that add up to more than the range shrink in proportion.
|
|
309
|
+
- The track reports the root's `value`, so pass the segments' sum.
|
|
310
|
+
- Label each color in a key beside the bar. Compose the key from the data package's `ColorSwatch`,
|
|
311
|
+
with `var(--colors-series-1)` for a series color, or from its `Status` for a palette.
|
|
312
|
+
|
|
313
|
+
### Accessibility
|
|
314
|
+
|
|
315
|
+
- A rendered `Progress.Label` names the track through `aria-labelledby`. A bar without a label takes
|
|
316
|
+
`aria-label` on `Progress.Track`.
|
|
317
|
+
- The marker and the segments are hidden from assistive technology. Write the marked value in
|
|
318
|
+
`ValueText` and in the track's `aria-valuetext`, and list the parts in a key.
|
|
319
|
+
- The track states the formatted value as `aria-valuetext`. For a count in the caller's units, pass
|
|
320
|
+
`aria-valuetext` to the track and the same words to `Progress.ValueText`.
|
|
321
|
+
- `Progress.ValueText` is not a live region, so a screen reader does not announce every change. Pass
|
|
322
|
+
`aria-live="polite"` to announce them.
|
|
323
|
+
- Under forced colors the range fills with `Highlight`, and the track has a hairline `CanvasText`
|
|
324
|
+
outline. The marker is `CanvasText` with a `Canvas` edge. The segments keep their colors, and a
|
|
325
|
+
`Canvas` hairline separates each segment from the one before it.
|
|
326
|
+
|
|
327
|
+
### Machine options
|
|
328
|
+
|
|
329
|
+
The root takes `value`, `defaultValue`, `onValueChange`, `min`, `max`, `formatOptions`, `locale`,
|
|
330
|
+
`id`, `ids` and `dir`. It does not take `orientation`, because the recipe styles a horizontal track
|
|
331
|
+
only, or `translations`, because the words are the caller's. The machine's `view` part is not
|
|
332
|
+
offered. Its circle parts are `ProgressCircle`.
|
|
333
|
+
|
|
334
|
+
## ProgressCircle
|
|
335
|
+
|
|
336
|
+
Reports how far a task has come as a ring, with the value in its middle. It runs the same machine as
|
|
337
|
+
`Progress`. The ring is the progress bar a screen reader reports.
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
import { ProgressCircle } from "@stealthscale/component-feedback";
|
|
341
|
+
|
|
342
|
+
<ProgressCircle.Root size="lg" value={62}>
|
|
343
|
+
<ProgressCircle.Circle>
|
|
344
|
+
<ProgressCircle.Track />
|
|
345
|
+
<ProgressCircle.Range />
|
|
346
|
+
</ProgressCircle.Circle>
|
|
347
|
+
<ProgressCircle.ValueText />
|
|
348
|
+
<ProgressCircle.Label>Uploading the report</ProgressCircle.Label>
|
|
349
|
+
</ProgressCircle.Root>;
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
| Part | Element | What it renders |
|
|
353
|
+
| ----------- | -------- | ---------------------------------------------------------- |
|
|
354
|
+
| `Root` | `div` | A grid of the ring, the value in its middle and the label |
|
|
355
|
+
| `Circle` | `svg` | The progress bar, with the machine's role and value range |
|
|
356
|
+
| `Track` | `circle` | The whole ring |
|
|
357
|
+
| `Range` | `circle` | The part of the ring from the top to the value |
|
|
358
|
+
| `ValueText` | `span` | Its children, or the formatted value, in the ring's middle |
|
|
359
|
+
| `Label` | `span` | The words that name the ring, under it or beside it |
|
|
360
|
+
|
|
361
|
+
| Axis | Values | Default |
|
|
362
|
+
| --------- | ------------------------------------------------------------------ | --------- |
|
|
363
|
+
| `size` | `xs`, `sm`, `md`, `lg`, `xl` | `md` |
|
|
364
|
+
| `variant` | `subtle`, `outline` | `outline` |
|
|
365
|
+
| `shape` | `square`, `full` | `full` |
|
|
366
|
+
| `palette` | `primary`, `secondary`, `accent`, `neutral`, and the four statuses | `primary` |
|
|
367
|
+
| `layout` | `stacked`, `inline` | `stacked` |
|
|
368
|
+
|
|
369
|
+
- The ring is 24, 32, 40, 48 and 64px wide, and 4, 4, 4, 6 and 8px thick, from `xs` to `xl`.
|
|
370
|
+
- The value text shows from `md` up, in the largest text style whose "100%" fits inside the ring.
|
|
371
|
+
`xs` and `sm` hide it. The ring's `aria-valuetext` states the value at every size.
|
|
372
|
+
- `stacked` puts the label under the ring. `inline` puts it beside the ring.
|
|
373
|
+
- The root is as wide as its content. A flex column or a grid does not stretch it.
|
|
374
|
+
- `shape` rounds or squares the ends of the range.
|
|
375
|
+
- `outline` strokes the track in the muted ground. `subtle` strokes it in the palette's muted role.
|
|
376
|
+
- `null` is a value the machine does not know. The ring then leaves out `aria-valuenow`. A quarter
|
|
377
|
+
arc turns once every 1.2 seconds.
|
|
378
|
+
- For a reader who asked for less motion, the ring stops, and the range is a dashed line around the
|
|
379
|
+
whole ring.
|
|
380
|
+
- The root takes the same machine options as `Progress.Root`.
|
|
381
|
+
|
|
382
|
+
### Accessibility
|
|
383
|
+
|
|
384
|
+
- A rendered `ProgressCircle.Label` names the ring through `aria-labelledby`. A ring without a label
|
|
385
|
+
takes `aria-label` on `ProgressCircle.Circle`.
|
|
386
|
+
- The ring states the formatted value as `aria-valuetext`. For a count in the caller's units, pass
|
|
387
|
+
`aria-valuetext` to `ProgressCircle.Circle`. Pass the short form, such as `3/5`, as the children
|
|
388
|
+
of `ProgressCircle.ValueText`.
|
|
389
|
+
- `ProgressCircle.ValueText` is not a live region. Pass `aria-live="polite"` to announce each
|
|
390
|
+
change.
|
|
391
|
+
- Under forced colors the range strokes in `Highlight`. The track is a hairline `CanvasText` circle
|
|
392
|
+
at the ring's outer edge.
|
|
393
|
+
|
|
394
|
+
## Meter
|
|
395
|
+
|
|
396
|
+
Shows a measurement within a known range, such as the storage an account uses. A meter is the
|
|
397
|
+
progress bar under a root of its own: `Meter.Root` renders `Progress.Root` with the track in the
|
|
398
|
+
`meter` role, and every other part is the progress bar's. The track is the meter a screen reader
|
|
399
|
+
reports, with the value range and the formatted value. A meter's value changes only when its caller
|
|
400
|
+
measures again. A progress bar reports a task that is still running.
|
|
401
|
+
|
|
402
|
+
```tsx
|
|
403
|
+
import { Meter } from "@stealthscale/component-feedback";
|
|
404
|
+
|
|
405
|
+
<Meter.Root formatOptions={{ style: "unit", unit: "gigabyte" }} max={50} value={38.2}>
|
|
406
|
+
<Meter.Label>Storage used</Meter.Label>
|
|
407
|
+
<Meter.ValueText />
|
|
408
|
+
<Meter.Track>
|
|
409
|
+
<Meter.Range />
|
|
410
|
+
</Meter.Track>
|
|
411
|
+
</Meter.Root>;
|
|
412
|
+
<Meter.Root max={268} palette="success" value={268}>
|
|
413
|
+
<Meter.Label>Deals closed this quarter</Meter.Label>
|
|
414
|
+
<Meter.ValueText>268 / 250</Meter.ValueText>
|
|
415
|
+
<Meter.Track aria-valuetext="268, target 250">
|
|
416
|
+
<Meter.Range />
|
|
417
|
+
<Meter.Marker value={250} />
|
|
418
|
+
</Meter.Track>
|
|
419
|
+
</Meter.Root>;
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
| Part | Element | What it renders |
|
|
423
|
+
| ----------- | ------- | --------------------------------------------------------------- |
|
|
424
|
+
| `Root` | `div` | The progress bar's root, with the track in the `meter` role |
|
|
425
|
+
| `Label` | `span` | The words that name the meter |
|
|
426
|
+
| `ValueText` | `span` | Its children, or the formatted value, `62%` by default |
|
|
427
|
+
| `Track` | `div` | The meter, with `role="meter"` and the value range |
|
|
428
|
+
| `Range` | `div` | The fill, as wide as the value's share of the track |
|
|
429
|
+
| `Segment` | `span` | One part of the fill, such as the photos in a disk's used space |
|
|
430
|
+
| `Marker` | `div` | A tick across the track at a value, such as a target |
|
|
431
|
+
|
|
432
|
+
| Axis | Values | Default |
|
|
433
|
+
| --------- | ------------------------------------------------------------------ | --------- |
|
|
434
|
+
| `size` | `xs`, `sm`, `md`, `lg`, `xl` | `md` |
|
|
435
|
+
| `variant` | `subtle`, `outline` | `outline` |
|
|
436
|
+
| `shape` | `square`, `rounded`, `full` | `full` |
|
|
437
|
+
| `palette` | `primary`, `secondary`, `accent`, `neutral`, and the four statuses | `primary` |
|
|
438
|
+
| `layout` | `stacked`, `inline` | `stacked` |
|
|
439
|
+
|
|
440
|
+
- `value` is a required number between `min` and `max`, 0 and 100 by default, and never `null`.
|
|
441
|
+
`formatOptions` and `locale` format it with `Intl.NumberFormat`, as a percentage unless
|
|
442
|
+
`formatOptions` states another style.
|
|
443
|
+
- The meter renders the progress recipe, so its elements take the progress bar's classes, such as
|
|
444
|
+
`progress__track`, and the tracks of a meter and a progress bar in one column start and end at the
|
|
445
|
+
same edges.
|
|
446
|
+
- The root does not take `defaultValue` or `onValueChange`, because only the caller changes a
|
|
447
|
+
meter's value.
|
|
448
|
+
- The root does not take `striped`, `animated` or `effect`. Those motions mark work under way, and a
|
|
449
|
+
meter reports a measurement.
|
|
450
|
+
- The caller picks `palette` from its own threshold, because a share is neither good nor bad by
|
|
451
|
+
itself: 90% is good news for a download and bad news for a disk.
|
|
452
|
+
- For a measure against a target, set `max` to the larger of the two and mark the target with
|
|
453
|
+
`Meter.Marker`. A measure past its target then fills the track.
|
|
454
|
+
- `Meter.Segment` and `Meter.Marker` work as they do on the progress bar.
|
|
455
|
+
|
|
456
|
+
### Accessibility
|
|
457
|
+
|
|
458
|
+
- A rendered `Meter.Label` names the track through `aria-labelledby`. A meter without a label takes
|
|
459
|
+
`aria-label` on `Meter.Track`.
|
|
460
|
+
- The track states the formatted value as `aria-valuetext`. For a value in words, such as a
|
|
461
|
+
password's strength or a target, pass `aria-valuetext` to the track and the same words to
|
|
462
|
+
`Meter.ValueText`.
|
|
463
|
+
- `Meter.ValueText` is not a live region.
|
|
464
|
+
- A meter split into the parts of a whole sets `value` to `max` and reports 100%. Pass the parts as
|
|
465
|
+
its `aria-valuetext`, joined with `Intl.ListFormat`.
|
|
466
|
+
|
|
467
|
+
### Not offered
|
|
468
|
+
|
|
469
|
+
- Thresholds such as the `low`, `high` and `optimum` of the HTML `meter` element. The caller picks
|
|
470
|
+
the palette.
|
|
471
|
+
- An orientation.
|
|
63
472
|
|
|
64
473
|
## EmptyState
|
|
65
474
|
|
|
66
|
-
|
|
67
|
-
|
|
475
|
+
Renders the panel a surface shows when it has no content. Compose it as `EmptyState.Root` around an
|
|
476
|
+
`EmptyState.Content` column of an icon, a title and a description.
|
|
68
477
|
|
|
69
478
|
```tsx
|
|
70
479
|
import { EmptyState } from "@stealthscale/component-feedback";
|
|
71
480
|
|
|
72
481
|
<EmptyState.Root size="lg">
|
|
73
482
|
<EmptyState.Content>
|
|
74
|
-
<EmptyState.Indicator
|
|
483
|
+
<EmptyState.Indicator>
|
|
75
484
|
<InboxIcon />
|
|
76
485
|
</EmptyState.Indicator>
|
|
77
486
|
<EmptyState.Title>No invoices yet</EmptyState.Title>
|
|
@@ -80,21 +489,89 @@ import { EmptyState } from "@stealthscale/component-feedback";
|
|
|
80
489
|
</EmptyState.Root>;
|
|
81
490
|
```
|
|
82
491
|
|
|
83
|
-
| Axis | Values
|
|
84
|
-
| ------ |
|
|
85
|
-
| `size` | `
|
|
492
|
+
| Axis | Values | Default |
|
|
493
|
+
| ------ | ---------------- | ------- |
|
|
494
|
+
| `size` | `sm`, `md`, `lg` | `md` |
|
|
495
|
+
|
|
496
|
+
`size` sets four values together:
|
|
86
497
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
498
|
+
| Size | Icon | Title | Gap | Inset |
|
|
499
|
+
| ---- | ---- | ------------ | ---- | ----- |
|
|
500
|
+
| `sm` | 32px | `heading.xs` | 8px | 24px |
|
|
501
|
+
| `md` | 40px | `heading.sm` | 12px | 32px |
|
|
502
|
+
| `lg` | 50px | `heading.md` | 16px | 40px |
|
|
503
|
+
|
|
504
|
+
The icon has twice the gap below it, so the title and the description read as one group. The
|
|
505
|
+
description stays at `body.sm`. Pass the icon without a size. The indicator is `aria-hidden` by
|
|
506
|
+
default, because the title states the same thing in words.
|
|
507
|
+
|
|
508
|
+
The title is an `h2`. Pass a deeper level with `as` where the page outline requires it. The root has
|
|
509
|
+
no role.
|
|
510
|
+
|
|
511
|
+
## Toast
|
|
512
|
+
|
|
513
|
+
Raises a short message into a region fixed to an edge of the window. Create one toaster in a module
|
|
514
|
+
the application shares, render its `Toast.Region` once at the application's root, and raise toasts
|
|
515
|
+
into the toaster from anywhere.
|
|
516
|
+
|
|
517
|
+
```tsx
|
|
518
|
+
import { XIcon } from "lucide-react";
|
|
519
|
+
|
|
520
|
+
import { Toast } from "@stealthscale/component-feedback";
|
|
521
|
+
|
|
522
|
+
export const toaster = Toast.createToaster({ placement: "bottom-end" });
|
|
523
|
+
|
|
524
|
+
<Toast.Region toaster={toaster}>
|
|
525
|
+
{(toast) => (
|
|
526
|
+
<Toast.Root>
|
|
527
|
+
<Toast.Content>
|
|
528
|
+
<Toast.Title>{toast.title}</Toast.Title>
|
|
529
|
+
<Toast.Description>{toast.description}</Toast.Description>
|
|
530
|
+
</Toast.Content>
|
|
531
|
+
<Toast.CloseTrigger>
|
|
532
|
+
<XIcon />
|
|
533
|
+
</Toast.CloseTrigger>
|
|
534
|
+
</Toast.Root>
|
|
535
|
+
)}
|
|
536
|
+
</Toast.Region>;
|
|
537
|
+
|
|
538
|
+
toaster.success({ description: "€1,240.00 is on its way.", title: "Payout sent" });
|
|
539
|
+
```
|
|
91
540
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
541
|
+
| Part | Element | What it renders |
|
|
542
|
+
| --------------- | -------- | -------------------------------------------------------------- |
|
|
543
|
+
| `Region` | `div` | The region of one toaster, and each toast through its function |
|
|
544
|
+
| `Root` | `div` | One toast, with `role="status"` |
|
|
545
|
+
| `Indicator` | `span` | The caller's glyph in the type's palette, `aria-hidden` |
|
|
546
|
+
| `Content` | `div` | The column of the title and the description |
|
|
547
|
+
| `Title` | `div` | The title the toast is named by |
|
|
548
|
+
| `Description` | `div` | The text the toast is described by |
|
|
549
|
+
| `ActionTrigger` | `button` | The button that runs the toast's action and dismisses it |
|
|
550
|
+
| `CloseTrigger` | `button` | The button that dismisses the toast |
|
|
96
551
|
|
|
97
|
-
|
|
552
|
+
- `Toast.createToaster` takes the store's options: `placement` (`top-start`, `top`, `top-end`,
|
|
553
|
+
`bottom-start`, `bottom`, `bottom-end`, default `bottom`), `max` (24), `overlap`, `gap` (16px),
|
|
554
|
+
`offsets` (`1rem`), `duration`, `removeDelay` (200 ms), `hotkey` (Alt+T) and `pauseOnPageIdle`
|
|
555
|
+
(true). Its toaster raises toasts with `create`, `success`, `error`, `warning`, `info`, `loading`
|
|
556
|
+
and `promise`, and changes them with `update`, `dismiss`, `pause` and `resume`.
|
|
557
|
+
- The region is a `region` landmark named `<label>, <placement> (alt+T)`, with `label`
|
|
558
|
+
`Notifications` by default, and announces each new toast through `aria-live="polite"`. Alt+T moves
|
|
559
|
+
focus to it. When the toast that has focus leaves, focus returns to the element it entered the
|
|
560
|
+
region from. The region's id comes from the placement, so render one region per placement.
|
|
561
|
+
- A toast closes after 5 seconds, a `success` toast after 2 and a `loading` toast never. `duration`
|
|
562
|
+
sets another. A pointer resting on the region or focus inside it pauses every toast, and the
|
|
563
|
+
region pauses every toast while the page is hidden.
|
|
564
|
+
- The type sets the toast's palette: `success`, `error`, `warning`, `info`, and `neutral` for
|
|
565
|
+
`loading`. The indicator inks the caller's glyph in the palette's solid. Pass a `Spinner` for a
|
|
566
|
+
loading toast.
|
|
567
|
+
- A toast is in the tab order, and Escape dismisses the focused toast. The close trigger is named
|
|
568
|
+
"Dismiss notification" unless you pass `aria-label`.
|
|
569
|
+
- With `overlap`, the toasts overlap until a pointer rests on the region or focus enters it. Past
|
|
570
|
+
`max`, the toaster queues toasts, errors first.
|
|
571
|
+
- A toast is at most `sizes.sm` wide, 384px at the default scale, and never wider than the window
|
|
572
|
+
less the offsets. The recipe has no axes.
|
|
573
|
+
- A missed toast is gone. Put nothing a reader needs only in a toast, and give a toast with an
|
|
574
|
+
action a longer `duration`.
|
|
98
575
|
|
|
99
576
|
## Licence
|
|
100
577
|
|