@scalewing/react 1.39.0 → 1.41.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 +33 -0
- package/README.md +255 -5
- package/dist/calendar-labels.d.ts +2 -0
- package/dist/calendar-labels.js +7 -0
- package/dist/calendar-month.d.ts +6 -0
- package/dist/calendar-month.js +15 -0
- package/dist/components/BarChart.d.ts +20 -4
- package/dist/components/BarChart.js +13 -6
- package/dist/components/ButtonGroup.d.ts +8 -1
- package/dist/components/ButtonGroup.js +11 -2
- package/dist/components/DateField.d.ts +14 -2
- package/dist/components/DateField.js +14 -11
- package/dist/components/DateRangeButton.d.ts +65 -0
- package/dist/components/DateRangeButton.js +54 -0
- package/dist/components/DenominationGrid.d.ts +3 -2
- package/dist/components/DenominationGrid.js +21 -3
- package/dist/components/Dialog.d.ts +29 -13
- package/dist/components/Dialog.js +22 -20
- package/dist/components/Field.d.ts +13 -2
- package/dist/components/Field.js +14 -38
- package/dist/components/FilterChips.d.ts +7 -37
- package/dist/components/FilterChips.js +32 -24
- package/dist/components/ListGroup.d.ts +33 -0
- package/dist/components/ListGroup.js +19 -0
- package/dist/components/ListRow.d.ts +60 -0
- package/dist/components/ListRow.js +60 -0
- package/dist/components/NavCount.d.ts +21 -0
- package/dist/components/NavCount.js +23 -0
- package/dist/components/SectionNav.js +1 -8
- package/dist/components/SegmentedControl.d.ts +17 -1
- package/dist/components/SegmentedControl.js +11 -3
- package/dist/components/StatTile.d.ts +11 -4
- package/dist/components/StatTile.js +11 -4
- package/dist/components/Switch.d.ts +16 -1
- package/dist/components/Switch.js +17 -6
- package/dist/components/action-bar/ActionBarSummary.d.ts +10 -2
- package/dist/components/action-bar/ActionBarSummary.js +11 -2
- package/dist/components/{date-field → calendar}/CalendarGrid.d.ts +17 -2
- package/dist/components/{date-field → calendar}/CalendarGrid.js +11 -3
- package/dist/components/calendar/CalendarPopover.d.ts +25 -0
- package/dist/components/calendar/CalendarPopover.js +22 -0
- package/dist/components/calendar/MonthStep.d.ts +16 -0
- package/dist/components/calendar/MonthStep.js +17 -0
- package/dist/components/calendar/use-grid-focus.d.ts +18 -0
- package/dist/components/calendar/use-grid-focus.js +35 -0
- package/dist/components/calendar/wrap-tab.d.ts +7 -0
- package/dist/components/calendar/wrap-tab.js +29 -0
- package/dist/components/date-field/CalendarDialog.js +9 -67
- package/dist/components/date-field/CalendarEntryButton.d.ts +24 -0
- package/dist/components/date-field/CalendarEntryButton.js +16 -0
- package/dist/components/date-field/CalendarHeader.js +2 -11
- package/dist/components/date-field/TypedEntry.d.ts +26 -0
- package/dist/components/date-field/TypedEntry.js +13 -0
- package/dist/components/date-range/DateRangePopover.d.ts +7 -0
- package/dist/components/date-range/DateRangePopover.js +17 -0
- package/dist/components/date-range/DateRangeSheet.d.ts +7 -0
- package/dist/components/date-range/DateRangeSheet.js +15 -0
- package/dist/components/date-range/RangeMonths.d.ts +28 -0
- package/dist/components/date-range/RangeMonths.js +50 -0
- package/dist/components/date-range/RangePresets.d.ts +16 -0
- package/dist/components/date-range/RangePresets.js +10 -0
- package/dist/components/date-range/RangeSummary.d.ts +15 -0
- package/dist/components/date-range/RangeSummary.js +11 -0
- package/dist/components/date-range/range-panel-props.d.ts +31 -0
- package/dist/components/date-range/range-panel-props.js +1 -0
- package/dist/components/date-range/use-range-popup.d.ts +21 -0
- package/dist/components/date-range/use-range-popup.js +53 -0
- package/dist/components/denomination-grid/CountCell.d.ts +16 -0
- package/dist/components/denomination-grid/CountCell.js +14 -0
- package/dist/components/denomination-grid/DenominationStrip.d.ts +4 -1
- package/dist/components/denomination-grid/DenominationStrip.js +2 -2
- package/dist/components/denomination-grid/EntryCell.d.ts +3 -1
- package/dist/components/denomination-grid/EntryCell.js +2 -2
- package/dist/components/denomination-grid/StripRow.js +1 -10
- package/dist/components/denomination-grid/TransposedStrip.d.ts +17 -0
- package/dist/components/denomination-grid/TransposedStrip.js +43 -0
- package/dist/components/denomination-grid/types.d.ts +31 -0
- package/dist/components/denomination-grid/types.js +7 -1
- package/dist/components/denomination-grid/validate-options.d.ts +7 -0
- package/dist/components/denomination-grid/validate-options.js +27 -0
- package/dist/components/dialog/DialogFooter.d.ts +6 -5
- package/dist/components/dialog/DialogFooter.js +6 -6
- package/dist/components/dialog/validate.d.ts +24 -0
- package/dist/components/dialog/validate.js +23 -0
- package/dist/components/field/field-checks.d.ts +34 -0
- package/dist/components/field/field-checks.js +42 -0
- package/dist/components/filter-chips/FilterChip.d.ts +16 -0
- package/dist/components/filter-chips/FilterChip.js +20 -0
- package/dist/components/filter-chips/options.d.ts +75 -0
- package/dist/components/filter-chips/options.js +11 -0
- package/dist/components/filter-chips/validate.d.ts +9 -0
- package/dist/components/filter-chips/validate.js +48 -0
- package/dist/components/link-press.d.ts +7 -0
- package/dist/components/link-press.js +13 -0
- package/dist/components/list/ListRowAccessoryMark.d.ts +5 -0
- package/dist/components/list/ListRowAccessoryMark.js +7 -0
- package/dist/components/list/ListRowContent.d.ts +22 -0
- package/dist/components/list/ListRowContent.js +11 -0
- package/dist/components/list/list-row-container.d.ts +5 -0
- package/dist/components/list/list-row-container.js +6 -0
- package/dist/components/use-media-query.d.ts +5 -0
- package/dist/components/use-media-query.js +18 -0
- package/dist/css/chart-tones.d.ts +2 -0
- package/dist/css/chart-tones.js +5 -1
- package/dist/css/chevron.d.ts +6 -0
- package/dist/css/chevron.js +14 -0
- package/dist/css/css-bar-chart.d.ts +17 -2
- package/dist/css/css-bar-chart.js +38 -7
- package/dist/css/css-button-group-attached.d.ts +3 -0
- package/dist/css/css-button-group-attached.js +26 -5
- package/dist/css/css-button-group-track.js +1 -1
- package/dist/css/css-canvas-grouped.d.ts +17 -0
- package/dist/css/css-canvas-grouped.js +40 -0
- package/dist/css/css-checkbox.js +2 -2
- package/dist/css/css-components.js +15 -0
- package/dist/css/css-count-pill.d.ts +2 -0
- package/dist/css/css-count-pill.js +54 -0
- package/dist/css/css-date-field-calendar-entry.d.ts +11 -0
- package/dist/css/css-date-field-calendar-entry.js +50 -0
- package/dist/css/css-date-field.js +7 -2
- package/dist/css/css-date-range-presets.d.ts +2 -0
- package/dist/css/css-date-range-presets.js +109 -0
- package/dist/css/css-date-range.d.ts +2 -0
- package/dist/css/css-date-range.js +197 -0
- package/dist/css/css-denomination-grid.js +4 -0
- package/dist/css/css-denomination-transposed.d.ts +4 -0
- package/dist/css/css-denomination-transposed.js +178 -0
- package/dist/css/css-dialog-canvas.d.ts +3 -0
- package/dist/css/css-dialog-canvas.js +20 -0
- package/dist/css/css-dialog-frame.js +19 -2
- package/dist/css/css-dialog-grabber.d.ts +4 -0
- package/dist/css/css-dialog-grabber.js +56 -0
- package/dist/css/css-dialog.js +3 -2
- package/dist/css/css-document.js +3 -0
- package/dist/css/css-field-width.d.ts +12 -0
- package/dist/css/css-field-width.js +49 -0
- package/dist/css/css-field.js +4 -0
- package/dist/css/css-filter-chips-layout.d.ts +2 -0
- package/dist/css/css-filter-chips-layout.js +42 -0
- package/dist/css/css-filter-chips.js +4 -0
- package/dist/css/css-list-row.d.ts +8 -0
- package/dist/css/css-list-row.js +209 -0
- package/dist/css/css-list.d.ts +3 -0
- package/dist/css/css-list.js +85 -0
- package/dist/css/css-scroll-region.d.ts +18 -0
- package/dist/css/css-scroll-region.js +41 -0
- package/dist/css/css-section-nav-filled.js +2 -25
- package/dist/css/css-segmented-sizing.d.ts +13 -0
- package/dist/css/css-segmented-sizing.js +66 -0
- package/dist/css/css-segmented.js +4 -0
- package/dist/css/css-stat-tile.d.ts +7 -0
- package/dist/css/css-stat-tile.js +16 -0
- package/dist/css/css-steps.js +2 -2
- package/dist/css/css-switch.d.ts +6 -0
- package/dist/css/css-switch.js +39 -0
- package/dist/css/css-table-bounded-edges.js +4 -27
- package/dist/css/range-band.d.ts +2 -0
- package/dist/css/range-band.js +7 -0
- package/dist/css/stacking.d.ts +1 -0
- package/dist/css/stacking.js +6 -3
- package/dist/css/stylesheet.js +10 -0
- package/dist/date-field-labels.d.ts +5 -0
- package/dist/date-field-labels.js +1 -0
- package/dist/date-range-draft.d.ts +26 -0
- package/dist/date-range-draft.js +36 -0
- package/dist/date-range-input.d.ts +26 -0
- package/dist/date-range-input.js +58 -0
- package/dist/date-range-labels.d.ts +22 -0
- package/dist/date-range-labels.js +26 -0
- package/dist/date-range-view.d.ts +12 -0
- package/dist/date-range-view.js +29 -0
- package/dist/date-range-words.d.ts +11 -0
- package/dist/date-range-words.js +40 -0
- package/dist/index.d.ts +16 -9
- package/dist/index.js +14 -7
- package/dist/list-options.d.ts +71 -0
- package/dist/list-options.js +84 -0
- package/dist/styles.css +863 -41
- package/dist/theme/ThemeProvider.d.ts +11 -1
- package/dist/theme/ThemeProvider.js +4 -2
- package/package.json +1 -1
package/dist/components/Field.js
CHANGED
|
@@ -4,45 +4,14 @@ import { cloneElement, isValidElement, useId, } from 'react';
|
|
|
4
4
|
import { adornedControlAria, FieldAdornment, } from './FieldAdornment.js';
|
|
5
5
|
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
6
6
|
import { assertRequiredMark, FieldLabelText, } from './FieldLabelText.js';
|
|
7
|
+
import { fieldWidthClass } from '../css/css-field-width.js';
|
|
7
8
|
import { SearchFrame } from './field/SearchFrame.js';
|
|
9
|
+
import { assertFieldProps, fieldVariants, passesInvalid, } from './field/field-checks.js';
|
|
8
10
|
import { Stack } from './Stack.js';
|
|
9
|
-
export
|
|
10
|
-
function assertSearch(props, child) {
|
|
11
|
-
const { variant = 'outlined' } = props;
|
|
12
|
-
if (!fieldVariants.includes(variant))
|
|
13
|
-
throw new RangeError(`Unknown Field variant: ${String(variant)}`);
|
|
14
|
-
if (variant !== 'search') {
|
|
15
|
-
if (props.leading !== undefined ||
|
|
16
|
-
props.clearLabel !== undefined ||
|
|
17
|
-
props.onClear !== undefined)
|
|
18
|
-
throw new TypeError('Field leading, clearLabel and onClear are for variant="search"');
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
if (!props.clearLabel?.trim())
|
|
22
|
-
throw new RangeError('A search Field needs a nonempty clearLabel');
|
|
23
|
-
if (typeof props.onClear !== 'function')
|
|
24
|
-
throw new TypeError('A search Field needs onClear');
|
|
25
|
-
if (props.prefix || props.suffix)
|
|
26
|
-
throw new TypeError('A search Field takes leading, not prefix or suffix');
|
|
27
|
-
if (!isValidElement(child) || child.type !== 'input')
|
|
28
|
-
throw new TypeError('A search Field needs one native input child');
|
|
29
|
-
if (child.props.value === undefined)
|
|
30
|
-
throw new TypeError('A search Field needs a controlled input: pass value (and onChange)');
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* `invalid` was passed, even as `false`, but not as `undefined` (a wrapper
|
|
34
|
-
* forwarding an optional prop passes nothing): such a field needs one native
|
|
35
|
-
* control from its first render, so a form learns it on the happy path
|
|
36
|
-
* instead of on its first failed submit. `error` keeps its older rule (it
|
|
37
|
-
* requires the control only while it holds a message), so a composed
|
|
38
|
-
* child beside `error={undefined}` still renders.
|
|
39
|
-
*/
|
|
40
|
-
function passesInvalid(props) {
|
|
41
|
-
return props.invalid !== undefined || props.changed !== undefined;
|
|
42
|
-
}
|
|
11
|
+
export { fieldVariants };
|
|
43
12
|
export function Field(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
|
-
|
|
13
|
+
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, width, } = props;
|
|
14
|
+
assertFieldProps(props, children);
|
|
46
15
|
const search = variant === 'search';
|
|
47
16
|
const controlId = useId();
|
|
48
17
|
const descriptionId = useId();
|
|
@@ -54,13 +23,20 @@ export function Field(props) {
|
|
|
54
23
|
const message = error || description;
|
|
55
24
|
const marksInvalid = Boolean(error) || invalid;
|
|
56
25
|
const adorned = Boolean(prefix || suffix);
|
|
57
|
-
const needsControl = Boolean(message ||
|
|
26
|
+
const needsControl = Boolean(message ||
|
|
27
|
+
marksInvalid ||
|
|
28
|
+
passesInvalid(props) ||
|
|
29
|
+
required ||
|
|
30
|
+
adorned ||
|
|
31
|
+
Boolean(width));
|
|
58
32
|
const className = [
|
|
59
33
|
'sw-field',
|
|
60
34
|
size === 'xs' && 'sw-field-xs',
|
|
61
35
|
marksInvalid && 'sw-field-invalid',
|
|
62
36
|
search && 'sw-field-search-field',
|
|
63
37
|
changed && !marksInvalid && 'sw-field-changed',
|
|
38
|
+
width && 'sw-field-sized',
|
|
39
|
+
width && fieldWidthClass(width),
|
|
64
40
|
]
|
|
65
41
|
.filter(Boolean)
|
|
66
42
|
.join(' ');
|
|
@@ -71,7 +47,7 @@ export function Field(props) {
|
|
|
71
47
|
typeof children.type !== 'string' ||
|
|
72
48
|
!['input', 'select', 'textarea'].includes(children.type)) {
|
|
73
49
|
if (needsControl)
|
|
74
|
-
throw new TypeError('Field validation and
|
|
50
|
+
throw new TypeError('Field validation, adornments and width require one native input, select, or textarea child');
|
|
75
51
|
return (_jsxs(Stack, { as: "label", className: className, gap: labelHidden ? 0 : gap, children: [labelText, children] }));
|
|
76
52
|
}
|
|
77
53
|
if (adorned && children.type !== 'input')
|
|
@@ -1,37 +1,7 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
export type FilterChipOption
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export type FilterChipsProps = {
|
|
9
|
-
label: string;
|
|
10
|
-
/** The chosen option's value, or `''` while nothing is chosen yet. */
|
|
11
|
-
value: string;
|
|
12
|
-
onChange: (value: string) => void;
|
|
13
|
-
options: readonly FilterChipOption[];
|
|
14
|
-
/**
|
|
15
|
-
* A choice must be made, such as a reason: each radio is `required` and
|
|
16
|
-
* the label shows the required mark (hidden from assistive technology,
|
|
17
|
-
* which reads the radios' own state).
|
|
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;
|
|
27
|
-
/**
|
|
28
|
-
* A message under the chips, such as "Pick a reason": a polite live
|
|
29
|
-
* region linked to the group by `aria-describedby`; the group is marked
|
|
30
|
-
* invalid and every chip takes the danger edge. An empty string is no
|
|
31
|
-
* error.
|
|
32
|
-
*/
|
|
33
|
-
error?: string;
|
|
34
|
-
/** More ids that describe the group, such as a hint beside it. */
|
|
35
|
-
'aria-describedby'?: string;
|
|
36
|
-
};
|
|
37
|
-
export declare function FilterChips({ label, value, onChange, options, required, requiredMark, error, 'aria-describedby': describedByProp, }: FilterChipsProps): import("react").JSX.Element;
|
|
1
|
+
import { type FilterChipsProps } from './filter-chips/options.js';
|
|
2
|
+
export { filterChipCountAppearances, filterChipsOverflows, type FilterChipCountAppearance, type FilterChipOption, type FilterChipsOverflow, type FilterChipsProps, } from './filter-chips/options.js';
|
|
3
|
+
/**
|
|
4
|
+
* A labelled row of chips: one choice (radios), or several at once
|
|
5
|
+
* (`multiple`, checkboxes), each chip optionally counted.
|
|
6
|
+
*/
|
|
7
|
+
export declare function FilterChips(props: FilterChipsProps): import("react").JSX.Element;
|
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsxs as _jsxs, jsx as _jsx
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
4
|
import { cx } from '../class-names.js';
|
|
5
|
+
import { radioTabStopValue } from '../tab-stop.js';
|
|
5
6
|
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { showsRequiredMark } from './FieldLabelText.js';
|
|
8
|
+
import { FilterChip } from './filter-chips/FilterChip.js';
|
|
9
|
+
import { assertFilterChips } from './filter-chips/validate.js';
|
|
10
|
+
export { filterChipCountAppearances, filterChipsOverflows, } from './filter-chips/options.js';
|
|
11
|
+
/** The values a change leaves on, in the options' order. */
|
|
12
|
+
function toggled(options, chosen, value, checked) {
|
|
13
|
+
return options
|
|
14
|
+
.map((option) => option.value)
|
|
15
|
+
.filter((each) => (each === value ? checked : chosen.includes(each)));
|
|
10
16
|
}
|
|
11
|
-
|
|
17
|
+
/**
|
|
18
|
+
* A labelled row of chips: one choice (radios), or several at once
|
|
19
|
+
* (`multiple`, checkboxes), each chip optionally counted.
|
|
20
|
+
*/
|
|
21
|
+
export function FilterChips(props) {
|
|
22
|
+
const { label, options, labelVisuallyHidden = false, requiredMark = 'visible', countAppearance = 'figure', overflow = 'wrap', error, 'aria-describedby': describedByProp, } = props;
|
|
12
23
|
const name = useId();
|
|
13
24
|
const errorId = useId();
|
|
25
|
+
assertFilterChips(props);
|
|
26
|
+
const required = props.multiple ? false : (props.required ?? false);
|
|
14
27
|
const describedBy = [describedByProp?.trim() || null, error ? errorId : null]
|
|
15
28
|
.filter((id) => id !== null)
|
|
16
29
|
.join(' ');
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const entry = radioTabStopValue(options, value);
|
|
31
|
-
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) => {
|
|
32
|
-
if (event.currentTarget.checked && !option.disabled)
|
|
33
|
-
onChange(option.value);
|
|
34
|
-
}, required: required, tabIndex: option.value === entry ? tabStop : undefined, 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 })] }));
|
|
30
|
+
const entry = props.multiple
|
|
31
|
+
? undefined
|
|
32
|
+
: radioTabStopValue(options, props.value);
|
|
33
|
+
function isChecked(value) {
|
|
34
|
+
return props.multiple ? props.value.includes(value) : props.value === value;
|
|
35
|
+
}
|
|
36
|
+
function change(value, checked) {
|
|
37
|
+
if (props.multiple)
|
|
38
|
+
props.onChange(toggled(options, props.value, value, checked));
|
|
39
|
+
else if (checked)
|
|
40
|
+
props.onChange(value);
|
|
41
|
+
}
|
|
42
|
+
return (_jsxs("fieldset", { "aria-describedby": describedBy || undefined, "aria-invalid": error ? true : undefined, className: cx('sw-filter-chips', overflow === 'scroll' && 'sw-filter-chips-scroll'), children: [_jsxs("legend", { className: cx('sw-filter-chips-legend', labelVisuallyHidden && 'sw-sr-only'), 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) => (_jsx(FilterChip, { checked: isChecked(option.value), countAppearance: countAppearance, name: name, onCheckedChange: (checked) => change(option.value, checked), option: option, required: required, tabStopHere: props.multiple ? true : option.value === entry, type: props.multiple ? 'checkbox' : 'radio' }, option.value))) }), _jsx(FieldErrorRegion, { className: "sw-filter-chips-error", id: errorId, message: error })] }));
|
|
35
43
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ListGroupMaxHeight, type ListGroupVariant } from '../list-options.js';
|
|
3
|
+
export type ListGroupProps = {
|
|
4
|
+
/** `ListRow` elements; the group draws a hairline between each pair. */
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/** Names the list, such as its card's heading. Required with `maxHeight`. */
|
|
7
|
+
'aria-label'?: string;
|
|
8
|
+
'aria-labelledby'?: string;
|
|
9
|
+
/**
|
|
10
|
+
* `panel` (the default) is one bordered panel; `plain` draws no panel
|
|
11
|
+
* and a hairline above the first row too, for rows inside a Card.
|
|
12
|
+
*/
|
|
13
|
+
variant?: ListGroupVariant;
|
|
14
|
+
/**
|
|
15
|
+
* Bounds the list to a row count of its own region, `{ rows }` rows of a
|
|
16
|
+
* title and one detail line, where the rest scroll; the region is a named
|
|
17
|
+
* keyboard stop and shades an edge with more rows past it.
|
|
18
|
+
*/
|
|
19
|
+
maxHeight?: ListGroupMaxHeight;
|
|
20
|
+
/**
|
|
21
|
+
* Pinned under the rows, outside the scroll, under a hairline: a total
|
|
22
|
+
* the rows add up to, such as a `ListRow` with the count and the sum.
|
|
23
|
+
*/
|
|
24
|
+
footer?: ReactNode;
|
|
25
|
+
className?: string;
|
|
26
|
+
id?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Rows in one list (`ul`, its list role kept) with hairlines between them:
|
|
30
|
+
* a panel of its own, or plain rows inside a surface. The ref reaches the
|
|
31
|
+
* outer box.
|
|
32
|
+
*/
|
|
33
|
+
export declare const ListGroup: import("react").ForwardRefExoticComponent<ListGroupProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { cx } from '../class-names.js';
|
|
5
|
+
import { assertListGroup, listMaxHeightStyle, } from '../list-options.js';
|
|
6
|
+
import { ListRowContainer } from './list/list-row-container.js';
|
|
7
|
+
import { ScrollRegion } from './ScrollRegion.js';
|
|
8
|
+
/**
|
|
9
|
+
* Rows in one list (`ul`, its list role kept) with hairlines between them:
|
|
10
|
+
* a panel of its own, or plain rows inside a surface. The ref reaches the
|
|
11
|
+
* outer box.
|
|
12
|
+
*/
|
|
13
|
+
export const ListGroup = forwardRef(function ListGroup({ children, 'aria-label': label, 'aria-labelledby': labelledBy, variant = 'panel', maxHeight, footer, className, id, }, ref) {
|
|
14
|
+
assertListGroup({ label, labelledBy, variant, maxHeight });
|
|
15
|
+
const list = (_jsx("ul", { "aria-label": label, "aria-labelledby": labelledBy, className: "sw-list",
|
|
16
|
+
// A list styled without bullets loses its role in Safari.
|
|
17
|
+
role: "list", children: _jsx(ListRowContainer.Provider, { value: "li", children: children }) }));
|
|
18
|
+
return (_jsxs("div", { className: cx('sw-list-group', `sw-list-group-${variant}`, className), id: id, ref: ref, children: [maxHeight === undefined ? (list) : (_jsx(ScrollRegion, { "aria-label": label, "aria-labelledby": labelledBy, className: "sw-list-group-scroll", style: listMaxHeightStyle(maxHeight), children: list })), footer === undefined ? null : (_jsx("div", { className: "sw-list-group-footer", children: _jsx(ListRowContainer.Provider, { value: "div", children: footer }) }))] }));
|
|
19
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type BadgeTone } from '../css/css-data.js';
|
|
3
|
+
import { type ListRowTitleAppearance, type ListRowTone, type ListRowValueTone } from '../list-options.js';
|
|
4
|
+
export type ListRowProps = {
|
|
5
|
+
title: string;
|
|
6
|
+
/** A muted line under the title. */
|
|
7
|
+
detail?: string;
|
|
8
|
+
/** A figure or word at the row's end, such as "+$1.48". */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** A muted line under the value, such as "checks $3,427". */
|
|
11
|
+
valueDetail?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Colours the value only, as a table cell's tone, or `warning` for a
|
|
14
|
+
* figure that needs a look; the words say why.
|
|
15
|
+
*/
|
|
16
|
+
valueTone?: ListRowValueTone;
|
|
17
|
+
/**
|
|
18
|
+
* Draws the value as a `Badge` in this tone, such as a status ("Open",
|
|
19
|
+
* "Blocked") at the row's end. Not with `valueTone`.
|
|
20
|
+
*/
|
|
21
|
+
valueBadge?: BadgeTone;
|
|
22
|
+
/**
|
|
23
|
+
* A consumer mark at the row's start, such as a status dot or a Lucide
|
|
24
|
+
* glyph (ADR 0008). It is hidden from assistive technology, so the words
|
|
25
|
+
* must say what it means.
|
|
26
|
+
*/
|
|
27
|
+
leading?: ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* Makes the whole row a button (or, with `href`, a link's plain press
|
|
30
|
+
* for a client router) and adds the chevron.
|
|
31
|
+
*/
|
|
32
|
+
onPress?: () => void;
|
|
33
|
+
/** Makes the whole row a link to `href`, with the chevron. */
|
|
34
|
+
href?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Makes the row a choice: a check while true, nothing while false, and
|
|
37
|
+
* the state (`aria-pressed` on a button, `aria-current` on a link). Only
|
|
38
|
+
* on a row with `onPress` or `href`.
|
|
39
|
+
*/
|
|
40
|
+
selected?: boolean;
|
|
41
|
+
/** Dims the row and blocks presses; a link row drops its `href`. */
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
/** `danger` draws the title in the danger colour, for a row that ends something. */
|
|
44
|
+
tone?: ListRowTone;
|
|
45
|
+
/** `plain` sets the title at the body weight, for a list of plain statements. */
|
|
46
|
+
titleAppearance?: ListRowTitleAppearance;
|
|
47
|
+
/** `none` hides the chevron of a pressable row that acts in place. */
|
|
48
|
+
accessory?: 'chevron' | 'none';
|
|
49
|
+
/**
|
|
50
|
+
* Replaces a pressable row's name, which is its title and value; its
|
|
51
|
+
* detail lines are its description.
|
|
52
|
+
*/
|
|
53
|
+
'aria-label'?: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* One row of a `ListGroup`: it opens something (`onPress` or `href`, with
|
|
57
|
+
* a chevron, the whole row pressing), picks one option in place
|
|
58
|
+
* (`selected`), or only states something (neither).
|
|
59
|
+
*/
|
|
60
|
+
export declare function ListRow({ title, detail, value, valueDetail, valueTone, valueBadge, leading, onPress, href, selected, disabled, tone, titleAppearance, accessory, 'aria-label': ariaLabel, }: ListRowProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useContext, useId } from 'react';
|
|
4
|
+
import { cx } from '../class-names.js';
|
|
5
|
+
import { assertListRow, listRowAccessory, } from '../list-options.js';
|
|
6
|
+
import { tabStop } from '../tab-stop.js';
|
|
7
|
+
import { plainPress } from './link-press.js';
|
|
8
|
+
import { ListRowContent } from './list/ListRowContent.js';
|
|
9
|
+
import { ListRowContainer } from './list/list-row-container.js';
|
|
10
|
+
function ids(...parts) {
|
|
11
|
+
const joined = parts.filter(Boolean).join(' ');
|
|
12
|
+
return joined === '' ? undefined : joined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* One row of a `ListGroup`: it opens something (`onPress` or `href`, with
|
|
16
|
+
* a chevron, the whole row pressing), picks one option in place
|
|
17
|
+
* (`selected`), or only states something (neither).
|
|
18
|
+
*/
|
|
19
|
+
export function ListRow({ title, detail, value, valueDetail, valueTone, valueBadge, leading, onPress, href, selected, disabled = false, tone = 'default', titleAppearance = 'strong', accessory = 'chevron', 'aria-label': ariaLabel, }) {
|
|
20
|
+
const pressable = onPress !== undefined || href !== undefined;
|
|
21
|
+
assertListRow({
|
|
22
|
+
title,
|
|
23
|
+
tone,
|
|
24
|
+
titleAppearance,
|
|
25
|
+
valueTone,
|
|
26
|
+
accessory,
|
|
27
|
+
value,
|
|
28
|
+
valueDetail,
|
|
29
|
+
valueBadge,
|
|
30
|
+
selected,
|
|
31
|
+
pressable,
|
|
32
|
+
});
|
|
33
|
+
const container = useContext(ListRowContainer);
|
|
34
|
+
const id = useId();
|
|
35
|
+
const kind = listRowAccessory({ pressable, selected, accessory });
|
|
36
|
+
const content = (_jsx(ListRowContent, { accessory: kind, detail: detail, id: id, leading: leading, title: title, titleAppearance: titleAppearance, tone: tone, value: value, valueBadge: valueBadge, valueDetail: valueDetail, valueTone: valueTone }));
|
|
37
|
+
const className = cx('sw-list-row', pressable && 'sw-list-row-pressable', disabled && 'sw-list-row-disabled');
|
|
38
|
+
const naming = {
|
|
39
|
+
'aria-label': ariaLabel,
|
|
40
|
+
'aria-labelledby': ariaLabel
|
|
41
|
+
? undefined
|
|
42
|
+
: ids(`${id}-title`, value !== undefined && `${id}-value`),
|
|
43
|
+
'aria-describedby': ids(Boolean(detail) && `${id}-detail`, Boolean(valueDetail) && `${id}-value-detail`),
|
|
44
|
+
};
|
|
45
|
+
let row;
|
|
46
|
+
if (href !== undefined)
|
|
47
|
+
row = (_jsx("a", { ...naming, "aria-current": selected ? 'true' : undefined, "aria-disabled": disabled || undefined, className: className, href: disabled ? undefined : href, onClick: onPress && !disabled
|
|
48
|
+
? (event) => {
|
|
49
|
+
if (!plainPress(event))
|
|
50
|
+
return;
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
onPress();
|
|
53
|
+
}
|
|
54
|
+
: undefined, role: disabled ? 'link' : undefined, tabIndex: disabled ? undefined : tabStop, children: content }));
|
|
55
|
+
else if (onPress !== undefined)
|
|
56
|
+
row = (_jsx("button", { ...naming, "aria-pressed": selected, className: className, disabled: disabled, onClick: onPress, tabIndex: tabStop, type: "button", children: content }));
|
|
57
|
+
else
|
|
58
|
+
row = (_jsx("div", { "aria-label": ariaLabel, className: className, role: ariaLabel ? 'group' : undefined, children: content }));
|
|
59
|
+
return container === 'li' ? (_jsx("li", { className: "sw-list-item", children: row })) : (_jsx("div", { className: "sw-list-item", children: row }));
|
|
60
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type NavCountProps = {
|
|
2
|
+
/**
|
|
3
|
+
* How many things wait behind the destination, such as tasks to do: a
|
|
4
|
+
* filled pill after its words, read in the link's name ("Tasks 8"). A
|
|
5
|
+
* whole number; 0 shows nothing.
|
|
6
|
+
*/
|
|
7
|
+
count: number;
|
|
8
|
+
/**
|
|
9
|
+
* What the count means, in the consumer's words ("8 tasks to do"): read
|
|
10
|
+
* in the name in place of the bare number ("Tasks 8 tasks to do"), the
|
|
11
|
+
* pill then hidden from assistive technology.
|
|
12
|
+
*/
|
|
13
|
+
countLabel?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A count after a Nav destination's words, placed inside its link or
|
|
17
|
+
* Button: `<a href="/tasks">Tasks<NavCount count={8} /></a>`. It is the
|
|
18
|
+
* pill SectionNav's item count draws, and it is part of the destination's
|
|
19
|
+
* accessible name, as there.
|
|
20
|
+
*/
|
|
21
|
+
export declare function NavCount({ count, countLabel }: NavCountProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Fails closed on a count that is not a whole number or an empty label. */
|
|
3
|
+
function assertNavCount({ count, countLabel }) {
|
|
4
|
+
if (!Number.isInteger(count) || count < 0)
|
|
5
|
+
throw new RangeError(`NavCount count must be a whole number from 0: ${String(count)}`);
|
|
6
|
+
if (countLabel !== undefined &&
|
|
7
|
+
(typeof countLabel !== 'string' || !countLabel.trim()))
|
|
8
|
+
throw new RangeError('NavCount countLabel must be non-empty text');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A count after a Nav destination's words, placed inside its link or
|
|
12
|
+
* Button: `<a href="/tasks">Tasks<NavCount count={8} /></a>`. It is the
|
|
13
|
+
* pill SectionNav's item count draws, and it is part of the destination's
|
|
14
|
+
* accessible name, as there.
|
|
15
|
+
*/
|
|
16
|
+
export function NavCount({ count, countLabel }) {
|
|
17
|
+
assertNavCount({ count, countLabel });
|
|
18
|
+
if (count === 0)
|
|
19
|
+
return null;
|
|
20
|
+
// The space is its own text node, so every browser and the name's
|
|
21
|
+
// computation keep it between the words and the count.
|
|
22
|
+
return (_jsxs(_Fragment, { children: [' ', countLabel ? _jsx("span", { className: "sw-sr-only", children: countLabel }) : null, _jsx("span", { "aria-hidden": countLabel ? true : undefined, className: "sw-nav-count", children: count })] }));
|
|
23
|
+
}
|
|
@@ -4,15 +4,8 @@ import { breakpoints } from '../css/breakpoints.js';
|
|
|
4
4
|
import { sectionNavVerticalClass } from '../css/css-section-nav.js';
|
|
5
5
|
import { assertSectionNavAppearance, } from '../css/css-section-nav-filled.js';
|
|
6
6
|
import { tabStop } from '../tab-stop.js';
|
|
7
|
+
import { plainPress } from './link-press.js';
|
|
7
8
|
export { sectionNavAppearances } from '../css/css-section-nav-filled.js';
|
|
8
|
-
function plainPress(event) {
|
|
9
|
-
return (event.button === 0 &&
|
|
10
|
-
!event.defaultPrevented &&
|
|
11
|
-
!event.metaKey &&
|
|
12
|
-
!event.ctrlKey &&
|
|
13
|
-
!event.shiftKey &&
|
|
14
|
-
!event.altKey);
|
|
15
|
-
}
|
|
16
9
|
function ariaCurrent(current) {
|
|
17
10
|
if (current === true)
|
|
18
11
|
return 'true';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { type AccessibleName } from '../accessible-name.js';
|
|
3
|
+
import { type Breakpoint } from '../css/breakpoints.js';
|
|
4
|
+
import { type SegmentedSize, segmentedSizes } from '../css/css-segmented-sizing.js';
|
|
5
|
+
export { segmentedSizes, type SegmentedSize };
|
|
3
6
|
export type SegmentedItem = {
|
|
4
7
|
id: string;
|
|
5
8
|
label: ReactNode;
|
|
@@ -46,6 +49,19 @@ export type SegmentedControlProps = AccessibleName & SegmentedChange & {
|
|
|
46
49
|
error?: string;
|
|
47
50
|
/** Sets `aria-required` on the group; the visible mark belongs to the element that labels it. */
|
|
48
51
|
required?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Below this breakpoint the control fills its column and its segments
|
|
54
|
+
* share the width equally, whatever the variant, such as a period
|
|
55
|
+
* switch across a phone. From it up it keeps its own width.
|
|
56
|
+
*/
|
|
57
|
+
fullWidthBelow?: Breakpoint;
|
|
58
|
+
/**
|
|
59
|
+
* `sm` pads every segment a step less and keeps each label on one line
|
|
60
|
+
* (ending in an ellipsis past its share; its name keeps the whole
|
|
61
|
+
* label), so three or four segments with counts fit a phone's card.
|
|
62
|
+
* Keep the labels short enough to fit: the hidden part cannot be read.
|
|
63
|
+
* `md` (the default) is the variant's own padding.
|
|
64
|
+
*/
|
|
65
|
+
size?: SegmentedSize;
|
|
49
66
|
};
|
|
50
67
|
export declare const SegmentedControl: import("react").ForwardRefExoticComponent<SegmentedControlProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
51
|
-
export {};
|
|
@@ -2,11 +2,19 @@
|
|
|
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 { breakpoints } from '../css/breakpoints.js';
|
|
5
6
|
import { segmentedRaisedClass } from '../css/css-segmented-raised.js';
|
|
7
|
+
import { segmentedFullWidthClass, segmentedSizes, segmentedSmClass, } from '../css/css-segmented-sizing.js';
|
|
6
8
|
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
7
9
|
import { tabStop } from '../tab-stop.js';
|
|
8
|
-
export
|
|
10
|
+
export { segmentedSizes };
|
|
11
|
+
export const SegmentedControl = forwardRef(function SegmentedControl({ disabled = false, error, fullWidthBelow, items, onChange, required = false, size = 'md', value, variant = 'compact', ...labelProps }, ref) {
|
|
9
12
|
const errorId = useId();
|
|
13
|
+
if (fullWidthBelow !== undefined &&
|
|
14
|
+
!breakpoints.includes(fullWidthBelow))
|
|
15
|
+
throw new RangeError(`Unknown SegmentedControl fullWidthBelow: ${String(fullWidthBelow)}`);
|
|
16
|
+
if (!segmentedSizes.includes(size))
|
|
17
|
+
throw new RangeError(`Unknown SegmentedControl size: ${String(size)}`);
|
|
10
18
|
const withIcons = items.some((item) => item.icon);
|
|
11
19
|
function onKeyDown(event) {
|
|
12
20
|
if (disabled || (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft')) {
|
|
@@ -25,7 +33,7 @@ export const SegmentedControl = forwardRef(function SegmentedControl({ disabled
|
|
|
25
33
|
}
|
|
26
34
|
// The wrapper is always rendered, so an error that appears later swaps
|
|
27
35
|
// text into a live region that already exists and the group keeps focus.
|
|
28
|
-
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) => {
|
|
36
|
+
return (_jsxs("div", { className: cx('sw-segmented-field', variant === 'filled' && 'sw-segmented-field-filled', fullWidthBelow && segmentedFullWidthClass(fullWidthBelow)), 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', size === 'sm' && segmentedSmClass), onKeyDown: onKeyDown, role: "radiogroup", ...labelProps, children: items.map((item) => {
|
|
29
37
|
const selected = item.id === value;
|
|
30
38
|
return (_jsxs("button", { "aria-checked": selected, className: cx('sw-segmented-item', selected && 'sw-segmented-item-selected'), disabled: disabled, onClick: () => {
|
|
31
39
|
if (!selected) {
|
|
@@ -34,6 +42,6 @@ export const SegmentedControl = forwardRef(function SegmentedControl({ disabled
|
|
|
34
42
|
}, role: "radio",
|
|
35
43
|
// Every item stays a tab stop, as each already was outside
|
|
36
44
|
// Safari: the arrow keys change the value, not the focus.
|
|
37
|
-
tabIndex: tabStop, type: "button", children: [item.icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-segmented-item-icon", children: item.icon })) : null, item.label] }, item.id));
|
|
45
|
+
tabIndex: tabStop, type: "button", children: [item.icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-segmented-item-icon", children: item.icon })) : null, size === 'sm' ? (_jsx("span", { className: "sw-segmented-item-label", children: item.label })) : (item.label)] }, item.id));
|
|
38
46
|
}) }), _jsx(FieldErrorRegion, { className: "sw-field-error", id: errorId, message: error })] }));
|
|
39
47
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
-
import { type StatTileTone, statTileTones } from '../css/css-stat-tile.js';
|
|
3
|
-
export type { StatTileTone };
|
|
4
|
-
export { statTileTones };
|
|
2
|
+
import { type StatTileAppearance, statTileAppearances, type StatTileTone, statTileTones } from '../css/css-stat-tile.js';
|
|
3
|
+
export type { StatTileAppearance, StatTileTone };
|
|
4
|
+
export { statTileAppearances, statTileTones };
|
|
5
5
|
export type StatTileEmphasis = 'default' | 'primary';
|
|
6
6
|
export type StatTileProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
7
7
|
/** A short name for the figure, set in the label variant. */
|
|
@@ -16,10 +16,17 @@ export type StatTileProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
|
16
16
|
caption?: ReactNode;
|
|
17
17
|
/** `primary` fills the tile in the accent for the one figure a page leads with. */
|
|
18
18
|
emphasis?: StatTileEmphasis;
|
|
19
|
+
/**
|
|
20
|
+
* `plain` drops the tile's surface, edge and padding, so the figure sits
|
|
21
|
+
* inside a card that already has them, such as four answers in a row
|
|
22
|
+
* with hairlines between them. A plain tile cannot be `primary`, which
|
|
23
|
+
* is a fill. Default `card`.
|
|
24
|
+
*/
|
|
25
|
+
appearance?: StatTileAppearance;
|
|
19
26
|
};
|
|
20
27
|
/**
|
|
21
28
|
* One prominent figure with its label: the tiles a report or a list opens
|
|
22
29
|
* with. Presentation only; the consumer formats the number and picks the
|
|
23
30
|
* glyph.
|
|
24
31
|
*/
|
|
25
|
-
export declare function StatTile({ caption, className, emphasis, glyph, label, tone, value, ...rest }: StatTileProps): import("react").JSX.Element;
|
|
32
|
+
export declare function StatTile({ appearance, caption, className, emphasis, glyph, label, tone, value, ...rest }: StatTileProps): import("react").JSX.Element;
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../class-names.js';
|
|
3
|
-
import { statTileTones } from '../css/css-stat-tile.js';
|
|
3
|
+
import { statTileAppearances, statTileTones, } from '../css/css-stat-tile.js';
|
|
4
4
|
import { Text } from './Text.js';
|
|
5
|
-
export { statTileTones };
|
|
5
|
+
export { statTileAppearances, statTileTones };
|
|
6
|
+
function assertStatTile(appearance, emphasis) {
|
|
7
|
+
if (!statTileAppearances.includes(appearance))
|
|
8
|
+
throw new RangeError(`Unknown StatTile appearance: ${String(appearance)}`);
|
|
9
|
+
if (appearance === 'plain' && emphasis === 'primary')
|
|
10
|
+
throw new RangeError('A plain StatTile has no surface to fill; emphasis primary needs appearance card');
|
|
11
|
+
}
|
|
6
12
|
/**
|
|
7
13
|
* One prominent figure with its label: the tiles a report or a list opens
|
|
8
14
|
* with. Presentation only; the consumer formats the number and picks the
|
|
9
15
|
* glyph.
|
|
10
16
|
*/
|
|
11
|
-
export function StatTile({ caption, className, emphasis = 'default', glyph, label, tone = 'default', value, ...rest }) {
|
|
17
|
+
export function StatTile({ appearance = 'card', caption, className, emphasis = 'default', glyph, label, tone = 'default', value, ...rest }) {
|
|
18
|
+
assertStatTile(appearance, emphasis);
|
|
12
19
|
const primary = emphasis === 'primary';
|
|
13
|
-
return (_jsxs("section", { className: cx('sw-stat-tile', primary && 'sw-stat-tile-primary', className), ...rest, children: [glyph ? (_jsx("span", { "aria-hidden": "true", className: "sw-stat-tile-glyph", children: glyph })) : null, _jsxs("span", { className: "sw-stat-tile-body", children: [_jsx(Text, { as: "span", className: "sw-stat-tile-label", color: primary ? 'onAccent' : 'muted', variant: "label", children: label }), _jsx(Text, { as: "strong", className: "sw-stat-tile-value", color: primary ? 'onAccent' : tone === 'default' ? 'text' : tone, variant: "heading", children: value }), caption ? (_jsx(Text, { as: "span", className: "sw-stat-tile-caption", color: primary ? 'onAccent' : 'muted', variant: "caption", children: caption })) : null] })] }));
|
|
20
|
+
return (_jsxs("section", { className: cx('sw-stat-tile', appearance === 'plain' && 'sw-stat-tile-plain', primary && 'sw-stat-tile-primary', className), ...rest, children: [glyph ? (_jsx("span", { "aria-hidden": "true", className: "sw-stat-tile-glyph", children: glyph })) : null, _jsxs("span", { className: "sw-stat-tile-body", children: [_jsx(Text, { as: "span", className: "sw-stat-tile-label", color: primary ? 'onAccent' : 'muted', variant: "label", children: label }), _jsx(Text, { as: "strong", className: "sw-stat-tile-value", color: primary ? 'onAccent' : tone === 'default' ? 'text' : tone, variant: "heading", children: value }), caption ? (_jsx(Text, { as: "span", className: "sw-stat-tile-caption", color: primary ? 'onAccent' : 'muted', variant: "caption", children: caption })) : null] })] }));
|
|
14
21
|
}
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
+
import { type SwitchCheckedTone, switchCheckedTones, type SwitchLayout, switchLayouts } from '../css/css-switch.js';
|
|
2
|
+
export { switchCheckedTones, switchLayouts, type SwitchCheckedTone, type SwitchLayout, };
|
|
1
3
|
export type SwitchProps = {
|
|
2
4
|
label: string;
|
|
3
5
|
checked: boolean;
|
|
4
6
|
onCheckedChange: (checked: boolean) => void;
|
|
5
7
|
disabled?: boolean;
|
|
6
8
|
description?: string;
|
|
9
|
+
/**
|
|
10
|
+
* `leading` (the default) puts the control before its label;
|
|
11
|
+
* `trailing` puts the label and description first and the control at
|
|
12
|
+
* the row's end, the row as wide as its column, as a settings list
|
|
13
|
+
* draws it.
|
|
14
|
+
*/
|
|
15
|
+
layout?: SwitchLayout;
|
|
16
|
+
/**
|
|
17
|
+
* The track's colour while on: `accent` (the default) or `success`, for
|
|
18
|
+
* a switch whose on state is the healthy one, such as a feature turned
|
|
19
|
+
* on. The thumb's place says the state either way.
|
|
20
|
+
*/
|
|
21
|
+
checkedTone?: SwitchCheckedTone;
|
|
7
22
|
};
|
|
8
|
-
export declare function Switch({ label, checked, onCheckedChange, disabled, description, }: SwitchProps): import("react").JSX.Element;
|
|
23
|
+
export declare function Switch({ label, checked, onCheckedChange, disabled, description, layout, checkedTone, }: SwitchProps): import("react").JSX.Element;
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
|
+
import { cx } from '../class-names.js';
|
|
5
|
+
import { switchCheckedTones, switchLayouts, } from '../css/css-switch.js';
|
|
4
6
|
import { tabStop } from '../tab-stop.js';
|
|
5
|
-
export
|
|
7
|
+
export { switchCheckedTones, switchLayouts, };
|
|
8
|
+
function assertOneOf(values, value, prop) {
|
|
9
|
+
if (!values.includes(value))
|
|
10
|
+
throw new RangeError(`Unknown Switch ${prop}: ${String(value)}`);
|
|
11
|
+
}
|
|
12
|
+
export function Switch({ label, checked, onCheckedChange, disabled = false, description, layout = 'leading', checkedTone = 'accent', }) {
|
|
13
|
+
assertOneOf(switchLayouts, layout, 'layout');
|
|
14
|
+
assertOneOf(switchCheckedTones, checkedTone, 'checkedTone');
|
|
6
15
|
const labelId = useId();
|
|
7
16
|
const descriptionId = useId();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
const control = (_jsxs("span", { className: "sw-switch-control", children: [_jsx("input", { "aria-checked": checked, "aria-describedby": description ? descriptionId : undefined, "aria-labelledby": labelId, checked: checked, className: "sw-switch-input", disabled: disabled, onChange: (event) => {
|
|
18
|
+
if (!disabled)
|
|
19
|
+
onCheckedChange(event.currentTarget.checked);
|
|
20
|
+
}, role: "switch", tabIndex: tabStop, type: "checkbox" }), _jsx("span", { "aria-hidden": "true", className: "sw-switch-track", children: _jsx("span", { className: "sw-switch-thumb" }) })] }));
|
|
21
|
+
const copy = (_jsxs("span", { className: "sw-switch-copy", children: [_jsx("span", { className: "sw-switch-label", id: labelId, children: label }), description ? (_jsx("span", { className: "sw-switch-description", id: descriptionId, children: description })) : null] }));
|
|
22
|
+
return (_jsx("label", { className: cx('sw-switch', layout === 'trailing' && 'sw-switch-trailing', checkedTone === 'success' && 'sw-switch-success'), children: layout === 'trailing' ? (_jsxs(_Fragment, { children: [copy, control] })) : (_jsxs(_Fragment, { children: [control, copy] })) }));
|
|
12
23
|
}
|