@scalewing/react 1.31.0 → 1.32.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 (86) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +19 -5
  3. package/dist/components/ActionMenu.d.ts +10 -1
  4. package/dist/components/ActionMenu.js +7 -2
  5. package/dist/components/AppHeader.d.ts +9 -1
  6. package/dist/components/AppHeader.js +6 -2
  7. package/dist/components/Box.d.ts +10 -2
  8. package/dist/components/Button.d.ts +27 -2
  9. package/dist/components/Button.js +5 -3
  10. package/dist/components/ButtonGroup.d.ts +9 -1
  11. package/dist/components/ButtonGroup.js +8 -2
  12. package/dist/components/Combobox.js +2 -2
  13. package/dist/components/DateField.d.ts +9 -1
  14. package/dist/components/DateField.js +4 -3
  15. package/dist/components/DenominationGrid.d.ts +5 -3
  16. package/dist/components/DenominationGrid.js +21 -11
  17. package/dist/components/Dialog.d.ts +9 -1
  18. package/dist/components/Dialog.js +8 -4
  19. package/dist/components/Field.d.ts +8 -0
  20. package/dist/components/Field.js +4 -3
  21. package/dist/components/FieldLabelText.d.ts +21 -3
  22. package/dist/components/FieldLabelText.js +16 -4
  23. package/dist/components/FilterChips.d.ts +9 -1
  24. package/dist/components/FilterChips.js +4 -2
  25. package/dist/components/Nav.d.ts +14 -2
  26. package/dist/components/Nav.js +6 -2
  27. package/dist/components/SegmentedControl.d.ts +5 -2
  28. package/dist/components/SegmentedControl.js +2 -1
  29. package/dist/components/Select.d.ts +9 -1
  30. package/dist/components/Select.js +4 -3
  31. package/dist/components/Stepper.d.ts +13 -0
  32. package/dist/components/Stepper.js +8 -2
  33. package/dist/components/combobox/types.d.ts +8 -0
  34. package/dist/components/denomination-grid/DenominationStrip.d.ts +4 -2
  35. package/dist/components/denomination-grid/DenominationStrip.js +3 -2
  36. package/dist/components/denomination-grid/DenominationTiles.d.ts +1 -1
  37. package/dist/components/denomination-grid/EntryCell.js +1 -1
  38. package/dist/components/denomination-grid/StripRow.js +5 -2
  39. package/dist/components/denomination-grid/types.d.ts +35 -1
  40. package/dist/components/dialog/DialogTitle.d.ts +5 -3
  41. package/dist/components/dialog/DialogTitle.js +4 -4
  42. package/dist/css/css-action-menu-trigger.d.ts +11 -0
  43. package/dist/css/css-action-menu-trigger.js +66 -0
  44. package/dist/css/css-action-menu.js +7 -2
  45. package/dist/css/css-app-header-bar.d.ts +10 -0
  46. package/dist/css/css-app-header-bar.js +26 -0
  47. package/dist/css/css-box-border.d.ts +6 -1
  48. package/dist/css/css-box-border.js +8 -4
  49. package/dist/css/css-button-group-attached.js +5 -2
  50. package/dist/css/css-button-group-track.d.ts +11 -0
  51. package/dist/css/css-button-group-track.js +48 -0
  52. package/dist/css/css-button.d.ts +19 -2
  53. package/dist/css/css-button.js +31 -1
  54. package/dist/css/css-chrome.js +14 -1
  55. package/dist/css/css-combobox.js +2 -1
  56. package/dist/css/css-components.js +3 -0
  57. package/dist/css/css-data.js +3 -2
  58. package/dist/css/css-date-field.js +2 -1
  59. package/dist/css/css-denomination-appearance.d.ts +21 -0
  60. package/dist/css/css-denomination-appearance.js +114 -0
  61. package/dist/css/css-denomination-cell-tones.d.ts +8 -0
  62. package/dist/css/css-denomination-cell-tones.js +20 -1
  63. package/dist/css/css-denomination-grid.js +4 -0
  64. package/dist/css/css-dialog-banded.d.ts +11 -0
  65. package/dist/css/css-dialog-banded.js +84 -0
  66. package/dist/css/css-multi-select.js +2 -1
  67. package/dist/css/css-nav-words.d.ts +13 -0
  68. package/dist/css/css-nav-words.js +51 -0
  69. package/dist/css/css-notice.d.ts +4 -11
  70. package/dist/css/css-notice.js +6 -22
  71. package/dist/css/css-segmented-raised.d.ts +3 -0
  72. package/dist/css/css-segmented-raised.js +72 -0
  73. package/dist/css/css-segmented.js +9 -3
  74. package/dist/css/css-select.js +2 -1
  75. package/dist/css/css-split.js +2 -1
  76. package/dist/css/css-stepper.js +7 -0
  77. package/dist/css/quiet-fill.d.ts +7 -0
  78. package/dist/css/quiet-fill.js +7 -0
  79. package/dist/css/stacking.js +2 -1
  80. package/dist/css/stylesheet.js +2 -0
  81. package/dist/css/tone-tint.d.ts +21 -0
  82. package/dist/css/tone-tint.js +23 -0
  83. package/dist/index.d.ts +11 -9
  84. package/dist/index.js +9 -7
  85. package/dist/styles.css +300 -10
  86. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @scalewing/react
2
2
 
3
+ ## 1.32.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2e203ef: `ActionMenu` `triggerAppearance` (`ActionMenuTriggerAppearance`, `actionMenuTriggerAppearances`): `'framed'` (default, unchanged) or `'text'`, an unframed trigger drawn as its words and a chevron in the text color, the quiet fill on hover and while open, the sm height with the 44 px coarse-pointer target (`docs/requests/teisoro-action-menu-text-trigger.md`, Teisoro F-011 release R6, S17 shell). An unknown value throws a `RangeError`. New generated class `sw-action-menu-trigger-text`. No new tokens or dependencies.
8
+ - 36e9de7: A full-width bar look for page chrome, opt-in (`docs/requests/teisoro-app-header-bar.md`, `docs/requests/teisoro-box-border-bottom.md`, Teisoro F-011 release R6):
9
+
10
+ - `AppHeader` `appearance` (`AppHeaderAppearance`, `appHeaderAppearances`): `'glass'` (default, unchanged) or `'bar'`, a full-width band of the solid surface with the border hairline under it, no blur, rounding, side or top border. An unknown value throws a `RangeError`.
11
+ - `Box` `border="bottom"` draws only the hairline under the box, for a sub-bar under the header. Every `Box`-based component takes it.
12
+
13
+ New generated classes `sw-app-header-bar`, `sw-border-bottom`. No new tokens or dependencies.
14
+
15
+ - d0eabcd: `ButtonGroup` `appearance` (`ButtonGroupAppearance`, `buttonGroupAppearances`): `'default'` (unchanged) or `'track'`, an attached group's buttons as words in the text color on one quiet grey track with a border hairline between them, whatever their variant (`docs/requests/teisoro-button-group-track.md`, Teisoro F-011 release R6, S17 shell; closes the "attached day buttons as separate filled buttons" item of D-37). An unknown value, or `track` without `attached`, throws a `RangeError`. New generated class `sw-button-group-track`. No new tokens or dependencies.
16
+ - ce334c3: `Button` `variant="neutral"` (web only; `webButtonVariants`, and the React `ButtonVariant` type now includes it) is a quiet grey fill in the text color, and `disabledAppearance` (`ButtonDisabledAppearance`, `buttonDisabledAppearances`): `'faded'` (default, unchanged) or `'neutral'`, a disabled button on that grey with muted words at full opacity, whatever its variant; a ghost still fades (`docs/requests/teisoro-button-neutral.md`, Teisoro F-011 release R6). An unknown `disabledAppearance` throws a `RangeError`. A Badge inside a neutral button takes the surface, as in any filled button. `CalendarButton` `variant` takes the same web list. The quiet grey fill is now one named constant (`css/quiet-fill.ts`) shared by the menu, list, calendar, split and dialog rules; their CSS is unchanged. New generated classes `sw-button-neutral`, `sw-button-disabled-neutral`. No new tokens or dependencies; `@scalewing/tokens` `buttonVariants` and React Native are unchanged.
17
+ - 547fe7a: `DenominationGrid` framed count-sheet look, every option off by default so existing grids are unchanged (`docs/requests/teisoro-denomination-grid-frame.md`, Teisoro F-011 release R6):
18
+
19
+ - `frame` (`DenominationGridFrame`, `denominationGridFrames`): `'none'` (default) or `'table'`, a strip drawn as a framed table (a border hairline at the `sm` radius on its scroll region, the heading row on the subtle fill, a rule between columns, counts at label size centred). The tiles layout ignores it.
20
+ - `totalEmphasis` (`DenominationTotalEmphasis`, `denominationTotalEmphases`): `'default'` or `'strong'`, bold label-size totals, a neutral row's in the text color.
21
+ - `cellToneAppearance` (`DenominationCellToneAppearance`, `denominationCellToneAppearances`): `'text'` or `'pill'`, a toned read-only strip count on its tone's tint in the tone's ink (the Notice's fill and words); outlined in forced colors.
22
+ - `zeroLabel=""` now leaves zero counts blank instead of throwing; a non-string `zeroLabel` throws.
23
+ - `DenominationGridEntry.limitAppearance` passes through to the entry Steppers.
24
+
25
+ Every read-only strip count now sits in a `sw-denomination-cell-value` span (no visual change). New generated classes `sw-denomination-scroll-framed`, `sw-denomination-framed`, `sw-denomination-totals-strong`, `sw-denomination-cell-tones-pill`, `sw-denomination-cell-value`. The Notice's tint and ink moved to a shared `tone-tint` module (no change to its CSS). No new tokens or dependencies.
26
+
27
+ - 8666e25: `Dialog` `appearance` (`DialogAppearance`, `dialogAppearances`): `'glass'` (default, unchanged) or `'banded'`, the dialog on the solid surface with the medium elevation, its title row a full-width band over a hairline (a lone title too), the footer the band it already is, and the close button a circle on the quiet muted fill (`docs/requests/teisoro-dialog-banded.md`, Teisoro F-011 release R6). Forced colors outline the dialog and keep the hairlines. An unknown value throws a `RangeError`. New generated class `sw-dialog-banded`. No new tokens or dependencies.
28
+ - 35dc401: `Nav` `appearance` (`NavAppearance`, `navAppearances`): `'default'` (unchanged) or `'words'`, every destination (a link or a Button of any variant) as plain words, the others muted at medium weight and the current one (`aria-current`) in the text color at label weight, with no fill, visible border or underline (destinations need visible words: a glyph-only one keeps `default`); forced colors draw the current one's border in Highlight and underline it (`docs/requests/teisoro-nav-words.md`, Teisoro F-011 release R6, S17 shell). An unknown value throws a `RangeError`. New generated class `sw-nav-words`. No new tokens or dependencies.
29
+ - 0a659ef: `requiredMark` (`RequiredMark`, `requiredMarks`) on `Field`, `Select`, `DateField`, `Combobox` and `FilterChips`: `'visible'` (default, unchanged) or `'hidden'`, which leaves out the label's `aria-hidden` " \*" while the control stays required (native `required` or `aria-required`) (`docs/requests/teisoro-required-mark.md`, Teisoro F-011 release R6). An unknown value throws a `RangeError`. No new classes, tokens or dependencies.
30
+ - 026764c: `SegmentedControl` `variant="raised"` (`SegmentedControlVariant` gains `'raised'`): a grey track (the quiet muted fill, no border) as wide as its segments, every segment the md control height in label type with its words in the text color, the selected one the surface raised by the small elevation (`docs/requests/teisoro-segmented-control-raised.md`, Teisoro F-011 release R6). In the dark scheme the selected segment draws a muted hairline ring (a shadow does not show there). Item icons, `error` (its danger outline is restated after the raised rules) and `disabled` behave as on the other tracks; the compact icon track's rules exclude it (their specificity unchanged). The default `compact` and `filled` tracks are unchanged. New generated class `sw-segmented-raised`. No new tokens or dependencies.
31
+ - 547fe7a: `Stepper` `limitAppearance` (`StepperLimitAppearance`, `stepperLimitAppearances`): `'faded'` (default, unchanged) or `'plain'`, which keeps − at `min` and + at `max` in their usual look while they stay disabled (`docs/requests/teisoro-stepper-limit.md`, Teisoro F-011 release R6). A disabled or read-only Stepper is unchanged; forced colors still grey a button at its limit. An unknown value throws a `RangeError`. New generated class `sw-stepper-limit-plain`. No new tokens or dependencies.
32
+
3
33
  ## 1.31.0
4
34
 
5
35
  ### Minor Changes
package/README.md CHANGED
@@ -53,7 +53,9 @@ import {
53
53
 
54
54
  Import the CSS once at the application entry. Do not copy it into your source tree. Optional: import one `@scalewing/react/palette/<id>.css` file after it, or set `data-palette` on the `data-theme` node. React apps can set `palette` on `ThemeProvider` instead.
55
55
 
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.
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. `border="bottom"` draws only the hairline under the box (`sw-border-bottom`, solid), for a full-width bar such as a sub-navigation strip under the header; give it `background="surface"` to read as a band. It does not make the box look or act like an input. Every `Box`-based component takes it.
57
+
58
+ `AppHeader` is the page's top chrome, sticky by default (`sticky={false}` leaves it in flow). Its default `appearance="glass"` is a rounded frosted card. `appearance="bar"` (`AppHeaderAppearance`, `appHeaderAppearances`) is a full-width band of the solid surface with the border hairline under it: no blur, no rounding, no side or top border, the same under Reduce Transparency, and the hairline kept in forced colors. Set it against the page's edges (no side gutter round it) and put the gutter inside it. An unknown `appearance` throws a `RangeError`.
57
59
 
58
60
  `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
61
 
@@ -77,6 +79,8 @@ Import the CSS once at the application entry. Do not copy it into your source tr
77
79
 
78
80
  `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.
79
81
 
82
+ `requiredMark="hidden"` (default `"visible"`; `requiredMarks`, `RequiredMark`) leaves out the label's `aria-hidden` " *" on a required `Field`, `Select`, `DateField`, `Combobox` or `FilterChips`, for a form that marks nothing as required (or marks the optional fields instead). The control stays required (native `required` or `aria-required`), so assistive technology still hears it; say in the form's words which fields must be filled. An unknown value throws a `RangeError`.
83
+
80
84
  `Field` `changed` marks a value the person changed from a saved one, such as a field in a correction: the control's border, or an adorned field's frame, takes the accent color a hairline thicker (an inset shadow, so nothing moves; `Highlight` in forced colors), and its fill takes a 12 % accent tint over its own background, so a changed field reads apart from the focused one, whose ring never tints. The value keeps 4.5:1 or more on the tint in every palette; the tint is on text controls and selects (not a range, color or file input), a select's chevron stays where its size puts it, while a placeholder shows the fill stays plain, on the tint an adorned field's prefix and suffix take the text color, and forced colors drop the tint. It is a cue beside the words, so say what the value was in `description`; it sets no ARIA state, and `invalid` and `error` win over it. Like `invalid`, passing it, even `false` but not `undefined`, needs one native control child from the first render (a `TypeError` otherwise).
81
85
 
82
86
  ```tsx
@@ -107,7 +111,7 @@ A disabled native text control (`<input>`, `<select>` or `<textarea>` on the can
107
111
  </Field>
108
112
  ```
109
113
 
110
- `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`.
114
+ `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. `limitAppearance="plain"` (default `"faded"`) keeps − at `min` and + at `max` in their usual look instead of faded, for a count sheet where a faded − reads as broken; they are still disabled, a disabled or read-only Stepper is unchanged, forced colors still grey them, and an unknown value throws a `RangeError`. 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`.
111
115
 
112
116
  ```tsx
113
117
  <Stepper
@@ -213,16 +217,22 @@ A `Button`'s own children are laid out in a row one token gap apart (spacing ste
213
217
  </Button>
214
218
  ```
215
219
 
220
+ On the web, `variant="neutral"` is a quiet grey fill (the muted color at 16 %, the fill of an open menu row) with its words in the text color and no border: an action beside the primary that should not draw the eye, such as a sheet's Cancel. It is web only (`webButtonVariants`; the shared `buttonVariants` in `@scalewing/tokens` and React Native do not have it). `disabledAppearance="neutral"` (default `"faded"`) draws a disabled button on that grey with its words in the muted color at 48 % (about #AEAEAE on the grey) and full opacity, whatever its variant, instead of fading its own fill (a disabled accent reads as a paler accent); a `ghost` button, which has no fill, still fades. The button is still a disabled `button`; forced colors draw it as any disabled button. An unknown `disabledAppearance` throws a `RangeError`.
221
+
216
222
  A `Button` that toggles something passes `aria-pressed`. The pressed button gets a 2 px accent ring outside its fill, past a 2 px gap in `--sw-color-background`, so the ring meets only the page and keeps the accent's contrast on the canvas (4.5:1 or more in every palette, at least the 3:1 a state indicator needs) whatever its `variant` or fill. A focused pressed button moves its focus outline out past the ring. In forced colors, which drop shadows, the pressed button keeps the forced button colors, so its label and any glyph or badge inside it stay readable, and draws the same ring and its own border in the system highlight (`Highlight`); nothing opts out of forced colors. An unpressed button is drawn at full strength, so its label keeps its contrast. To make the choice stand out further, give the pressed button `primary` and the others `secondary`.
217
223
 
218
224
  `ButtonGroup` is the action row of a form or dialog (`role="group"`, named by `aria-label`): one line from `md` up, aligned by `justify`, and full-width stacked buttons below it. `attached` makes it one joined control instead, such as Previous, Today and Next: its direct `Button` children sit side by side at one height with no gap, their borders overlapping into single dividers (a borderless variant draws its divider in its own text color at low strength), with square inner corners and `sm`-radius outer ones, mirrored right to left. It keeps its row on a phone and sizes to its buttons, so `justify` does not apply. The group is its own stacking context: a pressed toggle's ring and the focused button's usual outer ring paint over the neighbours. Give every button the same variant and size, such as `secondary`; a wrapper around a button (a tooltip's span) is not joined.
219
225
 
226
+ `ButtonGroup` `appearance="track"` (an `attached` group only; default `"default"`, `buttonGroupAppearances`) sets the joined buttons as words in the text color on one quiet grey track (the muted fill at 16 %, the sm radius) with the `border` token's hairline between them, whatever their variant, such as Previous, This week and Next above a period. It is for equal-weight navigation: every variant's meaning goes, so keep a primary or destructive action out of it. A pressed button keeps its ring, a focused one its outline and a disabled one fades (`disabledAppearance="neutral"` too); forced colors outline the track, keep every border and draw a pressed button's in Highlight. An unknown value, or `track` without `attached`, throws a `RangeError`.
227
+
220
228
  `Badge` is a non-interactive chicklet with a `tone` (`neutral`, `accent`, `success`, `danger`, `warning`) and a `size` (`md`, or `sm` for a table cell). A badge inside a filled `Button` (every variant but `ghost`) sits on `--sw-color-surface` with its words in the text color and its tone on its border, so it reads at 4.5:1 in every palette whatever the button's fill.
221
229
 
222
230
  `ActionMenu` opens independent commands from a labelled button. Provide localized command labels and callbacks; use `disabled` for unavailable commands and `destructive` for a dangerous command's presentation. Escape and choosing a command both return focus to the trigger (before the command runs, so a `Dialog` it opens hands focus back to the trigger on close), and outside interaction dismisses the menu. The open menu sits on the popover layer a `space-1` gap below its trigger (above it when it only fits there), at least `space-2` from every viewport edge (a classic scrollbar is not part of the viewport), and aligned to the trigger's inline start (its right edge in right-to-left), or to its other edge when the trigger ends a row and the menu would not fit from its start. `align="end"` (default `"start"`) lines the menu up with the trigger's inline end even where the start would fit, for a trigger that ends a card or a list row, so the menu stays over that card instead of hanging past it; it takes the start when the end would leave the screen. The menu is as wide as its longest command up to the viewport less both insets, and a longer command wraps. The menu follows its trigger through scroll, window resize, and a change in the menu's or the trigger's size. The menu is in the DOM only while it is open. The trigger and its commands use the compact xs control height for a fine pointer; on a coarse pointer the trigger is at least 44 × 44 px (the md control height, as on `CalendarButton`) and every command is at least 44 px tall. An item's `detail` is its current value at the command's end, such as "English" on a Language command that opens a choice: muted, before a chevron that points to the inline end (mirrored right to left), and read as part of the command's name ("Language English"); `detailLang` marks its language. It is meant for a short value; a long one wraps between words, never inside one. The command runs like any other and opens no submenu, so a command that opens a dialog or a picker says so in its label. A blank `detail`, or a blank `detailLang` or one without a `detail`, throws a `RangeError`. `separatorBefore` draws a hairline (`role="separator"`) before a command, setting it apart, such as Sign out; the arrow keys skip it, and it is never drawn before the first command.
223
231
 
224
232
  `ActionMenu` `header` is a non-interactive block above the commands, such as who is signed in: each child a line of its own, in the caption size and the muted color, set off by a hairline. It sits outside the `role="menu"` element and the arrow keys, and is the menu's description (`aria-describedby`), read as the menu opens; `null`, `false` and `''` are no header. An item's `lang` (a BCP 47 tag) marks its label as another language than the page's, such as "English" on a Spanish page (WCAG 3.1.2).
225
233
 
234
+ `ActionMenu` `triggerAppearance="text"` (opt-in; default `"framed"`, `actionMenuTriggerAppearances`) draws the trigger as its words (`trigger`, or `label`) in label type at medium weight in the text color, with the shared muted chevron pointing down after them and no fill or outline; the quiet grey fill shows on hover and while the menu is open. It is the sm control height (the 44 px target on a coarse pointer). Name the trigger with `label` as before, and include the words shown in it (WCAG 2.5.3, so voice control can say them): a person's name that opens their account menu takes a `label` such as "Account, Ada Okafor". The trigger's words are one flex item (mixed inline content keeps its spaces), then the chevron. Forced colors draw its border in the system's border color, the chevron in CanvasText, and an open trigger's border in Highlight. An unknown value throws a `RangeError`.
235
+
226
236
  ```tsx
227
237
  <ActionMenu
228
238
  header={
@@ -244,7 +254,7 @@ A `Button` that toggles something passes `aria-pressed`. The pressed button gets
244
254
 
245
255
  `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.
246
256
 
247
- 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`.
257
+ 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`. `appearance="banded"` (default `"glass"`) draws the dialog on the solid surface with the medium elevation instead of the frosted panel: its title row is a full-width band over a hairline (a lone title too), the footer the band it already is, and the close button a circle on the quiet muted fill in the text color, keeping its touch target; forced colors outline the dialog and keep the hairlines. An unknown `size`, `role` or `appearance`, a blank `description` or an invalid `summary` throws a `RangeError`.
248
258
 
249
259
  ```tsx
250
260
  <Dialog
@@ -333,6 +343,8 @@ An option's optional `description` (a `ReactNode`) is secondary text for that op
333
343
 
334
344
  `SegmentedControl` is one exclusive choice shown as a track of segments (`role="radiogroup"`), named by `aria-label` or `aria-labelledby`, with a controlled `value` and `onChange`; `variant="filled"` gives every segment the same width and fills the selection with the accent, and `disabled` keeps the choice visible but inert. `error` puts a message under the track (`sw-field-error`), links it to the group by `aria-describedby` and sets `aria-invalid` and a danger outline on the group (`Mark` in forced colors); as `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. `required` sets `aria-required` on the group; since the control has no label of its own, the element that labels it shows the mark. The track sits in a `.sw-segmented-field` wrapper that holds the message and takes the track's place in the layout, so a `ref` still reaches the `radiogroup`. `error` is for a control in a `Stack` or block layout, where the track keeps its width and the message wraps under it. While it shows a message the field is at least 24ch wide, capped at its container, so a short compact track does not squeeze the message into a column. In an `Inline` the field therefore grows and the rest of the row moves, while the track keeps its labels' width. An item's `icon` (such as a small `GlyphTile`) sits before its label, hidden from assistive technology, so the label names the segment; when any item has one, a compact track takes the `md` control height and label type on a subtle fill, with the selected item raised, and a filled track keeps its look and only gains the marks.
335
345
 
346
+ `variant="raised"` (opt-in; default `"compact"`) is a grey track (the quiet muted fill, no border, corners concentric with the segments' small radius) as wide as its segments even in a `Stack`; every segment is the `md` control height in label type with its words in the text color at medium weight, and the selected one is the surface raised by the small elevation at label weight; in the dark scheme, where a shadow does not show, it draws a hairline ring in the muted color instead (3:1 or more on the track in every dark palette). Item `icon`s keep that look, and `error` and `disabled` behave as on the other tracks. It is the same radiogroup with the same keys; forced colors outline the track and the selected segment.
347
+
336
348
  ```tsx
337
349
  <SegmentedControl
338
350
  aria-labelledby={directionLabelId}
@@ -352,6 +364,8 @@ An option's optional `description` (a `ReactNode`) is secondary text for that op
352
364
 
353
365
  `Nav` is a labelled `nav` of a workspace's destinations, links or buttons one token gap apart. A `Button` inside it is at least 44 × 44 px on a coarse pointer, whatever its `size`, so a compact row of destinations is still a touch target on a phone or a tablet.
354
366
 
367
+ `appearance="words"` (opt-in; default `"default"`, `navAppearances`) draws every destination, a link or a `Button` of any variant, as plain words: no fill, border or underline, label type, the others in the muted color at medium weight and the current one (`aria-current`, not `"false"`) in the text color at label weight. A Button keeps its size, padding, touch target and focus ring; hover lifts a destination to the text color where the pointer can hover, never a disabled one. The current mark is color and weight, so each destination's words must be visible: a glyph-only destination has no weight to change (keep `default` for a glyph rail). Forced colors draw the current one's border in Highlight and underline its words. An unknown value throws a `RangeError`.
368
+
355
369
  `SectionNav` is the navigation between the sections of one area, such as a portal's pages: a labelled `nav` (`aria-label` or `aria-labelledby`) of links from `items` (`{ id, label, href, current?, icon?, count? }`), quieter than a workspace's `Nav`. `current` is `'page'`, `'location'` (a page inside that section, such as a detail page under its list) or a boolean, written as `aria-current`; the current item is in the text color over an accent underline, or, from `verticalFrom` (a breakpoint) up, where the items stack as a side list, a bar at its start. `icon` is a decorative glyph before the label. `onNavigate` takes a plain press after preventing the browser's navigation, for a client router; a press with a modifier key or the middle button keeps the browser's behavior, and without `onNavigate` the links carry no handler, so a server component can render the nav. `count` (a whole number) is how many things wait in a section, such as tasks: a filled pill after the label, in the danger color with its on-color figures, read as part of the link's name ("Tasks 3"); 0 shows none, and there is no cap. `countLabel` says what the count means in your words ("3 tasks to do"): it is read in place of the bare number and the pill is hidden from assistive technology. `appearance="filled"` (default `"underline"`) is a quiet row of page buttons for an area's sub-navigation beside its name: no hairline and no underline or start bar, every link the `sm` control with the small radius at weight 500, the current one on a soft muted fill in the text color at 600 (a 2 px `Highlight` edge in forced colors), in the row or the side list. A coarse pointer gets 44 px targets. Empty or duplicate ids, empty labels, a `count` that is not a whole number from 0, a blank `countLabel` or one without a `count`, and an unknown `verticalFrom` or `appearance` throw a `RangeError`. Use `Tabs` for panels on one page.
356
370
 
357
371
  ```tsx
@@ -529,9 +543,9 @@ Values outside the catalog throw a `RangeError`. There is no per-breakpoint span
529
543
  </Grid>
530
544
  ```
531
545
 
532
- `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.
546
+ `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. Three more grid-level options match a framed count sheet, each off by default: `frame="table"` (default `"none"`) draws a strip as a framed table, its scroll region a surface inside a border hairline at the `sm` radius, the heading row on the subtle fill, a rule between columns, the counts at label size centred under their heads and the cells padded a space-3 step across (the tiles layout has no frame and ignores it); `totalEmphasis="strong"` (default `"default"`) sets the row totals in bold label type, a neutral row's in the text color and a toned row's in its tone, under the label on a phone too; and `cellToneAppearance="pill"` (default `"text"`) sets a `cellTones` count of a strip's read-only row on a rounded tint of its tone in the tone's ink (the `Notice` fill and words, 4.5:1 on the tint except where a palette's own `danger` is already under 4.5:1, harvest and mocha dark), outlined in forced colors; entry counts and tiles keep their tone border. With `zeroLabel=""` a toned zero or empty count shows nothing in either appearance, so tone only counts that are there. An unknown value of any of them throws a `RangeError`. `zeroLabel` (default `"—"`) is what a zero or empty read-only count shows; an empty string leaves those counts blank, so only the counts that are there stand out (it suits the strip, where the column header names each blank cell, better than tiles, where a blank tile is only its column's name), and a `zeroLabel` that is not a string 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.
533
547
 
534
- 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`.
548
+ 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. `entry.limitAppearance` is the Steppers' `limitAppearance`: `"plain"` keeps − at 0 (and + at a cap) unfaded. 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`.
535
549
 
536
550
  ```tsx
537
551
  <DenominationGrid
@@ -1,6 +1,8 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type InlineAlign } from '../anchored-position.js';
3
+ import { type ActionMenuTriggerAppearance, actionMenuTriggerAppearances } from '../css/css-action-menu-trigger.js';
3
4
  export type ActionMenuAlign = InlineAlign;
5
+ export { actionMenuTriggerAppearances, type ActionMenuTriggerAppearance };
4
6
  export type ActionMenuItem = {
5
7
  id: string;
6
8
  label: string;
@@ -46,5 +48,12 @@ export type ActionMenuProps = {
46
48
  * menu takes the other edge when the preferred one would leave the screen.
47
49
  */
48
50
  align?: ActionMenuAlign;
51
+ /**
52
+ * `text` draws the trigger as its words with a chevron after them, no
53
+ * fill or outline until hovered or open, such as a person's name opening
54
+ * their account menu; `framed` (the default) is the small outlined
55
+ * control.
56
+ */
57
+ triggerAppearance?: ActionMenuTriggerAppearance;
49
58
  };
50
- export declare function ActionMenu({ label, items, header, trigger, disabled, align, }: ActionMenuProps): import("react").JSX.Element;
59
+ export declare function ActionMenu({ label, items, header, trigger, disabled, align, triggerAppearance, }: ActionMenuProps): import("react").JSX.Element;
@@ -1,7 +1,10 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useId, useLayoutEffect, useRef, useState, } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ import { actionMenuTriggerAppearances, actionMenuTriggerTextClass, } from '../css/css-action-menu-trigger.js';
4
6
  import { ActionMenuList } from './action-menu/ActionMenuList.js';
7
+ export { actionMenuTriggerAppearances };
5
8
  function nextEnabled(items, current, direction) {
6
9
  if (items.length === 0)
7
10
  return -1;
@@ -12,7 +15,7 @@ function nextEnabled(items, current, direction) {
12
15
  }
13
16
  return -1;
14
17
  }
15
- export function ActionMenu({ label, items, header, trigger, disabled = false, align = 'start', }) {
18
+ export function ActionMenu({ label, items, header, trigger, disabled = false, align = 'start', triggerAppearance = 'framed', }) {
16
19
  const menuId = useId();
17
20
  const triggerRef = useRef(null);
18
21
  const openRef = useRef(false);
@@ -22,6 +25,8 @@ export function ActionMenu({ label, items, header, trigger, disabled = false, al
22
25
  (item.detailLang !== undefined &&
23
26
  (!item.detailLang.trim() || item.detail === undefined))))
24
27
  throw new RangeError('ActionMenu item detail and detailLang must not be blank; detailLang needs a detail');
28
+ if (!actionMenuTriggerAppearances.includes(triggerAppearance))
29
+ throw new RangeError(`Unknown ActionMenu triggerAppearance: ${String(triggerAppearance)}`);
25
30
  const enabled = !disabled && items.some((item) => !item.disabled);
26
31
  useLayoutEffect(() => {
27
32
  if (!enabled && open)
@@ -88,5 +93,5 @@ export function ActionMenu({ label, items, header, trigger, disabled = false, al
88
93
  close(true);
89
94
  item.onSelect();
90
95
  }
91
- return (_jsxs("div", { className: "sw-action-menu", children: [_jsx("button", { "aria-controls": open ? menuId : undefined, "aria-expanded": open, "aria-haspopup": "menu", "aria-label": label, className: "sw-action-menu-trigger", disabled: !enabled, onClick: () => (open ? close(true) : openMenu()), onKeyDown: onTriggerKeyDown, ref: triggerRef, type: "button", children: trigger ?? label }), open ? (_jsx(ActionMenuList, { align: align, focusIndex: focusIndex, header: header, id: menuId, items: items, label: label, onDismiss: () => close(), onFocusIndex: setFocusIndex, onKeyDown: onMenuKeyDown, onSelect: select, triggerRef: triggerRef })) : null] }));
96
+ return (_jsxs("div", { className: "sw-action-menu", children: [_jsx("button", { "aria-controls": open ? menuId : undefined, "aria-expanded": open, "aria-haspopup": "menu", "aria-label": label, className: cx('sw-action-menu-trigger', triggerAppearance === 'text' && actionMenuTriggerTextClass), disabled: !enabled, onClick: () => (open ? close(true) : openMenu()), onKeyDown: onTriggerKeyDown, ref: triggerRef, type: "button", children: triggerAppearance === 'text' ? (_jsx("span", { children: trigger ?? label })) : ((trigger ?? label)) }), open ? (_jsx(ActionMenuList, { align: align, focusIndex: focusIndex, header: header, id: menuId, items: items, label: label, onDismiss: () => close(), onFocusIndex: setFocusIndex, onKeyDown: onMenuKeyDown, onSelect: select, triggerRef: triggerRef })) : null] }));
92
97
  }
@@ -1,5 +1,13 @@
1
+ import { type AppHeaderAppearance, appHeaderAppearances } from '../css/css-app-header-bar.js';
1
2
  import { type BoxProps } from './Box.js';
3
+ export { appHeaderAppearances, type AppHeaderAppearance };
2
4
  export type AppHeaderProps = Omit<BoxProps, 'as'> & {
3
5
  sticky?: boolean;
6
+ /**
7
+ * `bar` draws the header as a full-width band of the solid surface with
8
+ * a hairline under it and no rounding; `glass` (the default) is the
9
+ * rounded frosted card.
10
+ */
11
+ appearance?: AppHeaderAppearance;
4
12
  };
5
- export declare function AppHeader({ className, padding, sticky, ...rest }: AppHeaderProps): import("react").JSX.Element;
13
+ export declare function AppHeader({ appearance, className, padding, sticky, ...rest }: AppHeaderProps): import("react").JSX.Element;
@@ -1,7 +1,11 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { cx } from '../class-names.js';
4
+ import { appHeaderAppearances, appHeaderBarClass, } from '../css/css-app-header-bar.js';
4
5
  import { Box } from './Box.js';
5
- export function AppHeader({ className, padding = 4, sticky = true, ...rest }) {
6
- return (_jsx(Box, { as: "header", className: cx('sw-app-header', sticky && 'sw-app-header-sticky', className), padding: padding, ...rest }));
6
+ export { appHeaderAppearances };
7
+ export function AppHeader({ appearance = 'glass', className, padding = 4, sticky = true, ...rest }) {
8
+ if (!appHeaderAppearances.includes(appearance))
9
+ throw new RangeError(`Unknown AppHeader appearance: ${String(appearance)}`);
10
+ return (_jsx(Box, { as: "header", className: cx('sw-app-header', appearance === 'bar' && appHeaderBarClass, sticky && 'sw-app-header-sticky', className), padding: padding, ...rest }));
7
11
  }
@@ -9,7 +9,11 @@ export type BoxProps = SpacingProps & VisibilityProps & ColumnSpanProps & HTMLAt
9
9
  as?: BoxElement;
10
10
  background?: Extract<SemanticColorKey, 'background' | 'surface'>;
11
11
  radius?: RadiusStep;
12
- /** A hairline token border: `true` is solid, `'dashed'` marks a space to fill in by hand. */
12
+ /**
13
+ * A hairline token border: `true` is solid, `'dashed'` marks a space to
14
+ * fill in by hand, `'bottom'` is a hairline under the box only (a
15
+ * full-width bar, such as a section's sub-navigation).
16
+ */
13
17
  border?: BoxBorder;
14
18
  };
15
19
  export type BoxBorder = BoxBorderValue;
@@ -17,6 +21,10 @@ export declare const Box: import("react").ForwardRefExoticComponent<SpacingProps
17
21
  as?: BoxElement;
18
22
  background?: Extract<SemanticColorKey, "background" | "surface">;
19
23
  radius?: RadiusStep;
20
- /** A hairline token border: `true` is solid, `'dashed'` marks a space to fill in by hand. */
24
+ /**
25
+ * A hairline token border: `true` is solid, `'dashed'` marks a space to
26
+ * fill in by hand, `'bottom'` is a hairline under the box only (a
27
+ * full-width bar, such as a section's sub-navigation).
28
+ */
21
29
  border?: BoxBorder;
22
30
  } & import("react").RefAttributes<HTMLElement>>;
@@ -1,17 +1,42 @@
1
- import { type ButtonSize, type ButtonVariant } from '@scalewing/tokens';
1
+ import { type ButtonSize } from '@scalewing/tokens';
2
+ import { type ButtonDisabledAppearance, type WebButtonVariant } from '../css/css-button.js';
2
3
  import { type ButtonHTMLAttributes, type ReactNode } from 'react';
3
- export type { ButtonSize, ButtonVariant };
4
+ /** The web variants: the shared tiers plus `neutral`. */
5
+ export type ButtonVariant = WebButtonVariant;
6
+ export type { ButtonDisabledAppearance, ButtonSize };
4
7
  export type ButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'type'> & {
5
8
  children: ReactNode;
6
9
  onPress: () => void;
7
10
  size?: ButtonSize;
8
11
  type?: 'button' | 'submit' | 'reset';
12
+ /**
13
+ * `primary` (default), `secondary`, `tertiary`, `ghost`, `danger`, or
14
+ * `neutral`: a quiet grey fill in the text color, for an action beside the
15
+ * primary that should not draw the eye.
16
+ */
9
17
  variant?: ButtonVariant;
18
+ /**
19
+ * How the button looks while `disabled`: `faded` (default) fades its
20
+ * variant; `neutral` draws it on the quiet grey fill with muted words,
21
+ * whatever its variant (a ghost button still fades).
22
+ */
23
+ disabledAppearance?: ButtonDisabledAppearance;
10
24
  };
11
25
  export declare const Button: import("react").ForwardRefExoticComponent<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onClick" | "type"> & {
12
26
  children: ReactNode;
13
27
  onPress: () => void;
14
28
  size?: ButtonSize;
15
29
  type?: "button" | "submit" | "reset";
30
+ /**
31
+ * `primary` (default), `secondary`, `tertiary`, `ghost`, `danger`, or
32
+ * `neutral`: a quiet grey fill in the text color, for an action beside the
33
+ * primary that should not draw the eye.
34
+ */
16
35
  variant?: ButtonVariant;
36
+ /**
37
+ * How the button looks while `disabled`: `faded` (default) fades its
38
+ * variant; `neutral` draws it on the quiet grey fill with muted words,
39
+ * whatever its variant (a ghost button still fades).
40
+ */
41
+ disabledAppearance?: ButtonDisabledAppearance;
17
42
  } & import("react").RefAttributes<HTMLButtonElement>>;
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { buttonClassNames } from '../css/css-button.js';
2
+ import { buttonClassNames, buttonDisabledAppearances, } from '../css/css-button.js';
3
3
  import { forwardRef } from 'react';
4
4
  import { cx } from '../class-names.js';
5
- export const Button = forwardRef(function Button({ children, className, disabled = false, onPress, size = 'md', type = 'button', variant = 'primary', ...rest }, ref) {
6
- return (_jsx("button", { ref: ref, className: cx(...buttonClassNames({ size, variant }), className), disabled: disabled, type: type, ...rest, onClick: onPress, children: children }));
5
+ export const Button = forwardRef(function Button({ children, className, disabled = false, disabledAppearance = 'faded', onPress, size = 'md', type = 'button', variant = 'primary', ...rest }, ref) {
6
+ if (!buttonDisabledAppearances.includes(disabledAppearance))
7
+ throw new RangeError(`Unknown Button disabledAppearance: ${String(disabledAppearance)}`);
8
+ return (_jsx("button", { ref: ref, className: cx(...buttonClassNames({ size, variant, disabledAppearance }), className), disabled: disabled, type: type, ...rest, onClick: onPress, children: children }));
7
9
  });
@@ -1,5 +1,7 @@
1
+ import { type ButtonGroupAppearance, buttonGroupAppearances } from '../css/css-button-group-track.js';
1
2
  import { type BoxProps } from './Box.js';
2
3
  export type ButtonGroupJustify = 'start' | 'end' | 'between';
4
+ export { buttonGroupAppearances, type ButtonGroupAppearance };
3
5
  export type ButtonGroupProps = BoxProps & {
4
6
  /** Where the row sits from the `md` breakpoint up; below it every button stacks full width in source order. */
5
7
  justify?: ButtonGroupJustify;
@@ -11,6 +13,12 @@ export type ButtonGroupProps = BoxProps & {
11
13
  * the group with `aria-label`.
12
14
  */
13
15
  attached?: boolean;
16
+ /**
17
+ * `track` (an `attached` group only) sets its buttons as words on one
18
+ * quiet grey track with a hairline between them, whatever their
19
+ * variant; `default` keeps each button's own fill and outline.
20
+ */
21
+ appearance?: ButtonGroupAppearance;
14
22
  };
15
23
  /**
16
24
  * The action row of a form or dialog: buttons on one line, wrapping when
@@ -19,4 +27,4 @@ export type ButtonGroupProps = BoxProps & {
19
27
  * (put the primary action last, as the row already does). It is a `group`,
20
28
  * so an `aria-label` names the row for assistive tech.
21
29
  */
22
- export declare function ButtonGroup({ attached, className, justify, ...rest }: ButtonGroupProps): import("react").JSX.Element;
30
+ export declare function ButtonGroup({ appearance, attached, className, justify, ...rest }: ButtonGroupProps): import("react").JSX.Element;
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cx } from '../class-names.js';
3
+ import { buttonGroupAppearances, buttonGroupTrackClass, } from '../css/css-button-group-track.js';
3
4
  import { Box } from './Box.js';
5
+ export { buttonGroupAppearances };
4
6
  /**
5
7
  * The action row of a form or dialog: buttons on one line, wrapping when
6
8
  * they must, aligned to the end by default. On a phone the same buttons
@@ -8,6 +10,10 @@ import { Box } from './Box.js';
8
10
  * (put the primary action last, as the row already does). It is a `group`,
9
11
  * so an `aria-label` names the row for assistive tech.
10
12
  */
11
- export function ButtonGroup({ attached = false, className, justify = 'end', ...rest }) {
12
- return (_jsx(Box, { className: cx('sw-button-group', `sw-button-group-${justify}`, attached && 'sw-button-group-attached', className), role: "group", ...rest }));
13
+ export function ButtonGroup({ appearance = 'default', attached = false, className, justify = 'end', ...rest }) {
14
+ if (!buttonGroupAppearances.includes(appearance))
15
+ throw new RangeError(`Unknown ButtonGroup appearance: ${String(appearance)}`);
16
+ if (appearance === 'track' && !attached)
17
+ throw new RangeError('ButtonGroup appearance="track" needs attached');
18
+ return (_jsx(Box, { className: cx('sw-button-group', `sw-button-group-${justify}`, attached && 'sw-button-group-attached', appearance === 'track' && buttonGroupTrackClass, className), role: "group", ...rest }));
13
19
  }
@@ -23,7 +23,7 @@ function isComposing(event) {
23
23
  */
24
24
  export function Combobox(props) {
25
25
  assertCombobox(props);
26
- const { action, countText, description, disabled = false, emptyText, error, groups, inputValue, label, labelVisuallyHidden = false, listLabel, note, onInputChange, onSelect, placeholder, required = false, size = 'md', } = props;
26
+ const { action, countText, description, disabled = false, emptyText, error, groups, inputValue, label, labelVisuallyHidden = false, listLabel, note, onInputChange, onSelect, placeholder, required = false, requiredMark = 'visible', size = 'md', } = props;
27
27
  const listId = useId();
28
28
  const noteId = useId();
29
29
  const inputRef = useRef(null);
@@ -111,7 +111,7 @@ export function Combobox(props) {
111
111
  }
112
112
  // Tab moves focus out, and the blur closes the list.
113
113
  }
114
- return (_jsxs("div", { className: "sw-combobox", children: [_jsx(Field, { description: description, error: error, label: label, labelVisuallyHidden: labelVisuallyHidden, required: required, size: size, children: _jsx("input", { "aria-activedescendant": active?.id, "aria-autocomplete": "list", "aria-controls": shown && hasList ? listId : undefined, "aria-describedby": shown && note ? noteId : undefined, "aria-expanded": shown && hasList, autoComplete: "off", disabled: disabled, onBlur: close, onChange: (event) => {
114
+ return (_jsxs("div", { className: "sw-combobox", children: [_jsx(Field, { description: description, error: error, label: label, labelVisuallyHidden: labelVisuallyHidden, required: required, requiredMark: requiredMark, size: size, children: _jsx("input", { "aria-activedescendant": active?.id, "aria-autocomplete": "list", "aria-controls": shown && hasList ? listId : undefined, "aria-describedby": shown && note ? noteId : undefined, "aria-expanded": shown && hasList, autoComplete: "off", disabled: disabled, onBlur: close, onChange: (event) => {
115
115
  onInputChange(event.currentTarget.value);
116
116
  setOpen(true);
117
117
  setActiveKey(null);
@@ -1,5 +1,6 @@
1
1
  import { type WeekStart } from '../calendar-month.js';
2
2
  import { type DateFieldLabels } from '../date-field-labels.js';
3
+ import { type RequiredMark } from './FieldLabelText.js';
3
4
  export type { DateFieldLabels, WeekStart };
4
5
  export type DateFieldProps = {
5
6
  label: string;
@@ -18,6 +19,13 @@ export type DateFieldProps = {
18
19
  today?: string;
19
20
  disabled?: boolean;
20
21
  required?: boolean;
22
+ /**
23
+ * `hidden` leaves out the label's " *" while the control stays required
24
+ * (and announced so); `visible` (default) draws it.
25
+ * When hidden, say in visible text which fields must be filled; a
26
+ * placeholder alone is not enough.
27
+ */
28
+ requiredMark?: RequiredMark;
21
29
  description?: string;
22
30
  error?: string;
23
31
  /**
@@ -30,4 +38,4 @@ export type DateFieldProps = {
30
38
  /** The control's own words; English by default. */
31
39
  labels?: Partial<DateFieldLabels>;
32
40
  };
33
- export declare function DateField({ label, value, onChange, min, max, today, disabled, required, description, error, locale, weekStartsOn, labels, }: DateFieldProps): import("react").JSX.Element;
41
+ export declare function DateField({ label, value, onChange, min, max, today, disabled, required, requiredMark, description, error, locale, weekStartsOn, labels, }: DateFieldProps): import("react").JSX.Element;
@@ -7,13 +7,14 @@ import { dateEntryPattern, dateEntryPlaceholder } from '../date-entry.js';
7
7
  import { dateEntryValidity } from '../date-entry-validity.js';
8
8
  import { assertDateBounds, isOutsideDateRange } from '../date-only.js';
9
9
  import { FieldErrorRegion } from './FieldErrorRegion.js';
10
- import { FieldLabelText } from './FieldLabelText.js';
10
+ import { assertRequiredMark, FieldLabelText, } from './FieldLabelText.js';
11
11
  import { CalendarDialog } from './date-field/CalendarDialog.js';
12
12
  import { DateFieldGlyph } from './date-field/DateFieldGlyph.js';
13
13
  import { useCalendarPopup } from './date-field/use-calendar-popup.js';
14
14
  import { useDateEntry } from './date-field/use-date-entry.js';
15
15
  import { useLangLocale } from './date-field/use-lang-locale.js';
16
- export function DateField({ label, value, onChange, min, max, today, disabled = false, required = false, description, error, locale, weekStartsOn = 0, labels, }) {
16
+ export function DateField({ label, value, onChange, min, max, today, disabled = false, required = false, requiredMark = 'visible', description, error, locale, weekStartsOn = 0, labels, }) {
17
+ assertRequiredMark(requiredMark);
17
18
  assertDateBounds(value, min, max);
18
19
  assertWeekStart(weekStartsOn);
19
20
  const words = resolveDateFieldLabels(labels);
@@ -51,7 +52,7 @@ export function DateField({ label, value, onChange, min, max, today, disabled =
51
52
  .filter((id) => id !== null)
52
53
  .join(' ');
53
54
  const invalid = Boolean(error) || entry.invalid || isOutsideDateRange(value, min, max);
54
- return (_jsxs("div", { className: "sw-date-field", ref: rootRef, children: [_jsx("label", { className: "sw-date-field-label", htmlFor: inputId, id: labelId, children: _jsx(FieldLabelText, { label: label, required: required }) }), _jsxs("div", { className: "sw-date-field-control", ref: controlRef, children: [_jsx("input", { "aria-describedby": describedBy || undefined, "aria-invalid": invalid, autoComplete: "off", className: "sw-date-field-input", disabled: disabled, id: inputId, onBlur: entry.check, onChange: (event) => entry.change(event.currentTarget.value), onKeyDown: (event) => {
55
+ return (_jsxs("div", { className: "sw-date-field", ref: rootRef, children: [_jsx("label", { className: "sw-date-field-label", htmlFor: inputId, id: labelId, children: _jsx(FieldLabelText, { label: label, required: required, requiredMark: requiredMark }) }), _jsxs("div", { className: "sw-date-field-control", ref: controlRef, children: [_jsx("input", { "aria-describedby": describedBy || undefined, "aria-invalid": invalid, autoComplete: "off", className: "sw-date-field-input", disabled: disabled, id: inputId, onBlur: entry.check, onChange: (event) => entry.change(event.currentTarget.value), onKeyDown: (event) => {
55
56
  if (event.key === 'Enter')
56
57
  entry.check();
57
58
  }, placeholder: dateEntryPlaceholder(pattern, {
@@ -1,6 +1,8 @@
1
+ import { denominationGridFrames, denominationTotalEmphases } from '../css/css-denomination-appearance.js';
2
+ import { denominationCellToneAppearances } from '../css/css-denomination-cell-tones.js';
1
3
  import { denominationLabelAppearances } from '../css/css-denomination-label.js';
2
4
  import { type DenominationGridProps } from './denomination-grid/types.js';
3
- export type { DenominationGridColumn, DenominationGridEntry, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowEmphasis, DenominationGridRowRole, DenominationGridTone, DenominationLabelWidth, } from './denomination-grid/types.js';
5
+ export type { DenominationCellToneAppearance, DenominationGridColumn, DenominationGridEntry, DenominationGridFrame, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowEmphasis, DenominationGridRowRole, DenominationGridTone, DenominationLabelWidth, DenominationTotalEmphasis, } from './denomination-grid/types.js';
4
6
  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;
7
+ export { denominationCellToneAppearances, denominationGridFrames, denominationLabelAppearances, denominationTotalEmphases, };
8
+ export declare function DenominationGrid({ label, columns, rows, layout, subtotal, zeroLabel, totalLabel, rowRole, labelWidth, labelAppearance, frame, totalEmphasis, cellToneAppearance, }: DenominationGridProps): import("react").JSX.Element;
@@ -1,27 +1,37 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../class-names.js';
2
3
  import { denominationLabelWidths } from '../css/css-denomination-aligned.js';
4
+ import { denominationGridFrames, denominationTotalEmphases, denominationTotalsStrongClass, } from '../css/css-denomination-appearance.js';
5
+ import { denominationCellToneAppearances, denominationCellTonesPillClass, } from '../css/css-denomination-cell-tones.js';
3
6
  import { denominationLabelAppearances, denominationLabelsPlainClass, } from '../css/css-denomination-label.js';
4
7
  import { DenominationStrip } from './denomination-grid/DenominationStrip.js';
5
8
  import { DenominationTiles } from './denomination-grid/DenominationTiles.js';
6
9
  import { assertDenominationGrid } from './denomination-grid/validate.js';
7
10
  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', }) {
11
+ export { denominationCellToneAppearances, denominationGridFrames, denominationLabelAppearances, denominationTotalEmphases, };
12
+ /** Fails closed on a value outside a closed set, naming the prop. */
13
+ function assertOneOf(values, value, prop) {
14
+ if (!values.includes(value))
15
+ throw new RangeError(`Unknown ${prop}: ${String(value)}`);
16
+ }
17
+ export function DenominationGrid({ label, columns, rows, layout = 'strip', subtotal, zeroLabel = '—', totalLabel, rowRole = 'region', labelWidth, labelAppearance = 'tone', frame = 'none', totalEmphasis = 'default', cellToneAppearance = 'text', }) {
10
18
  assertDenominationGrid(label, columns, rows);
11
19
  assertDenominationExtras(layout, columns, rows);
12
20
  assertDenominationEntries(layout, columns, rows);
13
- if (!zeroLabel)
14
- throw new RangeError('zeroLabel must not be empty');
21
+ if (typeof zeroLabel !== 'string')
22
+ throw new RangeError('zeroLabel must be a string');
15
23
  if (totalLabel !== undefined && !totalLabel.trim())
16
24
  throw new RangeError('totalLabel must not be empty');
17
25
  if (rowRole !== 'region' && rowRole !== 'group')
18
26
  throw new RangeError("rowRole must be 'region' or 'group'");
19
- if (labelWidth !== undefined && !denominationLabelWidths.includes(labelWidth))
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;
27
+ if (labelWidth !== undefined)
28
+ assertOneOf(denominationLabelWidths, labelWidth, 'labelWidth');
29
+ assertOneOf(denominationLabelAppearances, labelAppearance, 'labelAppearance');
30
+ assertOneOf(denominationGridFrames, frame, 'frame');
31
+ assertOneOf(denominationTotalEmphases, totalEmphasis, 'totalEmphasis');
32
+ assertOneOf(denominationCellToneAppearances, cellToneAppearance, 'cellToneAppearance');
33
+ const appearanceClass = cx(labelAppearance === 'plain' && denominationLabelsPlainClass, totalEmphasis === 'strong' && denominationTotalsStrongClass, cellToneAppearance === 'pill' && denominationCellTonesPillClass);
24
34
  if (layout === 'tiles')
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 }));
35
+ return (_jsx(DenominationTiles, { className: appearanceClass || undefined, columns: columns, label: label, rowRole: rowRole, rows: rows, subtotal: subtotal, zeroLabel: zeroLabel }));
36
+ return (_jsx(DenominationStrip, { className: appearanceClass || undefined, columns: columns, framed: frame === 'table', label: label, labelWidth: labelWidth, rows: rows, totalLabel: totalLabel, zeroLabel: zeroLabel }));
27
37
  }
@@ -1,6 +1,8 @@
1
1
  import { type DialogHTMLAttributes, type ReactNode } from 'react';
2
2
  import { type Breakpoint } from '../css/breakpoints.js';
3
+ import { type DialogAppearance, dialogAppearances } from '../css/css-dialog-banded.js';
3
4
  import { type ActionBarSummary } from './action-bar/ActionBarSummary.js';
5
+ export { dialogAppearances, type DialogAppearance };
4
6
  export declare const dialogSizes: readonly ["md", "lg", "xl"];
5
7
  /**
6
8
  * `md` is the reading width (32rem) for a message or a short form; `lg`
@@ -74,6 +76,12 @@ export type DialogProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'onClose
74
76
  summary?: ActionBarSummary;
75
77
  /** `alertdialog` for a warning that interrupts. Default `dialog`. */
76
78
  role?: DialogRole;
79
+ /**
80
+ * `banded` draws the dialog on the solid surface with its title row and
81
+ * footer as full-width bands over hairlines, and the close button on a
82
+ * quiet round fill; `glass` (the default) is one frosted panel.
83
+ */
84
+ appearance?: DialogAppearance;
77
85
  children: ReactNode;
78
86
  };
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;
87
+ export declare function Dialog({ 'aria-describedby': describedBy, appearance, children, className, closeLabel, description, footer, icon, role, summary, onCancel, onClose, onKeyDown, onPointerDown, onSubmit, open, sheetBelow, size, title, titleLevel, ...rest }: DialogProps): import("react").JSX.Element;