@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.
- package/CHANGELOG.md +30 -0
- package/README.md +19 -5
- package/dist/components/ActionMenu.d.ts +10 -1
- package/dist/components/ActionMenu.js +7 -2
- package/dist/components/AppHeader.d.ts +9 -1
- package/dist/components/AppHeader.js +6 -2
- package/dist/components/Box.d.ts +10 -2
- package/dist/components/Button.d.ts +27 -2
- package/dist/components/Button.js +5 -3
- package/dist/components/ButtonGroup.d.ts +9 -1
- package/dist/components/ButtonGroup.js +8 -2
- package/dist/components/Combobox.js +2 -2
- package/dist/components/DateField.d.ts +9 -1
- package/dist/components/DateField.js +4 -3
- package/dist/components/DenominationGrid.d.ts +5 -3
- package/dist/components/DenominationGrid.js +21 -11
- package/dist/components/Dialog.d.ts +9 -1
- package/dist/components/Dialog.js +8 -4
- package/dist/components/Field.d.ts +8 -0
- package/dist/components/Field.js +4 -3
- package/dist/components/FieldLabelText.d.ts +21 -3
- package/dist/components/FieldLabelText.js +16 -4
- package/dist/components/FilterChips.d.ts +9 -1
- package/dist/components/FilterChips.js +4 -2
- package/dist/components/Nav.d.ts +14 -2
- package/dist/components/Nav.js +6 -2
- package/dist/components/SegmentedControl.d.ts +5 -2
- package/dist/components/SegmentedControl.js +2 -1
- package/dist/components/Select.d.ts +9 -1
- package/dist/components/Select.js +4 -3
- package/dist/components/Stepper.d.ts +13 -0
- package/dist/components/Stepper.js +8 -2
- package/dist/components/combobox/types.d.ts +8 -0
- package/dist/components/denomination-grid/DenominationStrip.d.ts +4 -2
- package/dist/components/denomination-grid/DenominationStrip.js +3 -2
- package/dist/components/denomination-grid/DenominationTiles.d.ts +1 -1
- package/dist/components/denomination-grid/EntryCell.js +1 -1
- package/dist/components/denomination-grid/StripRow.js +5 -2
- package/dist/components/denomination-grid/types.d.ts +35 -1
- package/dist/components/dialog/DialogTitle.d.ts +5 -3
- package/dist/components/dialog/DialogTitle.js +4 -4
- package/dist/css/css-action-menu-trigger.d.ts +11 -0
- package/dist/css/css-action-menu-trigger.js +66 -0
- package/dist/css/css-action-menu.js +7 -2
- package/dist/css/css-app-header-bar.d.ts +10 -0
- package/dist/css/css-app-header-bar.js +26 -0
- package/dist/css/css-box-border.d.ts +6 -1
- package/dist/css/css-box-border.js +8 -4
- package/dist/css/css-button-group-attached.js +5 -2
- package/dist/css/css-button-group-track.d.ts +11 -0
- package/dist/css/css-button-group-track.js +48 -0
- package/dist/css/css-button.d.ts +19 -2
- package/dist/css/css-button.js +31 -1
- package/dist/css/css-chrome.js +14 -1
- package/dist/css/css-combobox.js +2 -1
- package/dist/css/css-components.js +3 -0
- package/dist/css/css-data.js +3 -2
- package/dist/css/css-date-field.js +2 -1
- package/dist/css/css-denomination-appearance.d.ts +21 -0
- package/dist/css/css-denomination-appearance.js +114 -0
- package/dist/css/css-denomination-cell-tones.d.ts +8 -0
- package/dist/css/css-denomination-cell-tones.js +20 -1
- package/dist/css/css-denomination-grid.js +4 -0
- package/dist/css/css-dialog-banded.d.ts +11 -0
- package/dist/css/css-dialog-banded.js +84 -0
- package/dist/css/css-multi-select.js +2 -1
- package/dist/css/css-nav-words.d.ts +13 -0
- package/dist/css/css-nav-words.js +51 -0
- package/dist/css/css-notice.d.ts +4 -11
- package/dist/css/css-notice.js +6 -22
- package/dist/css/css-segmented-raised.d.ts +3 -0
- package/dist/css/css-segmented-raised.js +72 -0
- package/dist/css/css-segmented.js +9 -3
- package/dist/css/css-select.js +2 -1
- package/dist/css/css-split.js +2 -1
- package/dist/css/css-stepper.js +7 -0
- package/dist/css/quiet-fill.d.ts +7 -0
- package/dist/css/quiet-fill.js +7 -0
- package/dist/css/stacking.js +2 -1
- package/dist/css/stylesheet.js +2 -0
- package/dist/css/tone-tint.d.ts +21 -0
- package/dist/css/tone-tint.js +23 -0
- package/dist/index.d.ts +11 -9
- package/dist/index.js +9 -7
- package/dist/styles.css +300 -10
- 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. */
|
package/dist/components/Field.js
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
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
|
|
8
|
-
*
|
|
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 })] }));
|
package/dist/components/Nav.d.ts
CHANGED
|
@@ -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
|
|
3
|
-
export
|
|
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;
|
package/dist/components/Nav.js
CHANGED
|
@@ -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
|
|
6
|
-
|
|
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'
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|