@scalewing/react 1.31.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 (86) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +19 -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.js +2 -2
  13. package/dist/components/DateField.d.ts +9 -1
  14. package/dist/components/DateField.js +4 -3
  15. package/dist/components/DenominationGrid.d.ts +5 -3
  16. package/dist/components/DenominationGrid.js +21 -11
  17. package/dist/components/Dialog.d.ts +9 -1
  18. package/dist/components/Dialog.js +8 -4
  19. package/dist/components/Field.d.ts +8 -0
  20. package/dist/components/Field.js +4 -3
  21. package/dist/components/FieldLabelText.d.ts +21 -3
  22. package/dist/components/FieldLabelText.js +16 -4
  23. package/dist/components/FilterChips.d.ts +9 -1
  24. package/dist/components/FilterChips.js +4 -2
  25. package/dist/components/Nav.d.ts +14 -2
  26. package/dist/components/Nav.js +6 -2
  27. package/dist/components/SegmentedControl.d.ts +5 -2
  28. package/dist/components/SegmentedControl.js +2 -1
  29. package/dist/components/Select.d.ts +9 -1
  30. package/dist/components/Select.js +4 -3
  31. package/dist/components/Stepper.d.ts +13 -0
  32. package/dist/components/Stepper.js +8 -2
  33. package/dist/components/combobox/types.d.ts +8 -0
  34. package/dist/components/denomination-grid/DenominationStrip.d.ts +4 -2
  35. package/dist/components/denomination-grid/DenominationStrip.js +3 -2
  36. package/dist/components/denomination-grid/DenominationTiles.d.ts +1 -1
  37. package/dist/components/denomination-grid/EntryCell.js +1 -1
  38. package/dist/components/denomination-grid/StripRow.js +5 -2
  39. package/dist/components/denomination-grid/types.d.ts +35 -1
  40. package/dist/components/dialog/DialogTitle.d.ts +5 -3
  41. package/dist/components/dialog/DialogTitle.js +4 -4
  42. package/dist/css/css-action-menu-trigger.d.ts +11 -0
  43. package/dist/css/css-action-menu-trigger.js +66 -0
  44. package/dist/css/css-action-menu.js +7 -2
  45. package/dist/css/css-app-header-bar.d.ts +10 -0
  46. package/dist/css/css-app-header-bar.js +26 -0
  47. package/dist/css/css-box-border.d.ts +6 -1
  48. package/dist/css/css-box-border.js +8 -4
  49. package/dist/css/css-button-group-attached.js +5 -2
  50. package/dist/css/css-button-group-track.d.ts +11 -0
  51. package/dist/css/css-button-group-track.js +48 -0
  52. package/dist/css/css-button.d.ts +19 -2
  53. package/dist/css/css-button.js +31 -1
  54. package/dist/css/css-chrome.js +14 -1
  55. package/dist/css/css-combobox.js +2 -1
  56. package/dist/css/css-components.js +3 -0
  57. package/dist/css/css-data.js +3 -2
  58. package/dist/css/css-date-field.js +2 -1
  59. package/dist/css/css-denomination-appearance.d.ts +21 -0
  60. package/dist/css/css-denomination-appearance.js +114 -0
  61. package/dist/css/css-denomination-cell-tones.d.ts +8 -0
  62. package/dist/css/css-denomination-cell-tones.js +20 -1
  63. package/dist/css/css-denomination-grid.js +4 -0
  64. package/dist/css/css-dialog-banded.d.ts +11 -0
  65. package/dist/css/css-dialog-banded.js +84 -0
  66. package/dist/css/css-multi-select.js +2 -1
  67. package/dist/css/css-nav-words.d.ts +13 -0
  68. package/dist/css/css-nav-words.js +51 -0
  69. package/dist/css/css-notice.d.ts +4 -11
  70. package/dist/css/css-notice.js +6 -22
  71. package/dist/css/css-segmented-raised.d.ts +3 -0
  72. package/dist/css/css-segmented-raised.js +72 -0
  73. package/dist/css/css-segmented.js +9 -3
  74. package/dist/css/css-select.js +2 -1
  75. package/dist/css/css-split.js +2 -1
  76. package/dist/css/css-stepper.js +7 -0
  77. package/dist/css/quiet-fill.d.ts +7 -0
  78. package/dist/css/quiet-fill.js +7 -0
  79. package/dist/css/stacking.js +2 -1
  80. package/dist/css/stylesheet.js +2 -0
  81. package/dist/css/tone-tint.d.ts +21 -0
  82. package/dist/css/tone-tint.js +23 -0
  83. package/dist/index.d.ts +11 -9
  84. package/dist/index.js +9 -7
  85. package/dist/styles.css +300 -10
  86. package/package.json +1 -1
@@ -0,0 +1,11 @@
1
+ export declare const actionMenuTriggerAppearances: readonly ["framed", "text"];
2
+ /**
3
+ * How an ActionMenu's trigger looks: `framed` (the default) is a small
4
+ * outlined control; `text` is its words in the text color with a chevron
5
+ * after them and no fill or outline until hovered or open, such as a
6
+ * person's name that opens their account menu in a header.
7
+ */
8
+ export type ActionMenuTriggerAppearance = (typeof actionMenuTriggerAppearances)[number];
9
+ export declare const actionMenuTriggerTextClass = "sw-action-menu-trigger-text";
10
+ export declare function cssActionMenuTriggerClasses(): string;
11
+ export declare function actionMenuTriggerClassCatalog(): string[];
@@ -0,0 +1,66 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { chevronStroke } from './chevron.js';
3
+ import { quietFill } from './quiet-fill.js';
4
+ import { coarsePointerQuery, touchTarget } from './touch-target.js';
5
+ const label = typographyVariants.label;
6
+ export const actionMenuTriggerAppearances = ['framed', 'text'];
7
+ export const actionMenuTriggerTextClass = 'sw-action-menu-trigger-text';
8
+ const text = `.sw-action-menu-trigger.${actionMenuTriggerTextClass}`;
9
+ /*
10
+ * A text trigger: no fill and a transparent border (kept, so forced colors
11
+ * can draw one), its words in label type at medium weight in the text
12
+ * color, the sm control height (the 44 px target on a coarse pointer), and
13
+ * the shared muted chevron pointing down after them, a space-2 gap apart.
14
+ * The quiet fill shows while the menu is open, so the open state is seen
15
+ * as well as announced, and on hover where the pointer can hover (a tap
16
+ * leaves no fill behind). Two classes, so it outranks the framed
17
+ * trigger's rules; the coarse-pointer target is restated after. Forced
18
+ * colors draw the kept border in the system's border color, the chevron
19
+ * in CanvasText as every chevron, and the open trigger's border in
20
+ * Highlight.
21
+ */
22
+ export function cssActionMenuTriggerClasses() {
23
+ return `${text} {
24
+ align-items: center;
25
+ background: transparent;
26
+ border-color: transparent;
27
+ display: inline-flex;
28
+ font-family: var(--sw-font-sans);
29
+ font-size: ${label.fontSize}px;
30
+ font-weight: 500;
31
+ gap: var(--sw-space-2);
32
+ letter-spacing: ${label.letterSpacing}px;
33
+ line-height: ${label.lineHeight}px;
34
+ min-height: var(--sw-control-sm-min-height);
35
+ padding-inline: var(--sw-space-3) var(--sw-space-2);
36
+ }
37
+
38
+ ${text}::after {
39
+ ${chevronStroke}
40
+ content: '';
41
+ flex: none;
42
+ transform: translateY(-25%) rotate(45deg);
43
+ }
44
+
45
+ ${text}[aria-expanded='true'] {
46
+ background: ${quietFill};
47
+ }
48
+
49
+ @media (hover: hover) {
50
+ ${text}:hover:not(:disabled) {
51
+ background: ${quietFill};
52
+ }
53
+ }
54
+
55
+ @media ${coarsePointerQuery} {
56
+ ${text} { min-height: ${touchTarget}; }
57
+ }
58
+
59
+ @media (forced-colors: active) {
60
+ ${text}::after { border-color: CanvasText; }
61
+ ${text}[aria-expanded='true'] { border-color: Highlight; }
62
+ }`;
63
+ }
64
+ export function actionMenuTriggerClassCatalog() {
65
+ return [actionMenuTriggerTextClass];
66
+ }
@@ -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
  }
@@ -1,5 +1,6 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { zIndex } from './stacking.js';
3
+ import { quietFill } from './quiet-fill.js';
3
4
  const caption = typographyVariants.caption;
4
5
  const label = typographyVariants.label;
5
6
  /*
@@ -81,7 +82,7 @@ export function cssComboboxClasses() {
81
82
  }
82
83
 
83
84
  .sw-combobox-option[aria-selected='true'] {
84
- background: color-mix(in srgb, var(--sw-color-muted) 16%, transparent);
85
+ background: ${quietFill};
85
86
  }
86
87
 
87
88
  .sw-combobox-option[aria-disabled='true'] {
@@ -13,6 +13,7 @@ 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';
17
18
  import { cssComboboxClasses } from './css-combobox.js';
18
19
  import { cssSelectClasses } from './css-select.js';
@@ -144,6 +145,8 @@ ${cssDialogClasses()}
144
145
 
145
146
  ${cssDialogFrameClasses()}
146
147
 
148
+ ${cssDialogBandedClasses()}
149
+
147
150
  ${cssAccordionClasses()}
148
151
 
149
152
  ${cssSelectClasses()}
@@ -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
+ }
@@ -1,4 +1,12 @@
1
1
  import { type BadgeTone } from './css-data.js';
2
+ export declare const denominationCellToneAppearances: readonly ["text", "pill"];
3
+ /**
4
+ * How a toned count shows its tone: `text` sets the count in the tone;
5
+ * `pill` sets it on a rounded tint of the tone in the tone's ink, the
6
+ * Notice's fill and words, in a strip's read-only counts.
7
+ */
8
+ export type DenominationCellToneAppearance = (typeof denominationCellToneAppearances)[number];
9
+ export declare const denominationCellTonesPillClass = "sw-denomination-cell-tones-pill";
2
10
  /**
3
11
  * The classes that tone one count: a marker that applies the shared rules and
4
12
  * the tone that sets their color. A strip cell (`td`) and a tile (`li`) take