@scalewing/react 1.25.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +110 -1
  3. package/dist/components/ActionBar.d.ts +14 -0
  4. package/dist/components/ActionBar.js +5 -2
  5. package/dist/components/DenominationGrid.d.ts +1 -1
  6. package/dist/components/DenominationGrid.js +3 -0
  7. package/dist/components/DescriptionList.d.ts +28 -0
  8. package/dist/components/DescriptionList.js +15 -0
  9. package/dist/components/Stepper.d.ts +44 -0
  10. package/dist/components/Stepper.js +77 -0
  11. package/dist/components/Steps.d.ts +18 -0
  12. package/dist/components/Steps.js +34 -0
  13. package/dist/components/action-bar/ActionBarSummary.d.ts +22 -0
  14. package/dist/components/action-bar/ActionBarSummary.js +23 -0
  15. package/dist/components/denomination-grid/ColumnHeadText.d.ts +7 -0
  16. package/dist/components/denomination-grid/ColumnHeadText.js +13 -0
  17. package/dist/components/denomination-grid/DenominationStrip.js +3 -7
  18. package/dist/components/denomination-grid/DenominationTiles.js +2 -1
  19. package/dist/components/denomination-grid/EntryCell.d.ts +15 -0
  20. package/dist/components/denomination-grid/EntryCell.js +16 -0
  21. package/dist/components/denomination-grid/RowLabelLine.d.ts +3 -2
  22. package/dist/components/denomination-grid/RowLabelLine.js +5 -3
  23. package/dist/components/denomination-grid/StripRow.d.ts +13 -0
  24. package/dist/components/denomination-grid/StripRow.js +19 -0
  25. package/dist/components/denomination-grid/types.d.ts +44 -1
  26. package/dist/components/denomination-grid/validate-entry.d.ts +15 -0
  27. package/dist/components/denomination-grid/validate-entry.js +78 -0
  28. package/dist/components/steps/Step.d.ts +24 -0
  29. package/dist/components/steps/Step.js +62 -0
  30. package/dist/components/steps/step-status.d.ts +26 -0
  31. package/dist/components/steps/step-status.js +37 -0
  32. package/dist/components/steps/steps-context.d.ts +16 -0
  33. package/dist/components/steps/steps-context.js +3 -0
  34. package/dist/css/css-action-bar-summary.d.ts +2 -0
  35. package/dist/css/css-action-bar-summary.js +48 -0
  36. package/dist/css/css-action-bar.js +4 -0
  37. package/dist/css/css-components.js +10 -0
  38. package/dist/css/css-denomination-entry.d.ts +2 -0
  39. package/dist/css/css-denomination-entry.js +78 -0
  40. package/dist/css/css-denomination-grid.js +4 -0
  41. package/dist/css/css-description-list.d.ts +12 -0
  42. package/dist/css/css-description-list.js +63 -0
  43. package/dist/css/css-stepper.d.ts +10 -0
  44. package/dist/css/css-stepper.js +126 -0
  45. package/dist/css/css-steps.d.ts +9 -0
  46. package/dist/css/css-steps.js +203 -0
  47. package/dist/css/stylesheet.js +6 -0
  48. package/dist/index.d.ts +6 -2
  49. package/dist/index.js +4 -1
  50. package/dist/stepper-count.d.ts +37 -0
  51. package/dist/stepper-count.js +50 -0
  52. package/dist/styles.css +367 -0
  53. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # @scalewing/react
2
2
 
3
+ ## 1.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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).
8
+
9
+ - `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.
10
+ - A blank `label`, `value` or `detail`, or an unknown `tone`, throws a `RangeError`.
11
+ - 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.
12
+
13
+ - db1d77b: `DenominationGrid` count entry (`docs/requests/teisoro-denomination-grid-entry.md`, Teisoro F-011 release R1), with the new types `DenominationGridEntry` and `DenominationGridRowEmphasis`:
14
+
15
+ - `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).
16
+ - `DenominationGridRow.caption` (a muted line under the label), `emphasis` (`'default' | 'quiet'`, the subtle fill) and `disabled` (faded, entry disabled, `aria-disabled`), both strip only.
17
+ - `DenominationGridColumn.caption`: a muted note after the column label, such as "× 100".
18
+ - Entry rows need the strip layout; bad caps, messages, button names, captions or emphasis throw a `RangeError`.
19
+ - `Stepper` is now `position: relative`, so its hidden label and empty error region never widen a scroll region's page.
20
+ - 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.
21
+
22
+ - 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).
23
+
24
+ - `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).
25
+ - `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.
26
+ - 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).
27
+ - 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.
28
+
29
+ - 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).
30
+
31
+ - `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.
32
+ - 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`.
33
+ - 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`.
34
+ - 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.
35
+
36
+ ## 1.26.0
37
+
38
+ ### Minor Changes
39
+
40
+ - e484799: `DescriptionList` and `DescriptionItem` (with `DescriptionListProps` and `DescriptionItemProps`): terms paired with what each says, one per row (`docs/requests/teisoro-description-list.md`, Teisoro F-006-S03).
41
+
42
+ - A `dl` of items, each a `div` grouping a `dt` (`term`) with its `dd` (`children`); the list and each item take their own HTML attributes and `className`.
43
+ - From `md` up the items share the list's two columns (`subgrid`): the term column as wide as the widest term, up to 40% of the list, then the detail. Every row aligns to its top, a hairline (`--sw-color-border`, `CanvasText` in forced colors) divides the rows, and the list is flush above its first row and below its last.
44
+ - Below `md` each term sits over its detail.
45
+ - A term and a detail are columns of lines: each child is its own line with its own line height, so a caption term starts level with a caption detail; each is as wide as its content unless it sets its own width (a `Progress` fills the detail), and long unbreakable words wrap. Wrap inline content in one element.
46
+ - New generated classes `sw-description-list`, `sw-description-item`, `sw-description-term` and `sw-description-detail`. No new tokens or dependencies.
47
+
3
48
  ## 1.25.0
4
49
 
5
50
  ### Minor Changes
package/README.md CHANGED
@@ -22,11 +22,16 @@ import {
22
22
  Tooltip,
23
23
  InfoTip,
24
24
  Separator,
25
+ DescriptionList,
26
+ DescriptionItem,
25
27
  DenominationGrid,
26
28
  FilterChips,
27
29
  Grid,
28
30
  DateField,
29
31
  Field,
32
+ Stepper,
33
+ Steps,
34
+ Step,
30
35
  Select,
31
36
  Switch,
32
37
  Split,
@@ -50,7 +55,25 @@ Import the CSS once at the application entry. Do not copy it into your source tr
50
55
 
51
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.
52
57
 
53
- `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. `Select` is a labeled listbox menu when the open list must match the 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.
54
77
 
55
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.
56
79
 
@@ -82,6 +105,21 @@ A disabled native text control (`<input>`, `<select>` or `<textarea>` on the can
82
105
  </Field>
83
106
  ```
84
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
+
85
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.
86
124
 
87
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`.
@@ -132,6 +170,26 @@ A disabled native text control (`<input>`, `<select>` or `<textarea>` on the can
132
170
 
133
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`.
134
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
+
135
193
  ```tsx
136
194
  <Stack gap={4}>
137
195
  {longForm}
@@ -310,6 +368,27 @@ A header `TableCell` (`as="th"`) with `sort` (`'ascending'`, `'descending'` or `
310
368
 
311
369
  `Separator` divides sections with a token-colored line. It is horizontal by default; use `orientation="vertical"` in a flex row. Set `decorative` when the line only supports layout so assistive technology ignores it.
312
370
 
371
+ `DescriptionList` pairs terms with what each says, one `DescriptionItem` per row: a `dl` whose items each group a `dt` (`term`) with its `dd` (`children`). From `md` up the term sits in a start column as wide as the widest term, up to 40% of the list, and the detail beside it; every row aligns to its top, so a term and its detail's first line start level, and a hairline divides the rows, with no space above the first row or below the last. Below `md` each term sits over its detail. The list sets only the layout: pass your own `Text` for the term and the detail. Each child of a term or a detail is its own line with its own line height (a column), as wide as its content unless it sets its own width (a `Progress` fills the detail; a `Field` keeps its input's width): wrap inline content, such as words and a link on one line, in one element such as an `Inline`. Long unbreakable words wrap inside their column. A `dl` has no ARIA role, so name a list with a visible heading, and `aria-labelledby` on the `section` around it, rather than an `aria-label` on the list.
372
+
373
+ ```tsx
374
+ <DescriptionList>
375
+ <DescriptionItem
376
+ term={
377
+ <Text variant="caption" color="muted">
378
+ Wetland
379
+ </Text>
380
+ }
381
+ >
382
+ <Inline align="start" gap={2} wrap>
383
+ <Text variant="data">12 herons</Text>
384
+ <Text variant="caption" color="muted">
385
+ Recounted
386
+ </Text>
387
+ </Inline>
388
+ </DescriptionItem>
389
+ </DescriptionList>
390
+ ```
391
+
313
392
  `Card` defaults to `glass`; `outlined` and `elevated` are solid surfaces, and `filled` is a quiet `--sw-color-subtle` fill with no border for plain information apart from pressable rows.
314
393
 
315
394
  `--sw-color-accentSubtle` is the accent tint from `@scalewing/tokens`: a quiet fill behind accent text or glyphs that keeps 4.5:1. The stylesheet and every palette file set it, and a palette that moves the accent or surface retints it.
@@ -346,6 +425,36 @@ Values outside the catalog throw a `RangeError`. There is no per-breakpoint span
346
425
 
347
426
  `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.
348
427
 
428
+ 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`.
429
+
430
+ ```tsx
431
+ <DenominationGrid
432
+ label="Tag packs"
433
+ columns={[
434
+ { key: 's', label: 'S', caption: '× 10' },
435
+ { key: 'm', label: 'M' },
436
+ ]}
437
+ totalLabel="Total"
438
+ rows={[
439
+ {
440
+ id: 'fitted',
441
+ label: 'Fitted',
442
+ caption: 'this morning',
443
+ cells: fitted,
444
+ total: `${tags(fitted)} tags`,
445
+ entry: {
446
+ onChange: (key, value) => setCount(key, value),
447
+ decrementLabel: 'One less',
448
+ incrementLabel: 'One more',
449
+ max: [6, 4],
450
+ placeholder: '0',
451
+ },
452
+ },
453
+ { id: 'stock', label: 'In stock', emphasis: 'quiet', cells: [6, 4] },
454
+ ]}
455
+ />
456
+ ```
457
+
349
458
  `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.
350
459
 
351
460
  `DateField` is a controlled date-only field that draws its own calendar. Supply `value`, `min`, and `max` as valid `YYYY-MM-DD` dates; use an empty `value` for a blank field. Its callback returns a date-only string or an empty string, without timezone conversion. Invalid serialized dates and inverted bounds throw; a value outside the bounds remains visible with invalid styling. `required` marks the label with the same `aria-hidden` asterisk as `Field` and sets the entry's native `required`. Its label is drawn as `Field`'s (the same label words and mark inside a `<label>` that keeps the canvas type), so its label row and its gap to the entry match a `Field` beside it. For a blank required field, supply `error` when form validation runs to show the message and invalid styling.
@@ -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
- return (_jsxs("div", { ref: ref, className: cx('sw-action-bar', actionBarStickyClass(stickyBelow), className), ...rest, children: [_jsx("div", { className: "sw-action-bar-status", role: "status", children: status }), _jsx("div", { className: "sw-action-bar-actions", children: children })] }));
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,3 @@
1
1
  import { type DenominationGridProps } from './denomination-grid/types.js';
2
- export type { DenominationGridColumn, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowRole, DenominationGridTone, DenominationLabelWidth, } from './denomination-grid/types.js';
2
+ export type { DenominationGridColumn, DenominationGridEntry, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowEmphasis, DenominationGridRowRole, DenominationGridTone, DenominationLabelWidth, } from './denomination-grid/types.js';
3
3
  export declare function DenominationGrid({ label, columns, rows, layout, subtotal, zeroLabel, totalLabel, rowRole, labelWidth, }: DenominationGridProps): import("react").JSX.Element;
@@ -3,8 +3,11 @@ import { denominationLabelWidths } from '../css/css-denomination-aligned.js';
3
3
  import { DenominationStrip } from './denomination-grid/DenominationStrip.js';
4
4
  import { DenominationTiles } from './denomination-grid/DenominationTiles.js';
5
5
  import { assertDenominationGrid } from './denomination-grid/validate.js';
6
+ import { assertDenominationEntries, assertDenominationExtras, } from './denomination-grid/validate-entry.js';
6
7
  export function DenominationGrid({ label, columns, rows, layout = 'strip', subtotal, zeroLabel = '—', totalLabel, rowRole = 'region', labelWidth, }) {
7
8
  assertDenominationGrid(label, columns, rows);
9
+ assertDenominationExtras(layout, columns, rows);
10
+ assertDenominationEntries(layout, columns, rows);
8
11
  if (!zeroLabel)
9
12
  throw new RangeError('zeroLabel must not be empty');
10
13
  if (totalLabel !== undefined && !totalLabel.trim())
@@ -0,0 +1,28 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type DescriptionListProps = Omit<HTMLAttributes<HTMLDListElement>, 'children'> & {
3
+ /** The list's `DescriptionItem`s. */
4
+ children: ReactNode;
5
+ };
6
+ /**
7
+ * Terms and what each one says, one item per row: the term in a start
8
+ * column as wide as the widest term (up to 40% of the list), the detail
9
+ * beside it, a hairline between rows, every row aligned to its top. Below
10
+ * the layout breakpoint (`md`) each term sits over its detail.
11
+ */
12
+ export declare function DescriptionList({ children, className, ...rest }: DescriptionListProps): import("react").JSX.Element;
13
+ export type DescriptionItemProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
14
+ /**
15
+ * What the row describes, such as a name; the consumer's own `Text`. Each
16
+ * child of the term, and of the detail, is its own line.
17
+ */
18
+ term: ReactNode;
19
+ /**
20
+ * What the term says: text, a link, or several lines. Each child is its
21
+ * own line, as wide as its content unless it sets its own width (a
22
+ * `Progress` fills the detail); wrap words and a link that share a line
23
+ * in one element such as `Inline`.
24
+ */
25
+ children: ReactNode;
26
+ };
27
+ /** One row of a `DescriptionList`: a `dt` and its `dd` in one group. */
28
+ export declare function DescriptionItem({ term, children, className, ...rest }: DescriptionItemProps): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from '../class-names.js';
3
+ /**
4
+ * Terms and what each one says, one item per row: the term in a start
5
+ * column as wide as the widest term (up to 40% of the list), the detail
6
+ * beside it, a hairline between rows, every row aligned to its top. Below
7
+ * the layout breakpoint (`md`) each term sits over its detail.
8
+ */
9
+ export function DescriptionList({ children, className, ...rest }) {
10
+ return (_jsx("dl", { className: cx('sw-description-list', className), ...rest, children: children }));
11
+ }
12
+ /** One row of a `DescriptionList`: a `dt` and its `dd` in one group. */
13
+ export function DescriptionItem({ term, children, className, ...rest }) {
14
+ return (_jsxs("div", { className: cx('sw-description-item', className), ...rest, children: [_jsx("dt", { className: "sw-description-term", children: term }), _jsx("dd", { className: "sw-description-detail", children: children })] }));
15
+ }
@@ -0,0 +1,44 @@
1
+ /** `fixed` sizes the count for a few digits; `full` fills its container, such as a table cell. */
2
+ export type StepperWidth = 'fixed' | 'full';
3
+ export type StepperProps = {
4
+ /** Names the count. Keep it visible unless headers around it say it (`labelVisuallyHidden`). */
5
+ label: string;
6
+ labelVisuallyHidden?: boolean;
7
+ /** The count, or `null` for an empty box (none). */
8
+ value: number | null;
9
+ /** A whole count, or `null` when the box is emptied. Typed counts are reported as typed, even outside the bounds. */
10
+ onChange: (value: number | null) => void;
11
+ /** The lowest count − reaches; defaults to 0. */
12
+ min?: number;
13
+ /** The highest count + reaches; no upper bound when left out. */
14
+ max?: number;
15
+ /** How much one press, or one arrow key, moves the count; defaults to 1. */
16
+ step?: number;
17
+ /** The − button's name, such as "One less". */
18
+ decrementLabel: string;
19
+ /** The + button's name, such as "One more". */
20
+ incrementLabel: string;
21
+ /**
22
+ * A message under the control, as `Field`'s: a polite live region linked by
23
+ * `aria-describedby`, with `aria-invalid` and the danger border. An empty
24
+ * string is no error.
25
+ */
26
+ error?: string;
27
+ /** `Field`'s changed look (the accent border and tint) for a count someone changed, or a suggestion. `error` wins. */
28
+ changed?: boolean;
29
+ disabled?: boolean;
30
+ /** Readable and focusable, never changed: the buttons are hidden and keep their room. */
31
+ readOnly?: boolean;
32
+ /** Says the count in words for assistive technology (`aria-valuetext`), such as "12 straps". */
33
+ valueText?: string;
34
+ /** Shown in an empty box, such as "0". */
35
+ placeholder?: string;
36
+ name?: string;
37
+ width?: StepperWidth;
38
+ };
39
+ /**
40
+ * A whole-number count between − and + buttons that can also be typed: one
41
+ * tab stop (the count, a spin button that ArrowUp and ArrowDown step), with
42
+ * the buttons as pointer targets beside it. − stops at `min`, + at `max`.
43
+ */
44
+ export declare const Stepper: import("react").ForwardRefExoticComponent<StepperProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,77 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useId, useState, } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ import { assertStepperBounds, readStepperEntry, stepperMoves, stepperText, } from '../stepper-count.js';
6
+ import { FieldErrorRegion } from './FieldErrorRegion.js';
7
+ import { FieldLabelText } from './FieldLabelText.js';
8
+ /** Keeps focus where it was (the count, or nowhere) when a button is pressed. */
9
+ function keepFocus(event) {
10
+ event.preventDefault();
11
+ }
12
+ /**
13
+ * A whole-number count between − and + buttons that can also be typed: one
14
+ * tab stop (the count, a spin button that ArrowUp and ArrowDown step), with
15
+ * the buttons as pointer targets beside it. − stops at `min`, + at `max`.
16
+ */
17
+ export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden = false, value, onChange, min = 0, max = Number.POSITIVE_INFINITY, step = 1, decrementLabel, incrementLabel, error, changed = false, disabled = false, readOnly = false, valueText, placeholder, name, width = 'fixed', }, ref) {
18
+ const bounds = { min, max, step };
19
+ assertStepperBounds(bounds);
20
+ if (value !== null && !Number.isSafeInteger(value))
21
+ throw new RangeError('Stepper value must be a whole number or null');
22
+ const inputId = useId();
23
+ const labelId = useId();
24
+ const errorId = useId();
25
+ const [draft, setDraft] = useState(null);
26
+ const allowsNegative = min < 0;
27
+ const moves = stepperMoves(value, bounds, disabled || readOnly);
28
+ // The typed text stays while it means the count the consumer holds, so
29
+ // "007" or a lone minus survives a render; otherwise the count's text.
30
+ const draftEntry = draft === null ? null : readStepperEntry(draft, allowsNegative);
31
+ const draftShowsValue = draftEntry !== null &&
32
+ (draftEntry.kind === 'empty'
33
+ ? value === null
34
+ : draftEntry.kind === 'count' && draftEntry.value === value);
35
+ const text = draft !== null && draftShowsValue ? draft : stepperText(value);
36
+ function move(next) {
37
+ if (next === null)
38
+ return;
39
+ setDraft(null);
40
+ onChange(next);
41
+ }
42
+ function onType(event) {
43
+ const next = event.target.value;
44
+ const entry = readStepperEntry(next, allowsNegative);
45
+ // A refused keystroke changes nothing: the controlled box keeps its text.
46
+ if (entry.kind === 'refused')
47
+ return;
48
+ setDraft(next);
49
+ const count = entry.kind === 'count' ? entry.value : null;
50
+ if (count !== value)
51
+ onChange(count);
52
+ }
53
+ /** ArrowUp and ArrowDown step; Home and End go to min and a finite max. */
54
+ function keyTarget(key) {
55
+ if (key === 'ArrowUp')
56
+ return moves.up;
57
+ if (key === 'ArrowDown')
58
+ return moves.down;
59
+ if (key === 'Home')
60
+ return moves.down === null ? null : min;
61
+ if (key === 'End' && Number.isFinite(max))
62
+ return moves.up === null ? null : max;
63
+ return undefined;
64
+ }
65
+ function onKeyDown(event) {
66
+ const target = keyTarget(event.key);
67
+ if (target === undefined)
68
+ return;
69
+ event.preventDefault();
70
+ move(target);
71
+ }
72
+ const invalid = Boolean(error);
73
+ return (_jsxs("div", { className: cx('sw-stepper', width === 'full' && 'sw-stepper-full', invalid && 'sw-stepper-invalid', changed && !invalid && 'sw-stepper-changed', readOnly && 'sw-stepper-readonly'), children: [_jsx("label", { className: labelVisuallyHidden ? 'sw-sr-only' : undefined, htmlFor: inputId, id: labelId, children: _jsx(FieldLabelText, { label: label }) }), _jsxs("div", { "aria-labelledby": labelId, className: "sw-stepper-track", role: "group", children: [_jsx("button", { "aria-controls": inputId, "aria-label": decrementLabel, className: "sw-stepper-button", disabled: moves.down === null, onClick: () => move(moves.down), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: '−' }) }), _jsx("input", { ref: ref, "aria-describedby": invalid ? errorId : undefined, "aria-invalid": invalid || undefined, "aria-valuemax": Number.isFinite(max) ? max : undefined, "aria-valuemin": min, "aria-valuenow": value ?? undefined, "aria-valuetext": valueText, autoComplete: "off", className: "sw-stepper-input", disabled: disabled, id: inputId,
74
+ // iOS's numeric pad has no minus key, so a range below zero takes
75
+ // the text keyboard.
76
+ inputMode: allowsNegative ? 'text' : 'numeric', name: name, onBlur: () => setDraft(null), onChange: onType, onKeyDown: onKeyDown, placeholder: placeholder, readOnly: readOnly, role: "spinbutton", type: "text", value: text }), _jsx("button", { "aria-controls": inputId, "aria-label": incrementLabel, className: "sw-stepper-button", disabled: moves.up === null, onClick: () => move(moves.up), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: "+" }) })] }), _jsx(FieldErrorRegion, { className: "sw-field-error", id: errorId, message: error })] }));
77
+ });
@@ -0,0 +1,18 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ import { type StepHeadingLevel, type StepsLabels } from './steps/step-status.js';
3
+ export { Step, type StepProps } from './steps/Step.js';
4
+ export type { StepHeadingLevel, StepsLabels, StepStatus, } from './steps/step-status.js';
5
+ export type StepsProps = Omit<HTMLAttributes<HTMLOListElement>, 'children'> & {
6
+ /** The `Step`s, in order; each is numbered by its place. */
7
+ children: ReactNode;
8
+ /** Each step title's heading level; defaults to 2. */
9
+ headingLevel?: StepHeadingLevel;
10
+ /** The status words assistive technology hears after a folded step's title. English by default. */
11
+ labels?: Partial<StepsLabels>;
12
+ };
13
+ /**
14
+ * A one-page vertical stepper: a form's steps in order, the current one
15
+ * open, the done ones folded to a line with their summary and Edit, the
16
+ * ones ahead greyed, a line joining each marker to the next.
17
+ */
18
+ export declare function Steps({ children, className, headingLevel, labels, ...rest }: StepsProps): import("react").JSX.Element;
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Children, Fragment, isValidElement, useRef, } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ import { defaultStepsLabels, stepHeadingLevels, } from './steps/step-status.js';
6
+ import { StepPlaceContext } from './steps/steps-context.js';
7
+ export { Step } from './steps/Step.js';
8
+ function assertSteps(labels, level) {
9
+ if (Object.values(labels).some((word) => typeof word !== 'string' || !word.trim()))
10
+ throw new RangeError('Steps labels must not be empty');
11
+ if (!stepHeadingLevels.includes(level))
12
+ throw new RangeError('Steps headingLevel must be 2, 3 or 4');
13
+ }
14
+ /**
15
+ * A one-page vertical stepper: a form's steps in order, the current one
16
+ * open, the done ones folded to a line with their summary and Edit, the
17
+ * ones ahead greyed, a line joining each marker to the next.
18
+ */
19
+ export function Steps({ children, className, headingLevel = 2, labels, ...rest }) {
20
+ const words = { ...defaultStepsLabels, ...labels };
21
+ assertSteps(words, headingLevel);
22
+ const steps = Children.toArray(children).filter(isValidElement);
23
+ // A Fragment would share one number among its steps: pass them directly.
24
+ if (steps.some((step) => step.type === Fragment))
25
+ throw new RangeError('Steps children must be Steps, not a Fragment');
26
+ // The first step to open in a commit claims focus (see Step).
27
+ const focusClaimRef = useRef(false);
28
+ return (_jsx("ol", { className: cx('sw-steps', className), ...rest, children: steps.map((step, index) => (_jsx(StepPlaceContext.Provider, { value: {
29
+ number: index + 1,
30
+ headingLevel,
31
+ labels: words,
32
+ focusClaimRef,
33
+ }, children: step }, step.key))) }));
34
+ }
@@ -0,0 +1,22 @@
1
+ export declare const actionBarSummaryTones: readonly ["default", "danger", "success"];
2
+ /** The value's color: the text color, or danger (short) or success (matched). */
3
+ export type ActionBarSummaryTone = (typeof actionBarSummaryTones)[number];
4
+ /** A labelled figure for the bar, such as "Counted $33,790.00 short $4,000.00". */
5
+ export type ActionBarSummary = {
6
+ /** What the figure is, such as "Counted". */
7
+ label: string;
8
+ /** The figure, formatted by the consumer, such as "$33,790.00". */
9
+ value: string;
10
+ /** A short line after the figure, such as "short $4,000.00". */
11
+ detail?: string;
12
+ /** Colors the value only; the words say why. Defaults to `default`. */
13
+ tone?: ActionBarSummaryTone;
14
+ };
15
+ export declare function assertActionBarSummary(summary: ActionBarSummary): void;
16
+ /**
17
+ * The bar's figure: its label, the value in the heading variant at tabular
18
+ * figures, and its detail, on one baseline, wrapping as one phrase.
19
+ */
20
+ export declare function ActionBarSummaryLine({ summary, }: {
21
+ summary: ActionBarSummary;
22
+ }): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from '../../class-names.js';
3
+ export const actionBarSummaryTones = ['default', 'danger', 'success'];
4
+ function isBlank(text) {
5
+ return typeof text !== 'string' || !text.trim();
6
+ }
7
+ export function assertActionBarSummary(summary) {
8
+ if (isBlank(summary.label) || isBlank(summary.value))
9
+ throw new RangeError('ActionBar summary needs a label and a value');
10
+ if (summary.detail !== undefined && isBlank(summary.detail))
11
+ throw new RangeError('ActionBar summary detail must not be empty');
12
+ if (summary.tone !== undefined &&
13
+ !actionBarSummaryTones.includes(summary.tone))
14
+ throw new RangeError(`Unknown ActionBar summary tone: ${String(summary.tone)}`);
15
+ }
16
+ /**
17
+ * The bar's figure: its label, the value in the heading variant at tabular
18
+ * figures, and its detail, on one baseline, wrapping as one phrase.
19
+ */
20
+ export function ActionBarSummaryLine({ summary, }) {
21
+ const tone = summary.tone ?? 'default';
22
+ return (_jsxs("span", { className: cx('sw-action-bar-summary', tone !== 'default' && `sw-action-bar-summary-${tone}`), children: [_jsx("span", { className: "sw-action-bar-summary-label", children: summary.label }), ' ', _jsx("span", { className: "sw-action-bar-summary-value sw-text-heading sw-tabular", children: summary.value }), summary.detail ? (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "sw-action-bar-summary-detail", children: summary.detail })] })) : null] }));
23
+ }
@@ -0,0 +1,7 @@
1
+ import { type DenominationGridColumn } from './types.js';
2
+ /** A column's name as its header reads: the label, then its caption ("$1 × 100"). */
3
+ export declare function columnName(column: DenominationGridColumn): string;
4
+ /** A column's label and, after it, its muted caption ("$1 × 100"). */
5
+ export declare function ColumnHeadText({ column }: {
6
+ column: DenominationGridColumn;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** A column's name as its header reads: the label, then its caption ("$1 × 100"). */
3
+ export function columnName(column) {
4
+ return column.caption === undefined
5
+ ? column.label
6
+ : `${column.label} ${column.caption}`;
7
+ }
8
+ /** A column's label and, after it, its muted caption ("$1 × 100"). */
9
+ export function ColumnHeadText({ column }) {
10
+ if (column.caption === undefined)
11
+ return _jsx(_Fragment, { children: column.label });
12
+ return (_jsxs(_Fragment, { children: [column.label, ' ', _jsx("span", { className: "sw-denomination-head-caption", children: column.caption })] }));
13
+ }