@scalewing/react 1.46.0 → 1.48.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 (134) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +228 -4
  3. package/dist/app-shell.test-support.d.ts +5 -0
  4. package/dist/app-shell.test-support.js +45 -0
  5. package/dist/command-palette.test-support.d.ts +21 -0
  6. package/dist/command-palette.test-support.js +55 -0
  7. package/dist/components/AppHeader.d.ts +3 -0
  8. package/dist/components/AppShell.d.ts +13 -0
  9. package/dist/components/AppShell.js +65 -0
  10. package/dist/components/Band.d.ts +30 -0
  11. package/dist/components/Band.js +23 -0
  12. package/dist/components/ButtonLink.d.ts +33 -0
  13. package/dist/components/ButtonLink.js +27 -0
  14. package/dist/components/CommandPalette.d.ts +12 -0
  15. package/dist/components/CommandPalette.js +143 -0
  16. package/dist/components/CommandPaletteTrigger.d.ts +9 -0
  17. package/dist/components/CommandPaletteTrigger.js +16 -0
  18. package/dist/components/ContextSwitcher.d.ts +12 -0
  19. package/dist/components/ContextSwitcher.js +62 -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/NavCount.d.ts +12 -5
  27. package/dist/components/NavCount.js +9 -4
  28. package/dist/components/SectionNav.d.ts +2 -1
  29. package/dist/components/SectionNav.js +3 -19
  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/app-shell/AppShellBar.d.ts +23 -0
  37. package/dist/components/app-shell/AppShellBar.js +25 -0
  38. package/dist/components/app-shell/AppShellDrawer.d.ts +24 -0
  39. package/dist/components/app-shell/AppShellDrawer.js +17 -0
  40. package/dist/components/app-shell/AppShellLink.d.ts +14 -0
  41. package/dist/components/app-shell/AppShellLink.js +27 -0
  42. package/dist/components/app-shell/AppShellNav.d.ts +15 -0
  43. package/dist/components/app-shell/AppShellNav.js +29 -0
  44. package/dist/components/app-shell/AppShellSidebar.d.ts +18 -0
  45. package/dist/components/app-shell/AppShellSidebar.js +18 -0
  46. package/dist/components/app-shell/types.d.ts +122 -0
  47. package/dist/components/app-shell/types.js +1 -0
  48. package/dist/components/app-shell/validate.d.ts +7 -0
  49. package/dist/components/app-shell/validate.js +70 -0
  50. package/dist/components/command-palette/PaletteList.d.ts +20 -0
  51. package/dist/components/command-palette/PaletteList.js +21 -0
  52. package/dist/components/command-palette/shortcut.d.ts +19 -0
  53. package/dist/components/command-palette/shortcut.js +75 -0
  54. package/dist/components/command-palette/types.d.ts +72 -0
  55. package/dist/components/command-palette/types.js +2 -0
  56. package/dist/components/command-palette/validate.d.ts +10 -0
  57. package/dist/components/command-palette/validate.js +50 -0
  58. package/dist/components/context-switcher/SwitcherOption.d.ts +17 -0
  59. package/dist/components/context-switcher/SwitcherOption.js +20 -0
  60. package/dist/components/context-switcher/SwitcherPopup.d.ts +31 -0
  61. package/dist/components/context-switcher/SwitcherPopup.js +111 -0
  62. package/dist/components/context-switcher/options.d.ts +33 -0
  63. package/dist/components/context-switcher/options.js +60 -0
  64. package/dist/components/context-switcher/types.d.ts +96 -0
  65. package/dist/components/context-switcher/types.js +1 -0
  66. package/dist/components/context-switcher/use-switcher-list.d.ts +29 -0
  67. package/dist/components/context-switcher/use-switcher-list.js +93 -0
  68. package/dist/components/context-switcher/validate.d.ts +8 -0
  69. package/dist/components/context-switcher/validate.js +94 -0
  70. package/dist/components/fixture/fixture-list-context.d.ts +4 -0
  71. package/dist/components/fixture/fixture-list-context.js +9 -0
  72. package/dist/components/fixture/types.d.ts +50 -0
  73. package/dist/components/fixture/types.js +1 -0
  74. package/dist/components/fixture/validate.d.ts +8 -0
  75. package/dist/components/fixture/validate.js +36 -0
  76. package/dist/components/nav-items.d.ts +21 -0
  77. package/dist/components/nav-items.js +26 -0
  78. package/dist/components/site-footer/types.d.ts +31 -0
  79. package/dist/components/site-footer/types.js +1 -0
  80. package/dist/components/site-footer/validate.d.ts +7 -0
  81. package/dist/components/site-footer/validate.js +22 -0
  82. package/dist/components/site-header/SiteHeaderLinks.d.ts +7 -0
  83. package/dist/components/site-header/SiteHeaderLinks.js +7 -0
  84. package/dist/components/site-header/types.d.ts +38 -0
  85. package/dist/components/site-header/types.js +1 -0
  86. package/dist/components/site-header/use-menu-dismiss.d.ts +8 -0
  87. package/dist/components/site-header/use-menu-dismiss.js +62 -0
  88. package/dist/components/site-header/validate.d.ts +7 -0
  89. package/dist/components/site-header/validate.js +17 -0
  90. package/dist/components/use-element-height.d.ts +10 -0
  91. package/dist/components/use-element-height.js +35 -0
  92. package/dist/context-switcher.test-support.d.ts +9 -0
  93. package/dist/context-switcher.test-support.js +41 -0
  94. package/dist/css/css-app-shell-drawer.d.ts +2 -0
  95. package/dist/css/css-app-shell-drawer.js +91 -0
  96. package/dist/css/css-app-shell-nav.d.ts +3 -0
  97. package/dist/css/css-app-shell-nav.js +167 -0
  98. package/dist/css/css-app-shell-page-bar.d.ts +3 -0
  99. package/dist/css/css-app-shell-page-bar.js +81 -0
  100. package/dist/css/css-app-shell.d.ts +4 -0
  101. package/dist/css/css-app-shell.js +155 -0
  102. package/dist/css/css-band.d.ts +10 -0
  103. package/dist/css/css-band.js +101 -0
  104. package/dist/css/css-button-link.d.ts +3 -0
  105. package/dist/css/css-button-link.js +48 -0
  106. package/dist/css/css-command-palette-key.d.ts +1 -0
  107. package/dist/css/css-command-palette-key.js +33 -0
  108. package/dist/css/css-command-palette-trigger.d.ts +2 -0
  109. package/dist/css/css-command-palette-trigger.js +88 -0
  110. package/dist/css/css-command-palette.d.ts +2 -0
  111. package/dist/css/css-command-palette.js +209 -0
  112. package/dist/css/css-components.js +27 -0
  113. package/dist/css/css-context-switcher-trigger.d.ts +15 -0
  114. package/dist/css/css-context-switcher-trigger.js +175 -0
  115. package/dist/css/css-context-switcher.d.ts +4 -0
  116. package/dist/css/css-context-switcher.js +219 -0
  117. package/dist/css/css-count-pill.d.ts +7 -0
  118. package/dist/css/css-count-pill.js +50 -12
  119. package/dist/css/css-fixture.d.ts +5 -0
  120. package/dist/css/css-fixture.js +260 -0
  121. package/dist/css/css-glyph-tile.d.ts +2 -0
  122. package/dist/css/css-glyph-tile.js +29 -1
  123. package/dist/css/css-site-footer.d.ts +2 -0
  124. package/dist/css/css-site-footer.js +152 -0
  125. package/dist/css/css-site-header.d.ts +2 -0
  126. package/dist/css/css-site-header.js +262 -0
  127. package/dist/css/css-skeleton.d.ts +10 -0
  128. package/dist/css/css-skeleton.js +110 -0
  129. package/dist/css/stacking.js +7 -4
  130. package/dist/css/stylesheet.js +18 -0
  131. package/dist/index.d.ts +14 -0
  132. package/dist/index.js +11 -0
  133. package/dist/styles.css +2122 -512
  134. 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';
@@ -21,6 +25,7 @@ import { cssCheckboxClasses } from './css-checkbox.js';
21
25
  import { cssRadioGroupClasses } from './css-radio-group.js';
22
26
  import { cssChromeClasses } from './css-chrome.js';
23
27
  import { cssSectionNavClasses } from './css-section-nav.js';
28
+ import { cssAppShellClasses } from './css-app-shell.js';
24
29
  import { cssDataClasses } from './css-data.js';
25
30
  import { cssSegmentedClasses } from './css-segmented.js';
26
31
  import { cssTableClasses } from './css-table.js';
@@ -37,6 +42,7 @@ import { cssSplitClasses } from './css-split.js';
37
42
  import { cssSwitchClasses } from './css-switch.js';
38
43
  import { cssTextClasses } from './css-text.js';
39
44
  import { cssFontMonoClasses } from './css-font-mono.js';
45
+ import { cssSkeletonClasses } from './css-skeleton.js';
40
46
  import { cssSpinnerClasses } from './css-spinner.js';
41
47
  import { cssProgressClasses } from './css-progress.js';
42
48
  import { cssNoticeClasses } from './css-notice.js';
@@ -44,6 +50,9 @@ import { cssGlyphTileClasses } from './css-glyph-tile.js';
44
50
  import { cssTooltipClasses } from './css-tooltip.js';
45
51
  import { cssInfoTipClasses } from './css-info-tip.js';
46
52
  import { cssSeparatorClasses } from './css-separator.js';
53
+ import { cssBandClasses } from './css-band.js';
54
+ import { cssSiteFooterClasses } from './css-site-footer.js';
55
+ import { cssSiteHeaderClasses } from './css-site-header.js';
47
56
  import { cssStatTileClasses } from './css-stat-tile.js';
48
57
  import { cssDescriptionListClasses } from './css-description-list.js';
49
58
  import { cssTabsClasses } from './css-tabs.js';
@@ -139,6 +148,8 @@ ${cssFontMonoClasses()}
139
148
 
140
149
  ${cssButtonClasses()}
141
150
 
151
+ ${cssButtonLinkClasses()}
152
+
142
153
  ${cssCheckboxClasses()}
143
154
 
144
155
  ${cssRadioGroupClasses()}
@@ -157,6 +168,8 @@ ${cssDateRangeClasses()}
157
168
 
158
169
  ${cssListClasses()}
159
170
 
171
+ ${cssFixtureClasses()}
172
+
160
173
  ${cssFileFieldClasses()}
161
174
 
162
175
  ${cssArticleClasses()}
@@ -185,6 +198,14 @@ ${cssChromeClasses()}
185
198
 
186
199
  ${cssSectionNavClasses()}
187
200
 
201
+ ${cssBandClasses()}
202
+
203
+ ${cssSiteFooterClasses()}
204
+
205
+ ${cssSiteHeaderClasses()}
206
+
207
+ ${cssAppShellClasses()}
208
+
188
209
  ${cssDialogClasses()}
189
210
 
190
211
  ${cssDialogFrameClasses()}
@@ -201,6 +222,10 @@ ${cssSelectClasses()}
201
222
 
202
223
  ${cssActionMenuClasses()}
203
224
 
225
+ ${cssContextSwitcherClasses()}
226
+
227
+ ${cssCommandPaletteClasses()}
228
+
204
229
  ${cssComboboxClasses()}
205
230
  ${cssMultiSelectClasses()}
206
231
 
@@ -210,6 +235,8 @@ ${cssSwitchClasses()}
210
235
 
211
236
  ${cssSpinnerClasses()}
212
237
 
238
+ ${cssSkeletonClasses()}
239
+
213
240
  ${cssProgressClasses()}
214
241
 
215
242
  ${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,2 +1,9 @@
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
+ /** The tones other than the pill's own danger. */
4
+ declare const pillTones: readonly ["accent", "warning"];
5
+ /** A toned pill's modifier class, such as `sw-nav-count-warning`. */
6
+ export declare function countToneClass(pill: (typeof tonedPills)[number], tone: (typeof pillTones)[number]): string;
1
7
  export declare function cssCountPillClasses(): string;
2
8
  export declare function countPillClassCatalog(): string[];
9
+ export {};
@@ -1,16 +1,41 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
+ import { toneTintFill, toneTintInk } from './tone-tint.js';
2
3
  const caption = typographyVariants.caption;
3
- /** The counts drawn as the pill: SectionNav's and a Nav word's. */
4
- const pills = ['sw-section-nav-count', 'sw-nav-count'];
5
- /** NavCount's news tone, a modifier of its pill. */
6
- const accentPill = 'sw-nav-count-accent';
4
+ /**
5
+ * The counts drawn as the pill: SectionNav's, a Nav word's, AppShell's and
6
+ * a ContextSwitcher option's.
7
+ */
8
+ const pills = [
9
+ 'sw-section-nav-count',
10
+ 'sw-nav-count',
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',
19
+ ];
20
+ /** The tones other than the pill's own danger. */
21
+ const pillTones = ['accent', 'warning'];
22
+ /** A toned pill's modifier class, such as `sw-nav-count-warning`. */
23
+ export function countToneClass(pill, tone) {
24
+ return `${pill}-${tone}`;
25
+ }
26
+ function tonedSelector(tone) {
27
+ return tonedPills
28
+ .map((pill) => `[data-theme] .${pill}.${countToneClass(pill, tone)}`)
29
+ .join(',\n');
30
+ }
7
31
  /*
8
32
  * A count after a destination's words (a number waiting there, such as
9
33
  * tasks to do) is a filled pill: the danger colour with its on-colour
10
34
  * figures (4.5:1 in every palette, as a danger Button's label), caption
11
35
  * size at weight 700, a space-5 less space-1 (20) high and at least as
12
- * wide. Forced colours draw its edge. SectionNav's item count and Nav's
13
- * NavCount share it, so an area's count reads the same in both.
36
+ * wide. Forced colours draw its edge. SectionNav's item count, Nav's
37
+ * NavCount and AppShell's item count share it, so an area's count reads
38
+ * the same in each.
14
39
  */
15
40
  export function cssCountPillClasses() {
16
41
  const selector = pills.map((name) => `[data-theme] .${name}`).join(',\n');
@@ -33,16 +58,26 @@ export function cssCountPillClasses() {
33
58
  }
34
59
 
35
60
  /*
36
- * NavCount's \`tone="accent"\`: a count that is news, not work waiting (what
37
- * was added since the person last looked), is the accent fill with its
38
- * on-colour figures (4.5:1 in every palette, as a primary Button's label).
39
- * One class more specific than the pill, so it wins in any order.
61
+ * \`tone="accent"\` (NavCount, an AppShell item's \`countTone\`): a count that
62
+ * is news, not work waiting (what was added since the person last looked),
63
+ * is the accent fill with its on-colour figures (4.5:1 in every palette, as
64
+ * a primary Button's label). \`tone="warning"\`: work that needs the person
65
+ * but is not urgent is amber, the warning tint with the warning ink (a
66
+ * warning Notice's, 4.5:1 on the tint in every palette) inside the warning
67
+ * edge a warning Badge draws. One class more specific than the pill, so
68
+ * each wins in any order.
40
69
  */
41
- [data-theme] .sw-nav-count.${accentPill} {
70
+ ${tonedSelector('accent')} {
42
71
  background: var(--sw-color-accent);
43
72
  color: var(--sw-color-onAccent);
44
73
  }
45
74
 
75
+ ${tonedSelector('warning')} {
76
+ background: ${toneTintFill('warning')};
77
+ border: 1px solid var(--sw-color-warning);
78
+ color: ${toneTintInk('warning')};
79
+ }
80
+
46
81
  /*
47
82
  * A Nav link holding a count lays out as SectionNav's link does: the words
48
83
  * and the pill centred on one line a space-2 apart (the space text node
@@ -63,5 +98,8 @@ export function cssCountPillClasses() {
63
98
  }`;
64
99
  }
65
100
  export function countPillClassCatalog() {
66
- return [...pills, accentPill];
101
+ return [
102
+ ...pills,
103
+ ...tonedPills.flatMap((pill) => pillTones.map((tone) => countToneClass(pill, tone))),
104
+ ];
67
105
  }
@@ -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[];