@scalewing/react 1.26.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/README.md +147 -3
- package/dist/components/ActionBar.d.ts +14 -0
- package/dist/components/ActionBar.js +5 -2
- package/dist/components/DenominationGrid.d.ts +5 -2
- package/dist/components/DenominationGrid.js +11 -3
- package/dist/components/Dialog.d.ts +28 -4
- package/dist/components/Dialog.js +20 -2
- package/dist/components/Notice.d.ts +69 -0
- package/dist/components/Notice.js +48 -0
- package/dist/components/Progress.d.ts +3 -1
- package/dist/components/Progress.js +4 -0
- package/dist/components/Stepper.d.ts +44 -0
- package/dist/components/Stepper.js +77 -0
- package/dist/components/Steps.d.ts +18 -0
- package/dist/components/Steps.js +34 -0
- package/dist/components/Toast.d.ts +20 -4
- package/dist/components/Toast.js +44 -21
- package/dist/components/action-bar/ActionBarSummary.d.ts +22 -0
- package/dist/components/action-bar/ActionBarSummary.js +23 -0
- package/dist/components/denomination-grid/ColumnHeadText.d.ts +7 -0
- package/dist/components/denomination-grid/ColumnHeadText.js +13 -0
- package/dist/components/denomination-grid/DenominationStrip.d.ts +5 -2
- package/dist/components/denomination-grid/DenominationStrip.js +4 -8
- package/dist/components/denomination-grid/DenominationTiles.d.ts +5 -2
- package/dist/components/denomination-grid/DenominationTiles.js +4 -3
- package/dist/components/denomination-grid/EntryCell.d.ts +15 -0
- package/dist/components/denomination-grid/EntryCell.js +16 -0
- package/dist/components/denomination-grid/RowLabelLine.d.ts +3 -2
- package/dist/components/denomination-grid/RowLabelLine.js +5 -3
- package/dist/components/denomination-grid/StripRow.d.ts +13 -0
- package/dist/components/denomination-grid/StripRow.js +19 -0
- package/dist/components/denomination-grid/types.d.ts +53 -1
- package/dist/components/denomination-grid/validate-entry.d.ts +15 -0
- package/dist/components/denomination-grid/validate-entry.js +78 -0
- package/dist/components/dialog/DialogFooter.d.ts +14 -0
- package/dist/components/dialog/DialogFooter.js +11 -0
- package/dist/components/dialog/DialogTitle.d.ts +11 -4
- package/dist/components/dialog/DialogTitle.js +8 -6
- package/dist/components/steps/Step.d.ts +24 -0
- package/dist/components/steps/Step.js +62 -0
- package/dist/components/steps/step-status.d.ts +26 -0
- package/dist/components/steps/step-status.js +37 -0
- package/dist/components/steps/steps-context.d.ts +16 -0
- package/dist/components/steps/steps-context.js +3 -0
- package/dist/components/toast/ToastContent.d.ts +22 -0
- package/dist/components/toast/ToastContent.js +21 -0
- package/dist/components/toast/use-toast-hold.d.ts +28 -0
- package/dist/components/toast/use-toast-hold.js +53 -0
- package/dist/components/toast/use-toast-timer.d.ts +15 -0
- package/dist/components/toast/use-toast-timer.js +34 -0
- package/dist/css/css-action-bar-summary.d.ts +2 -0
- package/dist/css/css-action-bar-summary.js +48 -0
- package/dist/css/css-action-bar.js +4 -0
- package/dist/css/css-components.js +16 -0
- package/dist/css/css-denomination-entry.d.ts +2 -0
- package/dist/css/css-denomination-entry.js +78 -0
- package/dist/css/css-denomination-grid.js +8 -0
- package/dist/css/css-denomination-label.d.ts +10 -0
- package/dist/css/css-denomination-label.js +36 -0
- package/dist/css/css-dialog-frame.d.ts +2 -0
- package/dist/css/css-dialog-frame.js +83 -0
- package/dist/css/css-dialog.js +20 -4
- package/dist/css/css-notice.d.ts +22 -0
- package/dist/css/css-notice.js +168 -0
- package/dist/css/css-progress.d.ts +3 -0
- package/dist/css/css-progress.js +13 -6
- package/dist/css/css-stepper.d.ts +10 -0
- package/dist/css/css-stepper.js +126 -0
- package/dist/css/css-steps.d.ts +9 -0
- package/dist/css/css-steps.js +203 -0
- package/dist/css/css-toast-card.d.ts +5 -0
- package/dist/css/css-toast-card.js +122 -0
- package/dist/css/css-variables.js +1 -0
- package/dist/css/stylesheet.js +10 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +8 -5
- package/dist/stepper-count.d.ts +37 -0
- package/dist/stepper-count.js +50 -0
- package/dist/styles.css +623 -1
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, forwardRef, } from 'react';
|
|
3
|
+
import { cx } from '../class-names.js';
|
|
4
|
+
import { noticeTones, noticeVariants, } from '../css/css-notice.js';
|
|
5
|
+
export { noticeTones, noticeVariants };
|
|
6
|
+
const noticeRoles = ['status', 'alert', 'none'];
|
|
7
|
+
function defaultRole(tone, variant) {
|
|
8
|
+
if (variant === 'band')
|
|
9
|
+
return 'none';
|
|
10
|
+
return tone === 'danger' ? 'alert' : 'status';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The message's parts, without the empty ones (`false`, `null`,
|
|
14
|
+
* `undefined`, `''`) a condition leaves; fragments flatten.
|
|
15
|
+
*/
|
|
16
|
+
function messageParts(children) {
|
|
17
|
+
return Children.toArray(children).filter((part) => part !== '');
|
|
18
|
+
}
|
|
19
|
+
/** A sentence, even one with values interpolated, may share the title's row. */
|
|
20
|
+
function isText(parts) {
|
|
21
|
+
return parts.every((part) => typeof part === 'string' || typeof part === 'number');
|
|
22
|
+
}
|
|
23
|
+
function assertNoticeProps(tone, title, role, variant, hasMessage) {
|
|
24
|
+
if (!noticeVariants.includes(variant))
|
|
25
|
+
throw new RangeError(`Unknown Notice variant: ${String(variant)}`);
|
|
26
|
+
if (!noticeTones.includes(tone))
|
|
27
|
+
throw new RangeError(`Unknown Notice tone: ${String(tone)}`);
|
|
28
|
+
if (title !== undefined && (typeof title !== 'string' || !title.trim()))
|
|
29
|
+
throw new RangeError('Notice title must not be empty');
|
|
30
|
+
if (role !== undefined && !noticeRoles.includes(role))
|
|
31
|
+
throw new RangeError(`Unknown Notice role: ${String(role)}`);
|
|
32
|
+
if (title === undefined && !hasMessage)
|
|
33
|
+
throw new RangeError('Notice needs a title or a message');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A tinted note in the page's flow: information, a good outcome, a caution
|
|
37
|
+
* or a refusal, with an optional bold lead, glyph and one action. It stays
|
|
38
|
+
* until the consumer removes it. `variant="band"` is the same note as a
|
|
39
|
+
* full-width strip of page chrome. Pass `tabIndex={-1}` and a ref to move
|
|
40
|
+
* focus to it.
|
|
41
|
+
*/
|
|
42
|
+
export const Notice = forwardRef(function Notice({ action, children, className, icon, role, title, tone, variant = 'default', ...rest }, ref) {
|
|
43
|
+
const parts = messageParts(children);
|
|
44
|
+
const hasMessage = parts.length > 0;
|
|
45
|
+
assertNoticeProps(tone, title, role, variant, hasMessage);
|
|
46
|
+
const announced = role ?? defaultRole(tone, variant);
|
|
47
|
+
return (_jsxs("div", { ...rest, ref: ref, className: cx('sw-notice', `sw-notice-${tone}`, variant === 'band' && 'sw-notice-band', className), role: announced === 'none' ? undefined : announced, children: [icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-notice-icon", children: icon })) : null, _jsxs("div", { className: cx('sw-notice-content', hasMessage && !isText(parts) && 'sw-notice-stacked'), children: [title ? _jsx("strong", { className: "sw-notice-title", children: title }) : null, hasMessage ? _jsx("div", { className: "sw-notice-body", children: children }) : null] }), action ? _jsx("div", { className: "sw-notice-action", children: action }) : null] }));
|
|
48
|
+
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
|
+
import { progressTones } from '../css/css-progress.js';
|
|
5
|
+
export { progressTones };
|
|
4
6
|
export function Progress({ label, value, max, tone = 'accent', showCount = true, }) {
|
|
5
7
|
if (!label.trim())
|
|
6
8
|
throw new RangeError('label must not be empty');
|
|
@@ -8,6 +10,8 @@ export function Progress({ label, value, max, tone = 'accent', showCount = true,
|
|
|
8
10
|
throw new RangeError('max must be a positive finite number');
|
|
9
11
|
if (!Number.isFinite(value) || value < 0 || value > max)
|
|
10
12
|
throw new RangeError('value must be finite and within 0..max');
|
|
13
|
+
if (!progressTones.includes(tone))
|
|
14
|
+
throw new RangeError(`Unknown Progress tone: ${String(tone)}`);
|
|
11
15
|
const labelId = useId();
|
|
12
16
|
return (_jsxs("div", { className: `sw-progress sw-progress-${tone}`, children: [_jsxs("div", { className: "sw-progress-heading", children: [_jsx("span", { className: "sw-progress-label", id: labelId, children: label }), showCount ? (_jsxs("span", { "aria-hidden": "true", className: "sw-progress-count", children: [value, " / ", max] })) : null] }), _jsx("progress", { "aria-labelledby": labelId, className: "sw-progress-bar", max: max, value: value })] }));
|
|
13
17
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** `fixed` sizes the count for a few digits; `full` fills its container, such as a table cell. */
|
|
2
|
+
export type StepperWidth = 'fixed' | 'full';
|
|
3
|
+
export type StepperProps = {
|
|
4
|
+
/** Names the count. Keep it visible unless headers around it say it (`labelVisuallyHidden`). */
|
|
5
|
+
label: string;
|
|
6
|
+
labelVisuallyHidden?: boolean;
|
|
7
|
+
/** The count, or `null` for an empty box (none). */
|
|
8
|
+
value: number | null;
|
|
9
|
+
/** A whole count, or `null` when the box is emptied. Typed counts are reported as typed, even outside the bounds. */
|
|
10
|
+
onChange: (value: number | null) => void;
|
|
11
|
+
/** The lowest count − reaches; defaults to 0. */
|
|
12
|
+
min?: number;
|
|
13
|
+
/** The highest count + reaches; no upper bound when left out. */
|
|
14
|
+
max?: number;
|
|
15
|
+
/** How much one press, or one arrow key, moves the count; defaults to 1. */
|
|
16
|
+
step?: number;
|
|
17
|
+
/** The − button's name, such as "One less". */
|
|
18
|
+
decrementLabel: string;
|
|
19
|
+
/** The + button's name, such as "One more". */
|
|
20
|
+
incrementLabel: string;
|
|
21
|
+
/**
|
|
22
|
+
* A message under the control, as `Field`'s: a polite live region linked by
|
|
23
|
+
* `aria-describedby`, with `aria-invalid` and the danger border. An empty
|
|
24
|
+
* string is no error.
|
|
25
|
+
*/
|
|
26
|
+
error?: string;
|
|
27
|
+
/** `Field`'s changed look (the accent border and tint) for a count someone changed, or a suggestion. `error` wins. */
|
|
28
|
+
changed?: boolean;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
/** Readable and focusable, never changed: the buttons are hidden and keep their room. */
|
|
31
|
+
readOnly?: boolean;
|
|
32
|
+
/** Says the count in words for assistive technology (`aria-valuetext`), such as "12 straps". */
|
|
33
|
+
valueText?: string;
|
|
34
|
+
/** Shown in an empty box, such as "0". */
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
width?: StepperWidth;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A whole-number count between − and + buttons that can also be typed: one
|
|
41
|
+
* tab stop (the count, a spin button that ArrowUp and ArrowDown step), with
|
|
42
|
+
* the buttons as pointer targets beside it. − stops at `min`, + at `max`.
|
|
43
|
+
*/
|
|
44
|
+
export declare const Stepper: import("react").ForwardRefExoticComponent<StepperProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useId, useState, } from 'react';
|
|
4
|
+
import { cx } from '../class-names.js';
|
|
5
|
+
import { assertStepperBounds, readStepperEntry, stepperMoves, stepperText, } from '../stepper-count.js';
|
|
6
|
+
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
7
|
+
import { FieldLabelText } from './FieldLabelText.js';
|
|
8
|
+
/** Keeps focus where it was (the count, or nowhere) when a button is pressed. */
|
|
9
|
+
function keepFocus(event) {
|
|
10
|
+
event.preventDefault();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A whole-number count between − and + buttons that can also be typed: one
|
|
14
|
+
* tab stop (the count, a spin button that ArrowUp and ArrowDown step), with
|
|
15
|
+
* the buttons as pointer targets beside it. − stops at `min`, + at `max`.
|
|
16
|
+
*/
|
|
17
|
+
export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden = false, value, onChange, min = 0, max = Number.POSITIVE_INFINITY, step = 1, decrementLabel, incrementLabel, error, changed = false, disabled = false, readOnly = false, valueText, placeholder, name, width = 'fixed', }, ref) {
|
|
18
|
+
const bounds = { min, max, step };
|
|
19
|
+
assertStepperBounds(bounds);
|
|
20
|
+
if (value !== null && !Number.isSafeInteger(value))
|
|
21
|
+
throw new RangeError('Stepper value must be a whole number or null');
|
|
22
|
+
const inputId = useId();
|
|
23
|
+
const labelId = useId();
|
|
24
|
+
const errorId = useId();
|
|
25
|
+
const [draft, setDraft] = useState(null);
|
|
26
|
+
const allowsNegative = min < 0;
|
|
27
|
+
const moves = stepperMoves(value, bounds, disabled || readOnly);
|
|
28
|
+
// The typed text stays while it means the count the consumer holds, so
|
|
29
|
+
// "007" or a lone minus survives a render; otherwise the count's text.
|
|
30
|
+
const draftEntry = draft === null ? null : readStepperEntry(draft, allowsNegative);
|
|
31
|
+
const draftShowsValue = draftEntry !== null &&
|
|
32
|
+
(draftEntry.kind === 'empty'
|
|
33
|
+
? value === null
|
|
34
|
+
: draftEntry.kind === 'count' && draftEntry.value === value);
|
|
35
|
+
const text = draft !== null && draftShowsValue ? draft : stepperText(value);
|
|
36
|
+
function move(next) {
|
|
37
|
+
if (next === null)
|
|
38
|
+
return;
|
|
39
|
+
setDraft(null);
|
|
40
|
+
onChange(next);
|
|
41
|
+
}
|
|
42
|
+
function onType(event) {
|
|
43
|
+
const next = event.target.value;
|
|
44
|
+
const entry = readStepperEntry(next, allowsNegative);
|
|
45
|
+
// A refused keystroke changes nothing: the controlled box keeps its text.
|
|
46
|
+
if (entry.kind === 'refused')
|
|
47
|
+
return;
|
|
48
|
+
setDraft(next);
|
|
49
|
+
const count = entry.kind === 'count' ? entry.value : null;
|
|
50
|
+
if (count !== value)
|
|
51
|
+
onChange(count);
|
|
52
|
+
}
|
|
53
|
+
/** ArrowUp and ArrowDown step; Home and End go to min and a finite max. */
|
|
54
|
+
function keyTarget(key) {
|
|
55
|
+
if (key === 'ArrowUp')
|
|
56
|
+
return moves.up;
|
|
57
|
+
if (key === 'ArrowDown')
|
|
58
|
+
return moves.down;
|
|
59
|
+
if (key === 'Home')
|
|
60
|
+
return moves.down === null ? null : min;
|
|
61
|
+
if (key === 'End' && Number.isFinite(max))
|
|
62
|
+
return moves.up === null ? null : max;
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function onKeyDown(event) {
|
|
66
|
+
const target = keyTarget(event.key);
|
|
67
|
+
if (target === undefined)
|
|
68
|
+
return;
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
move(target);
|
|
71
|
+
}
|
|
72
|
+
const invalid = Boolean(error);
|
|
73
|
+
return (_jsxs("div", { className: cx('sw-stepper', width === 'full' && 'sw-stepper-full', invalid && 'sw-stepper-invalid', changed && !invalid && 'sw-stepper-changed', readOnly && 'sw-stepper-readonly'), children: [_jsx("label", { className: labelVisuallyHidden ? 'sw-sr-only' : undefined, htmlFor: inputId, id: labelId, children: _jsx(FieldLabelText, { label: label }) }), _jsxs("div", { "aria-labelledby": labelId, className: "sw-stepper-track", role: "group", children: [_jsx("button", { "aria-controls": inputId, "aria-label": decrementLabel, className: "sw-stepper-button", disabled: moves.down === null, onClick: () => move(moves.down), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: '−' }) }), _jsx("input", { ref: ref, "aria-describedby": invalid ? errorId : undefined, "aria-invalid": invalid || undefined, "aria-valuemax": Number.isFinite(max) ? max : undefined, "aria-valuemin": min, "aria-valuenow": value ?? undefined, "aria-valuetext": valueText, autoComplete: "off", className: "sw-stepper-input", disabled: disabled, id: inputId,
|
|
74
|
+
// iOS's numeric pad has no minus key, so a range below zero takes
|
|
75
|
+
// the text keyboard.
|
|
76
|
+
inputMode: allowsNegative ? 'text' : 'numeric', name: name, onBlur: () => setDraft(null), onChange: onType, onKeyDown: onKeyDown, placeholder: placeholder, readOnly: readOnly, role: "spinbutton", type: "text", value: text }), _jsx("button", { "aria-controls": inputId, "aria-label": incrementLabel, className: "sw-stepper-button", disabled: moves.up === null, onClick: () => move(moves.up), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: "+" }) })] }), _jsx(FieldErrorRegion, { className: "sw-field-error", id: errorId, message: error })] }));
|
|
77
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import { type StepHeadingLevel, type StepsLabels } from './steps/step-status.js';
|
|
3
|
+
export { Step, type StepProps } from './steps/Step.js';
|
|
4
|
+
export type { StepHeadingLevel, StepsLabels, StepStatus, } from './steps/step-status.js';
|
|
5
|
+
export type StepsProps = Omit<HTMLAttributes<HTMLOListElement>, 'children'> & {
|
|
6
|
+
/** The `Step`s, in order; each is numbered by its place. */
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/** Each step title's heading level; defaults to 2. */
|
|
9
|
+
headingLevel?: StepHeadingLevel;
|
|
10
|
+
/** The status words assistive technology hears after a folded step's title. English by default. */
|
|
11
|
+
labels?: Partial<StepsLabels>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A one-page vertical stepper: a form's steps in order, the current one
|
|
15
|
+
* open, the done ones folded to a line with their summary and Edit, the
|
|
16
|
+
* ones ahead greyed, a line joining each marker to the next.
|
|
17
|
+
*/
|
|
18
|
+
export declare function Steps({ children, className, headingLevel, labels, ...rest }: StepsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Children, Fragment, isValidElement, useRef, } from 'react';
|
|
4
|
+
import { cx } from '../class-names.js';
|
|
5
|
+
import { defaultStepsLabels, stepHeadingLevels, } from './steps/step-status.js';
|
|
6
|
+
import { StepPlaceContext } from './steps/steps-context.js';
|
|
7
|
+
export { Step } from './steps/Step.js';
|
|
8
|
+
function assertSteps(labels, level) {
|
|
9
|
+
if (Object.values(labels).some((word) => typeof word !== 'string' || !word.trim()))
|
|
10
|
+
throw new RangeError('Steps labels must not be empty');
|
|
11
|
+
if (!stepHeadingLevels.includes(level))
|
|
12
|
+
throw new RangeError('Steps headingLevel must be 2, 3 or 4');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A one-page vertical stepper: a form's steps in order, the current one
|
|
16
|
+
* open, the done ones folded to a line with their summary and Edit, the
|
|
17
|
+
* ones ahead greyed, a line joining each marker to the next.
|
|
18
|
+
*/
|
|
19
|
+
export function Steps({ children, className, headingLevel = 2, labels, ...rest }) {
|
|
20
|
+
const words = { ...defaultStepsLabels, ...labels };
|
|
21
|
+
assertSteps(words, headingLevel);
|
|
22
|
+
const steps = Children.toArray(children).filter(isValidElement);
|
|
23
|
+
// A Fragment would share one number among its steps: pass them directly.
|
|
24
|
+
if (steps.some((step) => step.type === Fragment))
|
|
25
|
+
throw new RangeError('Steps children must be Steps, not a Fragment');
|
|
26
|
+
// The first step to open in a commit claims focus (see Step).
|
|
27
|
+
const focusClaimRef = useRef(false);
|
|
28
|
+
return (_jsx("ol", { className: cx('sw-steps', className), ...rest, children: steps.map((step, index) => (_jsx(StepPlaceContext.Provider, { value: {
|
|
29
|
+
number: index + 1,
|
|
30
|
+
headingLevel,
|
|
31
|
+
labels: words,
|
|
32
|
+
focusClaimRef,
|
|
33
|
+
}, children: step }, step.key))) }));
|
|
34
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import { toastTones, type ToastTone } from '../css/css-toast.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
3
|
+
import { toastPlacements, type ToastPlacement } from '../css/css-toast-card.js';
|
|
4
|
+
import { type ToastAction } from './toast/ToastContent.js';
|
|
5
|
+
export { toastPlacements, toastTones };
|
|
6
|
+
export type { ToastAction, ToastPlacement, ToastTone };
|
|
7
|
+
export type ToastProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'popover' | 'title'> & {
|
|
6
8
|
open: boolean;
|
|
7
9
|
onOpenChange: (open: boolean) => void;
|
|
8
10
|
/**
|
|
@@ -19,6 +21,20 @@ export type ToastProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'popo
|
|
|
19
21
|
tone?: ToastTone;
|
|
20
22
|
/** A consumer glyph before the message, hidden from assistive technology. */
|
|
21
23
|
icon?: ReactNode;
|
|
24
|
+
/** A bold first line; the children become the muted detail under it. */
|
|
25
|
+
title?: string;
|
|
26
|
+
/**
|
|
27
|
+
* One press at the end of the toast, such as "Open". It runs `onPress`,
|
|
28
|
+
* then closes the toast. The toast takes the pointer while it has one.
|
|
29
|
+
*/
|
|
30
|
+
action?: ToastAction;
|
|
31
|
+
/** A thin line under the message that runs down with the time left. */
|
|
32
|
+
showRemaining?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* `bottom` (default) centers it near the bottom edge; `top-end` puts it
|
|
35
|
+
* in the top corner at the end of the line, as a card.
|
|
36
|
+
*/
|
|
37
|
+
placement?: ToastPlacement;
|
|
22
38
|
children: ReactNode;
|
|
23
39
|
};
|
|
24
|
-
export declare function Toast({ anchor, children, className, onOpenChange, open, target, timeoutMs,
|
|
40
|
+
export declare function Toast({ action, anchor, children, className, icon, onBlur, onFocus, onOpenChange, onPointerEnter, onPointerLeave, open, placement, showRemaining, style, target, timeoutMs, title, tone, ...rest }: ToastProps): import("react").JSX.Element;
|
package/dist/components/Toast.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef, } from 'react';
|
|
4
4
|
import { cx } from '../class-names.js';
|
|
5
5
|
import { toastTones } from '../css/css-toast.js';
|
|
6
|
+
import { toastPlacements } from '../css/css-toast-card.js';
|
|
6
7
|
import { spacingClassNames } from '../spacing-classes.js';
|
|
7
8
|
import { applyTravelVars, clearTravelVars, travelTranslate, } from '../toast-travel.js';
|
|
8
|
-
|
|
9
|
+
import { assertToastContent, ToastContent, } from './toast/ToastContent.js';
|
|
10
|
+
import { useToastHold } from './toast/use-toast-hold.js';
|
|
11
|
+
import { useToastTimer } from './toast/use-toast-timer.js';
|
|
12
|
+
export { toastPlacements, toastTones };
|
|
9
13
|
const DEFAULT_TIMEOUT_MS = 800;
|
|
10
14
|
/** A warning or an error must be read, not glimpsed. */
|
|
11
15
|
const URGENT_TIMEOUT_MS = 6000;
|
|
@@ -17,10 +21,28 @@ function defaultTimeout(tone) {
|
|
|
17
21
|
function travels(anchor, target) {
|
|
18
22
|
return Boolean(anchor && target);
|
|
19
23
|
}
|
|
20
|
-
|
|
24
|
+
function reducedMotion() {
|
|
25
|
+
return (typeof window !== 'undefined' &&
|
|
26
|
+
typeof window.matchMedia === 'function' &&
|
|
27
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
28
|
+
}
|
|
29
|
+
export function Toast({ action, anchor = null, children, className, icon, onBlur, onFocus, onOpenChange, onPointerEnter, onPointerLeave, open, placement = 'bottom', showRemaining = false, style, target = null, timeoutMs, title, tone = 'neutral', ...rest }) {
|
|
30
|
+
assertToastContent(title, action);
|
|
31
|
+
if (!toastPlacements.includes(placement))
|
|
32
|
+
throw new RangeError(`Unknown Toast placement: ${String(placement)}`);
|
|
21
33
|
const nodeRef = useRef(null);
|
|
22
34
|
const dismissAfter = timeoutMs ?? defaultTimeout(tone);
|
|
23
35
|
const moving = travels(anchor, target);
|
|
36
|
+
const interactive = !moving && (Boolean(action) || showRemaining);
|
|
37
|
+
const { handlers, paused, returnFocus } = useToastHold({
|
|
38
|
+
enabled: interactive,
|
|
39
|
+
onBlur,
|
|
40
|
+
onFocus,
|
|
41
|
+
onPointerEnter,
|
|
42
|
+
onPointerLeave,
|
|
43
|
+
open,
|
|
44
|
+
});
|
|
45
|
+
const close = useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
24
46
|
useLayoutEffect(() => {
|
|
25
47
|
const node = nodeRef.current;
|
|
26
48
|
if (!node || typeof node.showPopover !== 'function') {
|
|
@@ -28,6 +50,7 @@ export function Toast({ anchor = null, children, className, onOpenChange, open,
|
|
|
28
50
|
}
|
|
29
51
|
if (!open) {
|
|
30
52
|
if (node.matches(':popover-open')) {
|
|
53
|
+
returnFocus(node);
|
|
31
54
|
node.hidePopover();
|
|
32
55
|
}
|
|
33
56
|
clearTravelVars(node);
|
|
@@ -44,28 +67,28 @@ export function Toast({ anchor = null, children, className, onOpenChange, open,
|
|
|
44
67
|
if (!node.matches(':popover-open')) {
|
|
45
68
|
node.showPopover();
|
|
46
69
|
}
|
|
47
|
-
}, [anchor, moving, open, target]);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
const timer = window.setTimeout(() => {
|
|
58
|
-
onOpenChange(false);
|
|
59
|
-
}, dismissAfter);
|
|
60
|
-
return () => {
|
|
61
|
-
window.clearTimeout(timer);
|
|
62
|
-
};
|
|
63
|
-
}, [dismissAfter, moving, open, onOpenChange]);
|
|
70
|
+
}, [anchor, moving, open, returnFocus, target]);
|
|
71
|
+
// A travelling toast closes when its animation ends, unless motion is
|
|
72
|
+
// reduced; every other toast closes on the timer, held while paused.
|
|
73
|
+
useToastTimer({
|
|
74
|
+
duration: dismissAfter,
|
|
75
|
+
onExpire: close,
|
|
76
|
+
paused,
|
|
77
|
+
running: open && (!moving || reducedMotion()),
|
|
78
|
+
});
|
|
64
79
|
function onAnimationEnd(event) {
|
|
65
80
|
if (event.target !== event.currentTarget || !moving) {
|
|
66
81
|
return;
|
|
67
82
|
}
|
|
68
83
|
onOpenChange(false);
|
|
69
84
|
}
|
|
70
|
-
return (
|
|
85
|
+
return (_jsxs("div", { ...rest, ref: nodeRef, className: cx('sw-toast', tone !== 'neutral' && `sw-toast-${tone}`, !moving && placement !== 'bottom' && `sw-toast-${placement}`, interactive && 'sw-toast-interactive', paused && 'sw-toast-paused', moving && 'sw-toast-travel', ...spacingClassNames({ padding: 3 }), className), onAnimationEnd: onAnimationEnd, ...handlers, popover: "manual", role: tone === 'danger' ? 'alert' : 'status', style: showRemaining
|
|
86
|
+
? {
|
|
87
|
+
...style,
|
|
88
|
+
'--sw-toast-duration': `${dismissAfter}ms`,
|
|
89
|
+
}
|
|
90
|
+
: style, children: [_jsx(ToastContent, { action: action, icon: icon, onAction: () => {
|
|
91
|
+
action?.onPress();
|
|
92
|
+
close();
|
|
93
|
+
}, title: title, children: children }), showRemaining && open && !moving ? (_jsx("span", { "aria-hidden": "true", className: "sw-toast-remaining" })) : null] }));
|
|
71
94
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const actionBarSummaryTones: readonly ["default", "danger", "success"];
|
|
2
|
+
/** The value's color: the text color, or danger (short) or success (matched). */
|
|
3
|
+
export type ActionBarSummaryTone = (typeof actionBarSummaryTones)[number];
|
|
4
|
+
/** A labelled figure for the bar, such as "Counted $33,790.00 short $4,000.00". */
|
|
5
|
+
export type ActionBarSummary = {
|
|
6
|
+
/** What the figure is, such as "Counted". */
|
|
7
|
+
label: string;
|
|
8
|
+
/** The figure, formatted by the consumer, such as "$33,790.00". */
|
|
9
|
+
value: string;
|
|
10
|
+
/** A short line after the figure, such as "short $4,000.00". */
|
|
11
|
+
detail?: string;
|
|
12
|
+
/** Colors the value only; the words say why. Defaults to `default`. */
|
|
13
|
+
tone?: ActionBarSummaryTone;
|
|
14
|
+
};
|
|
15
|
+
export declare function assertActionBarSummary(summary: ActionBarSummary): void;
|
|
16
|
+
/**
|
|
17
|
+
* The bar's figure: its label, the value in the heading variant at tabular
|
|
18
|
+
* figures, and its detail, on one baseline, wrapping as one phrase.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ActionBarSummaryLine({ summary, }: {
|
|
21
|
+
summary: ActionBarSummary;
|
|
22
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../../class-names.js';
|
|
3
|
+
export const actionBarSummaryTones = ['default', 'danger', 'success'];
|
|
4
|
+
function isBlank(text) {
|
|
5
|
+
return typeof text !== 'string' || !text.trim();
|
|
6
|
+
}
|
|
7
|
+
export function assertActionBarSummary(summary) {
|
|
8
|
+
if (isBlank(summary.label) || isBlank(summary.value))
|
|
9
|
+
throw new RangeError('ActionBar summary needs a label and a value');
|
|
10
|
+
if (summary.detail !== undefined && isBlank(summary.detail))
|
|
11
|
+
throw new RangeError('ActionBar summary detail must not be empty');
|
|
12
|
+
if (summary.tone !== undefined &&
|
|
13
|
+
!actionBarSummaryTones.includes(summary.tone))
|
|
14
|
+
throw new RangeError(`Unknown ActionBar summary tone: ${String(summary.tone)}`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The bar's figure: its label, the value in the heading variant at tabular
|
|
18
|
+
* figures, and its detail, on one baseline, wrapping as one phrase.
|
|
19
|
+
*/
|
|
20
|
+
export function ActionBarSummaryLine({ summary, }) {
|
|
21
|
+
const tone = summary.tone ?? 'default';
|
|
22
|
+
return (_jsxs("span", { className: cx('sw-action-bar-summary', tone !== 'default' && `sw-action-bar-summary-${tone}`), children: [_jsx("span", { className: "sw-action-bar-summary-label", children: summary.label }), ' ', _jsx("span", { className: "sw-action-bar-summary-value sw-text-heading sw-tabular", children: summary.value }), summary.detail ? (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "sw-action-bar-summary-detail", children: summary.detail })] })) : null] }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type DenominationGridColumn } from './types.js';
|
|
2
|
+
/** A column's name as its header reads: the label, then its caption ("$1 × 100"). */
|
|
3
|
+
export declare function columnName(column: DenominationGridColumn): string;
|
|
4
|
+
/** A column's label and, after it, its muted caption ("$1 × 100"). */
|
|
5
|
+
export declare function ColumnHeadText({ column }: {
|
|
6
|
+
column: DenominationGridColumn;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** A column's name as its header reads: the label, then its caption ("$1 × 100"). */
|
|
3
|
+
export function columnName(column) {
|
|
4
|
+
return column.caption === undefined
|
|
5
|
+
? column.label
|
|
6
|
+
: `${column.label} ${column.caption}`;
|
|
7
|
+
}
|
|
8
|
+
/** A column's label and, after it, its muted caption ("$1 × 100"). */
|
|
9
|
+
export function ColumnHeadText({ column }) {
|
|
10
|
+
if (column.caption === undefined)
|
|
11
|
+
return _jsx(_Fragment, { children: column.label });
|
|
12
|
+
return (_jsxs(_Fragment, { children: [column.label, ' ', _jsx("span", { className: "sw-denomination-head-caption", children: column.caption })] }));
|
|
13
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type DenominationGridProps } from './types.js';
|
|
2
|
-
type StripProps = Required<Pick<DenominationGridProps, 'label' | 'columns' | 'rows' | 'zeroLabel'>> & Pick<DenominationGridProps, 'totalLabel' | 'labelWidth'
|
|
3
|
-
|
|
2
|
+
type StripProps = Required<Pick<DenominationGridProps, 'label' | 'columns' | 'rows' | 'zeroLabel'>> & Pick<DenominationGridProps, 'totalLabel' | 'labelWidth'> & {
|
|
3
|
+
/** The row labels' appearance class, if any. */
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function DenominationStrip({ className, label, columns, rows, zeroLabel, totalLabel, labelWidth, }: StripProps): import("react").JSX.Element;
|
|
4
7
|
export {};
|
|
@@ -1,10 +1,9 @@
|
|
|
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 { denominationCellToneClassNames } from '../../css/css-denomination-cell-tones.js';
|
|
5
|
-
import { denominationCellView } from '../../denomination-cells.js';
|
|
6
4
|
import { ScrollRegion } from '../ScrollRegion.js';
|
|
7
|
-
import {
|
|
5
|
+
import { ColumnHeadText } from './ColumnHeadText.js';
|
|
6
|
+
import { StripRow } from './StripRow.js';
|
|
8
7
|
/**
|
|
9
8
|
* The total column's header: the consumer's name for the column, shown over
|
|
10
9
|
* the totals from md up in the column heads' style, and visually hidden
|
|
@@ -17,7 +16,7 @@ function TotalHead({ totalLabel }) {
|
|
|
17
16
|
return _jsx("td", { className: "sw-denomination-corner sw-denomination-total-head" });
|
|
18
17
|
return (_jsx("th", { className: "sw-denomination-head sw-denomination-total-head", scope: "col", children: _jsx("span", { className: "sw-denomination-total-label", children: totalLabel }) }));
|
|
19
18
|
}
|
|
20
|
-
export function DenominationStrip({ label, columns, rows, zeroLabel, totalLabel, labelWidth, }) {
|
|
19
|
+
export function DenominationStrip({ className, label, columns, rows, zeroLabel, totalLabel, labelWidth, }) {
|
|
21
20
|
const hasTotals = rows.some((row) => row.total !== undefined);
|
|
22
21
|
// An aligned strip sizes its columns from the region's width; it needs
|
|
23
22
|
// the column count and whether a total column takes a share.
|
|
@@ -27,8 +26,5 @@ export function DenominationStrip({ label, columns, rows, zeroLabel, totalLabel,
|
|
|
27
26
|
'--sw-denomination-columns': columns.length,
|
|
28
27
|
'--sw-denomination-total': hasTotals ? 1 : 0,
|
|
29
28
|
};
|
|
30
|
-
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', labelWidth && denominationLabelWidthClass(labelWidth)), 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: column
|
|
31
|
-
const view = denominationCellView(row.cells[index] ?? null, row.signed ?? false, zeroLabel);
|
|
32
|
-
return (_jsx("td", { className: cx('sw-denomination-cell', `sw-denomination-cell-${view.state}`, ...denominationCellToneClassNames(row.cellTones?.[index])), children: view.text }, column.key));
|
|
33
|
-
}), hasTotals ? (_jsx("td", { className: "sw-denomination-total", children: _jsx("span", { className: "sw-denomination-total-value", children: row.total ?? '' }) })) : null] }, row.id))) })] }) }));
|
|
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))) })] }) }));
|
|
34
30
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type DenominationGridProps } from './types.js';
|
|
2
|
-
type TilesProps = Required<Pick<DenominationGridProps, 'label' | 'columns' | 'rows' | 'zeroLabel' | 'rowRole'>> & Pick<DenominationGridProps, 'subtotal'
|
|
3
|
-
|
|
2
|
+
type TilesProps = Required<Pick<DenominationGridProps, 'label' | 'columns' | 'rows' | 'zeroLabel' | 'rowRole'>> & Pick<DenominationGridProps, 'subtotal'> & {
|
|
3
|
+
/** The row labels' appearance class, if any. */
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function DenominationTiles({ className, label, columns, rows, rowRole, subtotal, zeroLabel, }: TilesProps): import("react").JSX.Element;
|
|
4
7
|
export {};
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { cx } from '../../class-names.js';
|
|
3
3
|
import { denominationCellToneClassNames } from '../../css/css-denomination-cell-tones.js';
|
|
4
4
|
import { denominationCellView } from '../../denomination-cells.js';
|
|
5
|
+
import { ColumnHeadText } from './ColumnHeadText.js';
|
|
5
6
|
import { RowLabelLine } from './RowLabelLine.js';
|
|
6
7
|
/**
|
|
7
8
|
* A lone row is named by the grid's own label, so its label line shows only
|
|
@@ -20,8 +21,8 @@ function showsRowLabel(rowCount, row) {
|
|
|
20
21
|
function namesRow(rowCount, row, gridLabel) {
|
|
21
22
|
return showsRowLabel(rowCount, row) || row.label !== gridLabel;
|
|
22
23
|
}
|
|
23
|
-
export function DenominationTiles({ label, columns, rows, rowRole, subtotal, zeroLabel, }) {
|
|
24
|
-
return (_jsx("div", { "aria-label": label, className:
|
|
24
|
+
export function DenominationTiles({ className, label, columns, rows, rowRole, subtotal, zeroLabel, }) {
|
|
25
|
+
return (_jsx("div", { "aria-label": label, className: cx('sw-denomination-grid sw-denomination-tiles', className), role: "group", children: rows.map((row) => {
|
|
25
26
|
const labelled = showsRowLabel(rows.length, row);
|
|
26
27
|
const named = namesRow(rows.length, row, label);
|
|
27
28
|
// A named section is a region; role="group" keeps the name without
|
|
@@ -32,7 +33,7 @@ export function DenominationTiles({ label, columns, rows, rowRole, subtotal, zer
|
|
|
32
33
|
const amount = subtotal && cell !== null && cell !== 0
|
|
33
34
|
? subtotal(cell, column)
|
|
34
35
|
: null;
|
|
35
|
-
return (_jsxs("li", { className: cx('sw-denomination-tile', ...denominationCellToneClassNames(row.cellTones?.[index])), children: [_jsx("span", { className: "sw-denomination-head", children: column
|
|
36
|
+
return (_jsxs("li", { className: cx('sw-denomination-tile', ...denominationCellToneClassNames(row.cellTones?.[index])), children: [_jsx("span", { className: "sw-denomination-head", children: _jsx(ColumnHeadText, { column: column }) }), _jsx("span", { className: `sw-denomination-cell sw-denomination-cell-${view.state}`, children: view.text }), subtotal ? (_jsx("span", { className: "sw-denomination-subtotal", children: amount ?? zeroLabel })) : null] }, column.key));
|
|
36
37
|
}) })] }, row.id));
|
|
37
38
|
}) }));
|
|
38
39
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type DenominationGridColumn, type DenominationGridEntry, type DenominationGridRow } from './types.js';
|
|
2
|
+
type EntryCellProps = {
|
|
3
|
+
row: DenominationGridRow;
|
|
4
|
+
entry: DenominationGridEntry;
|
|
5
|
+
column: DenominationGridColumn;
|
|
6
|
+
index: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* One editable count of a strip row: a full-width `Stepper` named by the
|
|
10
|
+
* row and the column as its header reads ("Counted $1 × 100"), its cap and
|
|
11
|
+
* message from the entry, and the cell's tone on its border (an accent one
|
|
12
|
+
* is the Stepper's changed look).
|
|
13
|
+
*/
|
|
14
|
+
export declare function EntryCell({ row, entry, column, index }: EntryCellProps): import("react").JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../../class-names.js';
|
|
3
|
+
import { denominationCellToneClassNames } from '../../css/css-denomination-cell-tones.js';
|
|
4
|
+
import { Stepper } from '../Stepper.js';
|
|
5
|
+
import { columnName } from './ColumnHeadText.js';
|
|
6
|
+
/**
|
|
7
|
+
* One editable count of a strip row: a full-width `Stepper` named by the
|
|
8
|
+
* row and the column as its header reads ("Counted $1 × 100"), its cap and
|
|
9
|
+
* message from the entry, and the cell's tone on its border (an accent one
|
|
10
|
+
* is the Stepper's changed look).
|
|
11
|
+
*/
|
|
12
|
+
export function EntryCell({ row, entry, column, index }) {
|
|
13
|
+
const max = entry.max?.[index] ?? undefined;
|
|
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" }) }));
|
|
16
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type DenominationGridRow } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* A row label's glyph and words, kept on one line: when the words wrap,
|
|
4
|
-
* they wrap beside the glyph, never under it (Teisoro DRW-28).
|
|
5
|
-
*
|
|
4
|
+
* they wrap beside the glyph, never under it (Teisoro DRW-28). A caption is
|
|
5
|
+
* a muted line under the words, beside the same glyph. Both the strip and
|
|
6
|
+
* the tiles layouts draw a row label with it.
|
|
6
7
|
*/
|
|
7
8
|
export declare function RowLabelLine({ row }: {
|
|
8
9
|
row: DenominationGridRow;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* A row label's glyph and words, kept on one line: when the words wrap,
|
|
4
|
-
* they wrap beside the glyph, never under it (Teisoro DRW-28).
|
|
5
|
-
*
|
|
4
|
+
* they wrap beside the glyph, never under it (Teisoro DRW-28). A caption is
|
|
5
|
+
* a muted line under the words, beside the same glyph. Both the strip and
|
|
6
|
+
* the tiles layouts draw a row label with it.
|
|
6
7
|
*/
|
|
7
8
|
export function RowLabelLine({ row }) {
|
|
8
|
-
|
|
9
|
+
const text = _jsx("span", { className: "sw-denomination-label-text", children: row.label });
|
|
10
|
+
return (_jsxs("span", { className: "sw-denomination-label-line", children: [row.icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-icon", children: row.icon })) : null, row.caption === undefined ? (text) : (_jsxs("span", { className: "sw-denomination-label-words", children: [text, _jsx("span", { className: "sw-denomination-label-caption", children: row.caption })] }))] }));
|
|
9
11
|
}
|