@scalewing/react 1.44.0 → 1.46.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 +21 -0
  2. package/README.md +51 -2
  3. package/dist/components/Article.d.ts +19 -2
  4. package/dist/components/Article.js +9 -5
  5. package/dist/components/Button.d.ts +14 -1
  6. package/dist/components/Button.js +5 -2
  7. package/dist/components/FileField.d.ts +18 -2
  8. package/dist/components/FileField.js +26 -22
  9. package/dist/components/ListGroup.d.ts +8 -3
  10. package/dist/components/ListGroup.js +13 -5
  11. package/dist/components/ListRow.d.ts +11 -3
  12. package/dist/components/ListRow.js +6 -4
  13. package/dist/components/Select.d.ts +8 -5
  14. package/dist/components/Select.js +7 -4
  15. package/dist/components/file-field/ChosenFile.d.ts +45 -0
  16. package/dist/components/file-field/ChosenFile.js +48 -0
  17. package/dist/components/file-field/file-field-checks.d.ts +5 -2
  18. package/dist/components/file-field/file-field-checks.js +7 -2
  19. package/dist/components/list/ListRowContent.d.ts +5 -1
  20. package/dist/components/list/ListRowContent.js +4 -2
  21. package/dist/components/select/select-naming.d.ts +34 -0
  22. package/dist/components/select/select-naming.js +22 -0
  23. package/dist/components/steps/Step.d.ts +10 -1
  24. package/dist/components/steps/Step.js +15 -19
  25. package/dist/components/steps/StepTitleLine.d.ts +19 -0
  26. package/dist/components/steps/StepTitleLine.js +13 -0
  27. package/dist/components/steps/StepTitleWords.d.ts +27 -0
  28. package/dist/components/steps/StepTitleWords.js +21 -0
  29. package/dist/components/steps/assert-step.d.ts +7 -0
  30. package/dist/components/steps/assert-step.js +19 -0
  31. package/dist/css/css-button-width.d.ts +11 -0
  32. package/dist/css/css-button-width.js +36 -0
  33. package/dist/css/css-button.js +3 -0
  34. package/dist/css/css-file-field-compact.d.ts +2 -0
  35. package/dist/css/css-file-field-compact.js +106 -0
  36. package/dist/css/css-file-field.js +4 -0
  37. package/dist/css/css-list-tiles.d.ts +6 -0
  38. package/dist/css/css-list-tiles.js +50 -0
  39. package/dist/css/css-list.js +5 -1
  40. package/dist/css/css-steps-title-detail.d.ts +2 -0
  41. package/dist/css/css-steps-title-detail.js +36 -0
  42. package/dist/css/css-steps.d.ts +2 -2
  43. package/dist/css/css-steps.js +6 -2
  44. package/dist/index.d.ts +3 -1
  45. package/dist/index.js +2 -0
  46. package/dist/list-options.d.ts +19 -11
  47. package/dist/list-options.js +27 -11
  48. package/dist/styles.css +134 -0
  49. package/package.json +1 -1
@@ -0,0 +1,106 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { touchTarget } from './touch-target.js';
3
+ const body = typographyVariants.body;
4
+ /*
5
+ * Remove beside a chosen file in the full field: the change button and
6
+ * Remove together at the row's end, a space-2 apart.
7
+ */
8
+ function actionsRules() {
9
+ return `.sw-file-field-actions {
10
+ display: flex;
11
+ flex: none;
12
+ flex-wrap: wrap;
13
+ gap: var(--sw-space-2);
14
+ margin-inline-start: auto;
15
+ }`;
16
+ }
17
+ /*
18
+ * A compact field is a text field's height (the md control, 44 px), on
19
+ * one line. Empty, its zone holds the choose words at its start. Chosen,
20
+ * the row is the change button (the thumbnail or mark a space-1 from the
21
+ * frame's start, then the name in the body weight cut to one line) and a
22
+ * quiet Remove at its end. The button's focus ring sits inside the frame.
23
+ */
24
+ function compactRules() {
25
+ return `.sw-file-field-compact.sw-file-field-chosen {
26
+ flex-wrap: nowrap;
27
+ gap: var(--sw-space-1);
28
+ min-height: ${touchTarget};
29
+ padding-block: 0;
30
+ padding-inline: 0 var(--sw-space-1);
31
+ }
32
+
33
+ .sw-file-field-compact .sw-file-field-zone {
34
+ justify-content: flex-start;
35
+ padding: var(--sw-space-2) var(--sw-space-4);
36
+ text-align: start;
37
+ }
38
+
39
+ .sw-file-field-pick {
40
+ align-items: center;
41
+ appearance: none;
42
+ background: transparent;
43
+ border: 0;
44
+ border-radius: inherit;
45
+ box-sizing: border-box;
46
+ color: var(--sw-color-text);
47
+ cursor: pointer;
48
+ display: flex;
49
+ flex: 1 1 auto;
50
+ font-family: var(--sw-font-sans);
51
+ gap: var(--sw-space-3);
52
+ margin: 0;
53
+ min-height: ${touchTarget};
54
+ min-width: 0;
55
+ padding-block: var(--sw-space-1);
56
+ padding-inline: var(--sw-space-1) var(--sw-space-2);
57
+ text-align: start;
58
+ }
59
+
60
+ .sw-file-field-pick:focus { outline: none; }
61
+
62
+ .sw-file-field-pick:focus-visible {
63
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
64
+ outline-offset: calc(-1 * var(--sw-focus-ring-width));
65
+ }
66
+
67
+ .sw-file-field-pick:disabled { cursor: not-allowed; }
68
+
69
+ .sw-file-field-pick .sw-file-field-name {
70
+ font-weight: ${body.fontWeight};
71
+ min-width: 0;
72
+ overflow: hidden;
73
+ overflow-wrap: normal;
74
+ text-overflow: ellipsis;
75
+ white-space: nowrap;
76
+ }
77
+
78
+ /* With no thumbnail or mark, the name starts where a text field's words do. */
79
+ .sw-file-field-pick > .sw-file-field-name:first-child { margin-inline-start: var(--sw-space-3); }
80
+
81
+ .sw-file-field-compact .sw-file-field-preview,
82
+ .sw-file-field-compact .sw-file-field-icon { margin-inline-start: var(--sw-space-1); }
83
+
84
+ .sw-file-field-remove { flex: none; }
85
+
86
+ @media (forced-colors: active) {
87
+ .sw-file-field-pick { color: ButtonText; }
88
+ .sw-file-field-pick:disabled { color: GrayText; }
89
+ }
90
+
91
+ /* Disabled, the row fades as its Remove does. */
92
+ .sw-file-field-pick:disabled { opacity: var(--sw-disabled-opacity); }`;
93
+ }
94
+ export function cssFileFieldCompactClasses() {
95
+ return `${actionsRules()}
96
+
97
+ ${compactRules()}`;
98
+ }
99
+ export function fileFieldCompactClassCatalog() {
100
+ return [
101
+ 'sw-file-field-actions',
102
+ 'sw-file-field-compact',
103
+ 'sw-file-field-pick',
104
+ 'sw-file-field-remove',
105
+ ];
106
+ }
@@ -1,5 +1,6 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { disabledControlSurface } from './css-document.js';
3
+ import { cssFileFieldCompactClasses, fileFieldCompactClassCatalog, } from './css-file-field-compact.js';
3
4
  import { coarsePointerQuery, touchTarget } from './touch-target.js';
4
5
  const label = typographyVariants.label;
5
6
  const caption = typographyVariants.caption;
@@ -172,6 +173,8 @@ ${zoneRules()}
172
173
 
173
174
  ${fileRules()}
174
175
 
176
+ ${cssFileFieldCompactClasses()}
177
+
175
178
  ${forcedColorRules()}`;
176
179
  }
177
180
  export function fileFieldClassCatalog() {
@@ -190,5 +193,6 @@ export function fileFieldClassCatalog() {
190
193
  'sw-file-field-file',
191
194
  'sw-file-field-name',
192
195
  'sw-file-field-detail',
196
+ ...fileFieldCompactClassCatalog(),
193
197
  ];
194
198
  }
@@ -0,0 +1,6 @@
1
+ /** How many tiles share a row from `md` up; one a row below it. */
2
+ export declare const listTileColumnCounts: readonly [2, 3, 4];
3
+ export type ListGroupTileColumns = (typeof listTileColumnCounts)[number];
4
+ export declare function listTileColumnsClass(columns: ListGroupTileColumns): string;
5
+ export declare function cssListTilesClasses(): string;
6
+ export declare function listTilesClassCatalog(): string[];
@@ -0,0 +1,50 @@
1
+ import { breakpointQuery } from './breakpoints.js';
2
+ /** How many tiles share a row from `md` up; one a row below it. */
3
+ export const listTileColumnCounts = [2, 3, 4];
4
+ export function listTileColumnsClass(columns) {
5
+ return `sw-list-group-tiles-${columns}`;
6
+ }
7
+ const tiles = '.sw-list-group-tiles';
8
+ /*
9
+ * A tiled ListGroup is still one list (`sw-list`), its rows laid out as
10
+ * tiles: equal columns a space-3 apart from `md` up, one column below it,
11
+ * each row its own bordered panel on the surface (the panel variant's
12
+ * look, per row), with no hairlines between them. A row's press fill and
13
+ * its inset focus ring stay inside its tile.
14
+ *
15
+ * Generated after the list's hairline rules (css-list.ts), whose
16
+ * `.sw-list > .sw-list-item + .sw-list-item` has the same specificity as
17
+ * the tile rule: being later is what lets a tile's own border replace the
18
+ * hairline. `list-group-tiles.test.tsx` holds that order.
19
+ */
20
+ export function cssListTilesClasses() {
21
+ const columns = listTileColumnCounts.map((count) => `.${listTileColumnsClass(count)} > .sw-list { grid-template-columns: repeat(${count}, minmax(0, 1fr)); }`);
22
+ return `${tiles} > .sw-list {
23
+ display: grid;
24
+ gap: var(--sw-space-3);
25
+ }
26
+
27
+ ${columns.join('\n')}
28
+
29
+ ${tiles} > .sw-list > .sw-list-item {
30
+ background: var(--sw-color-surface);
31
+ border: 1px solid var(--sw-color-border);
32
+ border-radius: var(--sw-radius-md);
33
+ min-width: 0;
34
+ overflow: clip;
35
+ }
36
+
37
+ @media ${breakpointQuery('below', 'md')} {
38
+ ${tiles} > .sw-list { grid-template-columns: minmax(0, 1fr); }
39
+ }
40
+
41
+ @media (forced-colors: active) {
42
+ ${tiles} > .sw-list > .sw-list-item { border-color: CanvasText; }
43
+ }`;
44
+ }
45
+ export function listTilesClassCatalog() {
46
+ return [
47
+ 'sw-list-group-tiles',
48
+ ...listTileColumnCounts.map((count) => listTileColumnsClass(count)),
49
+ ];
50
+ }
@@ -2,6 +2,7 @@ import { typographyVariants } from '@scalewing/tokens';
2
2
  import { cssListRowClasses, listRowClassCatalog } from './css-list-row.js';
3
3
  import { cssListRowTrailingClasses, listRowTrailingClassCatalog, } from './css-list-row-trailing.js';
4
4
  import { cssListRowTruncateClasses, listRowTruncateClassCatalog, } from './css-list-row-truncate.js';
5
+ import { cssListTilesClasses, listTilesClassCatalog, } from './css-list-tiles.js';
5
6
  import { blockEdgeShadeRules, scrollRegionRules } from './css-scroll-region.js';
6
7
  const label = typographyVariants.label;
7
8
  const caption = typographyVariants.caption;
@@ -75,7 +76,9 @@ ${cssListRowClasses()}
75
76
 
76
77
  ${cssListRowTrailingClasses()}
77
78
 
78
- ${cssListRowTruncateClasses()}`;
79
+ ${cssListRowTruncateClasses()}
80
+
81
+ ${cssListTilesClasses()}`;
79
82
  }
80
83
  export function listClassCatalog() {
81
84
  return [
@@ -86,6 +89,7 @@ export function listClassCatalog() {
86
89
  'sw-list-group-plain',
87
90
  'sw-list-group-scroll',
88
91
  'sw-list-group-footer',
92
+ ...listTilesClassCatalog(),
89
93
  ...listRowClassCatalog(),
90
94
  ...listRowTrailingClassCatalog(),
91
95
  ...listRowTruncateClassCatalog(),
@@ -0,0 +1,2 @@
1
+ export declare function cssStepsTitleDetailClasses(): string;
2
+ export declare function stepsTitleDetailClassCatalog(): string[];
@@ -0,0 +1,36 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ const body = typographyVariants.body;
3
+ const label = typographyVariants.label;
4
+ /*
5
+ * An open step's muted words after its title ("Optional", "Wetlands done ·
6
+ * Woodlands empty"): the title and the words share one line that wraps, the
7
+ * words on the title's baseline a space-2 after it, or on a line of their
8
+ * own under the title when they do not fit beside it. The line gives way
9
+ * to a done step's summary and Edit in an open list, as the title does.
10
+ * The words are the label size at the body's weight in the muted colour,
11
+ * a skipped step's reason's colour beside its title; a long word breaks
12
+ * rather than pass the panel.
13
+ */
14
+ export function cssStepsTitleDetailClasses() {
15
+ return `.sw-steps-title-line {
16
+ align-items: baseline;
17
+ column-gap: var(--sw-space-2);
18
+ display: flex;
19
+ flex: 0 1 auto;
20
+ flex-wrap: wrap;
21
+ min-width: 0;
22
+ }
23
+
24
+ .sw-steps-title-detail {
25
+ color: var(--sw-color-muted);
26
+ font-size: ${label.fontSize}px;
27
+ font-weight: ${body.fontWeight};
28
+ letter-spacing: ${label.letterSpacing}px;
29
+ line-height: ${label.lineHeight}px;
30
+ min-width: 0;
31
+ overflow-wrap: anywhere;
32
+ }`;
33
+ }
34
+ export function stepsTitleDetailClassCatalog() {
35
+ return ['sw-steps-title-line', 'sw-steps-title-detail'];
36
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The one-page vertical stepper: an ordered list of glass panels a small
3
- * gap apart. Each has a header line (marker, title, and on a done step its
4
- * summary and Edit) and, while open (current or error), a body indented to
3
+ * gap apart. Each has a header line (marker, title, an open step's muted
4
+ * words beside it, and on a done step its summary and Edit) and, while open (current or error), a body indented to
5
5
  * the title. An open step's title is the title variant; a folded one's is
6
6
  * body size at label weight, muted ahead of the form or skipped. In an
7
7
  * open list (`sw-steps-open`) every step is open: each panel takes the open
@@ -1,5 +1,6 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { checkStrokeIn } from './chevron.js';
3
+ import { cssStepsTitleDetailClasses, stepsTitleDetailClassCatalog, } from './css-steps-title-detail.js';
3
4
  const body = typographyVariants.body;
4
5
  const caption = typographyVariants.caption;
5
6
  const label = typographyVariants.label;
@@ -69,8 +70,8 @@ function forcedColorRules() {
69
70
  }
70
71
  /**
71
72
  * The one-page vertical stepper: an ordered list of glass panels a small
72
- * gap apart. Each has a header line (marker, title, and on a done step its
73
- * summary and Edit) and, while open (current or error), a body indented to
73
+ * gap apart. Each has a header line (marker, title, an open step's muted
74
+ * words beside it, and on a done step its summary and Edit) and, while open (current or error), a body indented to
74
75
  * the title. An open step's title is the title variant; a folded one's is
75
76
  * body size at label weight, muted ahead of the form or skipped. In an
76
77
  * open list (`sw-steps-open`) every step is open: each panel takes the open
@@ -190,6 +191,8 @@ export function cssStepsClasses() {
190
191
  min-width: 0;
191
192
  }
192
193
 
194
+ ${cssStepsTitleDetailClasses()}
195
+
193
196
  ${markerRules()}
194
197
 
195
198
  ${connectorRules()}
@@ -214,5 +217,6 @@ export function stepsClassCatalog() {
214
217
  'sw-steps-edit',
215
218
  'sw-steps-body',
216
219
  'sw-steps-region',
220
+ ...stepsTitleDetailClassCatalog(),
217
221
  ];
218
222
  }
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { ListGroup, type ListGroupProps } from './components/ListGroup.js';
13
13
  export { inspectMarkdown, markdownOmissionKinds, MarkdownView, type MarkdownHeading, type MarkdownInspection, type MarkdownImage, type MarkdownImageResolver, type MarkdownOmission, type MarkdownOmissionCounts, type MarkdownOmissionKind, type MarkdownOptions, type MarkdownViewProps, } from './components/MarkdownView.js';
14
14
  export { ListRow, type ListRowProps } from './components/ListRow.js';
15
15
  export { listGroupVariants, listRowTitleAppearances, listRowTones, listRowValueTones, type ListGroupMaxHeight, type ListGroupVariant, type ListRowTitleAppearance, type ListRowTone, type ListRowValueTone, } from './list-options.js';
16
+ export { listTileColumnCounts, type ListGroupTileColumns, } from './css/css-list-tiles.js';
16
17
  export { Checkbox, type CheckboxProps } from './components/Checkbox.js';
17
18
  export { Spinner, type SpinnerProps, type SpinnerSize, } from './components/Spinner.js';
18
19
  export { GlyphTile, glyphTileSizes, type GlyphTileHue, type GlyphTileProps, type GlyphTileSize, } from './components/GlyphTile.js';
@@ -29,7 +30,8 @@ export { ColumnChart, type ColumnChartCallout, type ColumnChartLine, type Column
29
30
  export { LineChart, type LineChartCurve, type LineChartMarker, type LineChartPeriod, type LineChartPoint, type LineChartProps, lineChartCurves, } from './components/LineChart.js';
30
31
  export { DataBar, type DataBarProps, type DataBarTone, dataBarTones, } from './components/DataBar.js';
31
32
  export { Pagination, type PaginationLabels, type PaginationProps, } from './components/Pagination.js';
32
- export { Button, type ButtonDisabledAppearance, type ButtonProps, type ButtonSize, type ButtonVariant, } from './components/Button.js';
33
+ export { Button, type ButtonDisabledAppearance, type ButtonProps, type ButtonSize, type ButtonVariant, type ButtonWidth, } from './components/Button.js';
34
+ export { buttonWidths } from './css/css-button-width.js';
33
35
  export { buttonDisabledAppearances, webButtonVariants, } from './css/css-button.js';
34
36
  export { Box, type BoxBorder, type BoxElement, type BoxLink, type BoxProps, } from './components/Box.js';
35
37
  export { boxLinkAppearances } from './css/css-box-link.js';
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export { ListGroup } from './components/ListGroup.js';
12
12
  export { inspectMarkdown, markdownOmissionKinds, MarkdownView, } from './components/MarkdownView.js';
13
13
  export { ListRow } from './components/ListRow.js';
14
14
  export { listGroupVariants, listRowTitleAppearances, listRowTones, listRowValueTones, } from './list-options.js';
15
+ export { listTileColumnCounts, } from './css/css-list-tiles.js';
15
16
  export { Checkbox } from './components/Checkbox.js';
16
17
  export { Spinner, } from './components/Spinner.js';
17
18
  export { GlyphTile, glyphTileSizes, } from './components/GlyphTile.js';
@@ -29,6 +30,7 @@ export { LineChart, lineChartCurves, } from './components/LineChart.js';
29
30
  export { DataBar, dataBarTones, } from './components/DataBar.js';
30
31
  export { Pagination, } from './components/Pagination.js';
31
32
  export { Button, } from './components/Button.js';
33
+ export { buttonWidths } from './css/css-button-width.js';
32
34
  export { buttonDisabledAppearances, webButtonVariants, } from './css/css-button.js';
33
35
  export { Box, } from './components/Box.js';
34
36
  export { boxLinkAppearances } from './css/css-box-link.js';
@@ -4,9 +4,11 @@ import { listRowValueTones, type ListRowValueTone } from './css/css-list-row.js'
4
4
  /**
5
5
  * `panel` (the default) is one bordered panel on the surface; `plain`
6
6
  * draws no panel and a hairline above the first row too, for rows inside
7
- * a surface that already has one, such as a Card.
7
+ * a surface that already has one, such as a Card; `tiles` lays the rows
8
+ * out as tiles, each its own panel, in equal columns (one a row below
9
+ * `md`).
8
10
  */
9
- export declare const listGroupVariants: readonly ["panel", "plain"];
11
+ export declare const listGroupVariants: readonly ["panel", "plain", "tiles"];
10
12
  export type ListGroupVariant = (typeof listGroupVariants)[number];
11
13
  /**
12
14
  * A list's bounded height, counted in rows: as tall as `rows` rows of a
@@ -30,17 +32,23 @@ export type ListGroupInput = {
30
32
  labelledBy: string | undefined;
31
33
  variant: unknown;
32
34
  maxHeight: ListGroupMaxHeight | undefined;
35
+ tileColumns: unknown;
36
+ hasFooter: boolean;
33
37
  };
34
38
  /**
35
39
  * Fails closed on a ListGroup it cannot draw: an unknown variant, a
36
- * `maxHeight` that is not a whole row count from 1, or a bounded list with
37
- * no name (its scroll region is a keyboard stop and must say what it holds).
40
+ * `maxHeight` that is not a whole row count from 1, a bounded list with
41
+ * no name (its scroll region is a keyboard stop and must say what it
42
+ * holds), `tileColumns` other than 2, 3 or 4 or on a list that is not
43
+ * tiles, or tiles with `maxHeight` or a `footer` (tiles neither scroll as
44
+ * rows nor add up to a total).
38
45
  */
39
- export declare function assertListGroup({ label, labelledBy, variant, maxHeight, }: ListGroupInput): void;
46
+ export declare function assertListGroup({ label, labelledBy, variant, maxHeight, tileColumns, hasFooter, }: ListGroupInput): void;
40
47
  /** The row count the generated `sw-list-group-scroll` height reads. */
41
48
  export declare function listMaxHeightStyle(maxHeight: ListGroupMaxHeight): CSSProperties;
42
49
  export type ListRowInput = {
43
50
  title: string;
51
+ titleLang: string | undefined;
44
52
  tone: unknown;
45
53
  titleAppearance: unknown;
46
54
  valueTone: unknown;
@@ -54,12 +62,12 @@ export type ListRowInput = {
54
62
  unreadLabel: string | undefined;
55
63
  };
56
64
  /**
57
- * Fails closed on a ListRow it cannot draw: an empty title or value, an
58
- * unknown tone, title appearance, value tone, badge tone or accessory, a
59
- * `valueDetail`, `valueTone` or `valueBadge` with no value, a value both
60
- * toned and a badge, `selected` on a row that is not a button or link
61
- * (its check would be a state nothing can read), or an unread row with no
62
- * words for its dot (a colour alone says nothing).
65
+ * Fails closed on a ListRow it cannot draw: an empty title, titleLang or
66
+ * value, an unknown tone, title appearance, value tone, badge tone or
67
+ * accessory, a `valueDetail`, `valueTone` or `valueBadge` with no value, a
68
+ * value both toned and a badge, `selected` on a row that is not a button or
69
+ * link (its check would be a state nothing can read), or an unread row with
70
+ * no words for its dot (a colour alone says nothing).
63
71
  */
64
72
  export declare function assertListRow(input: ListRowInput): void;
65
73
  /**
@@ -1,12 +1,15 @@
1
1
  import { badgeTones } from './css/css-data.js';
2
2
  import { listRowValueTones, } from './css/css-list-row.js';
3
+ import { listTileColumnCounts } from './css/css-list-tiles.js';
3
4
  import { isOneOf } from './is-one-of.js';
4
5
  /**
5
6
  * `panel` (the default) is one bordered panel on the surface; `plain`
6
7
  * draws no panel and a hairline above the first row too, for rows inside
7
- * a surface that already has one, such as a Card.
8
+ * a surface that already has one, such as a Card; `tiles` lays the rows
9
+ * out as tiles, each its own panel, in equal columns (one a row below
10
+ * `md`).
8
11
  */
9
- export const listGroupVariants = ['panel', 'plain'];
12
+ export const listGroupVariants = ['panel', 'plain', 'tiles'];
10
13
  /** `danger` draws the title in the danger colour, for a row that ends something. */
11
14
  export const listRowTones = ['default', 'danger'];
12
15
  /** `strong` (the default) sets the title at the label weight; `plain` at the body's. */
@@ -17,12 +20,23 @@ function isBlank(text) {
17
20
  }
18
21
  /**
19
22
  * Fails closed on a ListGroup it cannot draw: an unknown variant, a
20
- * `maxHeight` that is not a whole row count from 1, or a bounded list with
21
- * no name (its scroll region is a keyboard stop and must say what it holds).
23
+ * `maxHeight` that is not a whole row count from 1, a bounded list with
24
+ * no name (its scroll region is a keyboard stop and must say what it
25
+ * holds), `tileColumns` other than 2, 3 or 4 or on a list that is not
26
+ * tiles, or tiles with `maxHeight` or a `footer` (tiles neither scroll as
27
+ * rows nor add up to a total).
22
28
  */
23
- export function assertListGroup({ label, labelledBy, variant, maxHeight, }) {
29
+ export function assertListGroup({ label, labelledBy, variant, maxHeight, tileColumns, hasFooter, }) {
24
30
  if (!isOneOf(listGroupVariants, variant))
25
31
  throw new RangeError(`Unknown ListGroup variant: ${String(variant)}`);
32
+ if (tileColumns !== undefined) {
33
+ if (variant !== 'tiles')
34
+ throw new RangeError('ListGroup tileColumns needs variant="tiles"');
35
+ if (!listTileColumnCounts.includes(tileColumns))
36
+ throw new RangeError(`ListGroup tileColumns must be 2, 3 or 4: ${String(tileColumns)}`);
37
+ }
38
+ if (variant === 'tiles' && (maxHeight !== undefined || hasFooter))
39
+ throw new RangeError('A ListGroup of tiles takes no maxHeight or footer');
26
40
  if (maxHeight === undefined)
27
41
  return;
28
42
  const rows = typeof maxHeight === 'object' && maxHeight !== null
@@ -38,16 +52,18 @@ export function listMaxHeightStyle(maxHeight) {
38
52
  return { '--sw-list-max-rows': maxHeight.rows };
39
53
  }
40
54
  /**
41
- * Fails closed on a ListRow it cannot draw: an empty title or value, an
42
- * unknown tone, title appearance, value tone, badge tone or accessory, a
43
- * `valueDetail`, `valueTone` or `valueBadge` with no value, a value both
44
- * toned and a badge, `selected` on a row that is not a button or link
45
- * (its check would be a state nothing can read), or an unread row with no
46
- * words for its dot (a colour alone says nothing).
55
+ * Fails closed on a ListRow it cannot draw: an empty title, titleLang or
56
+ * value, an unknown tone, title appearance, value tone, badge tone or
57
+ * accessory, a `valueDetail`, `valueTone` or `valueBadge` with no value, a
58
+ * value both toned and a badge, `selected` on a row that is not a button or
59
+ * link (its check would be a state nothing can read), or an unread row with
60
+ * no words for its dot (a colour alone says nothing).
47
61
  */
48
62
  export function assertListRow(input) {
49
63
  if (isBlank(input.title))
50
64
  throw new RangeError('ListRow title must be non-empty text');
65
+ if (input.titleLang !== undefined && isBlank(input.titleLang))
66
+ throw new RangeError('ListRow titleLang must be a non-empty language tag');
51
67
  if (!isOneOf(listRowTones, input.tone))
52
68
  throw new RangeError(`Unknown ListRow tone: ${String(input.tone)}`);
53
69
  if (!isOneOf(listRowTitleAppearances, input.titleAppearance))
package/dist/styles.css CHANGED
@@ -967,6 +967,19 @@ body {
967
967
  .sw-button-xs { min-height: 28px; padding-inline: 10px; }
968
968
  .sw-button-sm { min-height: 32px; padding-inline: 12px; }
969
969
  .sw-button-md { min-height: 44px; padding-inline: 16px; }
970
+ .sw-button-full {
971
+ width: 100%;
972
+ }
973
+
974
+ .sw-inline > .sw-button-full {
975
+ flex: 1 1 0;
976
+ min-width: 0;
977
+ }
978
+
979
+ .sw-inline.sw-wrap > .sw-button-full {
980
+ flex: 1 1 auto;
981
+ width: auto;
982
+ }
970
983
  .sw-button-group { align-items: center; display: flex; flex-wrap: wrap; gap: var(--sw-space-2); min-width: 0; }
971
984
  .sw-button-group-start { justify-content: flex-start; }
972
985
  .sw-button-group-end { justify-content: flex-end; }
@@ -3018,6 +3031,31 @@ body {
3018
3031
  white-space: nowrap;
3019
3032
  }
3020
3033
 
3034
+ .sw-list-group-tiles > .sw-list {
3035
+ display: grid;
3036
+ gap: var(--sw-space-3);
3037
+ }
3038
+
3039
+ .sw-list-group-tiles-2 > .sw-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
3040
+ .sw-list-group-tiles-3 > .sw-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
3041
+ .sw-list-group-tiles-4 > .sw-list { grid-template-columns: repeat(4, minmax(0, 1fr)); }
3042
+
3043
+ .sw-list-group-tiles > .sw-list > .sw-list-item {
3044
+ background: var(--sw-color-surface);
3045
+ border: 1px solid var(--sw-color-border);
3046
+ border-radius: var(--sw-radius-md);
3047
+ min-width: 0;
3048
+ overflow: clip;
3049
+ }
3050
+
3051
+ @media not all and (min-width: 48rem) {
3052
+ .sw-list-group-tiles > .sw-list { grid-template-columns: minmax(0, 1fr); }
3053
+ }
3054
+
3055
+ @media (forced-colors: active) {
3056
+ .sw-list-group-tiles > .sw-list > .sw-list-item { border-color: CanvasText; }
3057
+ }
3058
+
3021
3059
  .sw-file-field-frame {
3022
3060
  border: 1px solid var(--sw-color-border);
3023
3061
  border-radius: var(--sw-radius-md);
@@ -3147,6 +3185,83 @@ body {
3147
3185
  margin-inline-start: auto;
3148
3186
  }
3149
3187
 
3188
+ .sw-file-field-actions {
3189
+ display: flex;
3190
+ flex: none;
3191
+ flex-wrap: wrap;
3192
+ gap: var(--sw-space-2);
3193
+ margin-inline-start: auto;
3194
+ }
3195
+
3196
+ .sw-file-field-compact.sw-file-field-chosen {
3197
+ flex-wrap: nowrap;
3198
+ gap: var(--sw-space-1);
3199
+ min-height: var(--sw-control-md-min-height);
3200
+ padding-block: 0;
3201
+ padding-inline: 0 var(--sw-space-1);
3202
+ }
3203
+
3204
+ .sw-file-field-compact .sw-file-field-zone {
3205
+ justify-content: flex-start;
3206
+ padding: var(--sw-space-2) var(--sw-space-4);
3207
+ text-align: start;
3208
+ }
3209
+
3210
+ .sw-file-field-pick {
3211
+ align-items: center;
3212
+ appearance: none;
3213
+ background: transparent;
3214
+ border: 0;
3215
+ border-radius: inherit;
3216
+ box-sizing: border-box;
3217
+ color: var(--sw-color-text);
3218
+ cursor: pointer;
3219
+ display: flex;
3220
+ flex: 1 1 auto;
3221
+ font-family: var(--sw-font-sans);
3222
+ gap: var(--sw-space-3);
3223
+ margin: 0;
3224
+ min-height: var(--sw-control-md-min-height);
3225
+ min-width: 0;
3226
+ padding-block: var(--sw-space-1);
3227
+ padding-inline: var(--sw-space-1) var(--sw-space-2);
3228
+ text-align: start;
3229
+ }
3230
+
3231
+ .sw-file-field-pick:focus { outline: none; }
3232
+
3233
+ .sw-file-field-pick:focus-visible {
3234
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
3235
+ outline-offset: calc(-1 * var(--sw-focus-ring-width));
3236
+ }
3237
+
3238
+ .sw-file-field-pick:disabled { cursor: not-allowed; }
3239
+
3240
+ .sw-file-field-pick .sw-file-field-name {
3241
+ font-weight: 400;
3242
+ min-width: 0;
3243
+ overflow: hidden;
3244
+ overflow-wrap: normal;
3245
+ text-overflow: ellipsis;
3246
+ white-space: nowrap;
3247
+ }
3248
+
3249
+ /* With no thumbnail or mark, the name starts where a text field's words do. */
3250
+ .sw-file-field-pick > .sw-file-field-name:first-child { margin-inline-start: var(--sw-space-3); }
3251
+
3252
+ .sw-file-field-compact .sw-file-field-preview,
3253
+ .sw-file-field-compact .sw-file-field-icon { margin-inline-start: var(--sw-space-1); }
3254
+
3255
+ .sw-file-field-remove { flex: none; }
3256
+
3257
+ @media (forced-colors: active) {
3258
+ .sw-file-field-pick { color: ButtonText; }
3259
+ .sw-file-field-pick:disabled { color: GrayText; }
3260
+ }
3261
+
3262
+ /* Disabled, the row fades as its Remove does. */
3263
+ .sw-file-field-pick:disabled { opacity: var(--sw-disabled-opacity); }
3264
+
3150
3265
  @media (forced-colors: active) {
3151
3266
  .sw-file-field-frame { border-color: CanvasText; }
3152
3267
  .sw-field-invalid .sw-file-field-frame { border-color: Mark; }
@@ -7303,6 +7418,25 @@ body {
7303
7418
  min-width: 0;
7304
7419
  }
7305
7420
 
7421
+ .sw-steps-title-line {
7422
+ align-items: baseline;
7423
+ column-gap: var(--sw-space-2);
7424
+ display: flex;
7425
+ flex: 0 1 auto;
7426
+ flex-wrap: wrap;
7427
+ min-width: 0;
7428
+ }
7429
+
7430
+ .sw-steps-title-detail {
7431
+ color: var(--sw-color-muted);
7432
+ font-size: 15px;
7433
+ font-weight: 400;
7434
+ letter-spacing: -0.2px;
7435
+ line-height: 20px;
7436
+ min-width: 0;
7437
+ overflow-wrap: anywhere;
7438
+ }
7439
+
7306
7440
  .sw-steps-item-upcoming .sw-steps-marker,
7307
7441
  .sw-steps-item-skipped .sw-steps-marker {
7308
7442
  box-shadow: inset 0 0 0 2px var(--sw-color-border);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalewing/react",
3
- "version": "1.44.0",
3
+ "version": "1.46.0",
4
4
  "description": "Scalewing DOM components and package-owned CSS utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",