@scalewing/react 1.30.0 → 1.32.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 (100) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +23 -5
  3. package/dist/components/ActionMenu.d.ts +10 -1
  4. package/dist/components/ActionMenu.js +7 -2
  5. package/dist/components/AppHeader.d.ts +9 -1
  6. package/dist/components/AppHeader.js +6 -2
  7. package/dist/components/Box.d.ts +10 -2
  8. package/dist/components/Button.d.ts +27 -2
  9. package/dist/components/Button.js +5 -3
  10. package/dist/components/ButtonGroup.d.ts +9 -1
  11. package/dist/components/ButtonGroup.js +8 -2
  12. package/dist/components/Combobox.d.ts +14 -0
  13. package/dist/components/Combobox.js +125 -0
  14. package/dist/components/DateField.d.ts +9 -1
  15. package/dist/components/DateField.js +4 -3
  16. package/dist/components/DenominationGrid.d.ts +5 -3
  17. package/dist/components/DenominationGrid.js +21 -11
  18. package/dist/components/Dialog.d.ts +9 -1
  19. package/dist/components/Dialog.js +8 -4
  20. package/dist/components/Field.d.ts +25 -0
  21. package/dist/components/Field.js +56 -6
  22. package/dist/components/FieldLabelText.d.ts +21 -3
  23. package/dist/components/FieldLabelText.js +16 -4
  24. package/dist/components/FilterChips.d.ts +9 -1
  25. package/dist/components/FilterChips.js +4 -2
  26. package/dist/components/Nav.d.ts +14 -2
  27. package/dist/components/Nav.js +6 -2
  28. package/dist/components/SegmentedControl.d.ts +5 -2
  29. package/dist/components/SegmentedControl.js +2 -1
  30. package/dist/components/Select.d.ts +9 -1
  31. package/dist/components/Select.js +4 -3
  32. package/dist/components/Stepper.d.ts +13 -0
  33. package/dist/components/Stepper.js +8 -2
  34. package/dist/components/combobox/ComboboxPopup.d.ts +25 -0
  35. package/dist/components/combobox/ComboboxPopup.js +68 -0
  36. package/dist/components/combobox/entries.d.ts +18 -0
  37. package/dist/components/combobox/entries.js +34 -0
  38. package/dist/components/combobox/types.d.ts +71 -0
  39. package/dist/components/combobox/types.js +1 -0
  40. package/dist/components/combobox/validate.d.ts +3 -0
  41. package/dist/components/combobox/validate.js +44 -0
  42. package/dist/components/denomination-grid/DenominationStrip.d.ts +4 -2
  43. package/dist/components/denomination-grid/DenominationStrip.js +3 -2
  44. package/dist/components/denomination-grid/DenominationTiles.d.ts +1 -1
  45. package/dist/components/denomination-grid/EntryCell.js +1 -1
  46. package/dist/components/denomination-grid/StripRow.js +5 -2
  47. package/dist/components/denomination-grid/types.d.ts +35 -1
  48. package/dist/components/dialog/DialogTitle.d.ts +5 -3
  49. package/dist/components/dialog/DialogTitle.js +4 -4
  50. package/dist/components/field/SearchFrame.d.ts +16 -0
  51. package/dist/components/field/SearchFrame.js +32 -0
  52. package/dist/css/css-action-menu-trigger.d.ts +11 -0
  53. package/dist/css/css-action-menu-trigger.js +66 -0
  54. package/dist/css/css-action-menu.js +7 -2
  55. package/dist/css/css-app-header-bar.d.ts +10 -0
  56. package/dist/css/css-app-header-bar.js +26 -0
  57. package/dist/css/css-box-border.d.ts +6 -1
  58. package/dist/css/css-box-border.js +8 -4
  59. package/dist/css/css-button-group-attached.js +5 -2
  60. package/dist/css/css-button-group-track.d.ts +11 -0
  61. package/dist/css/css-button-group-track.js +48 -0
  62. package/dist/css/css-button.d.ts +19 -2
  63. package/dist/css/css-button.js +31 -1
  64. package/dist/css/css-chrome.js +14 -1
  65. package/dist/css/css-combobox.d.ts +2 -0
  66. package/dist/css/css-combobox.js +136 -0
  67. package/dist/css/css-components.js +6 -0
  68. package/dist/css/css-data.js +3 -2
  69. package/dist/css/css-date-field.js +2 -1
  70. package/dist/css/css-denomination-appearance.d.ts +21 -0
  71. package/dist/css/css-denomination-appearance.js +114 -0
  72. package/dist/css/css-denomination-cell-tones.d.ts +8 -0
  73. package/dist/css/css-denomination-cell-tones.js +20 -1
  74. package/dist/css/css-denomination-grid.js +4 -0
  75. package/dist/css/css-dialog-banded.d.ts +11 -0
  76. package/dist/css/css-dialog-banded.js +84 -0
  77. package/dist/css/css-field-search.d.ts +2 -0
  78. package/dist/css/css-field-search.js +118 -0
  79. package/dist/css/css-field.js +4 -0
  80. package/dist/css/css-multi-select.js +2 -1
  81. package/dist/css/css-nav-words.d.ts +13 -0
  82. package/dist/css/css-nav-words.js +51 -0
  83. package/dist/css/css-notice.d.ts +4 -11
  84. package/dist/css/css-notice.js +6 -22
  85. package/dist/css/css-segmented-raised.d.ts +3 -0
  86. package/dist/css/css-segmented-raised.js +72 -0
  87. package/dist/css/css-segmented.js +9 -3
  88. package/dist/css/css-select.js +2 -1
  89. package/dist/css/css-split.js +2 -1
  90. package/dist/css/css-stepper.js +7 -0
  91. package/dist/css/quiet-fill.d.ts +7 -0
  92. package/dist/css/quiet-fill.js +7 -0
  93. package/dist/css/stacking.js +6 -2
  94. package/dist/css/stylesheet.js +4 -0
  95. package/dist/css/tone-tint.d.ts +21 -0
  96. package/dist/css/tone-tint.js +23 -0
  97. package/dist/index.d.ts +13 -10
  98. package/dist/index.js +11 -8
  99. package/dist/styles.css +505 -12
  100. package/package.json +1 -1
@@ -1,7 +1,9 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { chevronStroke } from './chevron.js';
3
+ import { actionMenuTriggerClassCatalog, cssActionMenuTriggerClasses, } from './css-action-menu-trigger.js';
3
4
  import { zIndex } from './stacking.js';
4
5
  import { coarsePointerQuery, touchTarget } from './touch-target.js';
6
+ import { quietFill } from './quiet-fill.js';
5
7
  const caption = typographyVariants.caption;
6
8
  const label = typographyVariants.label;
7
9
  export function cssActionMenuClasses() {
@@ -103,7 +105,7 @@ export function cssActionMenuClasses() {
103
105
 
104
106
  .sw-action-menu-item:hover,
105
107
  .sw-action-menu-item:focus-visible {
106
- background: color-mix(in srgb, var(--sw-color-muted) 16%, transparent);
108
+ background: ${quietFill};
107
109
  }
108
110
 
109
111
  .sw-action-menu-item:disabled {
@@ -162,7 +164,9 @@ export function cssActionMenuClasses() {
162
164
  .sw-action-menu-item {
163
165
  min-height: ${touchTarget};
164
166
  }
165
- }`;
167
+ }
168
+
169
+ ${cssActionMenuTriggerClasses()}`;
166
170
  }
167
171
  export function actionMenuClassCatalog() {
168
172
  return [
@@ -176,5 +180,6 @@ export function actionMenuClassCatalog() {
176
180
  'sw-action-menu-detail',
177
181
  'sw-action-menu-chevron',
178
182
  'sw-action-menu-separator',
183
+ ...actionMenuTriggerClassCatalog(),
179
184
  ];
180
185
  }
@@ -0,0 +1,10 @@
1
+ export declare const appHeaderAppearances: readonly ["glass", "bar"];
2
+ /**
3
+ * How an AppHeader is drawn: `glass` (the default) is a rounded frosted
4
+ * card; `bar` is a full-width band of the solid surface with a hairline
5
+ * under it and no rounding, set against the page's edges.
6
+ */
7
+ export type AppHeaderAppearance = (typeof appHeaderAppearances)[number];
8
+ export declare const appHeaderBarClass = "sw-app-header-bar";
9
+ export declare function cssAppHeaderBarClasses(): string;
10
+ export declare function appHeaderBarClassCatalog(): string[];
@@ -0,0 +1,26 @@
1
+ export const appHeaderAppearances = ['glass', 'bar'];
2
+ export const appHeaderBarClass = 'sw-app-header-bar';
3
+ const bar = `.${appHeaderBarClass}`;
4
+ /*
5
+ * A bar header: the surface with no blur or specular line, no radius, and
6
+ * the border hairline under it only. One class, emitted after the glass
7
+ * header, so it wins by order; the Box border classes are emitted later
8
+ * still, so a Box `border` prop on the header wins over it, as on any
9
+ * Box-based frame. Reduce Transparency sets the same surface. Forced
10
+ * colors keep the hairline.
11
+ */
12
+ export function cssAppHeaderBarClasses() {
13
+ return `${bar} {
14
+ backdrop-filter: none;
15
+ -webkit-backdrop-filter: none;
16
+ background: var(--sw-color-surface);
17
+ border-color: var(--sw-color-border);
18
+ border-radius: 0;
19
+ border-style: solid;
20
+ border-width: 0 0 1px;
21
+ box-shadow: none;
22
+ }`;
23
+ }
24
+ export function appHeaderBarClassCatalog() {
25
+ return [appHeaderBarClass];
26
+ }
@@ -1,4 +1,9 @@
1
- export type BoxBorderValue = boolean | 'dashed';
1
+ /**
2
+ * `true` (solid) or `'dashed'` draws the hairline all round; `'bottom'`
3
+ * draws it under the box only, for a full-width bar such as a header or a
4
+ * section's sub-navigation.
5
+ */
6
+ export type BoxBorderValue = boolean | 'dashed' | 'bottom';
2
7
  /** The generated class for a Box `border` prop, or none for `false`. */
3
8
  export declare function boxBorderClass(border: BoxBorderValue | undefined): string | undefined;
4
9
  export declare function cssBoxBorderClasses(): string;
@@ -6,8 +6,11 @@ function borderClass(style) {
6
6
  export function boxBorderClass(border) {
7
7
  if (!border)
8
8
  return undefined;
9
+ if (border === 'bottom')
10
+ return bottomBorderClass;
9
11
  return borderClass(border === 'dashed' ? 'dashed' : 'solid');
10
12
  }
13
+ const bottomBorderClass = 'sw-border-bottom';
11
14
  /*
12
15
  * A Box `border` is a token hairline. These rules are emitted after every
13
16
  * component rule so that, at equal specificity, the prop still wins over a
@@ -15,10 +18,11 @@ export function boxBorderClass(border) {
15
18
  * forced colors the system border color replaces the token and the style stays.
16
19
  */
17
20
  export function cssBoxBorderClasses() {
18
- return borderStyles
19
- .map((style) => `.${borderClass(style)} { border: 1px ${style} var(--sw-color-border); }`)
20
- .join('\n');
21
+ return [
22
+ ...borderStyles.map((style) => `.${borderClass(style)} { border: 1px ${style} var(--sw-color-border); }`),
23
+ `.${bottomBorderClass} { border-width: 0 0 1px; border-style: solid; border-color: var(--sw-color-border); }`,
24
+ ].join('\n');
21
25
  }
22
26
  export function boxBorderClassCatalog() {
23
- return borderStyles.map(borderClass);
27
+ return [...borderStyles.map(borderClass), bottomBorderClass];
24
28
  }
@@ -1,4 +1,5 @@
1
1
  import { breakpointQuery } from './breakpoints.js';
2
+ import { buttonGroupTrackClassCatalog, cssButtonGroupTrackClasses, } from './css-button-group-track.js';
2
3
  import { joinedZIndex } from './stacking.js';
3
4
  /*
4
5
  * An attached ButtonGroup is one joined control, such as Previous, Today
@@ -63,8 +64,10 @@ ${group} > .sw-button:focus-visible {
63
64
  @media ${breakpointQuery('below', 'md')} {
64
65
  ${group} { align-items: stretch; flex-direction: row; }
65
66
  ${group} > * { width: auto; }
66
- }`;
67
+ }
68
+
69
+ ${cssButtonGroupTrackClasses()}`;
67
70
  }
68
71
  export function buttonGroupAttachedClassCatalog() {
69
- return ['sw-button-group-attached'];
72
+ return ['sw-button-group-attached', ...buttonGroupTrackClassCatalog()];
70
73
  }
@@ -0,0 +1,11 @@
1
+ export declare const buttonGroupAppearances: readonly ["default", "track"];
2
+ /**
3
+ * How an attached ButtonGroup is drawn: `default` keeps each button's own
4
+ * fill and outline, joined; `track` sets the buttons as words on one
5
+ * quiet grey track, a hairline between them, such as Previous, Today and
6
+ * Next above a day.
7
+ */
8
+ export type ButtonGroupAppearance = (typeof buttonGroupAppearances)[number];
9
+ export declare const buttonGroupTrackClass = "sw-button-group-track";
10
+ export declare function cssButtonGroupTrackClasses(): string;
11
+ export declare function buttonGroupTrackClassCatalog(): string[];
@@ -0,0 +1,48 @@
1
+ import { quietFill } from './quiet-fill.js';
2
+ export const buttonGroupAppearances = ['default', 'track'];
3
+ export const buttonGroupTrackClass = 'sw-button-group-track';
4
+ const track = `.sw-button-group.sw-button-group-attached.${buttonGroupTrackClass}`;
5
+ /*
6
+ * The track: the attached group takes the quiet fill and the sm radius
7
+ * (its buttons' outer corners already are), and every button in it loses
8
+ * its own fill and outline to take its words in the text color. The
9
+ * divider between two buttons is a border hairline at full height, after
10
+ * the attached group's tinted divider (one more class, so it wins at the
11
+ * same place); it is the second button's start border, so the one before
12
+ * a disabled button fades with it. The divider is the canvas hairline
13
+ * (about 1.2:1 on the track, as the board draws it; fainter on a dark
14
+ * card). A pressed button keeps its ring and a focused one its outline,
15
+ * over the track. A disabled button fades, `disabledAppearance="neutral"`
16
+ * included (its muted words would read as enabled ones here). Forced
17
+ * colors outline the track, keep every border and draw a pressed button's
18
+ * border in Highlight. A track is for equal-weight navigation: every
19
+ * variant's meaning goes, so keep a primary or destructive action out of it.
20
+ */
21
+ export function cssButtonGroupTrackClasses() {
22
+ return `${track} {
23
+ background: ${quietFill};
24
+ border-radius: var(--sw-radius-sm);
25
+ }
26
+
27
+ ${track} > .sw-button {
28
+ background: transparent;
29
+ border-color: transparent;
30
+ color: var(--sw-color-text);
31
+ }
32
+
33
+ ${track} > .sw-button + .sw-button {
34
+ border-inline-start-color: var(--sw-color-border);
35
+ }
36
+
37
+ ${track} > .sw-button:disabled {
38
+ opacity: var(--sw-disabled-opacity);
39
+ }
40
+
41
+ @media (forced-colors: active) {
42
+ ${track} { outline: 1px solid CanvasText; }
43
+ ${track} > .sw-button[aria-pressed='true'] { border-color: Highlight; }
44
+ }`;
45
+ }
46
+ export function buttonGroupTrackClassCatalog() {
47
+ return [buttonGroupTrackClass];
48
+ }
@@ -1,7 +1,24 @@
1
- import { type ButtonSize, type ButtonVariant } from '@scalewing/tokens';
1
+ import { type ButtonSize } from '@scalewing/tokens';
2
+ /**
3
+ * The web Button's variants: the shared tiers from `@scalewing/tokens`, plus
4
+ * `neutral`, a quiet grey fill in the text color for an action beside the
5
+ * primary that should not draw the eye (a Cancel on a sheet). Native has no
6
+ * `neutral` yet.
7
+ */
8
+ export declare const webButtonVariants: readonly ["primary", "secondary", "tertiary", "ghost", "danger", "neutral"];
9
+ export type WebButtonVariant = (typeof webButtonVariants)[number];
10
+ export declare const buttonDisabledAppearances: readonly ["faded", "neutral"];
11
+ /**
12
+ * How a disabled button looks: `faded` (the default) fades the variant as
13
+ * any disabled control; `neutral` draws a filled button on the quiet grey
14
+ * fill with muted words, whatever its variant (a ghost button, which has
15
+ * no fill, still fades).
16
+ */
17
+ export type ButtonDisabledAppearance = (typeof buttonDisabledAppearances)[number];
2
18
  export declare function buttonClassNames(options: {
3
19
  size: ButtonSize;
4
- variant: ButtonVariant;
20
+ variant: WebButtonVariant;
21
+ disabledAppearance?: ButtonDisabledAppearance;
5
22
  }): string[];
6
23
  export declare const pressedRingGap = 2;
7
24
  export declare const pressedRingWidth = 4;
@@ -1,12 +1,24 @@
1
1
  import { buttonSizes, buttonVariants, controlScale, typographyVariants, } from '@scalewing/tokens';
2
2
  import { breakpointQuery } from './breakpoints.js';
3
3
  import { buttonGroupAttachedClassCatalog, cssButtonGroupAttachedClasses, } from './css-button-group-attached.js';
4
+ import { quietFill } from './quiet-fill.js';
4
5
  const label = typographyVariants.label;
6
+ /**
7
+ * The web Button's variants: the shared tiers from `@scalewing/tokens`, plus
8
+ * `neutral`, a quiet grey fill in the text color for an action beside the
9
+ * primary that should not draw the eye (a Cancel on a sheet). Native has no
10
+ * `neutral` yet.
11
+ */
12
+ export const webButtonVariants = [...buttonVariants, 'neutral'];
13
+ export const buttonDisabledAppearances = ['faded', 'neutral'];
5
14
  export function buttonClassNames(options) {
6
15
  return [
7
16
  'sw-button',
8
17
  `sw-button-${options.variant}`,
9
18
  `sw-button-${options.size}`,
19
+ ...(options.disabledAppearance === 'neutral'
20
+ ? ['sw-button-disabled-neutral']
21
+ : []),
10
22
  ];
11
23
  }
12
24
  function variantRules() {
@@ -16,8 +28,24 @@ function variantRules() {
16
28
  `.sw-button-tertiary { background: var(--sw-color-tertiary); border-color: transparent; color: var(--sw-color-onTertiary); }`,
17
29
  `.sw-button-ghost { background: transparent; border-color: transparent; color: var(--sw-color-accent); }`,
18
30
  `.sw-button-danger { background: var(--sw-color-danger); border-color: transparent; color: var(--sw-color-onDanger); }`,
31
+ `.sw-button-neutral { background: ${quietFill}; border-color: transparent; color: var(--sw-color-text); }`,
19
32
  ].join('\n');
20
33
  }
34
+ /*
35
+ * A neutral disabled button: the quiet fill with its words in the muted
36
+ * color at 48 %, about #AEAEAE on the grey (a disabled control needs no
37
+ * contrast, and the faded words say it cannot be pressed), at full
38
+ * opacity so no accent shows through.
39
+ * Forced colors draw it as any disabled button.
40
+ */
41
+ function disabledNeutralRules() {
42
+ return `.sw-button-disabled-neutral:disabled:not(.sw-button-ghost) {
43
+ background: ${quietFill};
44
+ border-color: transparent;
45
+ color: color-mix(in srgb, var(--sw-color-muted) 48%, transparent);
46
+ opacity: 1;
47
+ }`;
48
+ }
21
49
  /*
22
50
  * A toggle button's pressed state is an accent ring drawn outside the fill,
23
51
  * past a 2 px gap in the page background: the ring meets the background on
@@ -92,6 +120,7 @@ export function cssButtonClasses() {
92
120
  }
93
121
 
94
122
  ${variantRules()}
123
+ ${disabledNeutralRules()}
95
124
  ${pressedRules()}
96
125
  ${sizeRules()}
97
126
  ${groupRules()}
@@ -118,7 +147,8 @@ function groupRules() {
118
147
  export function buttonClassCatalog() {
119
148
  return [
120
149
  'sw-button',
121
- ...buttonVariants.map((variant) => `sw-button-${variant}`),
150
+ ...webButtonVariants.map((variant) => `sw-button-${variant}`),
151
+ 'sw-button-disabled-neutral',
122
152
  ...buttonSizes.map((size) => `sw-button-${size}`),
123
153
  'sw-button-group',
124
154
  ...buttonGroupJustifies.map((name) => `sw-button-group-${name}`),
@@ -1,4 +1,6 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
+ import { appHeaderBarClassCatalog, cssAppHeaderBarClasses, } from './css-app-header-bar.js';
3
+ import { cssNavWordsClasses, navWordsClassCatalog } from './css-nav-words.js';
2
4
  import { zIndex } from './stacking.js';
3
5
  import { coarsePointerQuery, touchTarget } from './touch-target.js';
4
6
  const caption = typographyVariants.caption;
@@ -13,6 +15,8 @@ export function cssChromeClasses() {
13
15
  -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
14
16
  }
15
17
 
18
+ ${cssAppHeaderBarClasses()}
19
+
16
20
  .sw-app-header-sticky {
17
21
  position: sticky;
18
22
  top: 0;
@@ -49,6 +53,8 @@ export function cssChromeClasses() {
49
53
  }
50
54
  }
51
55
 
56
+ ${cssNavWordsClasses()}
57
+
52
58
  .sw-field-xs :is(select, input, textarea) {
53
59
  font-size: ${caption.fontSize}px;
54
60
  letter-spacing: ${caption.letterSpacing}px;
@@ -68,5 +74,12 @@ export function cssChromeClasses() {
68
74
  }`;
69
75
  }
70
76
  export function chromeClassCatalog() {
71
- return ['sw-app-header', 'sw-app-header-sticky', 'sw-nav', 'sw-field-xs'];
77
+ return [
78
+ 'sw-app-header',
79
+ 'sw-app-header-sticky',
80
+ ...appHeaderBarClassCatalog(),
81
+ 'sw-nav',
82
+ ...navWordsClassCatalog(),
83
+ 'sw-field-xs',
84
+ ];
72
85
  }
@@ -0,0 +1,2 @@
1
+ export declare function cssComboboxClasses(): string;
2
+ export declare function comboboxClassCatalog(): string[];
@@ -0,0 +1,136 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { zIndex } from './stacking.js';
3
+ import { quietFill } from './quiet-fill.js';
4
+ const caption = typographyVariants.caption;
5
+ const label = typographyVariants.label;
6
+ /*
7
+ * The combobox input is a Field's input. Its list is a glass popover on
8
+ * the popup layer (as Select's list), at least as wide as the input: a
9
+ * caption heading per group, then options at the md control height with
10
+ * the label, a badge and a muted description on one wrapping line. The
11
+ * active option (the one aria-activedescendant names) takes the soft
12
+ * muted fill; a hairline parts each option from the one before it, as
13
+ * the boards draw it; a disabled option is muted and not pressable; the action
14
+ * ends the list in the accent colour over a hairline. The empty text and
15
+ * the note are muted captions under the list.
16
+ */
17
+ export function cssComboboxClasses() {
18
+ return `.sw-combobox {
19
+ max-width: 100%;
20
+ min-width: 0;
21
+ }
22
+
23
+ .sw-combobox-popup {
24
+ background: var(--sw-glass-fill);
25
+ border: 1px solid var(--sw-glass-border);
26
+ border-radius: var(--sw-radius-sm);
27
+ box-shadow: var(--sw-elevation-sm), inset 0 1px 0 var(--sw-glass-specular);
28
+ backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
29
+ -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
30
+ box-sizing: border-box;
31
+ color: var(--sw-color-text);
32
+ inset: auto;
33
+ margin: 0;
34
+ max-height: min(var(--sw-select-max), calc(100% - var(--sw-space-2) - var(--sw-space-2)));
35
+ max-width: calc(100% - var(--sw-space-2) - var(--sw-space-2));
36
+ overflow: auto;
37
+ padding: var(--sw-space-1);
38
+ position: fixed;
39
+ ${zIndex('popup')}
40
+ }
41
+
42
+ .sw-combobox-heading {
43
+ color: var(--sw-color-muted);
44
+ font-family: var(--sw-font-sans);
45
+ font-size: ${caption.fontSize}px;
46
+ font-weight: ${label.fontWeight};
47
+ letter-spacing: ${caption.letterSpacing}px;
48
+ line-height: ${caption.lineHeight}px;
49
+ padding: var(--sw-space-2) var(--sw-space-3) var(--sw-space-1);
50
+ }
51
+
52
+ .sw-combobox-option {
53
+ align-items: center;
54
+ border-radius: var(--sw-radius-sm);
55
+ box-sizing: border-box;
56
+ column-gap: var(--sw-space-2);
57
+ cursor: pointer;
58
+ display: flex;
59
+ flex-wrap: wrap;
60
+ font-family: var(--sw-font-sans);
61
+ font-size: ${label.fontSize}px;
62
+ letter-spacing: ${label.letterSpacing}px;
63
+ line-height: ${label.lineHeight}px;
64
+ min-height: var(--sw-control-md-min-height);
65
+ overflow-wrap: anywhere;
66
+ padding: var(--sw-space-1) var(--sw-space-3);
67
+ }
68
+
69
+ .sw-combobox-option + .sw-combobox-option {
70
+ border-top: 1px solid var(--sw-color-border);
71
+ }
72
+
73
+ .sw-combobox-label {
74
+ font-weight: ${label.fontWeight};
75
+ }
76
+
77
+ .sw-combobox-description {
78
+ color: var(--sw-color-muted);
79
+ font-size: ${caption.fontSize}px;
80
+ letter-spacing: ${caption.letterSpacing}px;
81
+ line-height: ${caption.lineHeight}px;
82
+ }
83
+
84
+ .sw-combobox-option[aria-selected='true'] {
85
+ background: ${quietFill};
86
+ }
87
+
88
+ .sw-combobox-option[aria-disabled='true'] {
89
+ cursor: not-allowed;
90
+ }
91
+
92
+ .sw-combobox-option[aria-disabled='true'] .sw-combobox-label {
93
+ color: var(--sw-color-muted);
94
+ }
95
+
96
+ .sw-combobox-action {
97
+ border-top: 1px solid var(--sw-color-border);
98
+ color: var(--sw-color-accent);
99
+ margin-top: var(--sw-space-1);
100
+ }
101
+
102
+ .sw-combobox-empty,
103
+ .sw-combobox-note {
104
+ color: var(--sw-color-muted);
105
+ font-family: var(--sw-font-sans);
106
+ font-size: ${caption.fontSize}px;
107
+ letter-spacing: ${caption.letterSpacing}px;
108
+ line-height: ${caption.lineHeight}px;
109
+ padding: var(--sw-space-2) var(--sw-space-3);
110
+ }
111
+
112
+ @media (forced-colors: active) {
113
+ .sw-combobox-popup { border-color: CanvasText; }
114
+ .sw-combobox-option[aria-selected='true'] { background: Highlight; color: HighlightText; forced-color-adjust: none; }
115
+ .sw-combobox-option[aria-selected='true'] .sw-combobox-description { color: HighlightText; }
116
+ .sw-combobox-option[aria-selected='true'] .sw-badge { forced-color-adjust: auto; }
117
+ .sw-combobox-option[aria-disabled='true'] .sw-combobox-label { color: GrayText; }
118
+ .sw-combobox-option[aria-selected='true'][aria-disabled='true'] .sw-combobox-label { color: HighlightText; }
119
+ .sw-combobox-option + .sw-combobox-option { border-color: CanvasText; }
120
+ .sw-combobox-action { border-color: CanvasText; }
121
+ }`;
122
+ }
123
+ export function comboboxClassCatalog() {
124
+ return [
125
+ 'sw-combobox',
126
+ 'sw-combobox-popup',
127
+ 'sw-combobox-list',
128
+ 'sw-combobox-heading',
129
+ 'sw-combobox-option',
130
+ 'sw-combobox-label',
131
+ 'sw-combobox-description',
132
+ 'sw-combobox-action',
133
+ 'sw-combobox-empty',
134
+ 'sw-combobox-note',
135
+ ];
136
+ }
@@ -13,7 +13,9 @@ import { cssSegmentedClasses } from './css-segmented.js';
13
13
  import { cssTableClasses } from './css-table.js';
14
14
  import { cssDateFieldClasses } from './css-date-field.js';
15
15
  import { cssDialogClasses } from './css-dialog.js';
16
+ import { cssDialogBandedClasses } from './css-dialog-banded.js';
16
17
  import { cssDialogFrameClasses } from './css-dialog-frame.js';
18
+ import { cssComboboxClasses } from './css-combobox.js';
17
19
  import { cssSelectClasses } from './css-select.js';
18
20
  import { cssMultiSelectClasses } from './css-multi-select.js';
19
21
  import { cssSplitClasses } from './css-split.js';
@@ -64,6 +66,7 @@ function reducedTransparencyRules() {
64
66
  .sw-select-list,
65
67
  .sw-action-menu-list,
66
68
  .sw-multi-select-panel,
69
+ .sw-combobox-popup,
67
70
  .sw-date-field-calendar,
68
71
  .sw-toast {
69
72
  background: var(--sw-color-surface);
@@ -142,12 +145,15 @@ ${cssDialogClasses()}
142
145
 
143
146
  ${cssDialogFrameClasses()}
144
147
 
148
+ ${cssDialogBandedClasses()}
149
+
145
150
  ${cssAccordionClasses()}
146
151
 
147
152
  ${cssSelectClasses()}
148
153
 
149
154
  ${cssActionMenuClasses()}
150
155
 
156
+ ${cssComboboxClasses()}
151
157
  ${cssMultiSelectClasses()}
152
158
 
153
159
  ${cssActionBarClasses()}
@@ -1,4 +1,5 @@
1
- import { buttonVariants, typographyVariants } from '@scalewing/tokens';
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { webButtonVariants } from './css-button.js';
2
3
  export const badgeTones = [
3
4
  'neutral',
4
5
  'accent',
@@ -20,7 +21,7 @@ export function badgeClassNames(tone, size = 'md') {
20
21
  * button has no fill.
21
22
  */
22
23
  function badgeOnFilledButtonRules() {
23
- const filled = buttonVariants
24
+ const filled = webButtonVariants
24
25
  .filter((variant) => variant !== 'ghost')
25
26
  .map((variant) => `.sw-button-${variant}`)
26
27
  .join(', ');
@@ -1,6 +1,7 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { disabledControlForcedColors, disabledControlSurface, } from './css-document.js';
3
3
  import { zIndex } from './stacking.js';
4
+ import { quietFill } from './quiet-fill.js';
4
5
  const body = typographyVariants.body;
5
6
  const label = typographyVariants.label;
6
7
  const caption = typographyVariants.caption;
@@ -226,7 +227,7 @@ function calendarRules() {
226
227
 
227
228
  .sw-date-field-step:hover:not([aria-disabled='true']),
228
229
  .sw-date-field-day:hover:not([aria-disabled='true']) {
229
- background: color-mix(in srgb, var(--sw-color-muted) 16%, transparent);
230
+ background: ${quietFill};
230
231
  }
231
232
 
232
233
  /*
@@ -0,0 +1,21 @@
1
+ export declare const denominationGridFrames: readonly ["none", "table"];
2
+ /**
3
+ * How a strip sits on the page: `none` (the default) is open rows on the
4
+ * page's own surface; `table` is a framed table: the surface inside a
5
+ * hairline with rounded corners, the heading row on the subtle fill, a
6
+ * rule between columns, and the counts at label size, centred under
7
+ * their heads.
8
+ */
9
+ export type DenominationGridFrame = (typeof denominationGridFrames)[number];
10
+ export declare const denominationTotalEmphases: readonly ["default", "strong"];
11
+ /**
12
+ * How the row totals read: `default` is data type in the row's tone (a
13
+ * neutral row's muted); `strong` is label type in bold, a neutral row's in
14
+ * the text color and a toned row's in its tone.
15
+ */
16
+ export type DenominationTotalEmphasis = (typeof denominationTotalEmphases)[number];
17
+ export declare const denominationFramedClass = "sw-denomination-framed";
18
+ export declare const denominationScrollFramedClass = "sw-denomination-scroll-framed";
19
+ export declare const denominationTotalsStrongClass = "sw-denomination-totals-strong";
20
+ export declare function cssDenominationAppearanceClasses(): string;
21
+ export declare function denominationAppearanceClassCatalog(): string[];
@@ -0,0 +1,114 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { breakpointQuery } from './breakpoints.js';
3
+ const label = typographyVariants.label;
4
+ export const denominationGridFrames = ['none', 'table'];
5
+ export const denominationTotalEmphases = ['default', 'strong'];
6
+ export const denominationFramedClass = 'sw-denomination-framed';
7
+ export const denominationScrollFramedClass = 'sw-denomination-scroll-framed';
8
+ export const denominationTotalsStrongClass = 'sw-denomination-totals-strong';
9
+ const framed = `.sw-denomination-strip.${denominationFramedClass}`;
10
+ const strong = `.${denominationTotalsStrongClass}`;
11
+ /*
12
+ * A framed strip. The frame is the scroll region's border, so it stays put
13
+ * while a wide strip scrolls inside it and its shades still draw on the
14
+ * region's own shadow. The cells pad a space-3 step across (space-2 below
15
+ * md) and the body cells a space-4 step down, so a read-only row is about
16
+ * as tall as an entry row; the cell padding is zero-specificity past the
17
+ * frame class, so an entry cell keeps its own space-1 padding and, below
18
+ * md, the collapsed total column is collapsed again. The row labels pad
19
+ * the same step whatever their appearance. The pinned label sits on the
20
+ * surface at the same low specificity, so a quiet row's label keeps the
21
+ * subtle fill; the pinned corner takes the heading row's fill. The column
22
+ * rule is a border on every cell after a row's first, so forced colours
23
+ * keep it and RTL mirrors it; the collapsed total column draws none.
24
+ * Emitted after the grid's own rules, its label appearances and its
25
+ * narrow-screen padding, so it wins at every width.
26
+ */
27
+ function frameRules() {
28
+ return `.${denominationScrollFramedClass} {
29
+ background: var(--sw-color-surface);
30
+ border: 1px solid var(--sw-color-border);
31
+ border-radius: var(--sw-radius-sm);
32
+ box-sizing: border-box;
33
+ }
34
+
35
+ ${framed} :where(th, td) {
36
+ padding: var(--sw-space-4) var(--sw-space-3);
37
+ }
38
+
39
+ ${framed} thead :where(th, td),
40
+ ${framed} thead .sw-denomination-corner:first-child {
41
+ background: var(--sw-color-subtle);
42
+ padding-block: var(--sw-space-2);
43
+ }
44
+
45
+ ${framed} :is(th, td) + :is(th, td) {
46
+ border-inline-start: 1px solid var(--sw-color-border);
47
+ }
48
+
49
+ ${framed} :where(.sw-denomination-label) {
50
+ backdrop-filter: none;
51
+ -webkit-backdrop-filter: none;
52
+ background: var(--sw-color-surface);
53
+ }
54
+
55
+ ${framed} .sw-denomination-label {
56
+ padding-inline: var(--sw-space-3);
57
+ }
58
+
59
+ ${framed} .sw-denomination-head:not(.sw-denomination-total-head),
60
+ ${framed} .sw-denomination-cell {
61
+ text-align: center;
62
+ }
63
+
64
+ ${framed} .sw-denomination-cell {
65
+ font-size: ${label.fontSize}px;
66
+ letter-spacing: ${label.letterSpacing}px;
67
+ line-height: ${label.lineHeight}px;
68
+ }
69
+
70
+ @media ${breakpointQuery('below', 'md')} {
71
+ ${framed} :where(th, td),
72
+ ${framed} .sw-denomination-label { padding-inline: var(--sw-space-2); }
73
+ ${framed} .sw-denomination-total,
74
+ ${framed} .sw-denomination-total-head { border-inline-start: 0; padding: 0; }
75
+ }
76
+
77
+ @media (forced-colors: active) {
78
+ .${denominationScrollFramedClass},
79
+ ${framed} :is(th, td) + :is(th, td) { border-color: CanvasText; }
80
+ }`;
81
+ }
82
+ /*
83
+ * Strong totals: label type in bold, a neutral row's in the text color
84
+ * (its tone is the muted color) and a toned row's left in its tone, under
85
+ * the label on a phone too. One class more specific than the plain
86
+ * labels' inline total, so a neutral row's total is the text color there.
87
+ */
88
+ function totalRules() {
89
+ return `${strong} .sw-denomination-total {
90
+ font-size: ${label.fontSize}px;
91
+ font-weight: 700;
92
+ letter-spacing: ${label.letterSpacing}px;
93
+ line-height: ${label.lineHeight}px;
94
+ }
95
+
96
+ ${strong} .sw-denomination-total-inline { font-weight: 700; }
97
+
98
+ ${strong} .sw-denomination-row-neutral .sw-denomination-total,
99
+ ${strong} .sw-denomination-row-neutral .sw-denomination-total-inline {
100
+ color: var(--sw-color-text);
101
+ }`;
102
+ }
103
+ export function cssDenominationAppearanceClasses() {
104
+ return `${frameRules()}
105
+
106
+ ${totalRules()}`;
107
+ }
108
+ export function denominationAppearanceClassCatalog() {
109
+ return [
110
+ denominationScrollFramedClass,
111
+ denominationFramedClass,
112
+ denominationTotalsStrongClass,
113
+ ];
114
+ }