@scalewing/react 1.26.0 → 1.28.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/CHANGELOG.md +74 -0
- package/README.md +147 -3
- package/dist/components/ActionBar.d.ts +14 -0
- package/dist/components/ActionBar.js +5 -2
- package/dist/components/DenominationGrid.d.ts +5 -2
- package/dist/components/DenominationGrid.js +11 -3
- package/dist/components/Dialog.d.ts +28 -4
- package/dist/components/Dialog.js +20 -2
- package/dist/components/Notice.d.ts +69 -0
- package/dist/components/Notice.js +48 -0
- package/dist/components/Progress.d.ts +3 -1
- package/dist/components/Progress.js +4 -0
- package/dist/components/Stepper.d.ts +44 -0
- package/dist/components/Stepper.js +77 -0
- package/dist/components/Steps.d.ts +18 -0
- package/dist/components/Steps.js +34 -0
- package/dist/components/Toast.d.ts +20 -4
- package/dist/components/Toast.js +44 -21
- package/dist/components/action-bar/ActionBarSummary.d.ts +22 -0
- package/dist/components/action-bar/ActionBarSummary.js +23 -0
- package/dist/components/denomination-grid/ColumnHeadText.d.ts +7 -0
- package/dist/components/denomination-grid/ColumnHeadText.js +13 -0
- package/dist/components/denomination-grid/DenominationStrip.d.ts +5 -2
- package/dist/components/denomination-grid/DenominationStrip.js +4 -8
- package/dist/components/denomination-grid/DenominationTiles.d.ts +5 -2
- package/dist/components/denomination-grid/DenominationTiles.js +4 -3
- package/dist/components/denomination-grid/EntryCell.d.ts +15 -0
- package/dist/components/denomination-grid/EntryCell.js +16 -0
- package/dist/components/denomination-grid/RowLabelLine.d.ts +3 -2
- package/dist/components/denomination-grid/RowLabelLine.js +5 -3
- package/dist/components/denomination-grid/StripRow.d.ts +13 -0
- package/dist/components/denomination-grid/StripRow.js +19 -0
- package/dist/components/denomination-grid/types.d.ts +53 -1
- package/dist/components/denomination-grid/validate-entry.d.ts +15 -0
- package/dist/components/denomination-grid/validate-entry.js +78 -0
- package/dist/components/dialog/DialogFooter.d.ts +14 -0
- package/dist/components/dialog/DialogFooter.js +11 -0
- package/dist/components/dialog/DialogTitle.d.ts +11 -4
- package/dist/components/dialog/DialogTitle.js +8 -6
- package/dist/components/steps/Step.d.ts +24 -0
- package/dist/components/steps/Step.js +62 -0
- package/dist/components/steps/step-status.d.ts +26 -0
- package/dist/components/steps/step-status.js +37 -0
- package/dist/components/steps/steps-context.d.ts +16 -0
- package/dist/components/steps/steps-context.js +3 -0
- package/dist/components/toast/ToastContent.d.ts +22 -0
- package/dist/components/toast/ToastContent.js +21 -0
- package/dist/components/toast/use-toast-hold.d.ts +28 -0
- package/dist/components/toast/use-toast-hold.js +53 -0
- package/dist/components/toast/use-toast-timer.d.ts +15 -0
- package/dist/components/toast/use-toast-timer.js +34 -0
- package/dist/css/css-action-bar-summary.d.ts +2 -0
- package/dist/css/css-action-bar-summary.js +48 -0
- package/dist/css/css-action-bar.js +4 -0
- package/dist/css/css-components.js +16 -0
- package/dist/css/css-denomination-entry.d.ts +2 -0
- package/dist/css/css-denomination-entry.js +78 -0
- package/dist/css/css-denomination-grid.js +8 -0
- package/dist/css/css-denomination-label.d.ts +10 -0
- package/dist/css/css-denomination-label.js +36 -0
- package/dist/css/css-dialog-frame.d.ts +2 -0
- package/dist/css/css-dialog-frame.js +83 -0
- package/dist/css/css-dialog.js +20 -4
- package/dist/css/css-notice.d.ts +22 -0
- package/dist/css/css-notice.js +168 -0
- package/dist/css/css-progress.d.ts +3 -0
- package/dist/css/css-progress.js +13 -6
- package/dist/css/css-stepper.d.ts +10 -0
- package/dist/css/css-stepper.js +126 -0
- package/dist/css/css-steps.d.ts +9 -0
- package/dist/css/css-steps.js +203 -0
- package/dist/css/css-toast-card.d.ts +5 -0
- package/dist/css/css-toast-card.js +122 -0
- package/dist/css/css-variables.js +1 -0
- package/dist/css/stylesheet.js +10 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +8 -5
- package/dist/stepper-count.d.ts +37 -0
- package/dist/stepper-count.js +50 -0
- package/dist/styles.css +623 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 73e7cf1: `DenominationGrid` `labelAppearance` (with `DenominationLabelAppearance` and `denominationLabelAppearances`): `'tone'` (default, unchanged) or `'plain'`, row labels in the text color at label weight with no tone stripe, in both layouts; a row's tone still colors its total (`docs/requests/teisoro-denomination-grid-label.md`, Teisoro F-011 release R2). An unknown value throws a `RangeError`. New generated class `sw-denomination-labels-plain`. No new tokens or dependencies.
|
|
8
|
+
- 6fc5c25: `Dialog` `description`, `icon`, `footer`, `summary`, `size="xl"` and `role` (with `DialogRole` and `dialogSizes`): a sheet's header and pinned footer (`docs/requests/teisoro-dialog-header-footer.md`, Teisoro F-011 release R2).
|
|
9
|
+
|
|
10
|
+
- `description`: a muted line under the title and the dialog's accessible description. `icon`: a consumer glyph or tile before the title, `aria-hidden`.
|
|
11
|
+
- `footer`: the actions, pinned to the dialog's bottom edge under a hairline while the content scrolls. `summary`: ActionBar's labelled figure at the footer's start, in a polite status region.
|
|
12
|
+
- `size="xl"`: 65rem (`--sw-dialog-max-xl`). `role="alertdialog"` for a warning that interrupts.
|
|
13
|
+
- An unknown `size` or `role`, a blank `description` or an invalid `summary` throws a `RangeError` (an unknown size used to render `md`). `role` is typed `'dialog' | 'alertdialog'` (any other role used to pass through to the element).
|
|
14
|
+
- New generated classes `sw-dialog-xl`, `sw-dialog-header-framed`, `sw-dialog-icon`, `sw-dialog-titles`, `sw-dialog-description`, `sw-dialog-footer`, `sw-dialog-footer-status` and `sw-dialog-footer-actions`. No new tokens or dependencies.
|
|
15
|
+
|
|
16
|
+
- 792f7f5: `Notice` `variant="band"` (with `NoticeVariant` and `noticeVariants`): a full-width square strip of page chrome, such as an environment strip above the app header (`docs/requests/teisoro-environment-strip.md`, Teisoro F-011 release R2).
|
|
17
|
+
|
|
18
|
+
- Square, the container's full width, at least the `xs` control height, caption type, its words centered; the tone's fill and ink as for every notice.
|
|
19
|
+
- Its role defaults to `none` (no live region) unless `role` is given. Forced colors draw a rule under it.
|
|
20
|
+
- An unknown `variant` throws a `RangeError`.
|
|
21
|
+
- New generated class `sw-notice-band`. No new tokens or dependencies.
|
|
22
|
+
|
|
23
|
+
- f543d5f: `Notice` (with `NoticeProps`, `NoticeTone`, `NoticeRole` and `noticeTones`): a tinted note in the page's flow (`docs/requests/teisoro-notice.md`, Teisoro F-011 release R2).
|
|
24
|
+
|
|
25
|
+
- `tone: 'info' | 'success' | 'warning' | 'danger'`, `title?` (a bold lead), `children` (the message), `action?` (one link or small `Button`), `icon?` (a consumer glyph, hidden from assistive technology), `role?: 'status' | 'alert' | 'none'` (danger is an alert, the others a status; `none` is no live region), a forwarded ref.
|
|
26
|
+
- A text message shares the title's row while it fits; a message of elements (a sentence and a `Checkbox` or `Field`) stacks under the title.
|
|
27
|
+
- The fill mixes the tone into the surface (the accent tint for info) and the words move the tone toward the text color, keeping 4.5:1 on the fill in every palette (see the request for the two palettes whose own danger is below that). Forced colors draw a border.
|
|
28
|
+
- An unknown `tone` or `role`, a blank `title`, or nothing to say throws a `RangeError`.
|
|
29
|
+
- New generated classes `sw-notice`, `sw-notice-info`, `sw-notice-success`, `sw-notice-warning`, `sw-notice-danger`, `sw-notice-icon`, `sw-notice-content`, `sw-notice-stacked`, `sw-notice-title`, `sw-notice-body` and `sw-notice-action`. No new tokens or dependencies.
|
|
30
|
+
|
|
31
|
+
- 9e372e1: `Progress` `tone="warning"` (with `progressTones`): the bar in the semantic warning color, such as the time left before an idle sign-out (`docs/requests/teisoro-progress-warning.md`, Teisoro F-011 release R2). New generated class `sw-progress-warning`. An unknown `tone` now throws a `RangeError` instead of rendering an unstyled bar. No new tokens or dependencies.
|
|
32
|
+
- 3d761ba: `Toast` `title`, `action`, `showRemaining` and `placement` (with `ToastAction`, `ToastPlacement` and `toastPlacements`): a corner card that confirms a save (`docs/requests/teisoro-toast-action.md`, Teisoro F-011 release R2).
|
|
33
|
+
|
|
34
|
+
- `title`: a bold first line; the children become a muted detail.
|
|
35
|
+
- `action: { label, onPress }`: a ghost small Button at the end; it runs `onPress`, then closes the toast.
|
|
36
|
+
- `showRemaining`: a thin `aria-hidden` line that runs down over the toast's duration.
|
|
37
|
+
- `placement: 'bottom' | 'top-end'` (default `bottom`, unchanged).
|
|
38
|
+
- A toast with an action or the line takes the pointer, and its time is held while the pointer or focus is on it. A plain toast is unchanged and stays click-through.
|
|
39
|
+
- A travelling toast (`anchor` and `target`) ignores `placement` and `showRemaining`. When the toast closes with focus inside, focus returns to where it came from.
|
|
40
|
+
- `title` is now the toast's bold first line and is no longer forwarded to the element as the HTML `title` attribute.
|
|
41
|
+
- A blank `title` or `action.label`, a missing `onPress`, or an unknown `placement` throws a `RangeError`.
|
|
42
|
+
- New generated classes `sw-toast-title`, `sw-toast-detail`, `sw-toast-action`, `sw-toast-interactive`, `sw-toast-top-end`, `sw-toast-remaining` and `sw-toast-paused`. No new tokens or dependencies.
|
|
43
|
+
|
|
44
|
+
## 1.27.0
|
|
45
|
+
|
|
46
|
+
### Minor Changes
|
|
47
|
+
|
|
48
|
+
- 495d542: `ActionBar` `summary` (with `ActionBarSummary` and `ActionBarSummaryTone`): a labelled figure first in the bar's polite status region (`docs/requests/teisoro-action-bar-summary.md`, Teisoro F-011 release R1).
|
|
49
|
+
|
|
50
|
+
- `summary: { label, value, detail?, tone?: 'default' | 'danger' | 'success' }`: the label and detail in muted label-size words, the value in the `heading` variant at tabular figures, on one baseline; `tone` colors the value only. Any `status` follows it in the same live region.
|
|
51
|
+
- A blank `label`, `value` or `detail`, or an unknown `tone`, throws a `RangeError`.
|
|
52
|
+
- New generated classes `sw-action-bar-summary`, `sw-action-bar-summary-label`, `sw-action-bar-summary-value`, `sw-action-bar-summary-detail`, `sw-action-bar-summary-danger` and `sw-action-bar-summary-success`. No new tokens or dependencies.
|
|
53
|
+
|
|
54
|
+
- db1d77b: `DenominationGrid` count entry (`docs/requests/teisoro-denomination-grid-entry.md`, Teisoro F-011 release R1), with the new types `DenominationGridEntry` and `DenominationGridRowEmphasis`:
|
|
55
|
+
|
|
56
|
+
- `DenominationGridRow.entry` (`onChange(columnKey, value)`, `decrementLabel`, `incrementLabel`, `max?`, `errors?`, `placeholder?`) makes a strip row's `cells` editable, one full-width `Stepper` per column named by the row label and the column's label and caption. Entry counts are zero or more and unsigned, and every count's name is its own. A strip with an entry row centres its heads and read-only counts; a `cellTones` tone draws an entry count's border (`accent` is the `Stepper`'s changed look).
|
|
57
|
+
- `DenominationGridRow.caption` (a muted line under the label), `emphasis` (`'default' | 'quiet'`, the subtle fill) and `disabled` (faded, entry disabled, `aria-disabled`), both strip only.
|
|
58
|
+
- `DenominationGridColumn.caption`: a muted note after the column label, such as "× 100".
|
|
59
|
+
- Entry rows need the strip layout; bad caps, messages, button names, captions or emphasis throw a `RangeError`.
|
|
60
|
+
- `Stepper` is now `position: relative`, so its hidden label and empty error region never widen a scroll region's page.
|
|
61
|
+
- New generated classes `sw-denomination-entry`, `sw-denomination-strip-entry`, `sw-denomination-label-words`, `sw-denomination-label-caption`, `sw-denomination-head-caption`, `sw-denomination-row-quiet` and `sw-denomination-row-disabled`. No new tokens or dependencies.
|
|
62
|
+
|
|
63
|
+
- 92357d4: `Steps` and `Step` (with `StepsProps`, `StepProps`, `StepStatus`, `StepHeadingLevel` and `StepsLabels`): a one-page vertical stepper (`docs/requests/teisoro-steps.md`, Teisoro F-011 release R1).
|
|
64
|
+
|
|
65
|
+
- `Steps`: an `ol` of steps numbered by their place, with `headingLevel` (2 by default) and `labels` (the status words assistive technology hears; English by default).
|
|
66
|
+
- `Step`: `status` (`'upcoming' | 'current' | 'done' | 'error' | 'skipped'`), `title`, `summary`, `onEdit` and `editLabel`, `skippedReason`, `headingLevel`, `children`. The current and error steps are open with their fields; a done step folds to a check, its title, a truncated summary and a ghost Edit named by its title; steps ahead and skipped ones are greyed. A line joins each marker to the next.
|
|
67
|
+
- The current step has `aria-current="step"`; focus moves to a step's heading when it becomes current from a folded state (the first such step in an update).
|
|
68
|
+
- New generated classes `sw-steps`, `sw-steps-item`, `sw-steps-item-<status>`, `sw-steps-header`, `sw-steps-marker`, `sw-steps-title`, `sw-steps-reason`, `sw-steps-summary`, `sw-steps-edit` and `sw-steps-body`, from tokens (the `xs` control height marker, the `title` and body type). No new tokens or dependencies.
|
|
69
|
+
|
|
70
|
+
- 35934e1: `Stepper` (with `StepperProps` and `StepperWidth`): a whole-number count changed with − and + or typed, the web counterpart of the react-native `Stepper` (`docs/requests/teisoro-stepper.md`, Teisoro F-011 release R1).
|
|
71
|
+
|
|
72
|
+
- `label`, `labelVisuallyHidden`, `value` (`number | null`), `onChange`, `min` (default 0), `max` (open when left out), `step` (default 1), `decrementLabel`, `incrementLabel`, `error`, `changed`, `disabled`, `readOnly`, `valueText`, `placeholder`, `name`, `width` (`'fixed' | 'full'`), and a ref to the input.
|
|
73
|
+
- The count is a native text input with `role="spinbutton"` and the one tab stop (ArrowUp and ArrowDown step it, Home and End go to `min` and a finite `max`); the − and + buttons are named pointer targets outside the tab order that keep focus where it was, all three in a `group` named by the label. − stops at `min`, + at `max`; an empty box counts as `min`.
|
|
74
|
+
- Typed text other than digits (and a leading minus when `min` is below zero) is refused; a typed count is reported as typed, even outside the bounds, for the consumer's `error`.
|
|
75
|
+
- New generated classes `sw-stepper`, `sw-stepper-full`, `sw-stepper-track`, `sw-stepper-button`, `sw-stepper-input`, `sw-stepper-invalid`, `sw-stepper-changed` and `sw-stepper-readonly`, sized from the `md` control height and spacing step 6. No new tokens or dependencies.
|
|
76
|
+
|
|
3
77
|
## 1.26.0
|
|
4
78
|
|
|
5
79
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -29,6 +29,9 @@ import {
|
|
|
29
29
|
Grid,
|
|
30
30
|
DateField,
|
|
31
31
|
Field,
|
|
32
|
+
Stepper,
|
|
33
|
+
Steps,
|
|
34
|
+
Step,
|
|
32
35
|
Select,
|
|
33
36
|
Switch,
|
|
34
37
|
Split,
|
|
@@ -52,7 +55,25 @@ Import the CSS once at the application entry. Do not copy it into your source tr
|
|
|
52
55
|
|
|
53
56
|
`Box` `border` draws a hairline in the `border` color token: `border` (or `border={true}`) is solid, and `border="dashed"` is dashed (the generated `sw-border` and `sw-border-dashed` classes; a `style` you pass still wins), for a space to fill in by hand such as a blank on a printed form. It does not make the box look or act like an input. Every `Box`-based component takes it.
|
|
54
57
|
|
|
55
|
-
`Box hideBelow="md"` hides a region under 48rem and `hideFrom="md"` hides it at 48rem and wider, so a product can swap a wide layout for a narrow one without its own stylesheet. They also take `lg` (64rem), for content that fits only from a laptop up, such as a row of labelled destinations that a tablet shows as glyphs (name each glyph-only control, for example with `Tooltip relationship="label"`); the layout props keep `md`. `breakpointQuery('below', 'lg')` returns the query a hide class uses (`not all and (min-width: 64rem)`), for an app whose script must follow the same width. An unknown breakpoint throws a `RangeError`. Hidden regions leave the accessibility tree; do not use this to protect data. `Box as="a"` is a layout link. Use `Button` for press actions. `Field` associates a native `<input>` or `<select>` with a label and token gap; native text controls inherit the generated document canvas. `
|
|
58
|
+
`Box hideBelow="md"` hides a region under 48rem and `hideFrom="md"` hides it at 48rem and wider, so a product can swap a wide layout for a narrow one without its own stylesheet. They also take `lg` (64rem), for content that fits only from a laptop up, such as a row of labelled destinations that a tablet shows as glyphs (name each glyph-only control, for example with `Tooltip relationship="label"`); the layout props keep `md`. `breakpointQuery('below', 'lg')` returns the query a hide class uses (`not all and (min-width: 64rem)`), for an app whose script must follow the same width. An unknown breakpoint throws a `RangeError`. Hidden regions leave the accessibility tree; do not use this to protect data. `Box as="a"` is a layout link. Use `Button` for press actions. `Field` associates a native `<input>` or `<select>` with a label and token gap; native text controls inherit the generated document canvas. `Steps` is a one-page vertical stepper: an `ol` of `Step` panels in order, numbered by their place, a line joining each marker to the next (in the success color below a done step). A `Step` takes `status` (`'upcoming' | 'current' | 'done' | 'error' | 'skipped'`) and `title` (a heading at the list's `headingLevel`, 2 by default, or the step's own). The current step is open with its `children` and has `aria-current="step"`; an error step stays open with a "!" marker. A done step folds to one line: a check, the title, its `summary` (truncated) and, with `onEdit` and `editLabel`, a ghost small `Button` at the end named by its label and the title ("Edit The site"). A step ahead is greyed with its number in a ring; a skipped step is greyed with its `skippedReason` after the title (pass your own separator, such as "· not needed"). A folded step's heading ends in its status word for assistive technology (visually hidden; English by default, `labels` on `Steps` takes your words). When a step becomes current from a folded state (upcoming, done or skipped), focus moves to its heading, a programmatic focus target; if several do in one update, the first in the list takes it. A first render, a move between current and error, and a step that turns to error leave focus alone: after a refused submit, move focus to the first invalid field yourself, as with `Field`. An error step has no `aria-current`; its heading's status word says it needs attention. Scalewing holds no order rules: the consumer sets each step's status. Pass the `Step`s as direct children (a Fragment around several would share one number). A `Step` outside `Steps`, a Fragment child, a blank title or reason, an unknown status or heading level, `onEdit` without `editLabel`, or a blank status word throws a `RangeError`.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<Steps aria-label="Heron survey">
|
|
62
|
+
<Step
|
|
63
|
+
status="done"
|
|
64
|
+
title="The site"
|
|
65
|
+
summary="North marsh"
|
|
66
|
+
editLabel="Edit"
|
|
67
|
+
onEdit={() => setCurrent(0)}
|
|
68
|
+
/>
|
|
69
|
+
<Step status="current" title="The count">
|
|
70
|
+
{fields}
|
|
71
|
+
</Step>
|
|
72
|
+
<Step status="upcoming" title="Review" />
|
|
73
|
+
</Steps>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`Select` is a labeled listbox menu when the open list must match the canvas.
|
|
56
77
|
|
|
57
78
|
`Field` supports optional `description`, `error`, and `required` on one native input, select, or textarea child. It preserves an existing `aria-describedby` and appends a stable ID for the hint, or for the error, which replaces the hint on screen while it is supplied. `error` sets `aria-invalid` and token-owned invalid styling. The error sits in a polite live region (`aria-live="polite"`) that is always rendered, empty while there is no error, so an error that appears while the person types, or after a submit, is announced once, politely, when its text is swapped in; it is never `role="alert"`, so a submit that finds several errors does not fire several assertive alerts at once. Empty, the region takes no room in the layout. The consumer owns validation and localized messages, and after a refused submit it moves focus to the first invalid control (or to one form-level summary). `Checkbox`, `RadioGroup` and `DateField` expose their `error` the same way. `invalid` marks the control invalid the same way (`aria-invalid` and the danger border, `Mark` in forced colors) without a message of its own, for fields whose one error is shown elsewhere, such as a count that adds up to nothing under a group of count fields: point each control's `aria-describedby` at that message so it is read with the field. The field's own polite region stays empty and its `description` stays on screen; `error` implies `invalid`. Passing `invalid`, even `invalid={false}` but not `invalid={undefined}`, needs one native control child and throws a `TypeError` on the first render otherwise, so a form learns it while the fields are valid, not on its first failed submit. `error` keeps its rule: it needs the native control only while it holds a message. `invalid` is `Field`'s only: `DateField`, `Select` and the other controls with `error` do not have it yet.
|
|
58
79
|
|
|
@@ -84,6 +105,21 @@ A disabled native text control (`<input>`, `<select>` or `<textarea>` on the can
|
|
|
84
105
|
</Field>
|
|
85
106
|
```
|
|
86
107
|
|
|
108
|
+
`Stepper` is a whole-number count changed with − and + or typed. The count is a native text input with `role="spinbutton"` and `inputmode="numeric"`, named by `label` (visible above it, or `labelVisuallyHidden` where table headers say it) and the one tab stop: ArrowUp and ArrowDown step it by `step` (default 1), Home goes to `min` and End to a finite `max` (a range below zero takes `inputmode="text"`, since a phone's numeric pad has no minus key). The − and + buttons are pointer targets outside the tab order, named by `decrementLabel` and `incrementLabel` (your copy), and a press on one leaves focus where it was; the count and both buttons sit in a `group` named by the label. − is disabled at `min` (default 0) and + at `max` (no upper bound when left out). `value` is a count or `null` for an empty box, which the buttons treat as `min` and `placeholder` can fill. Typed text is refused unless it is digits (with a leading minus when `min` is below zero), so `onChange` only receives a whole number or `null`; a typed count is reported as typed even outside the bounds, so say why in `error` (a polite message, `aria-invalid` and the danger border, as `Field`'s), and the buttons then move it back inside them. `changed` takes `Field`'s changed look; `readOnly` keeps the count focusable and hides the buttons in their own room; `disabled` stops all three. The count is a few digits wide and keeps that width (`width="fixed"`), or fills its container (`width="full"`, for a table cell). Bounds that are not whole numbers, `min` above `max`, or a `step` that is not above zero throw a `RangeError`.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<Stepper
|
|
112
|
+
label="Herons"
|
|
113
|
+
value={count}
|
|
114
|
+
onChange={setCount}
|
|
115
|
+
max={40}
|
|
116
|
+
decrementLabel="One less"
|
|
117
|
+
incrementLabel="One more"
|
|
118
|
+
placeholder="0"
|
|
119
|
+
error={count !== null && count > nests ? `Only ${nests} nests.` : undefined}
|
|
120
|
+
/>
|
|
121
|
+
```
|
|
122
|
+
|
|
87
123
|
`Select` is a labeled listbox menu: supply `label`, `options` (`value` and `label`), a controlled `value` and `onChange`; `size="xs"` and `labelVisuallyHidden` are for toolbar chrome, and `action` is a last listbox command that never becomes the value. The closed trigger matches `Field`: its text is centred in the control, it ends in the `Accordion` chevron, and it is as wide as its longest option (as a native select is), so it keeps its width when the value changes; past the available width the label ellipsizes. The option labels that size it are drawn by CSS from `data-label`, so the trigger's text content and accessible value are only the current label. Its label is drawn as `Field`'s (the same label words and mark inside a `<label>` that keeps the canvas type), so a `Select` beside a `Field` lines up at the label and the control.
|
|
88
124
|
|
|
89
125
|
`Select` `placeholder` shows in the closed trigger, in the muted color, while `value` matches no option (such as `''`); it is not an option, is never selected in the list and never becomes the value, and it counts toward the trigger's width. `required` marks the label with `Field`'s `aria-hidden` asterisk and sets `aria-required` on the trigger. `error` renders a message under the control (`sw-field-error`), links it by `aria-describedby`, sets `aria-invalid` and the danger border; like `Field`'s it is announced politely from a live region that is always there, never as an alert, and an empty string is no error. A blank `placeholder` throws a `RangeError`.
|
|
@@ -134,6 +170,26 @@ A disabled native text control (`<input>`, `<select>` or `<textarea>` on the can
|
|
|
134
170
|
|
|
135
171
|
`ActionBar` keeps the actions of a long page on a glass bar stuck to the bottom of the viewport, with an optional short `status` (such as when the work was last saved): a string, or a node such as a `Badge` and then a short line. Put it last in the content it acts on: it stays stuck while that content scrolls by and then rests in its own place at the end. `stickyBelow="md"` sticks only on a phone and leaves the bar in page flow from `md` up. The bar clears `env(safe-area-inset-bottom)` (set `viewport-fit=cover` in the page's viewport meta for the inset to apply). Its children are the actions: on a phone they share one row under the status. The status is a polite live region (`role="status"`, kept in the page even while empty), so a new status such as "Draft saved at 5:00 PM" is announced; do not announce the same save a second time with your own notice. The status is a `div` whose children sit on one row a `space-2` gap apart and wrap under one another when the row is full, so a line that does not fit beside its badge drops under it on a phone; put a phrase that mixes text and elements in one `span` so it wraps as a sentence. `undefined`, `null` or `false` leaves the status empty. An open `Select`, `ActionMenu` or `Tooltip` paints over the bar, including inside a glass `Card` or `Accordion`.
|
|
136
172
|
|
|
173
|
+
`ActionBar` `summary` (`{ label, value, detail?, tone? }`, type `ActionBarSummary`) puts a labelled figure first in the same polite status region, before any `status`: the label and the detail in muted label-size words, the value between them in the `heading` variant (24 below `md`) at tabular figures, all on one baseline and wrapping as one phrase, such as "Counted **$33,790.00** short $4,000.00". `tone` (`'default' | 'danger' | 'success'`, such as danger while short or over and success once it matches) colors the value only, so say why in the detail. Format the value yourself; each new figure is announced politely. For a guided form, the actions are a secondary Cancel and one primary that stays `disabled` until the form is valid. A blank `label`, `value` or `detail`, or an unknown `tone`, throws a `RangeError`.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<ActionBar
|
|
177
|
+
summary={{
|
|
178
|
+
label: 'Counted',
|
|
179
|
+
value: '14 nests',
|
|
180
|
+
detail: 'short 2',
|
|
181
|
+
tone: 'danger',
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
<Button variant="secondary" onPress={cancel}>
|
|
185
|
+
Cancel
|
|
186
|
+
</Button>
|
|
187
|
+
<Button disabled={!matches} onPress={save}>
|
|
188
|
+
Save count
|
|
189
|
+
</Button>
|
|
190
|
+
</ActionBar>
|
|
191
|
+
```
|
|
192
|
+
|
|
137
193
|
```tsx
|
|
138
194
|
<Stack gap={4}>
|
|
139
195
|
{longForm}
|
|
@@ -184,8 +240,51 @@ A `Button` that toggles something passes `aria-pressed`. The pressed button gets
|
|
|
184
240
|
|
|
185
241
|
`Dialog` is a modal `<dialog>` controlled by `open`. Escape, a backdrop press, a platform close request, and a `<form method="dialog">` submit (or a submitter with `formmethod="dialog"`) are each prevented and call `onClose`; none of them closes the dialog itself. A consumer `onKeyDown`, `onCancel`, `onSubmit` or `onPointerDown` that prevents the event vetoes that request. Escape in a search field that holds text clears the field first, and a descendant's own `cancel` (a dismissed file picker) is not a close request. Set `open` to false to close it, or keep it true (for example while a form is saving) and it stays shown. `onClose` is not called when `open` turns false, and it is required: a dialog that must not be dismissed passes a callback that keeps `open` true. The `title` is an `h3` in the title style by default; `titleLevel={2}` renders it as an `h2` in the same style, for a dialog whose own section labels are `h3` headings, so they read as parts of the dialog and not as its peers. The default stays `3` so existing dialogs keep their outline; any value other than `2` renders an `h3`. `sheetBelow="md"` makes the dialog a bottom sheet below `md`: docked to the bottom edge at the full viewport width, top corners rounded and bottom corners square, a `space-8` strip of backdrop above it, and bottom padding that clears `env(safe-area-inset-bottom)` (with `viewport-fit=cover`); it slides up on open unless reduced motion is asked for, and has no grabber, since there is no drag. From `md` up it is the centered dialog at its `size`. `closeLabel` (such as "Close") adds an icon-only ghost close button at the end of the title row, named by the label, which calls `onClose` like Escape; the title still names the dialog. The button is the dialog's first control, so the browser focuses it on open; give a field `autoFocus` to start there instead. Without `closeLabel` the dialog has no close button and its markup is unchanged.
|
|
186
242
|
|
|
243
|
+
A sheet over a page takes a fuller frame. `icon` is a consumer glyph or tile before the title (hidden from assistive technology) and `description` a muted line under the title that also describes the dialog (unless you pass `aria-describedby`). `footer` holds the actions, pinned to the dialog's bottom edge under a hairline while the content scrolls, and `summary` (ActionBar's `{ label, value, detail?, tone? }`) puts a labelled figure at the footer's start in a polite live region. `size="xl"` (65rem) holds a six-column count grid with its labels and total. `role="alertdialog"` is for a warning that interrupts, such as an idle sign-out: give its safe action `autoFocus` (the browser otherwise focuses the first footer button), and point `aria-describedby` at the warning in its body when that is not the `description`. An unknown `size` or `role`, a blank `description` or an invalid `summary` throws a `RangeError`.
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
<Dialog
|
|
247
|
+
open={open}
|
|
248
|
+
onClose={close}
|
|
249
|
+
size="xl"
|
|
250
|
+
closeLabel="Close"
|
|
251
|
+
icon={<ArrowRight />}
|
|
252
|
+
title="Move herons to the lake"
|
|
253
|
+
description="The same herons leave the river count."
|
|
254
|
+
summary={{ label: 'Lake', value: '+14', detail: 'river −14' }}
|
|
255
|
+
footer={
|
|
256
|
+
<>
|
|
257
|
+
<Button variant="secondary" onPress={close}>
|
|
258
|
+
Cancel
|
|
259
|
+
</Button>
|
|
260
|
+
<Button onPress={move}>Move 14 herons</Button>
|
|
261
|
+
</>
|
|
262
|
+
}
|
|
263
|
+
>
|
|
264
|
+
{countGrid}
|
|
265
|
+
</Dialog>
|
|
266
|
+
```
|
|
267
|
+
|
|
187
268
|
`Toast` is an auto-dismiss confirmation on the popover layer, controlled by `open` and `onOpenChange`; it does not trap focus and closes after `timeoutMs` (default 800, or 6000 for a `warning` or `danger` toast, which must be read). `anchor` and `target` send it from a press to a destination. `tone` (`neutral` by default, `success`, `warning`, `danger`) tints its border and an optional consumer `icon` (a Lucide glyph, hidden from assistive technology); the message keeps the text color, so its words carry the meaning. A `danger` toast is announced as `role="alert"`, the others as `role="status"`, so do not wrap the message in another live region. A toast still dismisses itself: an error the person must act on belongs in the page. A toast is as wide as its message, up to the page's gutter (spacing step 4) on each side, so on a phone a long message wraps inside the gutter instead of running to the screen's edges.
|
|
188
269
|
|
|
270
|
+
A toast can also be a corner card that confirms a save: `placement="top-end"` puts it `space-4` from the viewport's top and end corner, `title` is a bold first line with the children as a muted detail under it (pass plain text, not a `Text`), `action` (`{ label, onPress }`) is one ghost small Button at the end that runs `onPress` and then closes the toast, and `showRemaining` adds a thin line that runs down with the time left. A toast with an action or the line takes the pointer, and its time (and the line) is held while the pointer is over it or focus is inside it; each opening starts with the whole time. Pass the time you want, such as `timeoutMs={10000}`, and close it yourself when it no longer applies (for example when the next sale starts). It is never modal and never takes focus; when it closes with focus inside (after its action), focus goes back to where it came from. Keyboard users reach the action in the Toast's place in the DOM, so render it right after the control that saves. A travelling toast ignores `placement` and `showRemaining`.
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
<Toast
|
|
274
|
+
open={saved}
|
|
275
|
+
onOpenChange={setSaved}
|
|
276
|
+
placement="top-end"
|
|
277
|
+
tone="success"
|
|
278
|
+
icon={<Check />}
|
|
279
|
+
title="Heron count saved"
|
|
280
|
+
action={{ label: 'Open', onPress: openCount }}
|
|
281
|
+
showRemaining
|
|
282
|
+
timeoutMs={10000}
|
|
283
|
+
>
|
|
284
|
+
12 herons · 2 nests
|
|
285
|
+
</Toast>
|
|
286
|
+
```
|
|
287
|
+
|
|
189
288
|
`Switch` is a controlled on/off input. It keeps native checkbox keyboard behavior and exposes switch semantics. Provide the current `checked` value and update it in `onCheckedChange`.
|
|
190
289
|
|
|
191
290
|
`Checkbox` is a controlled native checkbox for one independent form or confirmation choice. `checked` and `onCheckedChange` own its state. The consumer supplies `error` after validation to describe and style an invalid choice (associated by `aria-describedby` with `aria-invalid` and announced from a polite live region, as `Field`'s error is, never an alert); `required` retains native form semantics.
|
|
@@ -292,7 +391,22 @@ A header `TableCell` (`as="th"`) with `sort` (`'ascending'`, `'descending'` or `
|
|
|
292
391
|
|
|
293
392
|
`Spinner` shows indeterminate loading in small, medium, or large sizes. Supply localized `label` for the one announced status in a loading region. Use `decorative` on additional indicators beside that status so screen readers do not hear the same message repeatedly. Reduced motion leaves a static accented ring.
|
|
294
393
|
|
|
295
|
-
`
|
|
394
|
+
`Notice` is a tinted note in the page's flow (`tone`: `info`, `success`, `warning` or `danger`). The fill is the tone mixed into the surface (the accent tint for `info`) and the words are the tone moved toward the text color, so they keep 4.5:1 on the fill in every palette (only where a palette's own `danger` is under 4.5:1 on its surface does the note stay at least as readable as that). `title` is a bold lead; a text message (values interpolated in it included) shares its row while it fits and wraps under it otherwise, and a message with any element (a `span`, or a sentence and a `Checkbox` or `Field` in a `Stack`) is stacked under it. Write the message as plain text or a `span`, not a `Text`, which brings its own color. A control inside keeps its own colors; its muted description takes the notice's ink so it stays readable on the fill. `action` is one link or small `Button` at the end of the row; `icon` is a consumer glyph before the text, hidden from assistive technology. `danger` is `role="alert"` and the other tones `role="status"`; `role="none"` is for a note that is part of the page from the start (no live region). Screen readers announce an `alert` that appears, but not reliably a `status` mounted with its words: to have a status heard, keep the Notice mounted and change its words, or move focus to it. It never dismisses itself. Pass `tabIndex={-1}` and a `ref` to move focus to it. `variant="band"` makes it a full-width square strip of page chrome in caption type with its words centered, at least the `xs` control height, such as an environment strip first on the page (`<Notice tone="warning" variant="band" title="Field station">Records here stay out of the census.</Notice>`). It is as wide as its container, so put it outside any padded column, as the page's first child. A glyph or an action stays on the words' row and the words wrap. A band has no live region unless you pass `role`. An unknown `tone`, `role` or `variant`, a blank `title`, or a note with neither a title nor a message throws a `RangeError`.
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
<Notice tone="danger" title="$30.00 short.">
|
|
398
|
+
Add the bills or change the amount.
|
|
399
|
+
</Notice>
|
|
400
|
+
|
|
401
|
+
<Notice
|
|
402
|
+
tone="info"
|
|
403
|
+
role="none"
|
|
404
|
+
title="Three surveys are due this week."
|
|
405
|
+
action={<Button size="sm" variant="ghost" onPress={openTasks}>Open tasks</Button>}
|
|
406
|
+
/>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
`Progress` shows a known value between zero and a positive maximum. It uses native progressbar semantics and displays the value and maximum beside the localized label. Invalid bounds throw instead of silently clamping. Optional `tone` is `accent`, `success`, `warning` (such as the time left before a sign-out) or `danger`; an unknown tone throws. `showCount={false}` hides the visible `value / max` count when the page shows its own count caption, so the count appears once; the progress bar still exposes its value and maximum.
|
|
296
410
|
|
|
297
411
|
`Tooltip` adds supplementary plain-text help to one labelled, focusable trigger, or names an icon-only one (`relationship="label"`, below). Supply localized `content` and an existing trigger element; by default it has its own accessible name. It opens on hover or on a visible focus (`:focus-visible`: the keyboard, not the focus a click or a tap gives), closes on pointer leave, blur, Escape, or outside touch, and a tap on a control (a button, a link, a form control or its label, an editable region, an element with a widget role, or anything inside one) does what the control does and leaves the tooltip closed, so on a touch screen a `Tooltip` on a `Button` is never shown. For an icon-only hint whose button does nothing but show it, such as an ⓘ beside a figure, use `InfoTip` (below), which a tap, a click, Enter or Space opens; do not make a non-control focusable to get a tap. A tap still toggles a `Tooltip` whose trigger does nothing else on a tap, such as an existing focusable badge. The bubble is placed beside its trigger on the top layer while it shows and stays a `space-2` inset inside the screen. Keep required instructions visible outside the tooltip. `disabled` turns the tooltip off, not its trigger: the trigger stays mounted, enabled and focusable, and there is no tooltip, no `aria-describedby` from it, and Escape is left to the page. Use it when what the tooltip says is visible at some widths, such as text shown only from `md` up, instead of rendering the trigger with and without a `Tooltip`, which mounts a new trigger and drops its focus. Focus, hover and touch are still followed, so a trigger that still has focus, the pointer or an open touch toggle shows the tooltip as soon as it is enabled again (Escape pressed while disabled reaches the page and does not stop that). `disabled` must be the same in the server render and the first client render, or hydration mismatches; read a breakpoint through a hydration-safe store, such as `useSyncExternalStore` with a server snapshot. `relationship="label"` makes the tooltip its trigger's name instead of its description, for an icon-only control whose tooltip says what it is: the trigger gets `aria-labelledby` (after its own, if any) and no `aria-describedby` from the tooltip, so the text is read once. The tooltip stays in the page, hidden, so it names the trigger while it is not shown, and it wins over an `aria-label`. While `disabled` there is no tooltip to name the trigger, so give the trigger its own name for that state: visible text, such as a label shown only from `md` up, or an `aria-label`. Do not use `relationship="label"` while the trigger shows visible text that differs from `content`: the tooltip would replace that visible label as the name (WCAG 2.5.3, label in name). The default, `relationship="description"`, is the supplementary help above. `relationship` must also be the same in the server render and the first client render.
|
|
298
412
|
|
|
@@ -367,7 +481,37 @@ Values outside the catalog throw a `RangeError`. There is no per-breakpoint span
|
|
|
367
481
|
</Grid>
|
|
368
482
|
```
|
|
369
483
|
|
|
370
|
-
`DenominationGrid` shows integer counts per unit across a fixed set of columns. The `strip` layout is a captioned table: each row has a toned label with an icon slot, muted zero cells, optional signed deltas toned by sign, and an optional consumer-formatted `total` that shows under the label on a phone. Pass `totalLabel` (for example "Total") to name the total column: it renders a `th scope="col"` shown over the totals from `md` up and visually hidden below it, and without it the corner stays an empty cell as before (either way the column takes no width on a phone). `labelWidth` (`xs` to `xl`, the `Table` column sizes) lines a strip's columns up with every other strip of the same columns in a container of the same width, such as the cards of a feed: the row labels take that width and the count columns share the rest of the region equally, so no content moves a column. On a phone the total cell stays in the table, visually hidden, so a screen reader still reads each total in its row; the copy under the label is `aria-hidden`. A strip wider than its container scrolls sideways inside its own keyboard-focusable region, named after `label`, with the row labels pinned at the start and the same edge shade as `Table` while counts are scrolled out of view (the start shade is cast from the pinned labels' edge, since they cover the region's own); the page never scrolls sideways. The `tiles` layout stacks the column label, the count, and an optional `subtotal` string per column. Each tiles row shows its label line (icon, label and `total`) when the grid has several rows, or when a lone row has an icon or a `total`; a lone plain row is named by the grid's `label` alone. Every row is a region named by its `label`, except a lone plain row whose `label` is the grid's own: it has no name of its own, so assistive technology hears the grid's group once rather than a region repeating it. `rowRole="group"` (default `"region"`) keeps every named row's name but makes it a `group` instead of a landmark region, for a page with many grids (one per order card, say) whose rows would otherwise fill the landmark list; the strip ignores it, since its rows are table rows, and an unknown `rowRole` throws a `RangeError` in either layout. A row's icon and its label words stay on one line in both layouts (`sw-denomination-label-line`): a long label wraps its words beside the icon, never under it, and on a phone the strip's total goes on its own line under them. A row's `cellTones` (one Badge tone or `null` per column) tones a single count in place: the count is set in the tone over its signed or zero look, and in the tiles layout its tile's border takes the tone too, so a short bill turns red without leaving its row; `neutral` or `null` leaves a count as it is, a row's `tone` still colors only its label, and a `cellTones` that is not one known tone or `null` per column throws. The tone never says why on its own; put that in words beside the grid. Every negative count, signed or not and in either layout, is written with the typographic minus ("−1"), never a hyphen. The primitive does no arithmetic and no currency formatting; tones reuse the Badge vocabulary. Invalid columns, rows, or cells throw.
|
|
484
|
+
`DenominationGrid` shows integer counts per unit across a fixed set of columns. The `strip` layout is a captioned table: each row has a toned label with an icon slot, muted zero cells, optional signed deltas toned by sign, and an optional consumer-formatted `total` that shows under the label on a phone. Pass `totalLabel` (for example "Total") to name the total column: it renders a `th scope="col"` shown over the totals from `md` up and visually hidden below it, and without it the corner stays an empty cell as before (either way the column takes no width on a phone). `labelWidth` (`xs` to `xl`, the `Table` column sizes) lines a strip's columns up with every other strip of the same columns in a container of the same width, such as the cards of a feed: the row labels take that width and the count columns share the rest of the region equally, so no content moves a column. `labelAppearance="plain"` (default `"tone"`) sets every row label in the text color at label weight with no tone stripe, in both layouts, for a grid whose rows need no color to tell apart; a row's `tone` still colors its total, and an unknown value throws. On a phone the total cell stays in the table, visually hidden, so a screen reader still reads each total in its row; the copy under the label is `aria-hidden`. A strip wider than its container scrolls sideways inside its own keyboard-focusable region, named after `label`, with the row labels pinned at the start and the same edge shade as `Table` while counts are scrolled out of view (the start shade is cast from the pinned labels' edge, since they cover the region's own); the page never scrolls sideways. The `tiles` layout stacks the column label, the count, and an optional `subtotal` string per column. Each tiles row shows its label line (icon, label and `total`) when the grid has several rows, or when a lone row has an icon or a `total`; a lone plain row is named by the grid's `label` alone. Every row is a region named by its `label`, except a lone plain row whose `label` is the grid's own: it has no name of its own, so assistive technology hears the grid's group once rather than a region repeating it. `rowRole="group"` (default `"region"`) keeps every named row's name but makes it a `group` instead of a landmark region, for a page with many grids (one per order card, say) whose rows would otherwise fill the landmark list; the strip ignores it, since its rows are table rows, and an unknown `rowRole` throws a `RangeError` in either layout. A row's icon and its label words stay on one line in both layouts (`sw-denomination-label-line`): a long label wraps its words beside the icon, never under it, and on a phone the strip's total goes on its own line under them. A row's `cellTones` (one Badge tone or `null` per column) tones a single count in place: the count is set in the tone over its signed or zero look, and in the tiles layout its tile's border takes the tone too, so a short bill turns red without leaving its row; `neutral` or `null` leaves a count as it is, a row's `tone` still colors only its label, and a `cellTones` that is not one known tone or `null` per column throws. The tone never says why on its own; put that in words beside the grid. Every negative count, signed or not and in either layout, is written with the typographic minus ("−1"), never a hyphen. The primitive does no arithmetic and no currency formatting; tones reuse the Badge vocabulary. Invalid columns, rows, or cells throw.
|
|
485
|
+
|
|
486
|
+
A strip row with `entry` makes its counts editable: each column holds a `Stepper` (`width="full"`, its label hidden and named by the row label and the column's label and caption as its header reads, such as "Received $20" or "Counted $1 × 100") whose count is the row's `cells` entry (`null` is an empty box). `entry.onChange(columnKey, value)` reports a change; `entry.decrementLabel` and `entry.incrementLabel` name the buttons; `entry.max` caps each column (`null` for none), `entry.errors` puts a message under a column's count (`null` for none), and `entry.placeholder` fills an empty box. The minimum is 0. The grid still does no arithmetic, so recompute the row's `total` in `onChange`. A strip with an entry row centres its heads and read-only counts over the steppers, and each count keeps spacing step 8 of room, so a narrow strip scrolls in its region instead of crushing it. A `cellTones` tone on an entry count draws its border in the tone (danger for a difference); an `accent` tone, for a suggestion or a change, is the `Stepper`'s `changed` look (border and tint); a message in `entry.errors` wins. A row `caption` is a muted line under its label ("from the customer"), and a column `caption` a muted note after its label ("× 100"). Captions show in both layouts. In the strip, `emphasis="quiet"` sets a row on the subtle fill, for a row to compare against, and `disabled` fades a row that does not apply, disables its entry and marks it `aria-disabled`. Entry rows, quiet rows and disabled rows need the `strip` layout. An entry row's counts are zero or more and it is not `signed`; its label is its own among the entry rows, as each column's label and caption are, so every count has its own name. Breaking any of these, a cap or message list that is not one per column, a cap that is not a whole count of zero or more, blank button names or captions, an unknown `emphasis`, or a `disabled` that is not a boolean throws a `RangeError`.
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
<DenominationGrid
|
|
490
|
+
label="Tag packs"
|
|
491
|
+
columns={[
|
|
492
|
+
{ key: 's', label: 'S', caption: '× 10' },
|
|
493
|
+
{ key: 'm', label: 'M' },
|
|
494
|
+
]}
|
|
495
|
+
totalLabel="Total"
|
|
496
|
+
rows={[
|
|
497
|
+
{
|
|
498
|
+
id: 'fitted',
|
|
499
|
+
label: 'Fitted',
|
|
500
|
+
caption: 'this morning',
|
|
501
|
+
cells: fitted,
|
|
502
|
+
total: `${tags(fitted)} tags`,
|
|
503
|
+
entry: {
|
|
504
|
+
onChange: (key, value) => setCount(key, value),
|
|
505
|
+
decrementLabel: 'One less',
|
|
506
|
+
incrementLabel: 'One more',
|
|
507
|
+
max: [6, 4],
|
|
508
|
+
placeholder: '0',
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
{ id: 'stock', label: 'In stock', emphasis: 'quiet', cells: [6, 4] },
|
|
512
|
+
]}
|
|
513
|
+
/>
|
|
514
|
+
```
|
|
371
515
|
|
|
372
516
|
`FilterChips` presents a long, wrapping single-choice filter set. Provide a localized group `label`, controlled `value`, `onChange`, and options with stable values and localized labels. An optional non-negative integer `count` renders as a tabular chicklet after the label and is part of the option's accessible name; a zero count quiets the chip until it is selected: it drops its glass fill and sets its label in the muted color (4.5:1 in every palette), without fading, since it can still be chosen. Native radios provide arrow and Space navigation; disabled options are skipped.
|
|
373
517
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import { type Breakpoint } from '../css/breakpoints.js';
|
|
3
|
+
import { type ActionBarSummary } from './action-bar/ActionBarSummary.js';
|
|
4
|
+
export type { ActionBarSummary, ActionBarSummaryTone, } from './action-bar/ActionBarSummary.js';
|
|
3
5
|
export type ActionBarProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
4
6
|
/** The actions, usually two or three `Button`s. */
|
|
5
7
|
children: ReactNode;
|
|
@@ -13,6 +15,12 @@ export type ActionBarProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> &
|
|
|
13
15
|
* and elements in one `span` to keep it wrapping as a sentence.
|
|
14
16
|
*/
|
|
15
17
|
status?: ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* A labelled figure before the status, in the same polite live region:
|
|
20
|
+
* the label, the value large at tabular figures, and a detail, such as
|
|
21
|
+
* "Counted $33,790.00 short $4,000.00". `tone` colors the value.
|
|
22
|
+
*/
|
|
23
|
+
summary?: ActionBarSummary;
|
|
16
24
|
/** Sticky only below this breakpoint; from it up the bar sits in page flow. */
|
|
17
25
|
stickyBelow?: Breakpoint;
|
|
18
26
|
};
|
|
@@ -35,6 +43,12 @@ export declare const ActionBar: import("react").ForwardRefExoticComponent<Omit<H
|
|
|
35
43
|
* and elements in one `span` to keep it wrapping as a sentence.
|
|
36
44
|
*/
|
|
37
45
|
status?: ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* A labelled figure before the status, in the same polite live region:
|
|
48
|
+
* the label, the value large at tabular figures, and a detail, such as
|
|
49
|
+
* "Counted $33,790.00 short $4,000.00". `tone` colors the value.
|
|
50
|
+
*/
|
|
51
|
+
summary?: ActionBarSummary;
|
|
38
52
|
/** Sticky only below this breakpoint; from it up the bar sits in page flow. */
|
|
39
53
|
stickyBelow?: Breakpoint;
|
|
40
54
|
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -2,12 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { cx } from '../class-names.js';
|
|
4
4
|
import { actionBarStickyClass } from '../css/css-action-bar.js';
|
|
5
|
+
import { ActionBarSummaryLine, assertActionBarSummary, } from './action-bar/ActionBarSummary.js';
|
|
5
6
|
/**
|
|
6
7
|
* The actions for a long page on a glass bar that sticks to the bottom of
|
|
7
8
|
* the viewport while the content it follows scrolls by. Place it last in
|
|
8
9
|
* that content: it stays stuck while its parent is on screen and then rests
|
|
9
10
|
* in its own place at the end.
|
|
10
11
|
*/
|
|
11
|
-
export const ActionBar = forwardRef(function ActionBar({ children, className, status, stickyBelow, ...rest }, ref) {
|
|
12
|
-
|
|
12
|
+
export const ActionBar = forwardRef(function ActionBar({ children, className, status, stickyBelow, summary, ...rest }, ref) {
|
|
13
|
+
if (summary)
|
|
14
|
+
assertActionBarSummary(summary);
|
|
15
|
+
return (_jsxs("div", { ref: ref, className: cx('sw-action-bar', actionBarStickyClass(stickyBelow), className), ...rest, children: [_jsxs("div", { className: "sw-action-bar-status", role: "status", children: [summary ? _jsx(ActionBarSummaryLine, { summary: summary }) : null, status] }), _jsx("div", { className: "sw-action-bar-actions", children: children })] }));
|
|
13
16
|
});
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { denominationLabelAppearances } from '../css/css-denomination-label.js';
|
|
1
2
|
import { type DenominationGridProps } from './denomination-grid/types.js';
|
|
2
|
-
export type { DenominationGridColumn, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowRole, DenominationGridTone, DenominationLabelWidth, } from './denomination-grid/types.js';
|
|
3
|
-
export
|
|
3
|
+
export type { DenominationGridColumn, DenominationGridEntry, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowEmphasis, DenominationGridRowRole, DenominationGridTone, DenominationLabelWidth, } from './denomination-grid/types.js';
|
|
4
|
+
export type { DenominationLabelAppearance } from '../css/css-denomination-label.js';
|
|
5
|
+
export { denominationLabelAppearances };
|
|
6
|
+
export declare function DenominationGrid({ label, columns, rows, layout, subtotal, zeroLabel, totalLabel, rowRole, labelWidth, labelAppearance, }: DenominationGridProps): import("react").JSX.Element;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { denominationLabelWidths } from '../css/css-denomination-aligned.js';
|
|
3
|
+
import { denominationLabelAppearances, denominationLabelsPlainClass, } from '../css/css-denomination-label.js';
|
|
3
4
|
import { DenominationStrip } from './denomination-grid/DenominationStrip.js';
|
|
4
5
|
import { DenominationTiles } from './denomination-grid/DenominationTiles.js';
|
|
5
6
|
import { assertDenominationGrid } from './denomination-grid/validate.js';
|
|
6
|
-
|
|
7
|
+
import { assertDenominationEntries, assertDenominationExtras, } from './denomination-grid/validate-entry.js';
|
|
8
|
+
export { denominationLabelAppearances };
|
|
9
|
+
export function DenominationGrid({ label, columns, rows, layout = 'strip', subtotal, zeroLabel = '—', totalLabel, rowRole = 'region', labelWidth, labelAppearance = 'tone', }) {
|
|
7
10
|
assertDenominationGrid(label, columns, rows);
|
|
11
|
+
assertDenominationExtras(layout, columns, rows);
|
|
12
|
+
assertDenominationEntries(layout, columns, rows);
|
|
8
13
|
if (!zeroLabel)
|
|
9
14
|
throw new RangeError('zeroLabel must not be empty');
|
|
10
15
|
if (totalLabel !== undefined && !totalLabel.trim())
|
|
@@ -13,7 +18,10 @@ export function DenominationGrid({ label, columns, rows, layout = 'strip', subto
|
|
|
13
18
|
throw new RangeError("rowRole must be 'region' or 'group'");
|
|
14
19
|
if (labelWidth !== undefined && !denominationLabelWidths.includes(labelWidth))
|
|
15
20
|
throw new RangeError(`Unknown labelWidth: ${String(labelWidth)}`);
|
|
21
|
+
if (!denominationLabelAppearances.includes(labelAppearance))
|
|
22
|
+
throw new RangeError(`Unknown labelAppearance: ${String(labelAppearance)}`);
|
|
23
|
+
const labelClass = labelAppearance === 'plain' ? denominationLabelsPlainClass : undefined;
|
|
16
24
|
if (layout === 'tiles')
|
|
17
|
-
return (_jsx(DenominationTiles, { columns: columns, label: label, rowRole: rowRole, rows: rows, subtotal: subtotal, zeroLabel: zeroLabel }));
|
|
18
|
-
return (_jsx(DenominationStrip, { columns: columns, label: label, labelWidth: labelWidth, rows: rows, totalLabel: totalLabel, zeroLabel: zeroLabel }));
|
|
25
|
+
return (_jsx(DenominationTiles, { className: labelClass, columns: columns, label: label, rowRole: rowRole, rows: rows, subtotal: subtotal, zeroLabel: zeroLabel }));
|
|
26
|
+
return (_jsx(DenominationStrip, { className: labelClass, columns: columns, label: label, labelWidth: labelWidth, rows: rows, totalLabel: totalLabel, zeroLabel: zeroLabel }));
|
|
19
27
|
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { type DialogHTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import { type Breakpoint } from '../css/breakpoints.js';
|
|
3
|
+
import { type ActionBarSummary } from './action-bar/ActionBarSummary.js';
|
|
4
|
+
export declare const dialogSizes: readonly ["md", "lg", "xl"];
|
|
3
5
|
/**
|
|
4
6
|
* `md` is the reading width (32rem) for a message or a short form; `lg`
|
|
5
|
-
* (56rem) holds a row of six fields or a data grid without folding it
|
|
7
|
+
* (56rem) holds a row of six fields or a data grid without folding it;
|
|
8
|
+
* `xl` (65rem) holds a grid of six count columns with its labels and total.
|
|
6
9
|
*/
|
|
7
|
-
export type DialogSize =
|
|
10
|
+
export type DialogSize = (typeof dialogSizes)[number];
|
|
11
|
+
/** `alertdialog` for a dialog that interrupts to warn, such as an idle sign-out. */
|
|
12
|
+
export type DialogRole = 'dialog' | 'alertdialog';
|
|
8
13
|
/**
|
|
9
14
|
* The heading level of the dialog's title. It changes only the element (and
|
|
10
15
|
* so the outline a screen reader reads), never the title's look.
|
|
11
16
|
*/
|
|
12
17
|
export type DialogTitleLevel = 2 | 3;
|
|
13
|
-
export type DialogProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'onClose' | 'open' | 'title' | 'children'> & {
|
|
18
|
+
export type DialogProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'onClose' | 'open' | 'title' | 'children' | 'role'> & {
|
|
14
19
|
/** Whether the dialog is shown. The element follows it and nothing else. */
|
|
15
20
|
open: boolean;
|
|
16
21
|
/**
|
|
@@ -50,6 +55,25 @@ export type DialogProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'onClose
|
|
|
50
55
|
* dialog has no close button and its markup is unchanged.
|
|
51
56
|
*/
|
|
52
57
|
closeLabel?: string;
|
|
58
|
+
/**
|
|
59
|
+
* A muted line under the title that describes the dialog; it becomes the
|
|
60
|
+
* dialog's accessible description unless `aria-describedby` is given.
|
|
61
|
+
*/
|
|
62
|
+
description?: string;
|
|
63
|
+
/** A consumer glyph or tile before the title, hidden from assistive technology. */
|
|
64
|
+
icon?: ReactNode;
|
|
65
|
+
/**
|
|
66
|
+
* The dialog's actions, pinned to its bottom edge under a hairline while
|
|
67
|
+
* the content scrolls: a secondary Cancel and one named primary.
|
|
68
|
+
*/
|
|
69
|
+
footer?: ReactNode;
|
|
70
|
+
/**
|
|
71
|
+
* A labelled figure at the start of the footer, as ActionBar's, in a
|
|
72
|
+
* polite live region: "Drawer +$1,000.00 vault −$1,000.00".
|
|
73
|
+
*/
|
|
74
|
+
summary?: ActionBarSummary;
|
|
75
|
+
/** `alertdialog` for a warning that interrupts. Default `dialog`. */
|
|
76
|
+
role?: DialogRole;
|
|
53
77
|
children: ReactNode;
|
|
54
78
|
};
|
|
55
|
-
export declare function Dialog({ children, className, closeLabel, onCancel, onClose, onKeyDown, onPointerDown, onSubmit, open, sheetBelow, size, title, titleLevel, ...rest }: DialogProps): import("react").JSX.Element;
|
|
79
|
+
export declare function Dialog({ 'aria-describedby': describedBy, children, className, closeLabel, description, footer, icon, role, summary, onCancel, onClose, onKeyDown, onPointerDown, onSubmit, open, sheetBelow, size, title, titleLevel, ...rest }: DialogProps): import("react").JSX.Element;
|
|
@@ -5,10 +5,28 @@ import { cx } from '../class-names.js';
|
|
|
5
5
|
import { dialogSheetClass } from '../css/css-dialog.js';
|
|
6
6
|
import { spacingClassNames } from '../spacing-classes.js';
|
|
7
7
|
import { useDialogCloseRequests } from './dialog-close-requests.js';
|
|
8
|
+
import { assertActionBarSummary, } from './action-bar/ActionBarSummary.js';
|
|
9
|
+
import { DialogFooter } from './dialog/DialogFooter.js';
|
|
8
10
|
import { DialogTitle } from './dialog/DialogTitle.js';
|
|
9
11
|
import { Stack } from './Stack.js';
|
|
10
|
-
export
|
|
12
|
+
export const dialogSizes = ['md', 'lg', 'xl'];
|
|
13
|
+
const dialogRoles = ['dialog', 'alertdialog'];
|
|
14
|
+
function assertDialogProps(size, role, description, summary) {
|
|
15
|
+
if (!dialogSizes.includes(size))
|
|
16
|
+
throw new RangeError(`Unknown Dialog size: ${String(size)}`);
|
|
17
|
+
if (role !== undefined && !dialogRoles.includes(role))
|
|
18
|
+
throw new RangeError(`Unknown Dialog role: ${String(role)}`);
|
|
19
|
+
if (description !== undefined &&
|
|
20
|
+
(typeof description !== 'string' || !description.trim()))
|
|
21
|
+
throw new RangeError('Dialog description must not be empty');
|
|
22
|
+
if (summary)
|
|
23
|
+
assertActionBarSummary(summary);
|
|
24
|
+
}
|
|
25
|
+
export function Dialog({ 'aria-describedby': describedBy, children, className, closeLabel, description, footer, icon, role, summary, onCancel, onClose, onKeyDown, onPointerDown, onSubmit, open, sheetBelow, size = 'md', title, titleLevel = 3, ...rest }) {
|
|
26
|
+
assertDialogProps(size, role, description, summary);
|
|
11
27
|
const titleId = useId();
|
|
28
|
+
const descriptionId = useId();
|
|
29
|
+
const hasFooter = Boolean(footer) || Boolean(summary);
|
|
12
30
|
const { nodeRef, handlers } = useDialogCloseRequests({
|
|
13
31
|
open,
|
|
14
32
|
onClose,
|
|
@@ -17,5 +35,5 @@ export function Dialog({ children, className, closeLabel, onCancel, onClose, onK
|
|
|
17
35
|
onPointerDown,
|
|
18
36
|
onSubmit,
|
|
19
37
|
});
|
|
20
|
-
return (_jsx("dialog", { ...rest, ref: nodeRef, "aria-labelledby": titleId, className: cx('sw-dialog', size
|
|
38
|
+
return (_jsx("dialog", { ...rest, ref: nodeRef, "aria-describedby": describedBy ?? (description ? descriptionId : undefined), "aria-labelledby": titleId, className: cx('sw-dialog', size !== 'md' && `sw-dialog-${size}`, sheetBelow && dialogSheetClass(sheetBelow), ...spacingClassNames({ padding: 5 }), className), role: role === 'alertdialog' ? 'alertdialog' : undefined, ...handlers, children: _jsxs(Stack, { gap: 4, children: [_jsx(DialogTitle, { closeLabel: closeLabel, description: description, descriptionId: descriptionId, icon: icon, id: titleId, level: titleLevel === 2 ? 2 : 3, onClose: onClose, title: title }), children, hasFooter ? (_jsx(DialogFooter, { summary: summary, children: footer })) : null] }) }));
|
|
21
39
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import { type NoticeTone, type NoticeVariant, noticeTones, noticeVariants } from '../css/css-notice.js';
|
|
3
|
+
export { noticeTones, noticeVariants };
|
|
4
|
+
export type { NoticeTone, NoticeVariant };
|
|
5
|
+
/**
|
|
6
|
+
* How the notice is announced: `status` (polite), `alert` (assertive), or
|
|
7
|
+
* `none` (not a live region, for a notice that is part of the page from the
|
|
8
|
+
* start and needs no announcement).
|
|
9
|
+
*/
|
|
10
|
+
export type NoticeRole = 'status' | 'alert' | 'none';
|
|
11
|
+
export type NoticeProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'role' | 'title'> & {
|
|
12
|
+
/** The meaning, which sets the fill, the words' color and the default role. */
|
|
13
|
+
tone: NoticeTone;
|
|
14
|
+
/** A bold lead before the message, such as "$30.00 short." */
|
|
15
|
+
title?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The message. Text (values interpolated in it included) shares the
|
|
18
|
+
* title's row while it fits; any element (a `span`, a field, a checkbox)
|
|
19
|
+
* puts the message under the title.
|
|
20
|
+
*/
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
/** One action after the text, such as a link or a small Button. */
|
|
23
|
+
action?: ReactNode;
|
|
24
|
+
/** A consumer glyph (Lucide) before the text, hidden from assistive technology. */
|
|
25
|
+
icon?: ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* Defaults to `alert` for danger and `status` for the other tones; a band
|
|
28
|
+
* is page chrome and defaults to `none`.
|
|
29
|
+
*/
|
|
30
|
+
role?: NoticeRole;
|
|
31
|
+
/**
|
|
32
|
+
* `band` is a full-width square strip in caption type, its words
|
|
33
|
+
* centered, such as an environment strip above the app header.
|
|
34
|
+
*/
|
|
35
|
+
variant?: NoticeVariant;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A tinted note in the page's flow: information, a good outcome, a caution
|
|
39
|
+
* or a refusal, with an optional bold lead, glyph and one action. It stays
|
|
40
|
+
* until the consumer removes it. `variant="band"` is the same note as a
|
|
41
|
+
* full-width strip of page chrome. Pass `tabIndex={-1}` and a ref to move
|
|
42
|
+
* focus to it.
|
|
43
|
+
*/
|
|
44
|
+
export declare const Notice: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "title" | "children" | "role"> & {
|
|
45
|
+
/** The meaning, which sets the fill, the words' color and the default role. */
|
|
46
|
+
tone: NoticeTone;
|
|
47
|
+
/** A bold lead before the message, such as "$30.00 short." */
|
|
48
|
+
title?: string;
|
|
49
|
+
/**
|
|
50
|
+
* The message. Text (values interpolated in it included) shares the
|
|
51
|
+
* title's row while it fits; any element (a `span`, a field, a checkbox)
|
|
52
|
+
* puts the message under the title.
|
|
53
|
+
*/
|
|
54
|
+
children?: ReactNode;
|
|
55
|
+
/** One action after the text, such as a link or a small Button. */
|
|
56
|
+
action?: ReactNode;
|
|
57
|
+
/** A consumer glyph (Lucide) before the text, hidden from assistive technology. */
|
|
58
|
+
icon?: ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Defaults to `alert` for danger and `status` for the other tones; a band
|
|
61
|
+
* is page chrome and defaults to `none`.
|
|
62
|
+
*/
|
|
63
|
+
role?: NoticeRole;
|
|
64
|
+
/**
|
|
65
|
+
* `band` is a full-width square strip in caption type, its words
|
|
66
|
+
* centered, such as an environment strip above the app header.
|
|
67
|
+
*/
|
|
68
|
+
variant?: NoticeVariant;
|
|
69
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|