@scalewing/react 1.36.0 → 1.38.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 +15 -0
- package/README.md +41 -3
- package/dist/accessible-name.d.ts +13 -0
- package/dist/accessible-name.js +1 -0
- package/dist/components/DataBar.d.ts +48 -0
- package/dist/components/DataBar.js +28 -0
- package/dist/components/DenominationGrid.d.ts +1 -1
- package/dist/components/DenominationGrid.js +2 -0
- package/dist/components/Pagination.d.ts +34 -0
- package/dist/components/Pagination.js +27 -0
- package/dist/components/ScrollRegion.d.ts +9 -5
- package/dist/components/ScrollRegion.js +8 -6
- package/dist/components/SectionNav.d.ts +2 -8
- package/dist/components/SegmentedControl.d.ts +2 -8
- package/dist/components/Stepper.d.ts +5 -0
- package/dist/components/Stepper.js +4 -2
- package/dist/components/Table.d.ts +48 -4
- package/dist/components/Table.js +9 -3
- package/dist/components/Tabs.d.ts +2 -9
- package/dist/components/denomination-grid/DenominationStrip.js +3 -1
- 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.d.ts +8 -2
- package/dist/components/denomination-grid/StripRow.js +14 -5
- package/dist/components/denomination-grid/StripTotal.d.ts +11 -0
- package/dist/components/denomination-grid/StripTotal.js +12 -0
- package/dist/components/denomination-grid/StripValueRow.d.ts +25 -0
- package/dist/components/denomination-grid/StripValueRow.js +27 -0
- package/dist/components/denomination-grid/types.d.ts +19 -0
- package/dist/components/denomination-grid/validate-value-row.d.ts +6 -0
- package/dist/components/denomination-grid/validate-value-row.js +30 -0
- package/dist/components/table/TableRow.d.ts +8 -2
- package/dist/components/table/TableRow.js +1 -1
- package/dist/components/table/table-max-height.d.ts +20 -0
- package/dist/components/table/table-max-height.js +23 -0
- package/dist/components/use-scroll-overflow.d.ts +10 -8
- package/dist/components/use-scroll-overflow.js +23 -10
- package/dist/css/chevron.d.ts +6 -3
- package/dist/css/chevron.js +9 -4
- package/dist/css/css-components.js +6 -0
- package/dist/css/css-data-bar.d.ts +16 -0
- package/dist/css/css-data-bar.js +107 -0
- package/dist/css/css-denomination-grid.js +4 -0
- package/dist/css/css-denomination-value-row.d.ts +3 -0
- package/dist/css/css-denomination-value-row.js +54 -0
- package/dist/css/css-pagination.d.ts +2 -0
- package/dist/css/css-pagination.js +63 -0
- package/dist/css/css-scroll-region.d.ts +1 -0
- package/dist/css/css-scroll-region.js +10 -5
- package/dist/css/css-table-bounded-edges.d.ts +1 -0
- package/dist/css/css-table-bounded-edges.js +132 -0
- package/dist/css/css-table-bounded.d.ts +2 -0
- package/dist/css/css-table-bounded.js +73 -0
- package/dist/css/css-table-ledger.d.ts +5 -2
- package/dist/css/css-table-ledger.js +5 -2
- package/dist/css/css-table-metrics.d.ts +14 -0
- package/dist/css/css-table-metrics.js +13 -0
- package/dist/css/css-table-sort.js +2 -1
- package/dist/css/css-table-total-row.d.ts +2 -0
- package/dist/css/css-table-total-row.js +29 -0
- package/dist/css/css-table.js +14 -3
- package/dist/css/stylesheet.js +4 -0
- package/dist/data-bar-geometry.d.ts +24 -0
- package/dist/data-bar-geometry.js +41 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/pagination-pages.d.ts +13 -0
- package/dist/pagination-pages.js +22 -0
- package/dist/scroll-overflow.d.ts +12 -0
- package/dist/scroll-overflow.js +31 -7
- package/dist/styles.css +258 -0
- package/package.json +1 -1
package/dist/components/Table.js
CHANGED
|
@@ -3,15 +3,21 @@ import { forwardRef } from 'react';
|
|
|
3
3
|
import { cx } from '../class-names.js';
|
|
4
4
|
import { ScrollRegion } from './ScrollRegion.js';
|
|
5
5
|
import { assertTableLayout, assertTableStickyHeader, assertTableVerticalAlign, } from './table/table-layout.js';
|
|
6
|
+
import { assertTableMaxHeight, tableMaxHeightStyle, } from './table/table-max-height.js';
|
|
6
7
|
export { TableCell, } from './table/TableCell.js';
|
|
7
8
|
export { TableRow, } from './table/TableRow.js';
|
|
8
9
|
export { tableCellTones, tableRowVariants } from '../css/css-table-ledger.js';
|
|
9
|
-
/**
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* A wide table scrolls inside its own `ScrollRegion`, named after the
|
|
12
|
+
* table; a bounded one (`maxHeight`) scrolls its rows in it too.
|
|
13
|
+
*/
|
|
14
|
+
export const Table = forwardRef(function Table({ children, className, columnRules = false, density = 'comfortable', layout = 'auto', maxHeight, stickyHeader = true, verticalAlign = 'middle', ...rest }, ref) {
|
|
11
15
|
assertTableLayout(layout);
|
|
12
16
|
assertTableStickyHeader(stickyHeader);
|
|
13
17
|
assertTableVerticalAlign(verticalAlign);
|
|
14
|
-
|
|
18
|
+
assertTableMaxHeight(maxHeight, stickyHeader);
|
|
19
|
+
const bounded = maxHeight !== undefined;
|
|
20
|
+
return (_jsx(ScrollRegion, { "aria-label": rest['aria-label'], "aria-labelledby": rest['aria-labelledby'], className: cx('sw-table-wrap', bounded && 'sw-table-bounded'), markFits: !bounded, style: tableMaxHeightStyle(maxHeight), children: _jsx("table", { ref: ref, className: cx('sw-table', density === 'compact' && 'sw-table-compact', layout === 'fixed' && 'sw-table-fixed', stickyHeader !== false && 'sw-table-sticky', stickyHeader === 'page' && 'sw-table-sticky-page', verticalAlign === 'top' && 'sw-table-top', columnRules && 'sw-table-column-rules', className), ...rest, children: children }) }));
|
|
15
21
|
});
|
|
16
22
|
export const TableHeader = forwardRef(function TableHeader({ className, ...rest }, ref) {
|
|
17
23
|
return _jsx("thead", { ref: ref, className: className, ...rest });
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import { type AccessibleName } from '../accessible-name.js';
|
|
2
3
|
export type TabItem = {
|
|
3
4
|
id: string;
|
|
4
5
|
label: ReactNode;
|
|
5
6
|
};
|
|
6
|
-
type
|
|
7
|
-
'aria-label': string;
|
|
8
|
-
'aria-labelledby'?: never;
|
|
9
|
-
} | {
|
|
10
|
-
'aria-label'?: never;
|
|
11
|
-
'aria-labelledby': string;
|
|
12
|
-
};
|
|
13
|
-
export type TabsProps = TabsLabel & {
|
|
7
|
+
export type TabsProps = AccessibleName & {
|
|
14
8
|
/** Prefix for the tab and panel ids, so a `TabPanel` can point back at its tab. */
|
|
15
9
|
id: string;
|
|
16
10
|
items: readonly TabItem[];
|
|
@@ -58,4 +52,3 @@ export type TabPanelProps = Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
|
|
|
58
52
|
};
|
|
59
53
|
/** The content of one tab, labelled by its tab and hidden while another tab is current. */
|
|
60
54
|
export declare function TabPanel({ children, className, id, tabsId, value, ...rest }: TabPanelProps): import("react").JSX.Element;
|
|
61
|
-
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from 'react';
|
|
2
3
|
import { cx } from '../../class-names.js';
|
|
3
4
|
import { denominationLabelWidthClass } from '../../css/css-denomination-aligned.js';
|
|
4
5
|
import { denominationFramedClass, denominationScrollFramedClass, } from '../../css/css-denomination-appearance.js';
|
|
@@ -18,6 +19,7 @@ function TotalHead({ totalLabel }) {
|
|
|
18
19
|
return (_jsx("th", { className: "sw-denomination-head sw-denomination-total-head", scope: "col", children: _jsx("span", { className: "sw-denomination-total-label", children: totalLabel }) }));
|
|
19
20
|
}
|
|
20
21
|
export function DenominationStrip({ className, framed, label, columns, rows, zeroLabel, totalLabel, labelWidth, }) {
|
|
22
|
+
const idPrefix = useId();
|
|
21
23
|
const hasTotals = rows.some((row) => row.total !== undefined);
|
|
22
24
|
// An aligned strip sizes its columns from the region's width; it needs
|
|
23
25
|
// the column count and whether a total column takes a share.
|
|
@@ -27,5 +29,5 @@ export function DenominationStrip({ className, framed, label, columns, rows, zer
|
|
|
27
29
|
'--sw-denomination-columns': columns.length,
|
|
28
30
|
'--sw-denomination-total': hasTotals ? 1 : 0,
|
|
29
31
|
};
|
|
30
|
-
return (_jsx(ScrollRegion, { "aria-label": label, className: cx('sw-denomination-scroll', aligned && 'sw-denomination-scroll-aligned', framed && denominationScrollFramedClass), children: _jsxs("table", { className: cx('sw-denomination-grid sw-denomination-strip', labelWidth && 'sw-denomination-strip-aligned', rows.some((row) => row.entry) && 'sw-denomination-strip-entry', labelWidth && denominationLabelWidthClass(labelWidth), framed && denominationFramedClass, className), style: aligned, children: [_jsx("caption", { className: "sw-sr-only", children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("td", { className: "sw-denomination-corner" }), columns.map((column) => (_jsx("th", { className: "sw-denomination-head", scope: "col", children: _jsx(ColumnHeadText, { column: column }) }, column.key))), hasTotals ? _jsx(TotalHead, { totalLabel: totalLabel }) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsx(StripRow, { columns: columns, hasTotals: hasTotals, row: row, zeroLabel: zeroLabel }, row.id))) })] }) }));
|
|
32
|
+
return (_jsx(ScrollRegion, { "aria-label": label, className: cx('sw-denomination-scroll', aligned && 'sw-denomination-scroll-aligned', framed && denominationScrollFramedClass), children: _jsxs("table", { className: cx('sw-denomination-grid sw-denomination-strip', labelWidth && 'sw-denomination-strip-aligned', rows.some((row) => row.entry) && 'sw-denomination-strip-entry', labelWidth && denominationLabelWidthClass(labelWidth), framed && denominationFramedClass, className), style: aligned, children: [_jsx("caption", { className: "sw-sr-only", children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("td", { className: "sw-denomination-corner" }), columns.map((column) => (_jsx("th", { className: "sw-denomination-head", scope: "col", children: _jsx(ColumnHeadText, { column: column }) }, column.key))), hasTotals ? _jsx(TotalHead, { totalLabel: totalLabel }) : null] }) }), _jsx("tbody", { children: rows.map((row, rowIndex) => (_jsx(StripRow, { columns: columns, hasTotals: hasTotals, idPrefix: idPrefix, row: row, rowIndex: rowIndex, zeroLabel: zeroLabel }, row.id))) })] }) }));
|
|
31
33
|
}
|
|
@@ -4,6 +4,8 @@ type EntryCellProps = {
|
|
|
4
4
|
entry: DenominationGridEntry;
|
|
5
5
|
column: DenominationGridColumn;
|
|
6
6
|
index: number;
|
|
7
|
+
/** The value cell under this count, which describes it, if any. */
|
|
8
|
+
describedBy?: string;
|
|
7
9
|
};
|
|
8
10
|
/**
|
|
9
11
|
* One editable count of a strip row: a full-width `Stepper` named by the
|
|
@@ -11,5 +13,5 @@ type EntryCellProps = {
|
|
|
11
13
|
* message from the entry, and the cell's tone on its border (an accent one
|
|
12
14
|
* is the Stepper's changed look).
|
|
13
15
|
*/
|
|
14
|
-
export declare function EntryCell({ row, entry, column, index }: EntryCellProps): import("react").JSX.Element;
|
|
16
|
+
export declare function EntryCell({ row, entry, column, index, describedBy, }: EntryCellProps): import("react").JSX.Element;
|
|
15
17
|
export {};
|
|
@@ -9,8 +9,8 @@ import { columnName } from './ColumnHeadText.js';
|
|
|
9
9
|
* message from the entry, and the cell's tone on its border (an accent one
|
|
10
10
|
* is the Stepper's changed look).
|
|
11
11
|
*/
|
|
12
|
-
export function EntryCell({ row, entry, column, index }) {
|
|
12
|
+
export function EntryCell({ row, entry, column, index, describedBy, }) {
|
|
13
13
|
const max = entry.max?.[index] ?? undefined;
|
|
14
14
|
const tone = row.cellTones?.[index];
|
|
15
|
-
return (_jsx("td", { className: cx('sw-denomination-entry', ...denominationCellToneClassNames(tone)), children: _jsx(Stepper, { changed: tone === 'accent', decrementLabel: entry.decrementLabel, disabled: row.disabled, error: entry.errors?.[index] ?? undefined, incrementLabel: entry.incrementLabel, label: `${row.label} ${columnName(column)}`, labelVisuallyHidden: true, limitAppearance: entry.limitAppearance, max: max, onChange: (value) => entry.onChange(column.key, value), placeholder: entry.placeholder, value: row.cells[index] ?? null, width: "full" }) }));
|
|
15
|
+
return (_jsx("td", { className: cx('sw-denomination-entry', ...denominationCellToneClassNames(tone)), children: _jsx(Stepper, { "aria-describedby": describedBy, changed: tone === 'accent', decrementLabel: entry.decrementLabel, disabled: row.disabled, error: entry.errors?.[index] ?? undefined, incrementLabel: entry.incrementLabel, label: `${row.label} ${columnName(column)}`, labelVisuallyHidden: true, limitAppearance: entry.limitAppearance, max: max, onChange: (value) => entry.onChange(column.key, value), placeholder: entry.placeholder, value: row.cells[index] ?? null, width: "full" }) }));
|
|
16
16
|
}
|
|
@@ -4,10 +4,16 @@ type StripRowProps = {
|
|
|
4
4
|
columns: readonly DenominationGridColumn[];
|
|
5
5
|
zeroLabel: string;
|
|
6
6
|
hasTotals: boolean;
|
|
7
|
+
/** A per-grid prefix that keeps the value cells' ids unique on the page. */
|
|
8
|
+
idPrefix: string;
|
|
9
|
+
/** The row's place in the grid, for its value cells' ids. */
|
|
10
|
+
rowIndex: number;
|
|
7
11
|
};
|
|
8
12
|
/**
|
|
9
13
|
* One strip row: its pinned label (with the phone total under it), a count
|
|
10
|
-
* per column, read-only or entered, and its total.
|
|
14
|
+
* per column, read-only or entered, and its total. With a `valueRow`, the
|
|
15
|
+
* value line follows it and takes the total; each entry count is described
|
|
16
|
+
* by the value under it.
|
|
11
17
|
*/
|
|
12
|
-
export declare function StripRow({ row, columns, zeroLabel, hasTotals, }: StripRowProps): import("react").JSX.Element;
|
|
18
|
+
export declare function StripRow({ row, columns, zeroLabel, hasTotals, idPrefix, rowIndex, }: StripRowProps): import("react").JSX.Element;
|
|
13
19
|
export {};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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
5
|
import { EntryCell } from './EntryCell.js';
|
|
6
6
|
import { RowLabelLine } from './RowLabelLine.js';
|
|
7
|
+
import { StripTotalCell, StripTotalInline } from './StripTotal.js';
|
|
8
|
+
import { hasValue, StripValueRow, valueCellId } from './StripValueRow.js';
|
|
7
9
|
/**
|
|
8
10
|
* One count, read-only: the zero label, a signed delta, and the cell's tone.
|
|
9
11
|
* The value has its own box, which the `pill` tone appearance fills.
|
|
@@ -14,9 +16,16 @@ function CountCell({ row, index, zeroLabel, }) {
|
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* One strip row: its pinned label (with the phone total under it), a count
|
|
17
|
-
* per column, read-only or entered, and its total.
|
|
19
|
+
* per column, read-only or entered, and its total. With a `valueRow`, the
|
|
20
|
+
* value line follows it and takes the total; each entry count is described
|
|
21
|
+
* by the value under it.
|
|
18
22
|
*/
|
|
19
|
-
export function StripRow({ row, columns, zeroLabel, hasTotals, }) {
|
|
20
|
-
const { entry } = row;
|
|
21
|
-
|
|
23
|
+
export function StripRow({ row, columns, zeroLabel, hasTotals, idPrefix, rowIndex, }) {
|
|
24
|
+
const { entry, valueRow } = row;
|
|
25
|
+
// With a value line, the total is on it, not on the counts.
|
|
26
|
+
const total = valueRow ? undefined : row.total;
|
|
27
|
+
const describedBy = (index) => hasValue(valueRow?.cells[index])
|
|
28
|
+
? valueCellId(idPrefix, rowIndex, index)
|
|
29
|
+
: undefined;
|
|
30
|
+
return (_jsxs(_Fragment, { children: [_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 }), _jsx(StripTotalInline, { total: total })] }) }), columns.map((column, index) => entry ? (_jsx(EntryCell, { column: column, describedBy: describedBy(index), entry: entry, index: index, row: row }, column.key)) : (_jsx(CountCell, { index: index, row: row, zeroLabel: zeroLabel }, column.key))), hasTotals ? _jsx(StripTotalCell, { total: total }) : null] }), valueRow ? (_jsx(StripValueRow, { columns: columns, hasTotals: hasTotals, idPrefix: idPrefix, row: row, rowIndex: rowIndex, valueRow: valueRow })) : null] }));
|
|
22
31
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A strip row's total cell: the consumer's total, or empty. Below md it is
|
|
3
|
+
* visually hidden, and `StripTotalInline` shows it under the row's name.
|
|
4
|
+
*/
|
|
5
|
+
export declare function StripTotalCell({ total }: {
|
|
6
|
+
total: string | undefined;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
/** The total under a row's name on a phone; the cell reads it to everyone. */
|
|
9
|
+
export declare function StripTotalInline({ total }: {
|
|
10
|
+
total: string | undefined;
|
|
11
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A strip row's total cell: the consumer's total, or empty. Below md it is
|
|
4
|
+
* visually hidden, and `StripTotalInline` shows it under the row's name.
|
|
5
|
+
*/
|
|
6
|
+
export function StripTotalCell({ total }) {
|
|
7
|
+
return (_jsx("td", { className: "sw-denomination-total", children: _jsx("span", { className: "sw-denomination-total-value", children: total ?? '' }) }));
|
|
8
|
+
}
|
|
9
|
+
/** The total under a row's name on a phone; the cell reads it to everyone. */
|
|
10
|
+
export function StripTotalInline({ total }) {
|
|
11
|
+
return total !== undefined ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-total-inline", children: total })) : null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DenominationGridColumn, type DenominationGridRow, type DenominationGridValueRow } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The id of a value cell, which the count above it is described by: the
|
|
4
|
+
* grid's prefix and the cell's place, never consumer text, so it is always
|
|
5
|
+
* one valid token of an id list.
|
|
6
|
+
*/
|
|
7
|
+
export declare function valueCellId(idPrefix: string, rowIndex: number, columnIndex: number): string;
|
|
8
|
+
/** Whether a value cell shows something: not `null` and not blank. */
|
|
9
|
+
export declare function hasValue(cell: string | null | undefined): cell is string;
|
|
10
|
+
type StripValueRowProps = {
|
|
11
|
+
row: DenominationGridRow;
|
|
12
|
+
valueRow: DenominationGridValueRow;
|
|
13
|
+
columns: readonly DenominationGridColumn[];
|
|
14
|
+
hasTotals: boolean;
|
|
15
|
+
/** A per-grid prefix that keeps the value cells' ids unique on the page. */
|
|
16
|
+
idPrefix: string;
|
|
17
|
+
rowIndex: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The value line under a strip row: its name in the label column (with the
|
|
21
|
+
* row's total under it on a phone), a centred value per column, and the
|
|
22
|
+
* row's total. It keeps the row's tone, so a toned total stays toned.
|
|
23
|
+
*/
|
|
24
|
+
export declare function StripValueRow({ row, valueRow, columns, hasTotals, idPrefix, rowIndex, }: StripValueRowProps): import("react").JSX.Element;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../../class-names.js';
|
|
3
|
+
import { StripTotalCell, StripTotalInline } from './StripTotal.js';
|
|
4
|
+
import { denominationValueRowClass } from '../../css/css-denomination-value-row.js';
|
|
5
|
+
/**
|
|
6
|
+
* The id of a value cell, which the count above it is described by: the
|
|
7
|
+
* grid's prefix and the cell's place, never consumer text, so it is always
|
|
8
|
+
* one valid token of an id list.
|
|
9
|
+
*/
|
|
10
|
+
export function valueCellId(idPrefix, rowIndex, columnIndex) {
|
|
11
|
+
return `${idPrefix}value-${rowIndex}-${columnIndex}`;
|
|
12
|
+
}
|
|
13
|
+
/** Whether a value cell shows something: not `null` and not blank. */
|
|
14
|
+
export function hasValue(cell) {
|
|
15
|
+
return typeof cell === 'string' && cell.trim() !== '';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The value line under a strip row: its name in the label column (with the
|
|
19
|
+
* row's total under it on a phone), a centred value per column, and the
|
|
20
|
+
* row's total. It keeps the row's tone, so a toned total stays toned.
|
|
21
|
+
*/
|
|
22
|
+
export function StripValueRow({ row, valueRow, columns, hasTotals, idPrefix, rowIndex, }) {
|
|
23
|
+
return (_jsxs("tr", { "aria-disabled": row.disabled || undefined, className: cx('sw-denomination-row', denominationValueRowClass, `sw-denomination-row-${row.tone ?? 'neutral'}`, row.disabled && 'sw-denomination-row-disabled'), children: [_jsx("th", { "aria-label": `${row.label} ${valueRow.label}`, className: "sw-denomination-label", scope: "row", children: _jsxs("span", { className: "sw-denomination-label-body", children: [_jsx("span", { className: "sw-denomination-label-line", children: _jsx("span", { className: "sw-denomination-label-text", children: valueRow.label }) }), _jsx(StripTotalInline, { total: row.total })] }) }), columns.map((column, index) => {
|
|
24
|
+
const cell = valueRow.cells[index];
|
|
25
|
+
return (_jsx("td", { className: "sw-denomination-value", id: valueCellId(idPrefix, rowIndex, index), children: hasValue(cell) ? cell : '' }, column.key));
|
|
26
|
+
}), hasTotals ? _jsx(StripTotalCell, { total: row.total }) : null] }));
|
|
27
|
+
}
|
|
@@ -45,6 +45,20 @@ export type DenominationGridEntry = {
|
|
|
45
45
|
*/
|
|
46
46
|
limitAppearance?: StepperLimitAppearance;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* A line under a strip row that gives each column's value, such as the
|
|
50
|
+
* money its count adds up to: its own table row on the subtle fill, named
|
|
51
|
+
* in the label column, one consumer-formatted value per column centred
|
|
52
|
+
* under its count (`null` or `''` leaves a cell blank, such as for a zero
|
|
53
|
+
* count), and the row's `total` in the total column. The grid does no
|
|
54
|
+
* arithmetic and no currency formatting.
|
|
55
|
+
*/
|
|
56
|
+
export type DenominationGridValueRow = {
|
|
57
|
+
/** The line's name in the label column, such as "Value". */
|
|
58
|
+
label: string;
|
|
59
|
+
/** One formatted value, or `null` for none, per column. */
|
|
60
|
+
cells: readonly (string | null)[];
|
|
61
|
+
};
|
|
48
62
|
export type DenominationGridRow = {
|
|
49
63
|
id: string;
|
|
50
64
|
label: string;
|
|
@@ -80,6 +94,11 @@ export type DenominationGridRow = {
|
|
|
80
94
|
* `entry.errors` wins.
|
|
81
95
|
*/
|
|
82
96
|
entry?: DenominationGridEntry;
|
|
97
|
+
/**
|
|
98
|
+
* A value line under this row (the `strip` layout only); the row's
|
|
99
|
+
* `total` moves to it, and each entry count is described by its value.
|
|
100
|
+
*/
|
|
101
|
+
valueRow?: DenominationGridValueRow;
|
|
83
102
|
};
|
|
84
103
|
export type DenominationGridProps = {
|
|
85
104
|
label: string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type DenominationGridColumn, type DenominationGridLayout, type DenominationGridRow } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Value rows: the strip layout only, a name that is not blank, and one
|
|
4
|
+
* formatted value or null per column.
|
|
5
|
+
*/
|
|
6
|
+
export declare function assertDenominationValueRows(layout: DenominationGridLayout, columns: readonly DenominationGridColumn[], rows: readonly DenominationGridRow[]): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value rows: the strip layout only, a name that is not blank, and one
|
|
3
|
+
* formatted value or null per column.
|
|
4
|
+
*/
|
|
5
|
+
export function assertDenominationValueRows(layout, columns, rows) {
|
|
6
|
+
const valueRows = rows.flatMap((row) => row.valueRow == null ? [] : [row.valueRow]);
|
|
7
|
+
if (valueRows.length === 0)
|
|
8
|
+
return;
|
|
9
|
+
if (layout !== 'strip')
|
|
10
|
+
throw new RangeError('value rows need the strip layout');
|
|
11
|
+
for (const valueRow of valueRows) {
|
|
12
|
+
if (typeof valueRow !== 'object')
|
|
13
|
+
throw new RangeError('a value row is an object of label and cells');
|
|
14
|
+
if (typeof valueRow.label !== 'string' || !valueRow.label.trim())
|
|
15
|
+
throw new RangeError('a value row needs a label');
|
|
16
|
+
if (!fitsColumns(valueRow.cells, columns.length))
|
|
17
|
+
throw new RangeError('a value row needs one string or null per column');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function fitsColumns(cells, columnCount) {
|
|
21
|
+
if (!Array.isArray(cells) || cells.length !== columnCount)
|
|
22
|
+
return false;
|
|
23
|
+
// An index loop, not every(): every() skips the holes of a sparse array.
|
|
24
|
+
for (let index = 0; index < columnCount; index += 1) {
|
|
25
|
+
const cell = cells[index];
|
|
26
|
+
if (cell !== null && typeof cell !== 'string')
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
@@ -7,7 +7,10 @@ export type TableRowProps = HTMLAttributes<HTMLTableRowElement> & {
|
|
|
7
7
|
* `sub` sets the row under the one above it, as an item of a batch: its
|
|
8
8
|
* row header (`TableCell as="th" scope="row"`) is indented with a guide
|
|
9
9
|
* bar at the indent; a sub row without a row header is not indented.
|
|
10
|
-
* `
|
|
10
|
+
* `total` is a total mid-table, such as a subtotal the rows above add up
|
|
11
|
+
* to: its cells at weight 600 under a 2 px rule, as a
|
|
12
|
+
* `TableFooter` total is; name it with a row header and keep it in the
|
|
13
|
+
* body. `default` is a row of its own.
|
|
11
14
|
*/
|
|
12
15
|
variant?: TableRowVariant;
|
|
13
16
|
/**
|
|
@@ -40,7 +43,10 @@ export declare const TableRow: import("react").ForwardRefExoticComponent<HTMLAtt
|
|
|
40
43
|
* `sub` sets the row under the one above it, as an item of a batch: its
|
|
41
44
|
* row header (`TableCell as="th" scope="row"`) is indented with a guide
|
|
42
45
|
* bar at the indent; a sub row without a row header is not indented.
|
|
43
|
-
* `
|
|
46
|
+
* `total` is a total mid-table, such as a subtotal the rows above add up
|
|
47
|
+
* to: its cells at weight 600 under a 2 px rule, as a
|
|
48
|
+
* `TableFooter` total is; name it with a row header and keep it in the
|
|
49
|
+
* body. `default` is a row of its own.
|
|
44
50
|
*/
|
|
45
51
|
variant?: TableRowVariant;
|
|
46
52
|
/**
|
|
@@ -25,5 +25,5 @@ export const TableRow = forwardRef(function TableRow({ className, onClick, press
|
|
|
25
25
|
forwarding.current = false;
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
-
return (_jsx("tr", { ref: ref, "aria-selected": selected || undefined, className: cx(selected && 'sw-table-row-selected', variant === 'sub' && 'sw-table-row-sub', struck && 'sw-table-row-struck', pressable && 'sw-table-row-press', className), onClick: pressable || onClick ? press : undefined, ...rest }));
|
|
28
|
+
return (_jsx("tr", { ref: ref, "aria-selected": selected || undefined, className: cx(selected && 'sw-table-row-selected', variant === 'sub' && 'sw-table-row-sub', variant === 'total' && 'sw-table-row-total', struck && 'sw-table-row-struck', pressable && 'sw-table-row-press', className), onClick: pressable || onClick ? press : undefined, ...rest }));
|
|
29
29
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { TableStickyHeader } from './table-layout.js';
|
|
3
|
+
/**
|
|
4
|
+
* A table's bounded height, counted in rows: the table's region is as tall
|
|
5
|
+
* as `rows` single-line body rows plus its header row and, when it has one,
|
|
6
|
+
* its footer row, and the rest of the rows scroll inside the region. Rows
|
|
7
|
+
* taller than one line show fewer at a time.
|
|
8
|
+
*/
|
|
9
|
+
export type TableMaxHeight = {
|
|
10
|
+
rows: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A bounded table pins its header to its own region (`stickyHeader` true,
|
|
14
|
+
* the default); `'page'` and `false` contradict that and throw, as do a
|
|
15
|
+
* value that is not `{ rows }` and a row count that is not a whole number
|
|
16
|
+
* from 1.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertTableMaxHeight(maxHeight: TableMaxHeight | undefined, stickyHeader: TableStickyHeader): void;
|
|
19
|
+
/** The row count the generated `sw-table-bounded` height reads. */
|
|
20
|
+
export declare function tableMaxHeightStyle(maxHeight: TableMaxHeight | undefined): CSSProperties | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bounded table pins its header to its own region (`stickyHeader` true,
|
|
3
|
+
* the default); `'page'` and `false` contradict that and throw, as do a
|
|
4
|
+
* value that is not `{ rows }` and a row count that is not a whole number
|
|
5
|
+
* from 1.
|
|
6
|
+
*/
|
|
7
|
+
export function assertTableMaxHeight(maxHeight, stickyHeader) {
|
|
8
|
+
if (maxHeight === undefined)
|
|
9
|
+
return;
|
|
10
|
+
if (typeof maxHeight !== 'object' || maxHeight === null)
|
|
11
|
+
throw new RangeError(`Table maxHeight must be a row count, { rows }: ${String(maxHeight)}`);
|
|
12
|
+
const rows = maxHeight.rows;
|
|
13
|
+
if (typeof rows !== 'number' || !Number.isInteger(rows) || rows < 1)
|
|
14
|
+
throw new RangeError(`Table maxHeight rows must be a whole number from 1: ${String(rows)}`);
|
|
15
|
+
if (stickyHeader !== true)
|
|
16
|
+
throw new RangeError(`Table maxHeight pins the header to the table's own region; stickyHeader ${String(stickyHeader)} cannot be used with it`);
|
|
17
|
+
}
|
|
18
|
+
/** The row count the generated `sw-table-bounded` height reads. */
|
|
19
|
+
export function tableMaxHeightStyle(maxHeight) {
|
|
20
|
+
if (maxHeight === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
return { '--sw-table-max-rows': maxHeight.rows };
|
|
23
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { type RefObject } from 'react';
|
|
2
|
-
import { type ScrollOverflow } from '../scroll-overflow.js';
|
|
3
|
-
/** A box's overflow, and whether it has been measured yet. */
|
|
4
|
-
export type MeasuredScrollOverflow = ScrollOverflow & {
|
|
2
|
+
import { type BlockScrollOverflow, type ScrollOverflow } from '../scroll-overflow.js';
|
|
3
|
+
/** A box's overflow on both axes, and whether it has been measured yet. */
|
|
4
|
+
export type MeasuredScrollOverflow = ScrollOverflow & BlockScrollOverflow & {
|
|
5
5
|
measured: boolean;
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
* Follows which
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `measured: false`, so the box
|
|
12
|
-
* measured; a measured box with
|
|
8
|
+
* Follows which edges of a scroll box have more content past them, the
|
|
9
|
+
* inline start and end and the top and bottom: on mount, on every scroll,
|
|
10
|
+
* and whenever the box or its content changes size. Before mount (and on
|
|
11
|
+
* the server) it reports no overflow and `measured: false`, so the box
|
|
12
|
+
* renders as it always has until it is measured; a measured box with
|
|
13
|
+
* neither inline edge set fits its content's width. A box whose height
|
|
14
|
+
* follows its content never reports a block edge.
|
|
13
15
|
*/
|
|
14
16
|
export declare function useScrollOverflow(ref: RefObject<HTMLElement | null>): MeasuredScrollOverflow;
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import { noScrollOverflow, sameScrollOverflow, scrollOverflow, } from '../scroll-overflow.js';
|
|
3
|
+
import { blockScrollOverflow, noBlockScrollOverflow, noScrollOverflow, sameBlockScrollOverflow, sameScrollOverflow, scrollOverflow, } from '../scroll-overflow.js';
|
|
4
4
|
const unmeasured = {
|
|
5
5
|
...noScrollOverflow,
|
|
6
|
+
...noBlockScrollOverflow,
|
|
6
7
|
measured: false,
|
|
7
8
|
};
|
|
9
|
+
function measureBox(box) {
|
|
10
|
+
return {
|
|
11
|
+
...scrollOverflow(box.scrollLeft, box.scrollWidth, box.clientWidth),
|
|
12
|
+
...blockScrollOverflow(box.scrollTop, box.scrollHeight, box.clientHeight),
|
|
13
|
+
measured: true,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function sameMeasure(current, next) {
|
|
17
|
+
return (current.measured &&
|
|
18
|
+
sameScrollOverflow(current, next) &&
|
|
19
|
+
sameBlockScrollOverflow(current, next));
|
|
20
|
+
}
|
|
8
21
|
/**
|
|
9
|
-
* Follows which
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `measured: false`, so the box
|
|
13
|
-
* measured; a measured box with
|
|
22
|
+
* Follows which edges of a scroll box have more content past them, the
|
|
23
|
+
* inline start and end and the top and bottom: on mount, on every scroll,
|
|
24
|
+
* and whenever the box or its content changes size. Before mount (and on
|
|
25
|
+
* the server) it reports no overflow and `measured: false`, so the box
|
|
26
|
+
* renders as it always has until it is measured; a measured box with
|
|
27
|
+
* neither inline edge set fits its content's width. A box whose height
|
|
28
|
+
* follows its content never reports a block edge.
|
|
14
29
|
*/
|
|
15
30
|
export function useScrollOverflow(ref) {
|
|
16
31
|
const [overflow, setOverflow] = useState(unmeasured);
|
|
@@ -25,10 +40,8 @@ export function useScrollOverflow(ref) {
|
|
|
25
40
|
function measure() {
|
|
26
41
|
if (!box)
|
|
27
42
|
return;
|
|
28
|
-
const next =
|
|
29
|
-
setOverflow((current) => current
|
|
30
|
-
? current
|
|
31
|
-
: { ...next, measured: true });
|
|
43
|
+
const next = measureBox(box);
|
|
44
|
+
setOverflow((current) => (sameMeasure(current, next) ? current : next));
|
|
32
45
|
}
|
|
33
46
|
measure();
|
|
34
47
|
box.addEventListener('scroll', measure, { passive: true });
|
package/dist/css/chevron.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* A stroked chevron in `color`: two sides of a `space-2` square (its
|
|
3
|
+
* bottom and right borders, so forced colours keep it), turned by each
|
|
4
|
+
* surface to point where it needs.
|
|
4
5
|
*/
|
|
5
|
-
export declare
|
|
6
|
+
export declare function chevronStrokeIn(color: string): string;
|
|
7
|
+
/** The muted chevron Accordion, Select and the menus share. */
|
|
8
|
+
export declare const chevronStroke: string;
|
package/dist/css/chevron.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* A stroked chevron in `color`: two sides of a `space-2` square (its
|
|
3
|
+
* bottom and right borders, so forced colours keep it), turned by each
|
|
4
|
+
* surface to point where it needs.
|
|
4
5
|
*/
|
|
5
|
-
export
|
|
6
|
-
|
|
6
|
+
export function chevronStrokeIn(color) {
|
|
7
|
+
return `border-bottom: 2px solid ${color};
|
|
8
|
+
border-right: 2px solid ${color};
|
|
7
9
|
box-sizing: border-box;
|
|
8
10
|
height: var(--sw-space-2);
|
|
9
11
|
width: var(--sw-space-2);`;
|
|
12
|
+
}
|
|
13
|
+
/** The muted chevron Accordion, Select and the menus share. */
|
|
14
|
+
export const chevronStroke = chevronStrokeIn('var(--sw-color-muted)');
|
|
@@ -4,6 +4,8 @@ import { cssAccordionClasses } from './css-accordion.js';
|
|
|
4
4
|
import { cssButtonClasses } from './css-button.js';
|
|
5
5
|
import { cssCalendarButtonClasses } from './css-calendar-button.js';
|
|
6
6
|
import { cssChartClasses } from './css-chart.js';
|
|
7
|
+
import { cssDataBarClasses } from './css-data-bar.js';
|
|
8
|
+
import { cssPaginationClasses } from './css-pagination.js';
|
|
7
9
|
import { cssCheckboxClasses } from './css-checkbox.js';
|
|
8
10
|
import { cssRadioGroupClasses } from './css-radio-group.js';
|
|
9
11
|
import { cssChromeClasses } from './css-chrome.js';
|
|
@@ -137,6 +139,10 @@ ${cssCalendarButtonClasses()}
|
|
|
137
139
|
|
|
138
140
|
${cssChartClasses()}
|
|
139
141
|
|
|
142
|
+
${cssDataBarClasses()}
|
|
143
|
+
|
|
144
|
+
${cssPaginationClasses()}
|
|
145
|
+
|
|
140
146
|
${cssChromeClasses()}
|
|
141
147
|
|
|
142
148
|
${cssSectionNavClasses()}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a range bar's fill means: the start or a carried figure (`neutral`),
|
|
3
|
+
* money in (`success`), money out (`muted`, never danger: an outflow is not
|
|
4
|
+
* an error), a subtotal (`strong`) and what was counted or recorded
|
|
5
|
+
* (`accent`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const dataBarTones: readonly ["neutral", "success", "muted", "strong", "accent"];
|
|
8
|
+
export type DataBarTone = (typeof dataBarTones)[number];
|
|
9
|
+
export declare const dataBarToneMixes: Record<DataBarTone, {
|
|
10
|
+
color: 'muted' | 'success' | 'text' | 'accent';
|
|
11
|
+
share: number;
|
|
12
|
+
}>;
|
|
13
|
+
/** A tone's fill: its colour, mixed into the surface by its share. */
|
|
14
|
+
export declare function dataBarToneFill(tone: DataBarTone): string;
|
|
15
|
+
export declare function cssDataBarClasses(): string;
|
|
16
|
+
export declare function dataBarClassCatalog(): string[];
|