@scalewing/react 1.47.0 → 1.49.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 (135) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +215 -6
  3. package/dist/command-palette.test-support.d.ts +21 -0
  4. package/dist/command-palette.test-support.js +55 -0
  5. package/dist/components/AppHeader.d.ts +3 -0
  6. package/dist/components/AppShell.d.ts +4 -3
  7. package/dist/components/AppShell.js +24 -7
  8. package/dist/components/Band.d.ts +30 -0
  9. package/dist/components/Band.js +23 -0
  10. package/dist/components/ButtonLink.d.ts +33 -0
  11. package/dist/components/ButtonLink.js +27 -0
  12. package/dist/components/CommandPalette.d.ts +12 -0
  13. package/dist/components/CommandPalette.js +143 -0
  14. package/dist/components/CommandPaletteTrigger.d.ts +9 -0
  15. package/dist/components/CommandPaletteTrigger.js +16 -0
  16. package/dist/components/ContextSwitcher.d.ts +12 -0
  17. package/dist/components/ContextSwitcher.js +62 -0
  18. package/dist/components/EmptyState.d.ts +23 -0
  19. package/dist/components/EmptyState.js +40 -0
  20. package/dist/components/FixtureGroup.d.ts +9 -0
  21. package/dist/components/FixtureGroup.js +16 -0
  22. package/dist/components/FixtureRow.d.ts +10 -0
  23. package/dist/components/FixtureRow.js +46 -0
  24. package/dist/components/GlyphTile.d.ts +9 -3
  25. package/dist/components/GlyphTile.js +15 -4
  26. package/dist/components/Grid.d.ts +11 -3
  27. package/dist/components/Grid.js +13 -4
  28. package/dist/components/Hero.d.ts +26 -0
  29. package/dist/components/Hero.js +23 -0
  30. package/dist/components/SiteFooter.d.ts +12 -0
  31. package/dist/components/SiteFooter.js +23 -0
  32. package/dist/components/SiteHeader.d.ts +14 -0
  33. package/dist/components/SiteHeader.js +24 -0
  34. package/dist/components/Skeleton.d.ts +22 -0
  35. package/dist/components/Skeleton.js +30 -0
  36. package/dist/components/Stepper.d.ts +11 -1
  37. package/dist/components/Stepper.js +10 -8
  38. package/dist/components/Text.js +3 -0
  39. package/dist/components/app-shell/AppShellBar.d.ts +23 -0
  40. package/dist/components/app-shell/AppShellBar.js +25 -0
  41. package/dist/components/app-shell/types.d.ts +36 -1
  42. package/dist/components/app-shell/validate.d.ts +4 -3
  43. package/dist/components/app-shell/validate.js +21 -3
  44. package/dist/components/command-palette/PaletteList.d.ts +20 -0
  45. package/dist/components/command-palette/PaletteList.js +21 -0
  46. package/dist/components/command-palette/shortcut.d.ts +19 -0
  47. package/dist/components/command-palette/shortcut.js +75 -0
  48. package/dist/components/command-palette/types.d.ts +72 -0
  49. package/dist/components/command-palette/types.js +2 -0
  50. package/dist/components/command-palette/validate.d.ts +10 -0
  51. package/dist/components/command-palette/validate.js +50 -0
  52. package/dist/components/context-switcher/SwitcherOption.d.ts +17 -0
  53. package/dist/components/context-switcher/SwitcherOption.js +20 -0
  54. package/dist/components/context-switcher/SwitcherPopup.d.ts +31 -0
  55. package/dist/components/context-switcher/SwitcherPopup.js +111 -0
  56. package/dist/components/context-switcher/options.d.ts +33 -0
  57. package/dist/components/context-switcher/options.js +60 -0
  58. package/dist/components/context-switcher/types.d.ts +96 -0
  59. package/dist/components/context-switcher/types.js +1 -0
  60. package/dist/components/context-switcher/use-switcher-list.d.ts +29 -0
  61. package/dist/components/context-switcher/use-switcher-list.js +93 -0
  62. package/dist/components/context-switcher/validate.d.ts +8 -0
  63. package/dist/components/context-switcher/validate.js +94 -0
  64. package/dist/components/fixture/fixture-list-context.d.ts +4 -0
  65. package/dist/components/fixture/fixture-list-context.js +9 -0
  66. package/dist/components/fixture/types.d.ts +50 -0
  67. package/dist/components/fixture/types.js +1 -0
  68. package/dist/components/fixture/validate.d.ts +8 -0
  69. package/dist/components/fixture/validate.js +36 -0
  70. package/dist/components/site-footer/types.d.ts +31 -0
  71. package/dist/components/site-footer/types.js +1 -0
  72. package/dist/components/site-footer/validate.d.ts +7 -0
  73. package/dist/components/site-footer/validate.js +22 -0
  74. package/dist/components/site-header/SiteHeaderLinks.d.ts +7 -0
  75. package/dist/components/site-header/SiteHeaderLinks.js +7 -0
  76. package/dist/components/site-header/types.d.ts +38 -0
  77. package/dist/components/site-header/types.js +1 -0
  78. package/dist/components/site-header/use-menu-dismiss.d.ts +8 -0
  79. package/dist/components/site-header/use-menu-dismiss.js +62 -0
  80. package/dist/components/site-header/validate.d.ts +7 -0
  81. package/dist/components/site-header/validate.js +17 -0
  82. package/dist/components/stepper/StepperCountFrame.d.ts +15 -0
  83. package/dist/components/stepper/StepperCountFrame.js +25 -0
  84. package/dist/components/use-element-height.d.ts +10 -0
  85. package/dist/components/use-element-height.js +35 -0
  86. package/dist/context-switcher.test-support.d.ts +9 -0
  87. package/dist/context-switcher.test-support.js +41 -0
  88. package/dist/css/css-app-shell-page-bar.d.ts +3 -0
  89. package/dist/css/css-app-shell-page-bar.js +81 -0
  90. package/dist/css/css-app-shell.js +6 -1
  91. package/dist/css/css-band.d.ts +10 -0
  92. package/dist/css/css-band.js +101 -0
  93. package/dist/css/css-button-link.d.ts +3 -0
  94. package/dist/css/css-button-link.js +48 -0
  95. package/dist/css/css-command-palette-key.d.ts +1 -0
  96. package/dist/css/css-command-palette-key.js +33 -0
  97. package/dist/css/css-command-palette-trigger.d.ts +2 -0
  98. package/dist/css/css-command-palette-trigger.js +88 -0
  99. package/dist/css/css-command-palette.d.ts +2 -0
  100. package/dist/css/css-command-palette.js +209 -0
  101. package/dist/css/css-components.js +30 -0
  102. package/dist/css/css-context-switcher-trigger.d.ts +15 -0
  103. package/dist/css/css-context-switcher-trigger.js +175 -0
  104. package/dist/css/css-context-switcher.d.ts +4 -0
  105. package/dist/css/css-context-switcher.js +219 -0
  106. package/dist/css/css-count-pill.d.ts +2 -2
  107. package/dist/css/css-count-pill.js +11 -3
  108. package/dist/css/css-empty-state.d.ts +2 -0
  109. package/dist/css/css-empty-state.js +77 -0
  110. package/dist/css/css-fixture.d.ts +5 -0
  111. package/dist/css/css-fixture.js +260 -0
  112. package/dist/css/css-glyph-tile.d.ts +2 -0
  113. package/dist/css/css-glyph-tile.js +29 -1
  114. package/dist/css/css-grid-span.js +5 -2
  115. package/dist/css/css-grid.d.ts +13 -0
  116. package/dist/css/css-grid.js +17 -1
  117. package/dist/css/css-hero.d.ts +2 -0
  118. package/dist/css/css-hero.js +62 -0
  119. package/dist/css/css-site-footer.d.ts +2 -0
  120. package/dist/css/css-site-footer.js +152 -0
  121. package/dist/css/css-site-header.d.ts +2 -0
  122. package/dist/css/css-site-header.js +262 -0
  123. package/dist/css/css-skeleton.d.ts +10 -0
  124. package/dist/css/css-skeleton.js +110 -0
  125. package/dist/css/css-stepper-unit.d.ts +9 -0
  126. package/dist/css/css-stepper-unit.js +102 -0
  127. package/dist/css/css-stepper.js +5 -1
  128. package/dist/css/stacking.js +7 -4
  129. package/dist/css/stylesheet.js +20 -0
  130. package/dist/index.d.ts +18 -2
  131. package/dist/index.js +13 -1
  132. package/dist/stepper-count.d.ts +8 -0
  133. package/dist/stepper-count.js +17 -0
  134. package/dist/styles.css +2202 -702
  135. package/package.json +2 -2
@@ -1,13 +1,17 @@
1
1
  import { cssChartToneClasses } from './chart-tones.js';
2
2
  import { cssActionBarClasses } from './css-action-bar.js';
3
3
  import { cssActionMenuClasses } from './css-action-menu.js';
4
+ import { cssCommandPaletteClasses } from './css-command-palette.js';
5
+ import { cssContextSwitcherClasses } from './css-context-switcher.js';
4
6
  import { cssAccordionClasses } from './css-accordion.js';
5
7
  import { cssButtonClasses } from './css-button.js';
8
+ import { cssButtonLinkClasses } from './css-button-link.js';
6
9
  import { cssCalendarButtonClasses } from './css-calendar-button.js';
7
10
  import { cssDateRangeClasses } from './css-date-range.js';
8
11
  import { cssFileFieldClasses } from './css-file-field.js';
9
12
  import { cssArticleClasses } from './css-article.js';
10
13
  import { cssDocumentViewerClasses } from './css-document-viewer.js';
14
+ import { cssFixtureClasses } from './css-fixture.js';
11
15
  import { cssListClasses } from './css-list.js';
12
16
  import { cssMarkdownClasses } from './css-markdown.js';
13
17
  import { cssCountPillClasses } from './css-count-pill.js';
@@ -38,6 +42,8 @@ import { cssSplitClasses } from './css-split.js';
38
42
  import { cssSwitchClasses } from './css-switch.js';
39
43
  import { cssTextClasses } from './css-text.js';
40
44
  import { cssFontMonoClasses } from './css-font-mono.js';
45
+ import { cssSkeletonClasses } from './css-skeleton.js';
46
+ import { cssEmptyStateClasses } from './css-empty-state.js';
41
47
  import { cssSpinnerClasses } from './css-spinner.js';
42
48
  import { cssProgressClasses } from './css-progress.js';
43
49
  import { cssNoticeClasses } from './css-notice.js';
@@ -45,6 +51,10 @@ import { cssGlyphTileClasses } from './css-glyph-tile.js';
45
51
  import { cssTooltipClasses } from './css-tooltip.js';
46
52
  import { cssInfoTipClasses } from './css-info-tip.js';
47
53
  import { cssSeparatorClasses } from './css-separator.js';
54
+ import { cssBandClasses } from './css-band.js';
55
+ import { cssHeroClasses } from './css-hero.js';
56
+ import { cssSiteFooterClasses } from './css-site-footer.js';
57
+ import { cssSiteHeaderClasses } from './css-site-header.js';
48
58
  import { cssStatTileClasses } from './css-stat-tile.js';
49
59
  import { cssDescriptionListClasses } from './css-description-list.js';
50
60
  import { cssTabsClasses } from './css-tabs.js';
@@ -140,6 +150,8 @@ ${cssFontMonoClasses()}
140
150
 
141
151
  ${cssButtonClasses()}
142
152
 
153
+ ${cssButtonLinkClasses()}
154
+
143
155
  ${cssCheckboxClasses()}
144
156
 
145
157
  ${cssRadioGroupClasses()}
@@ -158,6 +170,8 @@ ${cssDateRangeClasses()}
158
170
 
159
171
  ${cssListClasses()}
160
172
 
173
+ ${cssFixtureClasses()}
174
+
161
175
  ${cssFileFieldClasses()}
162
176
 
163
177
  ${cssArticleClasses()}
@@ -186,6 +200,14 @@ ${cssChromeClasses()}
186
200
 
187
201
  ${cssSectionNavClasses()}
188
202
 
203
+ ${cssBandClasses()}
204
+
205
+ ${cssHeroClasses()}
206
+
207
+ ${cssSiteFooterClasses()}
208
+
209
+ ${cssSiteHeaderClasses()}
210
+
189
211
  ${cssAppShellClasses()}
190
212
 
191
213
  ${cssDialogClasses()}
@@ -204,6 +226,10 @@ ${cssSelectClasses()}
204
226
 
205
227
  ${cssActionMenuClasses()}
206
228
 
229
+ ${cssContextSwitcherClasses()}
230
+
231
+ ${cssCommandPaletteClasses()}
232
+
207
233
  ${cssComboboxClasses()}
208
234
  ${cssMultiSelectClasses()}
209
235
 
@@ -213,6 +239,10 @@ ${cssSwitchClasses()}
213
239
 
214
240
  ${cssSpinnerClasses()}
215
241
 
242
+ ${cssSkeletonClasses()}
243
+
244
+ ${cssEmptyStateClasses()}
245
+
216
246
  ${cssProgressClasses()}
217
247
 
218
248
  ${cssNoticeClasses()}
@@ -0,0 +1,15 @@
1
+ export declare const contextSwitcherTriggerAppearances: readonly ["framed", "text", "pill", "icon"];
2
+ /**
3
+ * How a ContextSwitcher's trigger looks: `framed` (the default) is the
4
+ * outlined control; `text` is its content and a chevron with no fill until
5
+ * hovered or open; `pill` is a rounded outlined pill at label weight;
6
+ * `icon` is a glyph alone in a round quiet button with no chevron.
7
+ */
8
+ export type ContextSwitcherTriggerAppearance = (typeof contextSwitcherTriggerAppearances)[number];
9
+ export declare const contextSwitcherWidths: readonly ["content", "full"];
10
+ /** `full` makes the trigger fill its column, such as a sidebar's top. */
11
+ export type ContextSwitcherWidth = (typeof contextSwitcherWidths)[number];
12
+ /** The modifier class of an appearance, such as `sw-context-switcher-trigger-pill`. */
13
+ export declare function contextSwitcherTriggerClass(appearance: ContextSwitcherTriggerAppearance): string;
14
+ export declare function cssContextSwitcherTriggerClasses(): string;
15
+ export declare function contextSwitcherTriggerClassCatalog(): string[];
@@ -0,0 +1,175 @@
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 contextSwitcherTriggerAppearances = [
7
+ 'framed',
8
+ 'text',
9
+ 'pill',
10
+ 'icon',
11
+ ];
12
+ export const contextSwitcherWidths = ['content', 'full'];
13
+ /** The modifier class of an appearance, such as `sw-context-switcher-trigger-pill`. */
14
+ export function contextSwitcherTriggerClass(appearance) {
15
+ return `sw-context-switcher-trigger-${appearance}`;
16
+ }
17
+ const trigger = '.sw-context-switcher-trigger';
18
+ const framed = `${trigger}.${contextSwitcherTriggerClass('framed')}`;
19
+ const pill = `${trigger}.${contextSwitcherTriggerClass('pill')}`;
20
+ const icon = `${trigger}.${contextSwitcherTriggerClass('icon')}`;
21
+ /*
22
+ * Every trigger: a real button in label type in the text colour, its
23
+ * content (a mark and words, which may be two lines) taking the free space
24
+ * at the inline start, then the shared muted chevron pointing down; the sm
25
+ * control height, the 44 px target on a coarse pointer. A transparent
26
+ * border is kept for forced colours. The quiet fill shows while open and
27
+ * under a hovering pointer, so the open state is seen as well as read.
28
+ */
29
+ function baseRules() {
30
+ return `.sw-context-switcher {
31
+ display: inline-flex;
32
+ max-width: 100%;
33
+ }
34
+
35
+ .sw-context-switcher-full {
36
+ display: flex;
37
+ width: 100%;
38
+ }
39
+
40
+ ${trigger} {
41
+ align-items: center;
42
+ appearance: none;
43
+ background: transparent;
44
+ border: 1px solid transparent;
45
+ border-radius: var(--sw-radius-sm);
46
+ box-sizing: border-box;
47
+ color: var(--sw-color-text);
48
+ cursor: pointer;
49
+ display: inline-flex;
50
+ font-family: var(--sw-font-sans);
51
+ font-size: ${label.fontSize}px;
52
+ font-weight: 500;
53
+ gap: var(--sw-space-2);
54
+ letter-spacing: ${label.letterSpacing}px;
55
+ line-height: ${label.lineHeight}px;
56
+ margin: 0;
57
+ max-width: 100%;
58
+ min-height: var(--sw-control-sm-min-height);
59
+ padding-block: var(--sw-space-1);
60
+ padding-inline: var(--sw-space-3) var(--sw-space-2);
61
+ text-align: start;
62
+ }
63
+
64
+ .sw-context-switcher-full ${trigger} {
65
+ width: 100%;
66
+ }
67
+
68
+ .sw-context-switcher-trigger-content {
69
+ align-items: center;
70
+ display: flex;
71
+ flex: 1 1 auto;
72
+ gap: var(--sw-space-2);
73
+ min-width: 0;
74
+ overflow-wrap: anywhere;
75
+ }
76
+
77
+ ${trigger}:not(.${contextSwitcherTriggerClass('icon')})::after {
78
+ ${chevronStroke}
79
+ content: '';
80
+ flex: none;
81
+ transform: translateY(-25%) rotate(45deg);
82
+ }
83
+
84
+ ${trigger}[aria-expanded='true'] {
85
+ background: ${quietFill};
86
+ }
87
+
88
+ @media (hover: hover) {
89
+ ${trigger}:hover {
90
+ background: ${quietFill};
91
+ }
92
+ }
93
+
94
+ ${trigger}:focus-visible {
95
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
96
+ outline-offset: var(--sw-focus-ring-offset);
97
+ }
98
+
99
+ @media ${coarsePointerQuery} {
100
+ ${trigger} { min-height: ${touchTarget}; }
101
+ }`;
102
+ }
103
+ /*
104
+ * The appearances: `framed` the field's glass fill inside the border
105
+ * hairline; `pill` the same hairline on the surface, fully rounded, at
106
+ * label weight; `icon` a round button as wide as it is tall, the glyph
107
+ * centred and muted, no chevron. Two classes, so each outranks the base.
108
+ */
109
+ function appearanceRules() {
110
+ return `${framed} {
111
+ background: var(--sw-glass-fill);
112
+ border-color: var(--sw-color-border);
113
+ }
114
+
115
+ ${pill} {
116
+ background: var(--sw-color-surface);
117
+ border-color: var(--sw-color-border);
118
+ border-radius: var(--sw-radius-pill);
119
+ font-weight: ${label.fontWeight};
120
+ }
121
+
122
+ ${framed}[aria-expanded='true'],
123
+ ${pill}[aria-expanded='true'] {
124
+ background: ${quietFill};
125
+ }
126
+
127
+ @media (hover: hover) {
128
+ ${framed}:hover,
129
+ ${pill}:hover {
130
+ background: ${quietFill};
131
+ }
132
+ }
133
+
134
+ ${icon} {
135
+ border-radius: var(--sw-radius-pill);
136
+ color: var(--sw-color-muted);
137
+ justify-content: center;
138
+ min-width: var(--sw-control-sm-min-height);
139
+ padding: 0;
140
+ }
141
+
142
+ ${icon} .sw-context-switcher-trigger-content {
143
+ flex: none;
144
+ justify-content: center;
145
+ }
146
+
147
+ ${icon}[aria-expanded='true'] {
148
+ color: var(--sw-color-text);
149
+ }
150
+
151
+ @media ${coarsePointerQuery} {
152
+ ${icon} { min-width: ${touchTarget}; }
153
+ }
154
+
155
+ @media (forced-colors: active) {
156
+ ${trigger}::after { border-color: CanvasText; }
157
+ ${trigger}[aria-expanded='true'] { border-color: Highlight; }
158
+ ${icon} { color: ButtonText; }
159
+ }`;
160
+ }
161
+ /* ContextSwitcher's trigger (see components/ContextSwitcher.tsx). */
162
+ export function cssContextSwitcherTriggerClasses() {
163
+ return `${baseRules()}
164
+
165
+ ${appearanceRules()}`;
166
+ }
167
+ export function contextSwitcherTriggerClassCatalog() {
168
+ return [
169
+ 'sw-context-switcher',
170
+ 'sw-context-switcher-full',
171
+ 'sw-context-switcher-trigger',
172
+ 'sw-context-switcher-trigger-content',
173
+ ...contextSwitcherTriggerAppearances.map(contextSwitcherTriggerClass),
174
+ ];
175
+ }
@@ -0,0 +1,4 @@
1
+ /** The trigger's width, set on the open popup so it is never narrower. */
2
+ export declare const contextSwitcherTriggerWidthVariable = "--sw-context-switcher-trigger-width";
3
+ export declare function cssContextSwitcherClasses(): string;
4
+ export declare function contextSwitcherClassCatalog(): string[];
@@ -0,0 +1,219 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { checkStrokeIn } from './chevron.js';
3
+ import { contextSwitcherTriggerClassCatalog, cssContextSwitcherTriggerClasses, } from './css-context-switcher-trigger.js';
4
+ import { quietFill } from './quiet-fill.js';
5
+ import { zIndex } from './stacking.js';
6
+ import { coarsePointerQuery, touchTarget } from './touch-target.js';
7
+ const caption = typographyVariants.caption;
8
+ const label = typographyVariants.label;
9
+ /* The popup's least width, a named geometry of this control. */
10
+ const popupMinWidth = '15rem';
11
+ /** The trigger's width, set on the open popup so it is never narrower. */
12
+ export const contextSwitcherTriggerWidthVariable = '--sw-context-switcher-trigger-width';
13
+ const viewportCap = 'calc(100% - var(--sw-space-2) - var(--sw-space-2))';
14
+ /*
15
+ * The popup: a glass panel on the top layer beside the trigger (placed by
16
+ * script, as the menus are), at least 15rem and as wide as the trigger
17
+ * (its width set by the component), capped by the dialog width (or the
18
+ * trigger's, when wider) and the viewport. A column: the filter field,
19
+ * the listbox (the only part that scrolls), the empty words, the footer.
20
+ */
21
+ function popupRules() {
22
+ return `.sw-context-switcher-popup {
23
+ background: var(--sw-glass-fill);
24
+ border: 1px solid var(--sw-glass-border);
25
+ border-radius: var(--sw-radius-sm);
26
+ box-shadow: var(--sw-elevation-sm), inset 0 1px 0 var(--sw-glass-specular);
27
+ backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
28
+ -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
29
+ box-sizing: border-box;
30
+ color: var(--sw-color-text);
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: var(--sw-space-1);
34
+ inset: auto;
35
+ margin: 0;
36
+ max-height: ${viewportCap};
37
+ max-width: min(max(var(--sw-dialog-max), var(${contextSwitcherTriggerWidthVariable}, 0px)), ${viewportCap});
38
+ min-width: min(max(${popupMinWidth}, var(${contextSwitcherTriggerWidthVariable}, 0px)), ${viewportCap});
39
+ padding: var(--sw-space-1);
40
+ position: fixed;
41
+ width: max-content;
42
+ ${zIndex('popup')}
43
+ }
44
+
45
+ .sw-context-switcher-filter {
46
+ box-sizing: border-box;
47
+ flex: none;
48
+ margin: var(--sw-space-1);
49
+ width: calc(100% - var(--sw-space-2));
50
+ }
51
+
52
+ .sw-context-switcher-list {
53
+ flex: 1 1 auto;
54
+ max-height: var(--sw-select-max);
55
+ min-height: 0;
56
+ overflow-y: auto;
57
+ overscroll-behavior: contain;
58
+ }
59
+
60
+ .sw-context-switcher-list:focus {
61
+ outline: none;
62
+ }
63
+
64
+ .sw-context-switcher-heading {
65
+ color: var(--sw-color-muted);
66
+ font-family: var(--sw-font-sans);
67
+ font-size: ${caption.fontSize}px;
68
+ font-weight: 600;
69
+ letter-spacing: ${caption.letterSpacing}px;
70
+ line-height: ${caption.lineHeight}px;
71
+ padding: var(--sw-space-2) var(--sw-space-2) var(--sw-space-1);
72
+ }
73
+
74
+ .sw-context-switcher-empty {
75
+ color: var(--sw-color-muted);
76
+ font-family: var(--sw-font-sans);
77
+ font-size: ${caption.fontSize}px;
78
+ letter-spacing: ${caption.letterSpacing}px;
79
+ line-height: ${caption.lineHeight}px;
80
+ margin: 0;
81
+ padding: var(--sw-space-2);
82
+ }
83
+
84
+ .sw-context-switcher-footer {
85
+ border-top: 1px solid var(--sw-color-border);
86
+ display: flex;
87
+ flex: none;
88
+ flex-wrap: wrap;
89
+ gap: var(--sw-space-1);
90
+ padding-top: var(--sw-space-1);
91
+ }
92
+
93
+ .sw-context-switcher-action-icon {
94
+ display: inline-flex;
95
+ flex: none;
96
+ }`;
97
+ }
98
+ /*
99
+ * An option: the mark, the label over its muted detail, then the badge,
100
+ * the count pill and the current one's check at the inline end. The sm
101
+ * control height (44 px on a coarse pointer). The active row (the arrow
102
+ * keys' or the pointer's) takes the quiet fill, and an accent ring while
103
+ * the list itself has visible focus; the current one is at label weight
104
+ * with the accent check. Forced colours draw the active row in Highlight.
105
+ */
106
+ function optionRules() {
107
+ return `.sw-context-switcher-option {
108
+ align-items: center;
109
+ border-radius: var(--sw-radius-sm);
110
+ box-sizing: border-box;
111
+ cursor: pointer;
112
+ display: flex;
113
+ font-family: var(--sw-font-sans);
114
+ font-size: ${label.fontSize}px;
115
+ gap: var(--sw-space-2);
116
+ letter-spacing: ${label.letterSpacing}px;
117
+ line-height: ${label.lineHeight}px;
118
+ min-height: var(--sw-control-sm-min-height);
119
+ overflow-wrap: anywhere;
120
+ padding: var(--sw-space-1) var(--sw-space-2);
121
+ }
122
+
123
+ .sw-context-switcher-option[data-active='true'] {
124
+ background: ${quietFill};
125
+ }
126
+
127
+ /* On the quiet fill the muted detail moves toward the text colour, keeping 4.5:1. */
128
+ .sw-context-switcher-option[data-active='true'] .sw-context-switcher-detail {
129
+ color: color-mix(in srgb, var(--sw-color-muted) 70%, var(--sw-color-text));
130
+ }
131
+
132
+ .sw-context-switcher-list:focus-visible .sw-context-switcher-option[data-active='true'] {
133
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
134
+ outline-offset: calc(-1 * var(--sw-focus-ring-width));
135
+ }
136
+
137
+ .sw-context-switcher-icon {
138
+ display: inline-flex;
139
+ flex: none;
140
+ }
141
+
142
+ .sw-context-switcher-words {
143
+ display: flex;
144
+ flex: 1 1 auto;
145
+ flex-direction: column;
146
+ min-width: 0;
147
+ }
148
+
149
+ .sw-context-switcher-option[aria-selected='true'] .sw-context-switcher-label {
150
+ font-weight: ${label.fontWeight};
151
+ }
152
+
153
+ .sw-context-switcher-detail {
154
+ color: var(--sw-color-muted);
155
+ font-size: ${caption.fontSize}px;
156
+ letter-spacing: ${caption.letterSpacing}px;
157
+ line-height: ${caption.lineHeight}px;
158
+ }
159
+
160
+ .sw-context-switcher-count {
161
+ flex: none;
162
+ }
163
+
164
+ .sw-context-switcher-check {
165
+ ${checkStrokeIn('var(--sw-color-accent)')}
166
+ box-sizing: border-box;
167
+ flex: none;
168
+ height: var(--sw-space-3);
169
+ margin-inline: var(--sw-space-1);
170
+ transform: rotate(45deg);
171
+ width: var(--sw-space-2);
172
+ }
173
+
174
+ @media ${coarsePointerQuery} {
175
+ .sw-context-switcher-option { min-height: ${touchTarget}; }
176
+ }
177
+
178
+ @media (forced-colors: active) {
179
+ .sw-context-switcher-popup { border-color: CanvasText; }
180
+ .sw-context-switcher-option[data-active='true'] { background: Highlight; color: HighlightText; forced-color-adjust: none; }
181
+ .sw-context-switcher-option[data-active='true'] .sw-context-switcher-detail { color: HighlightText; }
182
+ .sw-context-switcher-option[data-active='true'] .sw-badge,
183
+ .sw-context-switcher-option[data-active='true'] .sw-context-switcher-count { forced-color-adjust: auto; }
184
+ .sw-context-switcher-check { border-color: CanvasText; }
185
+ .sw-context-switcher-option[data-active='true'] .sw-context-switcher-check { border-color: HighlightText; }
186
+ .sw-context-switcher-footer { border-color: CanvasText; }
187
+ }`;
188
+ }
189
+ /*
190
+ * ContextSwitcher (see components/ContextSwitcher.tsx): the trigger is
191
+ * `css-context-switcher-trigger.ts`; its count joins the shared pill
192
+ * (`css-count-pill.ts`).
193
+ */
194
+ export function cssContextSwitcherClasses() {
195
+ return `${cssContextSwitcherTriggerClasses()}
196
+
197
+ ${popupRules()}
198
+
199
+ ${optionRules()}`;
200
+ }
201
+ export function contextSwitcherClassCatalog() {
202
+ return [
203
+ ...contextSwitcherTriggerClassCatalog(),
204
+ 'sw-context-switcher-popup',
205
+ 'sw-context-switcher-filter',
206
+ 'sw-context-switcher-list',
207
+ 'sw-context-switcher-heading',
208
+ 'sw-context-switcher-option',
209
+ 'sw-context-switcher-icon',
210
+ 'sw-context-switcher-words',
211
+ 'sw-context-switcher-label',
212
+ 'sw-context-switcher-detail',
213
+ 'sw-context-switcher-check',
214
+ 'sw-context-switcher-empty',
215
+ 'sw-context-switcher-footer',
216
+ 'sw-context-switcher-action-icon',
217
+ // sw-context-switcher-count is the shared pill's (css-count-pill.ts).
218
+ ];
219
+ }
@@ -1,5 +1,5 @@
1
- /** The pills that take a tone modifier: NavCount's and AppShell's item count. */
2
- declare const tonedPills: readonly ["sw-nav-count", "sw-app-shell-count"];
1
+ /** The pills that take a tone modifier: all but SectionNav's. */
2
+ declare const tonedPills: readonly ["sw-nav-count", "sw-app-shell-count", "sw-context-switcher-count"];
3
3
  /** The tones other than the pill's own danger. */
4
4
  declare const pillTones: readonly ["accent", "warning"];
5
5
  /** A toned pill's modifier class, such as `sw-nav-count-warning`. */
@@ -1,14 +1,22 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { toneTintFill, toneTintInk } from './tone-tint.js';
3
3
  const caption = typographyVariants.caption;
4
- /** The counts drawn as the pill: SectionNav's, a Nav word's and AppShell's. */
4
+ /**
5
+ * The counts drawn as the pill: SectionNav's, a Nav word's, AppShell's and
6
+ * a ContextSwitcher option's.
7
+ */
5
8
  const pills = [
6
9
  'sw-section-nav-count',
7
10
  'sw-nav-count',
8
11
  'sw-app-shell-count',
12
+ 'sw-context-switcher-count',
13
+ ];
14
+ /** The pills that take a tone modifier: all but SectionNav's. */
15
+ const tonedPills = [
16
+ 'sw-nav-count',
17
+ 'sw-app-shell-count',
18
+ 'sw-context-switcher-count',
9
19
  ];
10
- /** The pills that take a tone modifier: NavCount's and AppShell's item count. */
11
- const tonedPills = ['sw-nav-count', 'sw-app-shell-count'];
12
20
  /** The tones other than the pill's own danger. */
13
21
  const pillTones = ['accent', 'warning'];
14
22
  /** A toned pill's modifier class, such as `sw-nav-count-warning`. */
@@ -0,0 +1,2 @@
1
+ export declare function cssEmptyStateClasses(): string;
2
+ export declare function emptyStateClassCatalog(): string[];
@@ -0,0 +1,77 @@
1
+ import { coarsePointerQuery, touchTarget } from './touch-target.js';
2
+ /*
3
+ * EmptyState: a centred column on the surface inside a dashed hairline
4
+ * (the md radius), spacing step 8 above and below and step 4 at the sides,
5
+ * its parts a step 3 apart. The glyph sits in a decorative step-8 square of
6
+ * the subtle fill in the muted colour, a direct-child SVG drawn at step 5.
7
+ * The title and line keep a readable measure and wrap; a long word breaks.
8
+ * The actions are a centred row that wraps, a step 2 apart, each Button a
9
+ * 44 px target on a coarse pointer. Forced colours keep the dashed edge in
10
+ * the system's border colour and draw the glyph in CanvasText.
11
+ */
12
+ export function cssEmptyStateClasses() {
13
+ return `.sw-empty-state {
14
+ align-items: center;
15
+ background: var(--sw-color-surface);
16
+ border: 1px dashed var(--sw-color-border);
17
+ border-radius: var(--sw-radius-md);
18
+ box-sizing: border-box;
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: var(--sw-space-3);
22
+ min-width: 0;
23
+ overflow-wrap: anywhere;
24
+ padding: var(--sw-space-8) var(--sw-space-4);
25
+ text-align: center;
26
+ width: 100%;
27
+ }
28
+
29
+ .sw-empty-state-icon {
30
+ align-items: center;
31
+ background: var(--sw-color-subtle);
32
+ border-radius: var(--sw-radius-md);
33
+ color: var(--sw-color-muted);
34
+ display: inline-flex;
35
+ flex: none;
36
+ height: var(--sw-space-8);
37
+ justify-content: center;
38
+ width: var(--sw-space-8);
39
+ }
40
+
41
+ .sw-empty-state-icon > svg {
42
+ height: var(--sw-space-5);
43
+ width: var(--sw-space-5);
44
+ }
45
+
46
+ .sw-empty-state-title,
47
+ .sw-empty-state-description {
48
+ margin: 0;
49
+ max-width: var(--sw-reading-max);
50
+ }
51
+
52
+ .sw-empty-state-actions {
53
+ display: flex;
54
+ flex-wrap: wrap;
55
+ gap: var(--sw-space-2);
56
+ justify-content: center;
57
+ margin-top: var(--sw-space-1);
58
+ }
59
+
60
+ @media ${coarsePointerQuery} {
61
+ .sw-empty-state-actions .sw-button { min-height: ${touchTarget}; }
62
+ }
63
+
64
+ @media (forced-colors: active) {
65
+ .sw-empty-state { border-color: CanvasText; }
66
+ .sw-empty-state-icon { color: CanvasText; }
67
+ }`;
68
+ }
69
+ export function emptyStateClassCatalog() {
70
+ return [
71
+ 'sw-empty-state',
72
+ 'sw-empty-state-icon',
73
+ 'sw-empty-state-title',
74
+ 'sw-empty-state-description',
75
+ 'sw-empty-state-actions',
76
+ ];
77
+ }
@@ -0,0 +1,5 @@
1
+ export declare const fixtureCenterKinds: readonly ["time", "score"];
2
+ /** `time` is a kickoff time in a grey chip; `score` is bold figures. */
3
+ export type FixtureCenterKind = (typeof fixtureCenterKinds)[number];
4
+ export declare function cssFixtureClasses(): string;
5
+ export declare function fixtureClassCatalog(): string[];