@scalewing/react 1.26.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +87 -1
  3. package/dist/components/ActionBar.d.ts +14 -0
  4. package/dist/components/ActionBar.js +5 -2
  5. package/dist/components/DenominationGrid.d.ts +1 -1
  6. package/dist/components/DenominationGrid.js +3 -0
  7. package/dist/components/Stepper.d.ts +44 -0
  8. package/dist/components/Stepper.js +77 -0
  9. package/dist/components/Steps.d.ts +18 -0
  10. package/dist/components/Steps.js +34 -0
  11. package/dist/components/action-bar/ActionBarSummary.d.ts +22 -0
  12. package/dist/components/action-bar/ActionBarSummary.js +23 -0
  13. package/dist/components/denomination-grid/ColumnHeadText.d.ts +7 -0
  14. package/dist/components/denomination-grid/ColumnHeadText.js +13 -0
  15. package/dist/components/denomination-grid/DenominationStrip.js +3 -7
  16. package/dist/components/denomination-grid/DenominationTiles.js +2 -1
  17. package/dist/components/denomination-grid/EntryCell.d.ts +15 -0
  18. package/dist/components/denomination-grid/EntryCell.js +16 -0
  19. package/dist/components/denomination-grid/RowLabelLine.d.ts +3 -2
  20. package/dist/components/denomination-grid/RowLabelLine.js +5 -3
  21. package/dist/components/denomination-grid/StripRow.d.ts +13 -0
  22. package/dist/components/denomination-grid/StripRow.js +19 -0
  23. package/dist/components/denomination-grid/types.d.ts +44 -1
  24. package/dist/components/denomination-grid/validate-entry.d.ts +15 -0
  25. package/dist/components/denomination-grid/validate-entry.js +78 -0
  26. package/dist/components/steps/Step.d.ts +24 -0
  27. package/dist/components/steps/Step.js +62 -0
  28. package/dist/components/steps/step-status.d.ts +26 -0
  29. package/dist/components/steps/step-status.js +37 -0
  30. package/dist/components/steps/steps-context.d.ts +16 -0
  31. package/dist/components/steps/steps-context.js +3 -0
  32. package/dist/css/css-action-bar-summary.d.ts +2 -0
  33. package/dist/css/css-action-bar-summary.js +48 -0
  34. package/dist/css/css-action-bar.js +4 -0
  35. package/dist/css/css-components.js +7 -0
  36. package/dist/css/css-denomination-entry.d.ts +2 -0
  37. package/dist/css/css-denomination-entry.js +78 -0
  38. package/dist/css/css-denomination-grid.js +4 -0
  39. package/dist/css/css-stepper.d.ts +10 -0
  40. package/dist/css/css-stepper.js +126 -0
  41. package/dist/css/css-steps.d.ts +9 -0
  42. package/dist/css/css-steps.js +203 -0
  43. package/dist/css/stylesheet.js +4 -0
  44. package/dist/index.d.ts +4 -2
  45. package/dist/index.js +3 -1
  46. package/dist/stepper-count.d.ts +37 -0
  47. package/dist/stepper-count.js +50 -0
  48. package/dist/styles.css +324 -0
  49. package/package.json +1 -1
@@ -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). Both the
5
- * strip and the tiles layouts draw a row label with it.
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
- return (_jsxs("span", { className: "sw-denomination-label-line", children: [row.icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-icon", children: row.icon })) : null, _jsx("span", { className: "sw-denomination-label-text", children: row.label })] }));
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
  }
@@ -0,0 +1,13 @@
1
+ import { type DenominationGridColumn, type DenominationGridRow } from './types.js';
2
+ type StripRowProps = {
3
+ row: DenominationGridRow;
4
+ columns: readonly DenominationGridColumn[];
5
+ zeroLabel: string;
6
+ hasTotals: boolean;
7
+ };
8
+ /**
9
+ * One strip row: its pinned label (with the phone total under it), a count
10
+ * per column, read-only or entered, and its total.
11
+ */
12
+ export declare function StripRow({ row, columns, zeroLabel, hasTotals, }: StripRowProps): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from '../../class-names.js';
3
+ import { denominationCellToneClassNames } from '../../css/css-denomination-cell-tones.js';
4
+ import { denominationCellView } from '../../denomination-cells.js';
5
+ import { EntryCell } from './EntryCell.js';
6
+ import { RowLabelLine } from './RowLabelLine.js';
7
+ /** One count, read-only: the zero label, a signed delta, and the cell's tone. */
8
+ function CountCell({ row, index, zeroLabel, }) {
9
+ 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 }));
11
+ }
12
+ /**
13
+ * One strip row: its pinned label (with the phone total under it), a count
14
+ * per column, read-only or entered, and its total.
15
+ */
16
+ export function StripRow({ row, columns, zeroLabel, hasTotals, }) {
17
+ const { entry } = row;
18
+ return (_jsxs("tr", { "aria-disabled": row.disabled || undefined, className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`, row.signed && 'sw-denomination-row-signed', row.emphasis === 'quiet' && 'sw-denomination-row-quiet', row.disabled && 'sw-denomination-row-disabled'), children: [_jsx("th", { className: "sw-denomination-label", scope: "row", children: _jsxs("span", { className: "sw-denomination-label-body", children: [_jsx(RowLabelLine, { row: row }), row.total !== undefined ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-total-inline", children: row.total })) : null] }) }), columns.map((column, index) => entry ? (_jsx(EntryCell, { column: column, entry: entry, index: index, row: row }, column.key)) : (_jsx(CountCell, { index: index, row: row, zeroLabel: zeroLabel }, column.key))), hasTotals ? (_jsx("td", { className: "sw-denomination-total", children: _jsx("span", { className: "sw-denomination-total-value", children: row.total ?? '' }) })) : null] }));
19
+ }
@@ -12,6 +12,29 @@ export type DenominationGridRowRole = 'region' | 'group';
12
12
  export type DenominationGridColumn = {
13
13
  key: string;
14
14
  label: string;
15
+ /** A muted note after the label, such as "× 100" for a strap of $1 bills. */
16
+ caption?: string;
17
+ };
18
+ /** How a row sits among the others: `quiet` sets it on the subtle fill, for a row to compare against. */
19
+ export type DenominationGridRowEmphasis = 'default' | 'quiet';
20
+ /**
21
+ * Makes a strip row's counts editable: one `Stepper` per column, holding the
22
+ * row's `cells` (`null` is an empty box), named by the row label and the
23
+ * column's label and caption ("Counted $1 × 100"). The minimum is 0. The
24
+ * grid does no arithmetic: recompute the row's `total` in `onChange`.
25
+ */
26
+ export type DenominationGridEntry = {
27
+ onChange: (columnKey: string, value: number | null) => void;
28
+ /** The − buttons' name, such as "One less". */
29
+ decrementLabel: string;
30
+ /** The + buttons' name, such as "One more". */
31
+ incrementLabel: string;
32
+ /** A cap per column (`null` for none), such as the bills the drawer holds. */
33
+ max?: readonly (number | null)[];
34
+ /** A message per column (`null` for none), shown under that count. */
35
+ errors?: readonly (string | null)[];
36
+ /** What an empty box shows, such as "0". */
37
+ placeholder?: string;
15
38
  };
16
39
  export type DenominationGridRow = {
17
40
  id: string;
@@ -25,9 +48,29 @@ export type DenominationGridRow = {
25
48
  * A tone per count, one entry per column (null for none), for a single
26
49
  * count that needs attention, such as a bill the vault is short of. The
27
50
  * count is set in the tone and, in the tiles layout, its tile's border
28
- * too, in place. The tone never says why alone: put that in words.
51
+ * too, in place; an entry count's border takes it. The tone never says
52
+ * why alone: put that in words.
29
53
  */
30
54
  cellTones?: readonly (DenominationGridTone | null)[];
55
+ /** A muted line under the label, such as "from the customer". */
56
+ caption?: string;
57
+ /** The `strip` layout only. */
58
+ emphasis?: DenominationGridRowEmphasis;
59
+ /**
60
+ * A row that does not apply now (the `strip` layout only): its entry is
61
+ * disabled, its label, counts and total fade, and the row is
62
+ * `aria-disabled`.
63
+ */
64
+ disabled?: boolean;
65
+ /**
66
+ * Count entry for this row (the `strip` layout only). Its counts are zero
67
+ * or more, it is not `signed`, and its label is its own among the entry
68
+ * rows, as each column's label and caption are, so every count has its
69
+ * own name. A cell tone draws the count's border in that tone (an
70
+ * `accent` one is the `Stepper`'s changed look); a message in
71
+ * `entry.errors` wins.
72
+ */
73
+ entry?: DenominationGridEntry;
31
74
  };
32
75
  export type DenominationGridProps = {
33
76
  label: string;
@@ -0,0 +1,15 @@
1
+ import { type DenominationGridColumn, type DenominationGridLayout, type DenominationGridRow } from './types.js';
2
+ /**
3
+ * The parts of a row and column that only label or set a row apart: a
4
+ * caption is never blank, an emphasis is a known one, `disabled` is a
5
+ * boolean, and a quiet or disabled row is a strip row (tiles have no row
6
+ * fill to set apart).
7
+ */
8
+ export declare function assertDenominationExtras(layout: DenominationGridLayout, columns: readonly DenominationGridColumn[], rows: readonly DenominationGridRow[]): void;
9
+ /**
10
+ * Entry rows: the strip layout only, named buttons, counts of zero or more,
11
+ * no signed deltas, one cap and one message (or null) per column with caps
12
+ * that are whole counts of zero or more, and one name per count: every
13
+ * entry row label and every column name (label and caption) is its own.
14
+ */
15
+ export declare function assertDenominationEntries(layout: DenominationGridLayout, columns: readonly DenominationGridColumn[], rows: readonly DenominationGridRow[]): void;
@@ -0,0 +1,78 @@
1
+ import { columnName } from './ColumnHeadText.js';
2
+ const emphases = ['default', 'quiet'];
3
+ /**
4
+ * The parts of a row and column that only label or set a row apart: a
5
+ * caption is never blank, an emphasis is a known one, `disabled` is a
6
+ * boolean, and a quiet or disabled row is a strip row (tiles have no row
7
+ * fill to set apart).
8
+ */
9
+ export function assertDenominationExtras(layout, columns, rows) {
10
+ if (columns.some((column) => isBlank(column.caption)))
11
+ throw new RangeError('a column caption must not be empty');
12
+ if (rows.some((row) => isBlank(row.caption)))
13
+ throw new RangeError('a row caption must not be empty');
14
+ if (rows.some((row) => row.emphasis !== undefined && !emphases.includes(row.emphasis)))
15
+ throw new RangeError("emphasis must be 'default' or 'quiet'");
16
+ if (rows.some((row) => row.disabled !== undefined && typeof row.disabled !== 'boolean'))
17
+ throw new RangeError('disabled must be a boolean');
18
+ if (layout !== 'strip' &&
19
+ rows.some((row) => row.emphasis === 'quiet' || row.disabled))
20
+ throw new RangeError('quiet and disabled rows need the strip layout');
21
+ }
22
+ /**
23
+ * Entry rows: the strip layout only, named buttons, counts of zero or more,
24
+ * no signed deltas, one cap and one message (or null) per column with caps
25
+ * that are whole counts of zero or more, and one name per count: every
26
+ * entry row label and every column name (label and caption) is its own.
27
+ */
28
+ export function assertDenominationEntries(layout, columns, rows) {
29
+ const entryRows = rows.filter((row) => row.entry);
30
+ if (entryRows.length === 0)
31
+ return;
32
+ if (layout !== 'strip')
33
+ throw new RangeError('entry rows need the strip layout');
34
+ if (!allDistinct(entryRows.map((row) => row.label)))
35
+ throw new RangeError('entry rows need distinct labels');
36
+ if (!allDistinct(columns.map(columnName)))
37
+ throw new RangeError('entry rows need columns with distinct labels and captions');
38
+ for (const row of entryRows) {
39
+ if (row.signed)
40
+ throw new RangeError('an entry row cannot be signed');
41
+ if (row.cells.some((cell) => cell !== null && cell < 0))
42
+ throw new RangeError('an entry row counts zero or more');
43
+ assertEntry(row.entry, columns.length);
44
+ }
45
+ }
46
+ function assertEntry(entry, columnCount) {
47
+ if (typeof entry.onChange !== 'function')
48
+ throw new RangeError('entry needs an onChange');
49
+ if (!entry.decrementLabel.trim() || !entry.incrementLabel.trim())
50
+ throw new RangeError('entry needs decrementLabel and incrementLabel');
51
+ if (!fitsColumns(entry.max, columnCount, isCap))
52
+ throw new RangeError('entry.max needs one whole count of zero or more, or null, per column');
53
+ if (!fitsColumns(entry.errors, columnCount, isMessage))
54
+ throw new RangeError('entry.errors needs one message or null per column');
55
+ }
56
+ function allDistinct(names) {
57
+ return new Set(names).size === names.length;
58
+ }
59
+ function isBlank(text) {
60
+ return text !== undefined && !text.trim();
61
+ }
62
+ function isCap(cap) {
63
+ return cap === null || (Number.isSafeInteger(cap) && cap >= 0);
64
+ }
65
+ function isMessage(message) {
66
+ return message === null || typeof message === 'string';
67
+ }
68
+ function fitsColumns(values, columnCount, fits) {
69
+ if (values === undefined)
70
+ return true;
71
+ if (!Array.isArray(values) || values.length !== columnCount)
72
+ return false;
73
+ // An index loop, not every(): every() skips the holes of a sparse array.
74
+ for (let index = 0; index < columnCount; index += 1)
75
+ if (!fits(values[index]))
76
+ return false;
77
+ return true;
78
+ }
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type StepHeadingLevel, type StepStatus } from './step-status.js';
3
+ export type StepProps = {
4
+ status: StepStatus;
5
+ title: string;
6
+ /** One line beside a done step's title, such as "Cash · $480.00"; truncated. */
7
+ summary?: ReactNode;
8
+ /** Reopens a done step; needs `editLabel`. */
9
+ onEdit?: () => void;
10
+ /** The Edit button's words; its name adds the step's title ("Edit The sale"). */
11
+ editLabel?: string;
12
+ /** Muted words after a skipped step's title, such as "not needed". */
13
+ skippedReason?: string;
14
+ /** Overrides the list's heading level for this step's title. */
15
+ headingLevel?: StepHeadingLevel;
16
+ /** The step's fields, shown while it is current or in error. */
17
+ children?: ReactNode;
18
+ };
19
+ /**
20
+ * One step of a `Steps` list: a numbered marker, the title as a heading,
21
+ * and, by status, its fields (current, error) or one folded line (a done
22
+ * step's summary and Edit; an upcoming or skipped step's title alone).
23
+ */
24
+ export declare function Step(props: StepProps): import("react").JSX.Element;
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Children, useContext, useEffect, useId, useRef, } from 'react';
4
+ import { cx } from '../../class-names.js';
5
+ import { Button } from '../Button.js';
6
+ import { isOpenStep, stepHeadingLevels, stepStatuses, stepStatusWord, stepTakesFocus, } from './step-status.js';
7
+ import { StepPlaceContext } from './steps-context.js';
8
+ function assertStep(props, level) {
9
+ if (!props.title.trim())
10
+ throw new RangeError('Step title must not be empty');
11
+ if (!stepStatuses.includes(props.status))
12
+ throw new RangeError(`Unknown Step status: ${String(props.status)}`);
13
+ if (!stepHeadingLevels.includes(level))
14
+ throw new RangeError('Step headingLevel must be 2, 3 or 4');
15
+ if (props.onEdit && !props.editLabel?.trim())
16
+ throw new RangeError('Step onEdit needs an editLabel');
17
+ if (props.skippedReason !== undefined && !props.skippedReason.trim())
18
+ throw new RangeError('Step skippedReason must not be empty');
19
+ }
20
+ /**
21
+ * Moves focus to the heading when the step becomes current from a folded
22
+ * state, unless an earlier step of the list already took it in this commit
23
+ * (effects run in list order, so the first one wins; the claim is released
24
+ * after the flush).
25
+ */
26
+ function useFocusWhenOpened(status, heading, focusClaimRef) {
27
+ const previous = useRef(undefined);
28
+ useEffect(() => {
29
+ if (stepTakesFocus(previous.current, status) && !focusClaimRef.current) {
30
+ focusClaimRef.current = true;
31
+ heading.current?.focus();
32
+ // One commit's effects run in one flush: release the claim after it,
33
+ // whichever component re-rendered.
34
+ queueMicrotask(() => {
35
+ focusClaimRef.current = false;
36
+ });
37
+ }
38
+ previous.current = status;
39
+ }, [status, heading, focusClaimRef]);
40
+ }
41
+ /**
42
+ * One step of a `Steps` list: a numbered marker, the title as a heading,
43
+ * and, by status, its fields (current, error) or one folded line (a done
44
+ * step's summary and Edit; an upcoming or skipped step's title alone).
45
+ */
46
+ export function Step(props) {
47
+ const place = useContext(StepPlaceContext);
48
+ if (!place)
49
+ throw new RangeError('Step must be a child of Steps');
50
+ const level = props.headingLevel ?? place.headingLevel;
51
+ assertStep(props, level);
52
+ const { status, title, summary, onEdit, editLabel, skippedReason, children } = props;
53
+ const heading = useRef(null);
54
+ const titleId = useId();
55
+ const editId = useId();
56
+ useFocusWhenOpened(status, heading, place.focusClaimRef);
57
+ const Heading = `h${level}`;
58
+ const reason = status === 'skipped' ? skippedReason : undefined;
59
+ const word = stepStatusWord(status, place.labels, reason !== undefined);
60
+ const done = status === 'done';
61
+ return (_jsxs("li", { "aria-current": status === 'current' ? 'step' : undefined, className: cx('sw-steps-item', `sw-steps-item-${status}`), children: [_jsxs("div", { className: "sw-steps-header", children: [_jsx("span", { "aria-hidden": "true", className: "sw-steps-marker", children: status === 'error' ? '!' : done ? null : place.number }), _jsxs(Heading, { ref: heading, className: "sw-steps-title", tabIndex: -1, children: [_jsx("span", { id: titleId, children: title }), reason ? (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "sw-steps-reason", children: reason })] })) : null, word ? _jsxs("span", { className: "sw-sr-only", children: [", ", word] }) : null] }), done && summary ? (_jsx("span", { className: "sw-steps-summary", children: summary })) : null, done && onEdit ? (_jsx(Button, { "aria-labelledby": `${editId} ${titleId}`, className: "sw-steps-edit", id: editId, onPress: onEdit, size: "sm", variant: "ghost", children: editLabel })) : null] }), isOpenStep(status) && Children.toArray(children).length > 0 ? (_jsx("div", { className: "sw-steps-body", children: children })) : null] }));
62
+ }
@@ -0,0 +1,26 @@
1
+ /** Where a step stands in its form. */
2
+ export declare const stepStatuses: readonly ["upcoming", "current", "done", "error", "skipped"];
3
+ export type StepStatus = (typeof stepStatuses)[number];
4
+ /** A step title's heading level. */
5
+ export type StepHeadingLevel = 2 | 3 | 4;
6
+ export declare const stepHeadingLevels: readonly StepHeadingLevel[];
7
+ /** The words assistive technology hears after a folded step's title. */
8
+ export type StepsLabels = {
9
+ done: string;
10
+ error: string;
11
+ skipped: string;
12
+ upcoming: string;
13
+ };
14
+ export declare const defaultStepsLabels: StepsLabels;
15
+ /** A current or error step shows its fields; the others fold to one line. */
16
+ export declare function isOpenStep(status: StepStatus): boolean;
17
+ /**
18
+ * Whether a step that was `previous` and is now `next` should take focus: it
19
+ * became current from a folded state (upcoming, done, skipped). A first
20
+ * render (`previous` undefined), a move between current and error, and a
21
+ * step that turns to error keep focus where it is: after a refused submit
22
+ * the consumer focuses the first invalid field, as `Field` asks.
23
+ */
24
+ export declare function stepTakesFocus(previous: StepStatus | undefined, next: StepStatus): boolean;
25
+ /** The status word read after a step's title, or none (current, or a skipped step that shows its reason). */
26
+ export declare function stepStatusWord(status: StepStatus, labels: StepsLabels, hasReason: boolean): string | null;
@@ -0,0 +1,37 @@
1
+ /** Where a step stands in its form. */
2
+ export const stepStatuses = [
3
+ 'upcoming',
4
+ 'current',
5
+ 'done',
6
+ 'error',
7
+ 'skipped',
8
+ ];
9
+ export const stepHeadingLevels = [2, 3, 4];
10
+ export const defaultStepsLabels = {
11
+ done: 'Done',
12
+ error: 'Needs attention',
13
+ skipped: 'Not needed',
14
+ upcoming: 'Not started',
15
+ };
16
+ /** A current or error step shows its fields; the others fold to one line. */
17
+ export function isOpenStep(status) {
18
+ return status === 'current' || status === 'error';
19
+ }
20
+ /**
21
+ * Whether a step that was `previous` and is now `next` should take focus: it
22
+ * became current from a folded state (upcoming, done, skipped). A first
23
+ * render (`previous` undefined), a move between current and error, and a
24
+ * step that turns to error keep focus where it is: after a refused submit
25
+ * the consumer focuses the first invalid field, as `Field` asks.
26
+ */
27
+ export function stepTakesFocus(previous, next) {
28
+ return previous !== undefined && !isOpenStep(previous) && next === 'current';
29
+ }
30
+ /** The status word read after a step's title, or none (current, or a skipped step that shows its reason). */
31
+ export function stepStatusWord(status, labels, hasReason) {
32
+ if (status === 'current')
33
+ return null;
34
+ if (status === 'skipped' && hasReason)
35
+ return null;
36
+ return labels[status];
37
+ }
@@ -0,0 +1,16 @@
1
+ import { type RefObject } from 'react';
2
+ import { type StepHeadingLevel, type StepsLabels } from './step-status.js';
3
+ /**
4
+ * What a `Step` learns from its `Steps`: its number, its heading level, the
5
+ * status words, and the list's focus claim for this commit (the first step
6
+ * to open in a commit takes focus and releases the claim after that
7
+ * commit's effects).
8
+ */
9
+ export type StepPlace = {
10
+ number: number;
11
+ headingLevel: StepHeadingLevel;
12
+ labels: StepsLabels;
13
+ focusClaimRef: RefObject<boolean>;
14
+ };
15
+ /** Null outside `Steps`: a `Step` must be one of a list's steps. */
16
+ export declare const StepPlaceContext: import("react").Context<StepPlace | null>;
@@ -0,0 +1,3 @@
1
+ import { createContext } from 'react';
2
+ /** Null outside `Steps`: a `Step` must be one of a list's steps. */
3
+ export const StepPlaceContext = createContext(null);
@@ -0,0 +1,2 @@
1
+ export declare function cssActionBarSummaryClasses(): string;
2
+ export declare function actionBarSummaryClassCatalog(): string[];
@@ -0,0 +1,48 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ const label = typographyVariants.label;
3
+ /*
4
+ * The bar's figure: label, value and detail on one baseline, a small gap
5
+ * apart, wrapping as one phrase. The value takes the heading variant and
6
+ * tabular figures from the text classes it carries (so it steps down below
7
+ * md with every heading); the label and detail are muted label-size words
8
+ * at regular weight. A tone colors the value only. Forced colors keep the
9
+ * system text color.
10
+ */
11
+ export function cssActionBarSummaryClasses() {
12
+ return `.sw-action-bar-summary {
13
+ align-items: baseline;
14
+ column-gap: var(--sw-space-2);
15
+ display: inline-flex;
16
+ flex-wrap: wrap;
17
+ min-width: 0;
18
+ }
19
+
20
+ .sw-action-bar-summary-label,
21
+ .sw-action-bar-summary-detail {
22
+ color: var(--sw-color-muted);
23
+ font-size: ${label.fontSize}px;
24
+ letter-spacing: ${label.letterSpacing}px;
25
+ line-height: ${label.lineHeight}px;
26
+ }
27
+
28
+ .sw-action-bar-summary-value {
29
+ color: var(--sw-color-text);
30
+ }
31
+
32
+ .sw-action-bar-summary-danger .sw-action-bar-summary-value { color: var(--sw-color-danger); }
33
+ .sw-action-bar-summary-success .sw-action-bar-summary-value { color: var(--sw-color-success); }
34
+
35
+ @media (forced-colors: active) {
36
+ .sw-action-bar-summary-value { color: CanvasText; }
37
+ }`;
38
+ }
39
+ export function actionBarSummaryClassCatalog() {
40
+ return [
41
+ 'sw-action-bar-summary',
42
+ 'sw-action-bar-summary-label',
43
+ 'sw-action-bar-summary-value',
44
+ 'sw-action-bar-summary-detail',
45
+ 'sw-action-bar-summary-danger',
46
+ 'sw-action-bar-summary-success',
47
+ ];
48
+ }
@@ -1,5 +1,6 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { breakpointQuery, breakpoints, } from './breakpoints.js';
3
+ import { actionBarSummaryClassCatalog, cssActionBarSummaryClasses, } from './css-action-bar-summary.js';
3
4
  import { zIndex } from './stacking.js';
4
5
  const caption = typographyVariants.caption;
5
6
  /* The bar floats a step above the bottom edge and clears the home indicator. */
@@ -93,6 +94,8 @@ export function cssActionBarClasses() {
93
94
  .sw-action-bar-actions > * { flex: 1 1 auto; }
94
95
  }
95
96
 
97
+ ${cssActionBarSummaryClasses()}
98
+
96
99
  ${stickyRules()}`;
97
100
  }
98
101
  export function actionBarClassCatalog() {
@@ -101,6 +104,7 @@ export function actionBarClassCatalog() {
101
104
  'sw-action-bar-status',
102
105
  'sw-action-bar-actions',
103
106
  'sw-action-bar-sticky',
107
+ ...actionBarSummaryClassCatalog(),
104
108
  ...breakpoints.map((breakpoint) => actionBarStickyClass(breakpoint)),
105
109
  ];
106
110
  }
@@ -31,6 +31,8 @@ import { cssGridClasses } from './css-grid.js';
31
31
  import { cssGridSpanClasses } from './css-grid-span.js';
32
32
  import { cssDenominationGridClasses } from './css-denomination-grid.js';
33
33
  import { cssFieldClasses } from './css-field.js';
34
+ import { cssStepperClasses } from './css-stepper.js';
35
+ import { cssStepsClasses } from './css-steps.js';
34
36
  import { cssToastClasses } from './css-toast.js';
35
37
  import { cssBoxBorderClasses } from './css-box-border.js';
36
38
  import { cssPopupHostRules } from './stacking.js';
@@ -44,6 +46,7 @@ function reducedTransparencyRules() {
44
46
  .sw-card-glass,
45
47
  .sw-app-header,
46
48
  .sw-action-bar,
49
+ .sw-steps-item,
47
50
  .sw-badge-neutral,
48
51
  .sw-segmented,
49
52
  .sw-table-sticky thead th,
@@ -164,6 +167,10 @@ ${cssDenominationGridClasses()}
164
167
 
165
168
  ${cssFieldClasses()}
166
169
 
170
+ ${cssStepperClasses()}
171
+
172
+ ${cssStepsClasses()}
173
+
167
174
  ${cssSplitClasses()}
168
175
 
169
176
  ${cssToastClasses()}
@@ -0,0 +1,2 @@
1
+ export declare function cssDenominationEntryClasses(): string;
2
+ export declare function denominationEntryClassCatalog(): string[];
@@ -0,0 +1,78 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ const caption = typographyVariants.caption;
3
+ /** An enabled entry count, unless its Stepper shows an error (whose danger border wins). */
4
+ const tonedEntryInput = '[data-theme] .sw-denomination-cell-toned .sw-stepper:not(.sw-stepper-invalid) input.sw-stepper-input:not(:disabled)';
5
+ /*
6
+ * Count entry and the labels around it. An entry cell holds a full-width
7
+ * Stepper; its count keeps spacing step 8 (room for three digits), so a narrow strip scrolls
8
+ * in its region instead of crushing the counts. A cell tone draws the
9
+ * count's border in the tone, a hairline thicker through an inset shadow so
10
+ * nothing moves. A caption is a muted caption-size line: under a row label,
11
+ * or after a column label. A quiet row sits on the subtle fill, its pinned
12
+ * label too. A disabled row fades its label, counts and total (its entry is
13
+ * disabled by its Stepper); a pinned label fades its contents, not its
14
+ * fill, so counts never show through it.
15
+ */
16
+ export function cssDenominationEntryClasses() {
17
+ return `.sw-denomination-strip td.sw-denomination-entry {
18
+ padding: var(--sw-space-1);
19
+ }
20
+
21
+ .sw-denomination-entry .sw-stepper-full .sw-stepper-track {
22
+ grid-template-columns: var(--sw-space-6) minmax(var(--sw-space-8), 1fr) var(--sw-space-6);
23
+ }
24
+
25
+ /* A strip with entry rows centres its heads and read-only counts over the steppers. */
26
+ .sw-denomination-strip-entry .sw-denomination-head:not(.sw-denomination-total-head),
27
+ .sw-denomination-strip-entry .sw-denomination-cell {
28
+ text-align: center;
29
+ }
30
+
31
+ ${tonedEntryInput} {
32
+ border-color: var(--sw-denomination-cell-tone);
33
+ box-shadow: inset 0 0 0 1px var(--sw-denomination-cell-tone);
34
+ }
35
+
36
+ .sw-denomination-label-words {
37
+ display: flex;
38
+ flex-direction: column;
39
+ min-width: 0;
40
+ }
41
+
42
+ .sw-denomination-label-caption,
43
+ .sw-denomination-head-caption {
44
+ color: var(--sw-color-muted);
45
+ font-size: ${caption.fontSize}px;
46
+ font-weight: ${caption.fontWeight};
47
+ letter-spacing: ${caption.letterSpacing}px;
48
+ line-height: ${caption.lineHeight}px;
49
+ }
50
+
51
+ .sw-denomination-row-quiet,
52
+ .sw-denomination-strip .sw-denomination-row-quiet .sw-denomination-label {
53
+ background: var(--sw-color-subtle);
54
+ }
55
+
56
+ .sw-denomination-strip .sw-denomination-row-disabled .sw-denomination-label-body,
57
+ .sw-denomination-strip .sw-denomination-row-disabled .sw-denomination-cell,
58
+ .sw-denomination-strip .sw-denomination-row-disabled .sw-denomination-total {
59
+ opacity: var(--sw-disabled-opacity);
60
+ }
61
+
62
+ @media (forced-colors: active) {
63
+ ${tonedEntryInput} { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; }
64
+ .sw-denomination-label-caption,
65
+ .sw-denomination-head-caption { color: CanvasText; }
66
+ }`;
67
+ }
68
+ export function denominationEntryClassCatalog() {
69
+ return [
70
+ 'sw-denomination-entry',
71
+ 'sw-denomination-strip-entry',
72
+ 'sw-denomination-label-words',
73
+ 'sw-denomination-label-caption',
74
+ 'sw-denomination-head-caption',
75
+ 'sw-denomination-row-quiet',
76
+ 'sw-denomination-row-disabled',
77
+ ];
78
+ }
@@ -2,6 +2,7 @@ import { typographyVariants } from '@scalewing/tokens';
2
2
  import { breakpointQuery } from './breakpoints.js';
3
3
  import { badgeTones } from './css-data.js';
4
4
  import { cssDenominationAlignedClasses, denominationAlignedClassCatalog, } from './css-denomination-aligned.js';
5
+ import { cssDenominationEntryClasses, denominationEntryClassCatalog, } from './css-denomination-entry.js';
5
6
  import { cssDenominationCellToneClasses, denominationCellToneClassCatalog, } from './css-denomination-cell-tones.js';
6
7
  import { pinnedStartShadeRules, scrollRegionRules, } from './css-scroll-region.js';
7
8
  import { visuallyHiddenDeclarations } from './css-utilities.js';
@@ -249,6 +250,8 @@ ${pinnedStartShadeRules('.sw-denomination-scroll', [
249
250
 
250
251
  ${cssDenominationCellToneClasses()}
251
252
 
253
+ ${cssDenominationEntryClasses()}
254
+
252
255
  ${cssDenominationAlignedClasses()}
253
256
 
254
257
  @media (forced-colors: active) {
@@ -290,6 +293,7 @@ export function denominationGridClassCatalog() {
290
293
  'sw-denomination-tile',
291
294
  'sw-denomination-subtotal',
292
295
  ...denominationCellToneClassCatalog(),
296
+ ...denominationEntryClassCatalog(),
293
297
  ...denominationAlignedClassCatalog(),
294
298
  ];
295
299
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The web Stepper: a label over a track of − count +. The count is a few
3
+ * digits wide by default, the track at its own width even under a wider label
4
+ * or message (`5ch` plus its padding, so its width never follows
5
+ * its value), or fills its container with `sw-stepper-full`. The buttons are
6
+ * spacing step 6 wide on the subtle fill and every part is the `md` control
7
+ * height.
8
+ */
9
+ export declare function cssStepperClasses(): string;
10
+ export declare function stepperClassCatalog(): string[];