@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
@@ -3,17 +3,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useId } from 'react';
4
4
  import { cx } from '../class-names.js';
5
5
  import { dialogSheetClass } from '../css/css-dialog.js';
6
+ import { dialogAppearances, dialogBandedClass, } from '../css/css-dialog-banded.js';
6
7
  import { spacingClassNames } from '../spacing-classes.js';
7
8
  import { useDialogCloseRequests } from './dialog-close-requests.js';
8
9
  import { assertActionBarSummary, } from './action-bar/ActionBarSummary.js';
9
10
  import { DialogFooter } from './dialog/DialogFooter.js';
10
11
  import { DialogTitle } from './dialog/DialogTitle.js';
11
12
  import { Stack } from './Stack.js';
13
+ export { dialogAppearances };
12
14
  export const dialogSizes = ['md', 'lg', 'xl'];
13
15
  const dialogRoles = ['dialog', 'alertdialog'];
14
- function assertDialogProps(size, role, description, summary) {
16
+ function assertDialogProps(size, role, description, summary, appearance) {
15
17
  if (!dialogSizes.includes(size))
16
18
  throw new RangeError(`Unknown Dialog size: ${String(size)}`);
19
+ if (!dialogAppearances.includes(appearance))
20
+ throw new RangeError(`Unknown Dialog appearance: ${String(appearance)}`);
17
21
  if (role !== undefined && !dialogRoles.includes(role))
18
22
  throw new RangeError(`Unknown Dialog role: ${String(role)}`);
19
23
  if (description !== undefined &&
@@ -22,8 +26,8 @@ function assertDialogProps(size, role, description, summary) {
22
26
  if (summary)
23
27
  assertActionBarSummary(summary);
24
28
  }
25
- export function Dialog({ 'aria-describedby': describedBy, children, className, closeLabel, description, footer, icon, role, summary, onCancel, onClose, onKeyDown, onPointerDown, onSubmit, open, sheetBelow, size = 'md', title, titleLevel = 3, ...rest }) {
26
- assertDialogProps(size, role, description, summary);
29
+ export function Dialog({ 'aria-describedby': describedBy, appearance = 'glass', children, className, closeLabel, description, footer, icon, role, summary, onCancel, onClose, onKeyDown, onPointerDown, onSubmit, open, sheetBelow, size = 'md', title, titleLevel = 3, ...rest }) {
30
+ assertDialogProps(size, role, description, summary, appearance);
27
31
  const titleId = useId();
28
32
  const descriptionId = useId();
29
33
  const hasFooter = Boolean(footer) || Boolean(summary);
@@ -35,5 +39,5 @@ export function Dialog({ 'aria-describedby': describedBy, children, className, c
35
39
  onPointerDown,
36
40
  onSubmit,
37
41
  });
38
- return (_jsx("dialog", { ...rest, ref: nodeRef, "aria-describedby": describedBy ?? (description ? descriptionId : undefined), "aria-labelledby": titleId, className: cx('sw-dialog', size !== 'md' && `sw-dialog-${size}`, sheetBelow && dialogSheetClass(sheetBelow), ...spacingClassNames({ padding: 5 }), className), role: role === 'alertdialog' ? 'alertdialog' : undefined, ...handlers, children: _jsxs(Stack, { gap: 4, children: [_jsx(DialogTitle, { closeLabel: closeLabel, description: description, descriptionId: descriptionId, icon: icon, id: titleId, level: titleLevel === 2 ? 2 : 3, onClose: onClose, title: title }), children, hasFooter ? (_jsx(DialogFooter, { summary: summary, children: footer })) : null] }) }));
42
+ return (_jsx("dialog", { ...rest, ref: nodeRef, "aria-describedby": describedBy ?? (description ? descriptionId : undefined), "aria-labelledby": titleId, className: cx('sw-dialog', size !== 'md' && `sw-dialog-${size}`, sheetBelow && dialogSheetClass(sheetBelow), appearance === 'banded' && dialogBandedClass, ...spacingClassNames({ padding: 5 }), className), role: role === 'alertdialog' ? 'alertdialog' : undefined, ...handlers, children: _jsxs(Stack, { gap: 4, children: [_jsx(DialogTitle, { banded: appearance === 'banded', closeLabel: closeLabel, description: description, descriptionId: descriptionId, icon: icon, id: titleId, level: titleLevel === 2 ? 2 : 3, onClose: onClose, title: title }), children, hasFooter ? (_jsx(DialogFooter, { summary: summary, children: footer })) : null] }) }));
39
43
  }
@@ -1,5 +1,6 @@
1
1
  import { type SpacingStep } from '@scalewing/tokens';
2
2
  import { type ReactNode } from 'react';
3
+ import { type RequiredMark } from './FieldLabelText.js';
3
4
  export type FieldSize = 'xs' | 'md';
4
5
  export declare const fieldVariants: readonly ["outlined", "search"];
5
6
  export type FieldVariant = (typeof fieldVariants)[number];
@@ -33,6 +34,13 @@ export type FieldProps = {
33
34
  */
34
35
  changed?: boolean;
35
36
  required?: boolean;
37
+ /**
38
+ * `hidden` leaves out the label's " *" while the control stays required
39
+ * (and announced so); `visible` (default) draws it.
40
+ * When hidden, say in visible text which fields must be filled; a
41
+ * placeholder alone is not enough.
42
+ */
43
+ requiredMark?: RequiredMark;
36
44
  /** Short text inside the control frame before the value, such as a currency sign. Not part of the value. */
37
45
  prefix?: string;
38
46
  /** Short text inside the control frame after the value, such as a unit. Not part of the value. */
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { cloneElement, isValidElement, useId, } from 'react';
4
4
  import { adornedControlAria, FieldAdornment, } from './FieldAdornment.js';
5
5
  import { FieldErrorRegion } from './FieldErrorRegion.js';
6
- import { FieldLabelText } from './FieldLabelText.js';
6
+ import { assertRequiredMark, FieldLabelText, } from './FieldLabelText.js';
7
7
  import { SearchFrame } from './field/SearchFrame.js';
8
8
  import { Stack } from './Stack.js';
9
9
  export const fieldVariants = ['outlined', 'search'];
@@ -41,7 +41,7 @@ function passesInvalid(props) {
41
41
  return props.invalid !== undefined || props.changed !== undefined;
42
42
  }
43
43
  export function Field(props) {
44
- const { children, gap = 1, label, labelVisuallyHidden = false, size = 'md', description, error, invalid = false, changed = false, required = false, prefix, suffix, variant = 'outlined', leading, clearLabel = '', onClear, } = props;
44
+ const { children, gap = 1, label, labelVisuallyHidden = false, size = 'md', description, error, invalid = false, changed = false, required = false, requiredMark = 'visible', prefix, suffix, variant = 'outlined', leading, clearLabel = '', onClear, } = props;
45
45
  assertSearch(props, children);
46
46
  const search = variant === 'search';
47
47
  const controlId = useId();
@@ -50,6 +50,7 @@ export function Field(props) {
50
50
  const labelId = useId();
51
51
  const prefixId = useId();
52
52
  const suffixId = useId();
53
+ assertRequiredMark(requiredMark);
53
54
  const message = error || description;
54
55
  const marksInvalid = Boolean(error) || invalid;
55
56
  const adorned = Boolean(prefix || suffix);
@@ -65,7 +66,7 @@ export function Field(props) {
65
66
  .join(' ');
66
67
  // A search field hides its whole label element instead (see below).
67
68
  const labelHidden = labelVisuallyHidden && !search;
68
- const labelText = (_jsx(FieldLabelText, { label: label, required: required, size: size, visuallyHidden: labelHidden }));
69
+ const labelText = (_jsx(FieldLabelText, { label: label, required: required, requiredMark: requiredMark, size: size, visuallyHidden: labelHidden }));
69
70
  if (!isValidElement(children) ||
70
71
  typeof children.type !== 'string' ||
71
72
  !['input', 'select', 'textarea'].includes(children.type)) {
@@ -1,13 +1,31 @@
1
+ export declare const requiredMarks: readonly ["visible", "hidden"];
2
+ /**
3
+ * Whether a required control's label shows the required mark: `visible`
4
+ * (the default) draws " *" after the label; `hidden` leaves it out, for a
5
+ * form that marks nothing as required (or marks what is optional instead).
6
+ * The control stays required either way, so assistive technology still
7
+ * hears it. When hidden, say in visible text which fields must be filled
8
+ * (a form-level note, or "Required" in the description); a placeholder
9
+ * alone is not enough (WCAG 3.3.2).
10
+ */
11
+ export type RequiredMark = (typeof requiredMarks)[number];
12
+ /** Fails closed on a value that is not a known required mark. */
13
+ export declare function assertRequiredMark(value: unknown): void;
14
+ /** Whether the mark is drawn: a required control whose mark is visible. */
15
+ export declare function showsRequiredMark(required: boolean, requiredMark: RequiredMark): boolean;
1
16
  /**
2
17
  * A form control's label words and its required mark, as `Field` draws
3
18
  * them. The `<label>` around it inherits the canvas's body type, so its
4
19
  * line box, and the gap to the control below, is the same for every field
5
- * that uses it (`Field`, `DateField` and `Select`). The mark is `aria-hidden`: the
6
- * control's own required state is what assistive technology reads.
20
+ * that uses it (`Field`, `DateField` and `Select`; `Combobox` through
21
+ * `Field`, and `FilterChips` shares `showsRequiredMark` for its legend). The mark is `aria-hidden`: the
22
+ * control's own required state is what assistive technology reads, and
23
+ * `requiredMark="hidden"` leaves it out.
7
24
  */
8
- export declare function FieldLabelText({ label, required, size, visuallyHidden, }: {
25
+ export declare function FieldLabelText({ label, required, requiredMark, size, visuallyHidden, }: {
9
26
  label: string;
10
27
  required?: boolean;
28
+ requiredMark?: RequiredMark;
11
29
  size?: 'xs' | 'md';
12
30
  visuallyHidden?: boolean;
13
31
  }): import("react").JSX.Element;
@@ -1,12 +1,24 @@
1
1
  import { jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Text } from './Text.js';
3
+ export const requiredMarks = ['visible', 'hidden'];
4
+ /** Fails closed on a value that is not a known required mark. */
5
+ export function assertRequiredMark(value) {
6
+ if (!requiredMarks.includes(value))
7
+ throw new RangeError(`Unknown requiredMark: ${String(value)}`);
8
+ }
9
+ /** Whether the mark is drawn: a required control whose mark is visible. */
10
+ export function showsRequiredMark(required, requiredMark) {
11
+ return required && requiredMark === 'visible';
12
+ }
3
13
  /**
4
14
  * A form control's label words and its required mark, as `Field` draws
5
15
  * them. The `<label>` around it inherits the canvas's body type, so its
6
16
  * line box, and the gap to the control below, is the same for every field
7
- * that uses it (`Field`, `DateField` and `Select`). The mark is `aria-hidden`: the
8
- * control's own required state is what assistive technology reads.
17
+ * that uses it (`Field`, `DateField` and `Select`; `Combobox` through
18
+ * `Field`, and `FilterChips` shares `showsRequiredMark` for its legend). The mark is `aria-hidden`: the
19
+ * control's own required state is what assistive technology reads, and
20
+ * `requiredMark="hidden"` leaves it out.
9
21
  */
10
- export function FieldLabelText({ label, required = false, size = 'md', visuallyHidden = false, }) {
11
- return (_jsxs(Text, { as: "span", className: visuallyHidden ? 'sw-sr-only' : undefined, variant: size === 'xs' ? 'caption' : 'label', children: [label, required ? (_jsxs("span", { "aria-hidden": "true", className: "sw-field-required", children: [' ', "*"] })) : null] }));
22
+ export function FieldLabelText({ label, required = false, requiredMark = 'visible', size = 'md', visuallyHidden = false, }) {
23
+ return (_jsxs(Text, { as: "span", className: visuallyHidden ? 'sw-sr-only' : undefined, variant: size === 'xs' ? 'caption' : 'label', children: [label, showsRequiredMark(required, requiredMark) ? (_jsxs("span", { "aria-hidden": "true", className: "sw-field-required", children: [' ', "*"] })) : null] }));
12
24
  }
@@ -1,3 +1,4 @@
1
+ import { type RequiredMark } from './FieldLabelText.js';
1
2
  export type FilterChipOption = {
2
3
  value: string;
3
4
  label: string;
@@ -16,6 +17,13 @@ export type FilterChipsProps = {
16
17
  * which reads the radios' own state).
17
18
  */
18
19
  required?: boolean;
20
+ /**
21
+ * `hidden` leaves out the label's " *" while the control stays required
22
+ * (and announced so); `visible` (default) draws it.
23
+ * When hidden, say in visible text which fields must be filled; a
24
+ * placeholder alone is not enough.
25
+ */
26
+ requiredMark?: RequiredMark;
19
27
  /**
20
28
  * A message under the chips, such as "Pick a reason": a polite live
21
29
  * region linked to the group by `aria-describedby`; the group is marked
@@ -26,4 +34,4 @@ export type FilterChipsProps = {
26
34
  /** More ids that describe the group, such as a hint beside it. */
27
35
  'aria-describedby'?: string;
28
36
  };
29
- export declare function FilterChips({ label, value, onChange, options, required, error, 'aria-describedby': describedByProp, }: FilterChipsProps): import("react").JSX.Element;
37
+ export declare function FilterChips({ label, value, onChange, options, required, requiredMark, error, 'aria-describedby': describedByProp, }: FilterChipsProps): import("react").JSX.Element;
@@ -3,10 +3,11 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useId } from 'react';
4
4
  import { cx } from '../class-names.js';
5
5
  import { FieldErrorRegion } from './FieldErrorRegion.js';
6
+ import { assertRequiredMark, showsRequiredMark, } from './FieldLabelText.js';
6
7
  function isCount(count) {
7
8
  return count === undefined || (Number.isInteger(count) && count >= 0);
8
9
  }
9
- export function FilterChips({ label, value, onChange, options, required = false, error, 'aria-describedby': describedByProp, }) {
10
+ export function FilterChips({ label, value, onChange, options, required = false, requiredMark = 'visible', error, 'aria-describedby': describedByProp, }) {
10
11
  const name = useId();
11
12
  const errorId = useId();
12
13
  const describedBy = [describedByProp?.trim() || null, error ? errorId : null]
@@ -15,6 +16,7 @@ export function FilterChips({ label, value, onChange, options, required = false,
15
16
  const values = options.map((option) => option.value);
16
17
  if (!label.trim())
17
18
  throw new RangeError('label must not be empty');
19
+ assertRequiredMark(requiredMark);
18
20
  if (options.length === 0 ||
19
21
  options.some((option) => !option.value.trim() || !option.label.trim()))
20
22
  throw new RangeError('options must have nonempty values and labels');
@@ -24,7 +26,7 @@ export function FilterChips({ label, value, onChange, options, required = false,
24
26
  throw new RangeError('value must match an option or be empty');
25
27
  if (options.some((option) => !isCount(option.count)))
26
28
  throw new RangeError('count must be a non-negative integer');
27
- return (_jsxs("fieldset", { "aria-describedby": describedBy || undefined, "aria-invalid": error ? true : undefined, className: "sw-filter-chips", children: [_jsxs("legend", { className: "sw-filter-chips-legend", children: [label, required ? (_jsxs("span", { "aria-hidden": "true", className: "sw-field-required", children: [' ', "*"] })) : null] }), _jsx("span", { className: "sw-filter-chips-options", children: options.map((option) => (_jsxs("label", { className: cx('sw-filter-chip', option.count === 0 && 'sw-filter-chip-quiet'), children: [_jsx("input", { checked: option.value === value, className: "sw-filter-chip-input", disabled: option.disabled, name: name, onChange: (event) => {
29
+ return (_jsxs("fieldset", { "aria-describedby": describedBy || undefined, "aria-invalid": error ? true : undefined, className: "sw-filter-chips", children: [_jsxs("legend", { className: "sw-filter-chips-legend", children: [label, showsRequiredMark(required, requiredMark) ? (_jsxs("span", { "aria-hidden": "true", className: "sw-field-required", children: [' ', "*"] })) : null] }), _jsx("span", { className: "sw-filter-chips-options", children: options.map((option) => (_jsxs("label", { className: cx('sw-filter-chip', option.count === 0 && 'sw-filter-chip-quiet'), children: [_jsx("input", { checked: option.value === value, className: "sw-filter-chip-input", disabled: option.disabled, name: name, onChange: (event) => {
28
30
  if (event.currentTarget.checked && !option.disabled)
29
31
  onChange(option.value);
30
32
  }, required: required, type: "radio", value: option.value }), _jsxs("span", { className: "sw-filter-chip-face", children: [option.label, option.count === undefined ? null : (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "sw-filter-chip-count", children: option.count })] }))] })] }, option.value))) }), _jsx(FieldErrorRegion, { className: "sw-filter-chips-error", id: errorId, message: error })] }));
@@ -1,3 +1,15 @@
1
+ import { type NavAppearance, navAppearances } from '../css/css-nav-words.js';
1
2
  import { type BoxProps } from './Box.js';
2
- export type NavProps = Omit<BoxProps, 'as'>;
3
- export declare function Nav({ className, ...rest }: NavProps): import("react").JSX.Element;
3
+ export { navAppearances, type NavAppearance };
4
+ export type NavProps = Omit<BoxProps, 'as'> & {
5
+ /**
6
+ * `words` draws every destination (a link or a Button) as plain words:
7
+ * the current one (`aria-current`) in the text color at label weight,
8
+ * the others muted, with no fill or underline. The words must be visible:
9
+ * a glyph-only destination has no weight to change, so its current mark
10
+ * would be color alone (keep `default` there). `default` keeps each
11
+ * destination's own look.
12
+ */
13
+ appearance?: NavAppearance;
14
+ };
15
+ export declare function Nav({ appearance, className, ...rest }: NavProps): 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 { navAppearances, navWordsClass, } from '../css/css-nav-words.js';
4
5
  import { Box } from './Box.js';
5
- export function Nav({ className, ...rest }) {
6
- return _jsx(Box, { as: "nav", className: cx('sw-nav', className), ...rest });
6
+ export { navAppearances };
7
+ export function Nav({ appearance = 'default', className, ...rest }) {
8
+ if (!navAppearances.includes(appearance))
9
+ throw new RangeError(`Unknown Nav appearance: ${String(appearance)}`);
10
+ return (_jsx(Box, { as: "nav", className: cx('sw-nav', appearance === 'words' && navWordsClass, className), ...rest }));
7
11
  }
@@ -22,9 +22,12 @@ type SegmentedLabel = {
22
22
  * `compact` is the quiet chip track for a section switch; `filled` gives every
23
23
  * segment the same width and paints the selected one in the accent, for a
24
24
  * choice that decides what a form does. It sizes to its container: the full
25
- * width in a Stack, the width of its widest label in an Inline.
25
+ * width in a Stack, the width of its widest label in an Inline. `raised` is
26
+ * a grey track whose selected segment is the surface raised by a small
27
+ * shadow, every segment at the md control height with its words in the
28
+ * text color, sized to its segments even in a Stack (unlike `compact`).
26
29
  */
27
- export type SegmentedControlVariant = 'compact' | 'filled';
30
+ export type SegmentedControlVariant = 'compact' | 'filled' | 'raised';
28
31
  /**
29
32
  * A disabled control can never report a change, so it needs no `onChange`;
30
33
  * a live one must have it.
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { forwardRef, useId } from 'react';
4
4
  import { cx } from '../class-names.js';
5
+ import { segmentedRaisedClass } from '../css/css-segmented-raised.js';
5
6
  import { FieldErrorRegion } from './FieldErrorRegion.js';
6
7
  export const SegmentedControl = forwardRef(function SegmentedControl({ disabled = false, error, items, onChange, required = false, value, variant = 'compact', ...labelProps }, ref) {
7
8
  const errorId = useId();
@@ -23,7 +24,7 @@ export const SegmentedControl = forwardRef(function SegmentedControl({ disabled
23
24
  }
24
25
  // The wrapper is always rendered, so an error that appears later swaps
25
26
  // text into a live region that already exists and the group keeps focus.
26
- return (_jsxs("div", { className: cx('sw-segmented-field', variant === 'filled' && 'sw-segmented-field-filled'), children: [_jsx("div", { ref: ref, "aria-describedby": error ? errorId : undefined, "aria-disabled": disabled || undefined, "aria-invalid": error ? true : undefined, "aria-required": required || undefined, className: cx('sw-segmented', variant === 'filled' && 'sw-segmented-filled', withIcons && 'sw-segmented-with-icons', disabled && 'sw-segmented-disabled'), onKeyDown: onKeyDown, role: "radiogroup", ...labelProps, children: items.map((item) => {
27
+ return (_jsxs("div", { className: cx('sw-segmented-field', variant === 'filled' && 'sw-segmented-field-filled'), children: [_jsx("div", { ref: ref, "aria-describedby": error ? errorId : undefined, "aria-disabled": disabled || undefined, "aria-invalid": error ? true : undefined, "aria-required": required || undefined, className: cx('sw-segmented', variant === 'filled' && 'sw-segmented-filled', variant === 'raised' && segmentedRaisedClass, withIcons && 'sw-segmented-with-icons', disabled && 'sw-segmented-disabled'), onKeyDown: onKeyDown, role: "radiogroup", ...labelProps, children: items.map((item) => {
27
28
  const selected = item.id === value;
28
29
  return (_jsxs("button", { "aria-checked": selected, className: cx('sw-segmented-item', selected && 'sw-segmented-item-selected'), disabled: disabled, onClick: () => {
29
30
  if (!selected) {
@@ -1,5 +1,6 @@
1
1
  import { type SelectOption } from '../select-list.js';
2
2
  import { type FieldSize } from './Field.js';
3
+ import { type RequiredMark } from './FieldLabelText.js';
3
4
  export type { SelectOption };
4
5
  /**
5
6
  * `content` (the default) sizes the field to its longest option, as a native
@@ -26,6 +27,13 @@ export type SelectProps = {
26
27
  placeholder?: string;
27
28
  /** Marks the label as Field does and sets `aria-required`. */
28
29
  required?: boolean;
30
+ /**
31
+ * `hidden` leaves out the label's " *" while the control stays required
32
+ * (and announced so); `visible` (default) draws it.
33
+ * When hidden, say in visible text which fields must be filled; a
34
+ * placeholder alone is not enough.
35
+ */
36
+ requiredMark?: RequiredMark;
29
37
  /** A validation message under the control, wired as Field's error is. */
30
38
  error?: string;
31
39
  /**
@@ -35,4 +43,4 @@ export type SelectProps = {
35
43
  */
36
44
  width?: SelectWidth;
37
45
  };
38
- export declare function Select({ action, label, labelVisuallyHidden, onChange, options, size, value, placeholder, required, error, width, }: SelectProps): import("react").JSX.Element;
46
+ export declare function Select({ action, label, labelVisuallyHidden, onChange, options, size, value, placeholder, required, requiredMark, error, width, }: SelectProps): import("react").JSX.Element;
@@ -4,12 +4,13 @@ import { useId, useRef } from 'react';
4
4
  import { cx } from '../class-names.js';
5
5
  import { selectedSelectIndex } from '../select-list.js';
6
6
  import { FieldErrorRegion } from './FieldErrorRegion.js';
7
- import { FieldLabelText } from './FieldLabelText.js';
7
+ import { assertRequiredMark, FieldLabelText, } from './FieldLabelText.js';
8
8
  import { SelectListbox } from './select/SelectListbox.js';
9
9
  import { SelectValue } from './select/SelectValue.js';
10
10
  import { useSelectList } from './select/use-select-list.js';
11
11
  import { Stack } from './Stack.js';
12
- export function Select({ action, label, labelVisuallyHidden = false, onChange, options, size = 'md', value, placeholder, required = false, error, width = 'content', }) {
12
+ export function Select({ action, label, labelVisuallyHidden = false, onChange, options, size = 'md', value, placeholder, required = false, requiredMark = 'visible', error, width = 'content', }) {
13
+ assertRequiredMark(requiredMark);
13
14
  if (placeholder !== undefined && !placeholder.trim())
14
15
  throw new RangeError('placeholder must not be empty');
15
16
  const labelId = useId();
@@ -22,7 +23,7 @@ export function Select({ action, label, labelVisuallyHidden = false, onChange, o
22
23
  const showsPlaceholder = placeholder !== undefined &&
23
24
  !options.some((option) => option.value === value);
24
25
  const { commit, highlight, onTriggerKeyDown, open, openList, setHighlight, setOpen, } = useSelectList({ action, onChange, options, rootRef, value });
25
- return (_jsx("div", { ref: rootRef, className: cx('sw-select', size === 'xs' && 'sw-select-xs', width === 'full' && 'sw-select-full', error && 'sw-select-invalid'), children: _jsxs(Stack, { gap: labelVisuallyHidden ? 0 : 1, children: [_jsx("label", { htmlFor: triggerId, id: labelId, children: _jsx(FieldLabelText, { label: label, required: required, size: size, visuallyHidden: labelVisuallyHidden }) }), _jsxs("div", { className: "sw-select-control", children: [_jsx("button", { "aria-activedescendant": open ? `${optionIdPrefix}-${highlight}` : undefined, "aria-controls": open ? listId : undefined, "aria-describedby": error ? errorId : undefined, "aria-expanded": open, "aria-haspopup": "listbox", "aria-invalid": error ? true : undefined, "aria-labelledby": labelId, "aria-required": required || undefined, className: "sw-select-trigger", id: triggerId, onBlur: (event) => {
26
+ return (_jsx("div", { ref: rootRef, className: cx('sw-select', size === 'xs' && 'sw-select-xs', width === 'full' && 'sw-select-full', error && 'sw-select-invalid'), children: _jsxs(Stack, { gap: labelVisuallyHidden ? 0 : 1, children: [_jsx("label", { htmlFor: triggerId, id: labelId, children: _jsx(FieldLabelText, { label: label, required: required, requiredMark: requiredMark, size: size, visuallyHidden: labelVisuallyHidden }) }), _jsxs("div", { className: "sw-select-control", children: [_jsx("button", { "aria-activedescendant": open ? `${optionIdPrefix}-${highlight}` : undefined, "aria-controls": open ? listId : undefined, "aria-describedby": error ? errorId : undefined, "aria-expanded": open, "aria-haspopup": "listbox", "aria-invalid": error ? true : undefined, "aria-labelledby": labelId, "aria-required": required || undefined, className: "sw-select-trigger", id: triggerId, onBlur: (event) => {
26
27
  const next = event.relatedTarget;
27
28
  if (next instanceof Node && rootRef.current?.contains(next)) {
28
29
  return;
@@ -1,5 +1,12 @@
1
1
  /** `fixed` sizes the count for a few digits; `full` fills its container, such as a table cell. */
2
2
  export type StepperWidth = 'fixed' | 'full';
3
+ export declare const stepperLimitAppearances: readonly ["faded", "plain"];
4
+ /**
5
+ * How − looks at `min` and + at `max`, where a press cannot move the count:
6
+ * `faded` (the default) fades the button as any disabled control; `plain`
7
+ * keeps its usual look. Either way the button is disabled.
8
+ */
9
+ export type StepperLimitAppearance = (typeof stepperLimitAppearances)[number];
3
10
  export type StepperProps = {
4
11
  /** Names the count. Keep it visible unless headers around it say it (`labelVisuallyHidden`). */
5
12
  label: string;
@@ -35,6 +42,12 @@ export type StepperProps = {
35
42
  placeholder?: string;
36
43
  name?: string;
37
44
  width?: StepperWidth;
45
+ /**
46
+ * `plain` keeps − at `min` and + at `max` in their usual look instead of
47
+ * fading them (`faded`, the default). The button is still disabled, and a
48
+ * disabled or read-only Stepper is unchanged.
49
+ */
50
+ limitAppearance?: StepperLimitAppearance;
38
51
  };
39
52
  /**
40
53
  * A whole-number count between − and + buttons that can also be typed: one
@@ -5,6 +5,7 @@ import { cx } from '../class-names.js';
5
5
  import { assertStepperBounds, readStepperEntry, stepperMoves, stepperText, } from '../stepper-count.js';
6
6
  import { FieldErrorRegion } from './FieldErrorRegion.js';
7
7
  import { FieldLabelText } from './FieldLabelText.js';
8
+ export const stepperLimitAppearances = ['faded', 'plain'];
8
9
  /** Keeps focus where it was (the count, or nowhere) when a button is pressed. */
9
10
  function keepFocus(event) {
10
11
  event.preventDefault();
@@ -14,9 +15,11 @@ function keepFocus(event) {
14
15
  * tab stop (the count, a spin button that ArrowUp and ArrowDown step), with
15
16
  * the buttons as pointer targets beside it. − stops at `min`, + at `max`.
16
17
  */
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
+ 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', limitAppearance = 'faded', }, ref) {
18
19
  const bounds = { min, max, step };
19
20
  assertStepperBounds(bounds);
21
+ if (!stepperLimitAppearances.includes(limitAppearance))
22
+ throw new RangeError(`Unknown Stepper limitAppearance: ${String(limitAppearance)}`);
20
23
  if (value !== null && !Number.isSafeInteger(value))
21
24
  throw new RangeError('Stepper value must be a whole number or null');
22
25
  const inputId = useId();
@@ -70,7 +73,10 @@ export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden
70
73
  move(target);
71
74
  }
72
75
  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,
76
+ 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', limitAppearance === 'plain' &&
77
+ !disabled &&
78
+ !readOnly &&
79
+ 'sw-stepper-limit-plain'), 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
80
  // iOS's numeric pad has no minus key, so a range below zero takes
75
81
  // the text keyboard.
76
82
  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 })] }));
@@ -1,4 +1,5 @@
1
1
  import { type BadgeTone } from '../../css/css-data.js';
2
+ import { type RequiredMark } from '../FieldLabelText.js';
2
3
  /** A short tag after an option's label, such as "Warning" or "Blocked". */
3
4
  export type ComboboxBadge = {
4
5
  label: string;
@@ -58,6 +59,13 @@ export type ComboboxProps = {
58
59
  description?: string;
59
60
  error?: string;
60
61
  required?: boolean;
62
+ /**
63
+ * `hidden` leaves out the label's " *" while the input stays required
64
+ * (and announced so); `visible` (default) draws it.
65
+ * When hidden, say in visible text which fields must be filled; a
66
+ * placeholder alone is not enough.
67
+ */
68
+ requiredMark?: RequiredMark;
61
69
  disabled?: boolean;
62
70
  size?: ComboboxSize;
63
71
  };
@@ -1,7 +1,9 @@
1
1
  import { type DenominationGridProps } from './types.js';
2
2
  type StripProps = Required<Pick<DenominationGridProps, 'label' | 'columns' | 'rows' | 'zeroLabel'>> & Pick<DenominationGridProps, 'totalLabel' | 'labelWidth'> & {
3
- /** The row labels' appearance class, if any. */
3
+ /** The grid's appearance classes (labels, totals, cell tones), if any. */
4
4
  className?: string;
5
+ /** The `table` frame: a hairline around the region and a heading-row fill. */
6
+ framed: boolean;
5
7
  };
6
- export declare function DenominationStrip({ className, label, columns, rows, zeroLabel, totalLabel, labelWidth, }: StripProps): import("react").JSX.Element;
8
+ export declare function DenominationStrip({ className, framed, label, columns, rows, zeroLabel, totalLabel, labelWidth, }: StripProps): import("react").JSX.Element;
7
9
  export {};
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../../class-names.js';
3
3
  import { denominationLabelWidthClass } from '../../css/css-denomination-aligned.js';
4
+ import { denominationFramedClass, denominationScrollFramedClass, } from '../../css/css-denomination-appearance.js';
4
5
  import { ScrollRegion } from '../ScrollRegion.js';
5
6
  import { ColumnHeadText } from './ColumnHeadText.js';
6
7
  import { StripRow } from './StripRow.js';
@@ -16,7 +17,7 @@ function TotalHead({ totalLabel }) {
16
17
  return _jsx("td", { className: "sw-denomination-corner sw-denomination-total-head" });
17
18
  return (_jsx("th", { className: "sw-denomination-head sw-denomination-total-head", scope: "col", children: _jsx("span", { className: "sw-denomination-total-label", children: totalLabel }) }));
18
19
  }
19
- export function DenominationStrip({ className, label, columns, rows, zeroLabel, totalLabel, labelWidth, }) {
20
+ export function DenominationStrip({ className, framed, label, columns, rows, zeroLabel, totalLabel, labelWidth, }) {
20
21
  const hasTotals = rows.some((row) => row.total !== undefined);
21
22
  // An aligned strip sizes its columns from the region's width; it needs
22
23
  // the column count and whether a total column takes a share.
@@ -26,5 +27,5 @@ export function DenominationStrip({ className, label, columns, rows, zeroLabel,
26
27
  '--sw-denomination-columns': columns.length,
27
28
  '--sw-denomination-total': hasTotals ? 1 : 0,
28
29
  };
29
- return (_jsx(ScrollRegion, { "aria-label": label, className: cx('sw-denomination-scroll', aligned && 'sw-denomination-scroll-aligned'), children: _jsxs("table", { className: cx('sw-denomination-grid sw-denomination-strip', labelWidth && 'sw-denomination-strip-aligned', rows.some((row) => row.entry) && 'sw-denomination-strip-entry', labelWidth && denominationLabelWidthClass(labelWidth), className), style: aligned, children: [_jsx("caption", { className: "sw-sr-only", children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("td", { className: "sw-denomination-corner" }), columns.map((column) => (_jsx("th", { className: "sw-denomination-head", scope: "col", children: _jsx(ColumnHeadText, { column: column }) }, column.key))), hasTotals ? _jsx(TotalHead, { totalLabel: totalLabel }) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsx(StripRow, { columns: columns, hasTotals: hasTotals, row: row, zeroLabel: zeroLabel }, row.id))) })] }) }));
30
+ return (_jsx(ScrollRegion, { "aria-label": label, className: cx('sw-denomination-scroll', aligned && 'sw-denomination-scroll-aligned', framed && denominationScrollFramedClass), children: _jsxs("table", { className: cx('sw-denomination-grid sw-denomination-strip', labelWidth && 'sw-denomination-strip-aligned', rows.some((row) => row.entry) && 'sw-denomination-strip-entry', labelWidth && denominationLabelWidthClass(labelWidth), framed && denominationFramedClass, className), style: aligned, children: [_jsx("caption", { className: "sw-sr-only", children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("td", { className: "sw-denomination-corner" }), columns.map((column) => (_jsx("th", { className: "sw-denomination-head", scope: "col", children: _jsx(ColumnHeadText, { column: column }) }, column.key))), hasTotals ? _jsx(TotalHead, { totalLabel: totalLabel }) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsx(StripRow, { columns: columns, hasTotals: hasTotals, row: row, zeroLabel: zeroLabel }, row.id))) })] }) }));
30
31
  }
@@ -1,6 +1,6 @@
1
1
  import { type DenominationGridProps } from './types.js';
2
2
  type TilesProps = Required<Pick<DenominationGridProps, 'label' | 'columns' | 'rows' | 'zeroLabel' | 'rowRole'>> & Pick<DenominationGridProps, 'subtotal'> & {
3
- /** The row labels' appearance class, if any. */
3
+ /** The grid's appearance classes (labels, totals), if any. */
4
4
  className?: string;
5
5
  };
6
6
  export declare function DenominationTiles({ className, label, columns, rows, rowRole, subtotal, zeroLabel, }: TilesProps): import("react").JSX.Element;
@@ -12,5 +12,5 @@ import { columnName } from './ColumnHeadText.js';
12
12
  export function EntryCell({ row, entry, column, index }) {
13
13
  const max = entry.max?.[index] ?? undefined;
14
14
  const tone = row.cellTones?.[index];
15
- return (_jsx("td", { className: cx('sw-denomination-entry', ...denominationCellToneClassNames(tone)), children: _jsx(Stepper, { changed: tone === 'accent', decrementLabel: entry.decrementLabel, disabled: row.disabled, error: entry.errors?.[index] ?? undefined, incrementLabel: entry.incrementLabel, label: `${row.label} ${columnName(column)}`, labelVisuallyHidden: true, max: max, onChange: (value) => entry.onChange(column.key, value), placeholder: entry.placeholder, value: row.cells[index] ?? null, width: "full" }) }));
15
+ return (_jsx("td", { className: cx('sw-denomination-entry', ...denominationCellToneClassNames(tone)), children: _jsx(Stepper, { changed: tone === 'accent', decrementLabel: entry.decrementLabel, disabled: row.disabled, error: entry.errors?.[index] ?? undefined, incrementLabel: entry.incrementLabel, label: `${row.label} ${columnName(column)}`, labelVisuallyHidden: true, limitAppearance: entry.limitAppearance, max: max, onChange: (value) => entry.onChange(column.key, value), placeholder: entry.placeholder, value: row.cells[index] ?? null, width: "full" }) }));
16
16
  }
@@ -4,10 +4,13 @@ import { denominationCellToneClassNames } from '../../css/css-denomination-cell-
4
4
  import { denominationCellView } from '../../denomination-cells.js';
5
5
  import { EntryCell } from './EntryCell.js';
6
6
  import { RowLabelLine } from './RowLabelLine.js';
7
- /** One count, read-only: the zero label, a signed delta, and the cell's tone. */
7
+ /**
8
+ * One count, read-only: the zero label, a signed delta, and the cell's tone.
9
+ * The value has its own box, which the `pill` tone appearance fills.
10
+ */
8
11
  function CountCell({ row, index, zeroLabel, }) {
9
12
  const view = denominationCellView(row.cells[index] ?? null, row.signed ?? false, zeroLabel);
10
- return (_jsx("td", { className: cx('sw-denomination-cell', `sw-denomination-cell-${view.state}`, ...denominationCellToneClassNames(row.cellTones?.[index])), children: view.text }));
13
+ return (_jsx("td", { className: cx('sw-denomination-cell', `sw-denomination-cell-${view.state}`, ...denominationCellToneClassNames(row.cellTones?.[index])), children: _jsx("span", { className: "sw-denomination-cell-value", children: view.text }) }));
11
14
  }
12
15
  /**
13
16
  * One strip row: its pinned label (with the phone total under it), a count
@@ -1,8 +1,11 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type BadgeTone } from '../../css/css-data.js';
3
+ import { type DenominationGridFrame, type DenominationTotalEmphasis } from '../../css/css-denomination-appearance.js';
4
+ import { type DenominationCellToneAppearance } from '../../css/css-denomination-cell-tones.js';
3
5
  import { type DenominationLabelAppearance } from '../../css/css-denomination-label.js';
4
6
  import { type DenominationLabelWidth } from '../../css/css-denomination-aligned.js';
5
- export type { DenominationLabelWidth };
7
+ import { type StepperLimitAppearance } from '../Stepper.js';
8
+ export type { DenominationCellToneAppearance, DenominationGridFrame, DenominationLabelWidth, DenominationTotalEmphasis, };
6
9
  export type DenominationGridTone = BadgeTone;
7
10
  export type DenominationGridLayout = 'strip' | 'tiles';
8
11
  /**
@@ -36,6 +39,11 @@ export type DenominationGridEntry = {
36
39
  errors?: readonly (string | null)[];
37
40
  /** What an empty box shows, such as "0". */
38
41
  placeholder?: string;
42
+ /**
43
+ * The Steppers' `limitAppearance`: `plain` keeps − at 0 (and + at a cap)
44
+ * in its usual look instead of faded; it is still disabled.
45
+ */
46
+ limitAppearance?: StepperLimitAppearance;
39
47
  };
40
48
  export type DenominationGridRow = {
41
49
  id: string;
@@ -79,6 +87,11 @@ export type DenominationGridProps = {
79
87
  rows: readonly DenominationGridRow[];
80
88
  layout?: DenominationGridLayout;
81
89
  subtotal?: (count: number, column: DenominationGridColumn) => string;
90
+ /**
91
+ * What a zero or empty read-only count shows, `—` by default. An empty
92
+ * string leaves those counts blank, so only the counts that are there
93
+ * stand out (the column header still names each cell).
94
+ */
82
95
  zeroLabel?: string;
83
96
  /**
84
97
  * Names the strip's total column, such as "Total": a column header over
@@ -111,4 +124,25 @@ export type DenominationGridProps = {
111
124
  * total.
112
125
  */
113
126
  labelAppearance?: DenominationLabelAppearance;
127
+ /**
128
+ * `table` frames the strip: its surface inside a hairline with rounded
129
+ * corners, the heading row on the subtle fill, a rule between columns,
130
+ * and the counts at label size, centred under their heads. `none` (the
131
+ * default) leaves open rows on the page's surface. Only the strip has a
132
+ * frame.
133
+ */
134
+ frame?: DenominationGridFrame;
135
+ /**
136
+ * `strong` sets the row totals in bold label type, a neutral row's in the
137
+ * text color and a toned row's in its tone; `default` keeps them in data
138
+ * type in the row's tone (a neutral row's muted).
139
+ */
140
+ totalEmphasis?: DenominationTotalEmphasis;
141
+ /**
142
+ * How a `cellTones` tone shows on a strip's read-only count: `text` (the
143
+ * default) sets the count in the tone; `pill` sets it on a rounded tint of
144
+ * the tone in the tone's ink, such as a difference in danger or a
145
+ * changed count in accent. Entry counts and tiles keep their tone border.
146
+ */
147
+ cellToneAppearance?: DenominationCellToneAppearance;
114
148
  };
@@ -11,12 +11,14 @@ type DialogTitleProps = {
11
11
  /** The close button's accessible name; without it there is no button. */
12
12
  closeLabel?: string;
13
13
  onClose: () => void;
14
+ /** A banded dialog's title is always a row, which draws the band. */
15
+ banded: boolean;
14
16
  };
15
17
  /**
16
18
  * The dialog's title, which names it through `aria-labelledby`. Alone it is
17
19
  * the heading, as it always was. With an icon, a description or a
18
- * `closeLabel` it is a row: the icon, the title over its description, and
19
- * an icon-only ghost close button at the end.
20
+ * `closeLabel`, or in a banded dialog, it is a row: the icon, the title over
21
+ * its description, and an icon-only ghost close button at the end.
20
22
  */
21
- export declare function DialogTitle({ closeLabel, description, descriptionId, icon, id, level, onClose, title, }: DialogTitleProps): import("react").JSX.Element;
23
+ export declare function DialogTitle({ banded, closeLabel, description, descriptionId, icon, id, level, onClose, title, }: DialogTitleProps): import("react").JSX.Element;
22
24
  export {};
@@ -5,12 +5,12 @@ import { Text } from '../Text.js';
5
5
  /**
6
6
  * The dialog's title, which names it through `aria-labelledby`. Alone it is
7
7
  * the heading, as it always was. With an icon, a description or a
8
- * `closeLabel` it is a row: the icon, the title over its description, and
9
- * an icon-only ghost close button at the end.
8
+ * `closeLabel`, or in a banded dialog, it is a row: the icon, the title over
9
+ * its description, and an icon-only ghost close button at the end.
10
10
  */
11
- export function DialogTitle({ closeLabel, description, descriptionId, icon, id, level, onClose, title, }) {
11
+ export function DialogTitle({ banded, closeLabel, description, descriptionId, icon, id, level, onClose, title, }) {
12
12
  const heading = (_jsx(Text, { as: level === 2 ? 'h2' : 'h3', id: id, variant: "title", children: title }));
13
- if (!closeLabel && !icon && !description)
13
+ if (!banded && !closeLabel && !icon && !description)
14
14
  return heading;
15
15
  return (_jsxs("div", { className: cx('sw-dialog-header', Boolean(icon || description) && 'sw-dialog-header-framed'), children: [icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-dialog-icon", children: icon })) : null, description ? (_jsxs("div", { className: "sw-dialog-titles", children: [heading, _jsx(Text, { as: "p", className: "sw-dialog-description", color: "muted", id: descriptionId, variant: "caption", children: description })] })) : (heading), closeLabel ? (_jsxs(Button, { className: "sw-dialog-close", onPress: onClose, size: "sm", variant: "ghost", children: [_jsx("span", { className: "sw-sr-only", children: closeLabel }), _jsx("span", { "aria-hidden": "true", className: "sw-dialog-close-glyph" })] })) : null] }));
16
16
  }