@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
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from '@testing-library/react';
3
+ import { vi } from 'vitest';
4
+ import { ContextSwitcher, } from './components/ContextSwitcher.js';
5
+ /** Field stations: a bare group, then a labelled one with badges and counts. */
6
+ export const stations = [
7
+ {
8
+ id: 'home',
9
+ options: [
10
+ {
11
+ id: 'all',
12
+ label: 'All stations',
13
+ icon: _jsx("svg", { "data-glyph": "all" }),
14
+ count: 7,
15
+ countLabel: '7 need you',
16
+ countTone: 'warning',
17
+ },
18
+ ],
19
+ },
20
+ {
21
+ id: 'mine',
22
+ label: 'Stations you run',
23
+ options: [
24
+ {
25
+ id: 'heron',
26
+ label: 'Heron Marsh',
27
+ detail: 'Warden',
28
+ badge: { label: 'Live', tone: 'success' },
29
+ },
30
+ { id: 'pine', label: 'Pine Ridge', detail: 'Ranger' },
31
+ { id: 'bittern', label: 'Bittern Fen', count: 2 },
32
+ { id: 'bay', label: 'Bayside Dunes', detail: 'Árbol team' },
33
+ ],
34
+ },
35
+ ];
36
+ /** The switcher with its required props, and any prop overridden. */
37
+ export function renderSwitcher(props = {}) {
38
+ const onSelect = vi.fn();
39
+ const view = render(_jsx(ContextSwitcher, { currentId: "heron", groups: stations, label: "Station", onSelect: onSelect, trigger: "Heron Marsh", ...props }));
40
+ return { ...view, onSelect };
41
+ }
@@ -0,0 +1,3 @@
1
+ export declare const appShellBarHeightVariable = "--sw-app-shell-bar-height";
2
+ export declare function cssAppShellPageBarClasses(): string;
3
+ export declare function appShellPageBarClassCatalog(): string[];
@@ -0,0 +1,81 @@
1
+ import { breakpointQuery } from './breakpoints.js';
2
+ import { zIndex } from './stacking.js';
3
+ import { coarsePointerQuery, touchTarget } from './touch-target.js';
4
+ /*
5
+ * The compact bar's measured height, set on `.sw-app-shell` by the
6
+ * component while it draws a page bar, so the page bar sticks right under
7
+ * the bar whatever its end controls make it.
8
+ */
9
+ export const appShellBarHeightVariable = '--sw-app-shell-bar-height';
10
+ /*
11
+ * The bar's usual height before it is measured (and without script): the
12
+ * sm menu button, or the touch target on a coarse pointer, inside the
13
+ * bar's space-2 padding, and its hairline.
14
+ */
15
+ function barHeightFallback(control) {
16
+ return `calc(${control} + 2 * var(--sw-space-2) + 1px)`;
17
+ }
18
+ const fromLg = breakpointQuery('from', 'lg');
19
+ /*
20
+ * `pageBar`: a band at the top of `main` on every layout, the solid
21
+ * surface with a hairline under it and the page's inline padding (space-4,
22
+ * space-6 from `lg`), its children a wrapping row with space between them.
23
+ * Sticky on the top chrome's layer: under the compact bar below `lg`, at
24
+ * the window's top from `lg`, where the bar is gone. Reduce Transparency
25
+ * needs nothing (it is already solid); forced colours keep the hairline.
26
+ */
27
+ function pageBarRules() {
28
+ return `.sw-app-shell-page-bar {
29
+ align-items: center;
30
+ background: var(--sw-color-surface);
31
+ border-bottom: 1px solid var(--sw-color-border);
32
+ box-sizing: border-box;
33
+ display: flex;
34
+ flex-wrap: wrap;
35
+ gap: var(--sw-space-2) var(--sw-space-3);
36
+ justify-content: space-between;
37
+ padding: var(--sw-space-2) var(--sw-space-4);
38
+ position: sticky;
39
+ top: var(${appShellBarHeightVariable}, ${barHeightFallback('var(--sw-control-sm-min-height)')});
40
+ ${zIndex('topChrome')}
41
+ }
42
+
43
+ @media ${coarsePointerQuery} {
44
+ .sw-app-shell-page-bar {
45
+ top: var(${appShellBarHeightVariable}, ${barHeightFallback(touchTarget)});
46
+ }
47
+ }
48
+
49
+ @media ${fromLg} {
50
+ .sw-app-shell-page-bar {
51
+ padding-inline: var(--sw-space-6);
52
+ top: 0;
53
+ }
54
+ }`;
55
+ }
56
+ /*
57
+ * `menuCount`: the shared count pill (`sw-app-shell-count`, its tones
58
+ * included) on the menu button's top end corner, over the glyph's edge as
59
+ * on a rail glyph, mirrored in right-to-left.
60
+ */
61
+ function menuCountRules() {
62
+ return `.sw-app-shell-menu {
63
+ position: relative;
64
+ }
65
+
66
+ .sw-app-shell-menu .sw-app-shell-count {
67
+ inset-block-start: calc(-1 * var(--sw-space-1));
68
+ inset-inline-end: calc(-1 * var(--sw-space-1));
69
+ pointer-events: none;
70
+ position: absolute;
71
+ }`;
72
+ }
73
+ /* AppShell's page bar and the menu button's count (see components/AppShell.tsx). */
74
+ export function cssAppShellPageBarClasses() {
75
+ return `${pageBarRules()}
76
+
77
+ ${menuCountRules()}`;
78
+ }
79
+ export function appShellPageBarClassCatalog() {
80
+ return ['sw-app-shell-page-bar'];
81
+ }
@@ -1,6 +1,7 @@
1
1
  import { breakpointQuery } from './breakpoints.js';
2
2
  import { appShellDrawerClassCatalog, cssAppShellDrawerClasses, } from './css-app-shell-drawer.js';
3
3
  import { appShellNavClassCatalog, cssAppShellNavClasses, cssAppShellRailNavRules, } from './css-app-shell-nav.js';
4
+ import { appShellPageBarClassCatalog, cssAppShellPageBarClasses, } from './css-app-shell-page-bar.js';
4
5
  import { iconButtonRules } from './css-icon-button.js';
5
6
  /*
6
7
  * The width from which AppShell's sidebar is full rather than a rail: an
@@ -110,7 +111,8 @@ ${cssAppShellRailNavRules(' ')}
110
111
  /*
111
112
  * AppShell: a workspace frame with a pinned side navigation (see
112
113
  * components/AppShell.tsx). The links, groups and the rail's nav are
113
- * `css-app-shell-nav.ts`; the menu drawer is `css-app-shell-drawer.ts`.
114
+ * `css-app-shell-nav.ts`; the page bar and the menu's count are
115
+ * `css-app-shell-page-bar.ts`; the menu drawer is `css-app-shell-drawer.ts`.
114
116
  */
115
117
  export function cssAppShellClasses() {
116
118
  return `${barRules()}
@@ -127,6 +129,8 @@ export function cssAppShellClasses() {
127
129
 
128
130
  ${cssAppShellNavClasses()}
129
131
 
132
+ ${cssAppShellPageBarClasses()}
133
+
130
134
  ${sidebarRules()}
131
135
 
132
136
  ${cssAppShellDrawerClasses()}`;
@@ -145,6 +149,7 @@ export function appShellClassCatalog() {
145
149
  'sw-app-shell-header',
146
150
  'sw-app-shell-footer',
147
151
  ...appShellNavClassCatalog(),
152
+ ...appShellPageBarClassCatalog(),
148
153
  ...appShellDrawerClassCatalog(),
149
154
  ];
150
155
  }
@@ -0,0 +1,10 @@
1
+ /** A band's fill: the page's surface, the quiet subtle fill, or the dark inverse band. */
2
+ export declare const bandTones: readonly ["surface", "subtle", "inverse"];
3
+ export type BandTone = (typeof bandTones)[number];
4
+ /** How wide a band's content column runs: the page container or the reading width. */
5
+ export declare const bandWidths: readonly ["container", "reading"];
6
+ export type BandWidth = (typeof bandWidths)[number];
7
+ export declare function bandToneClass(tone: BandTone): string;
8
+ export declare function bandWidthClass(width: BandWidth): string;
9
+ export declare function cssBandClasses(): string;
10
+ export declare function bandClassCatalog(): string[];
@@ -0,0 +1,101 @@
1
+ import { breakpointQuery } from './breakpoints.js';
2
+ /** A band's fill: the page's surface, the quiet subtle fill, or the dark inverse band. */
3
+ export const bandTones = ['surface', 'subtle', 'inverse'];
4
+ /** How wide a band's content column runs: the page container or the reading width. */
5
+ export const bandWidths = ['container', 'reading'];
6
+ export function bandToneClass(tone) {
7
+ return `sw-band-${tone}`;
8
+ }
9
+ export function bandWidthClass(width) {
10
+ return `sw-band-inner-${width}`;
11
+ }
12
+ /*
13
+ * The inverse band re-scopes the theme's colours for everything inside it:
14
+ * words, muted words, hairlines, the quiet subtle fill, links and the
15
+ * accent (a primary button, the focus ring) read on the dark fill, so
16
+ * Text, links and Buttons need no band-aware variant. Every plain link,
17
+ * a quiet one included, takes the band's words with a half-strength
18
+ * underline, since its colour is the words' own (WCAG 1.4.1). The muted
19
+ * words, hairline and subtle fill are mixes of the two band colours, so a
20
+ * brand overlay of `inverse` moves them too; the muted mix keeps 7:1 on
21
+ * the default bands and on a navy one.
22
+ */
23
+ function inverseScope() {
24
+ return `.sw-band-inverse {
25
+ --sw-color-background: var(--sw-color-inverse);
26
+ --sw-color-surface: var(--sw-color-inverse);
27
+ --sw-color-text: var(--sw-color-onInverse);
28
+ --sw-color-muted: color-mix(in srgb, var(--sw-color-onInverse) 78%, var(--sw-color-inverse));
29
+ --sw-color-border: color-mix(in srgb, var(--sw-color-onInverse) 18%, var(--sw-color-inverse));
30
+ --sw-color-subtle: color-mix(in srgb, var(--sw-color-onInverse) 10%, var(--sw-color-inverse));
31
+ --sw-color-accent: var(--sw-color-onInverse);
32
+ --sw-color-onAccent: var(--sw-color-inverse);
33
+ background: var(--sw-color-inverse);
34
+ color: var(--sw-color-onInverse);
35
+ }
36
+
37
+ [data-theme] .sw-band-inverse a:not(.sw-button) {
38
+ color: var(--sw-color-onInverse);
39
+ text-decoration: underline;
40
+ text-decoration-color: color-mix(in srgb, currentColor 50%, transparent);
41
+ text-underline-offset: 0.2em;
42
+ }
43
+
44
+ [data-theme] .sw-band-inverse a:not(.sw-button):hover {
45
+ text-decoration-color: currentColor;
46
+ }`;
47
+ }
48
+ /*
49
+ * The surface band is the cards' fill (white in light, a step above the
50
+ * black page in dark), so it stands apart from a grouped or dark page; the
51
+ * subtle band is the quiet grey.
52
+ *
53
+ * A band is a full-width stretch of the page in one fill with its content
54
+ * in a centred column, the page gutter at its sides (the 16 px phone
55
+ * gutter, 24 px from md).
56
+ */
57
+ export function cssBandClasses() {
58
+ return `.sw-band {
59
+ box-sizing: border-box;
60
+ width: 100%;
61
+ }
62
+
63
+ .sw-band-surface {
64
+ background: var(--sw-color-surface);
65
+ color: var(--sw-color-text);
66
+ }
67
+
68
+ .sw-band-subtle {
69
+ background: var(--sw-color-subtle);
70
+ color: var(--sw-color-text);
71
+ }
72
+
73
+ ${inverseScope()}
74
+
75
+ .sw-band-inner {
76
+ box-sizing: border-box;
77
+ margin-inline: auto;
78
+ padding-inline: var(--sw-space-4);
79
+ width: 100%;
80
+ }
81
+
82
+ .${bandWidthClass('container')} { max-width: calc(var(--sw-container-max) + 2 * var(--sw-space-5)); }
83
+
84
+ .${bandWidthClass('reading')} { max-width: calc(var(--sw-reading-max) + 2 * var(--sw-space-5)); }
85
+
86
+ @media ${breakpointQuery('from', 'md')} {
87
+ .sw-band-inner { padding-inline: var(--sw-space-5); }
88
+ }
89
+
90
+ @media (forced-colors: active) {
91
+ .sw-band-inverse { border-block: 1px solid CanvasText; }
92
+ }`;
93
+ }
94
+ export function bandClassCatalog() {
95
+ return [
96
+ 'sw-band',
97
+ 'sw-band-inner',
98
+ ...bandTones.map(bandToneClass),
99
+ ...bandWidths.map(bandWidthClass),
100
+ ];
101
+ }
@@ -0,0 +1,3 @@
1
+ export declare const buttonLinkClass = "sw-button-link";
2
+ export declare function cssButtonLinkClasses(): string;
3
+ export declare function buttonLinkClassCatalog(): string[];
@@ -0,0 +1,48 @@
1
+ import { pressedRingGap, pressedRingWidth, webButtonVariants, } from './css-button.js';
2
+ /*
3
+ * A link drawn as a Button (`ButtonLink`) wears the Button's own classes.
4
+ * The canvas paints every link in the accent, with no underline, and in the
5
+ * text colour on hover, from `[data-theme] a`; these rules restore each
6
+ * variant's words over that, hover included, at one class more. A link
7
+ * that is the current page (`aria-current="page"`, such as a site's
8
+ * Contact us on Contact) takes the pressed button's accent ring past a gap
9
+ * in the page background, so the mark keeps the accent's contrast whatever
10
+ * the fill; forced colours drop box shadows, so there the same ring is a
11
+ * Highlight border on an out-of-flow ::after, as a pressed Button's is.
12
+ */
13
+ const variantWords = {
14
+ primary: 'var(--sw-color-onAccent)',
15
+ secondary: 'var(--sw-color-onSecondary)',
16
+ tertiary: 'var(--sw-color-onTertiary)',
17
+ ghost: 'var(--sw-color-accent)',
18
+ danger: 'var(--sw-color-onDanger)',
19
+ neutral: 'var(--sw-color-text)',
20
+ };
21
+ export const buttonLinkClass = 'sw-button-link';
22
+ export function cssButtonLinkClasses() {
23
+ const words = webButtonVariants
24
+ .map((variant) => `[data-theme] a.${buttonLinkClass}.sw-button-${variant},\n[data-theme] a.${buttonLinkClass}.sw-button-${variant}:hover { color: ${variantWords[variant]}; }`)
25
+ .join('\n');
26
+ return `[data-theme] a.${buttonLinkClass} {
27
+ text-decoration: none;
28
+ }
29
+
30
+ ${words}
31
+
32
+ [data-theme] a.${buttonLinkClass}[aria-current='page'] {
33
+ box-shadow: 0 0 0 ${pressedRingGap}px var(--sw-color-background), 0 0 0 ${pressedRingWidth}px var(--sw-color-accent);
34
+ }
35
+
36
+ [data-theme] a.${buttonLinkClass}[aria-current='page']:focus-visible {
37
+ outline-offset: calc(var(--sw-focus-ring-offset) + ${pressedRingWidth}px);
38
+ }
39
+
40
+ @media (forced-colors: active) {
41
+ [data-theme] a.${buttonLinkClass} { color: LinkText; }
42
+ [data-theme] a.${buttonLinkClass}[aria-current='page'] { border-color: Highlight; position: relative; }
43
+ [data-theme] a.${buttonLinkClass}[aria-current='page']::after { border: ${pressedRingWidth - pressedRingGap}px solid Highlight; border-radius: inherit; content: ''; inset: calc(-1px - ${pressedRingWidth}px); pointer-events: none; position: absolute; }
44
+ }`;
45
+ }
46
+ export function buttonLinkClassCatalog() {
47
+ return [buttonLinkClass];
48
+ }
@@ -0,0 +1 @@
1
+ export declare function cssCommandPaletteKeyClasses(): string;
@@ -0,0 +1,33 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ const caption = typographyVariants.caption;
3
+ /*
4
+ * A key cap: a key's name or glyph ("⌘K", "↵") in a small rounded hairline
5
+ * box in the muted colour, caption size at weight 500 with tabular figures.
6
+ * CommandPaletteTrigger's shortcut hint and the palette's Enter hint share
7
+ * it. Forced colours keep its edge.
8
+ */
9
+ export function cssCommandPaletteKeyClasses() {
10
+ return `.sw-command-palette-key {
11
+ align-items: center;
12
+ border: 1px solid var(--sw-color-border);
13
+ border-radius: var(--sw-radius-sm);
14
+ box-sizing: border-box;
15
+ color: var(--sw-color-muted);
16
+ display: inline-flex;
17
+ flex: none;
18
+ font-family: var(--sw-font-sans);
19
+ font-size: ${caption.fontSize}px;
20
+ font-variant-numeric: tabular-nums;
21
+ font-weight: 500;
22
+ justify-content: center;
23
+ letter-spacing: ${caption.letterSpacing}px;
24
+ line-height: ${caption.lineHeight}px;
25
+ min-width: var(--sw-space-5);
26
+ padding-inline: var(--sw-space-1);
27
+ white-space: nowrap;
28
+ }
29
+
30
+ @media (forced-colors: active) {
31
+ .sw-command-palette-key { border-color: CanvasText; color: CanvasText; }
32
+ }`;
33
+ }
@@ -0,0 +1,2 @@
1
+ export declare function cssCommandPaletteTriggerClasses(): string;
2
+ export declare function commandPaletteTriggerClassCatalog(): string[];
@@ -0,0 +1,88 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { quietFill } from './quiet-fill.js';
3
+ import { coarsePointerQuery, touchTarget } from './touch-target.js';
4
+ const label = typographyVariants.label;
5
+ /* The full trigger's least width, a named geometry of this control. */
6
+ const triggerMinWidth = '12rem';
7
+ const trigger = '.sw-command-palette-trigger';
8
+ const compact = `${trigger}.sw-command-palette-trigger-compact`;
9
+ /*
10
+ * The trigger: a search-looking pill (the surface inside the border
11
+ * hairline, fully rounded) holding the glyph, the placeholder words in the
12
+ * muted colour taking the free space, and the key hint at the end; the sm
13
+ * control height, the 44 px target on a coarse pointer. `compact` is the
14
+ * glyph alone in a round quiet button. Hover takes the quiet fill.
15
+ */
16
+ export function cssCommandPaletteTriggerClasses() {
17
+ return `${trigger} {
18
+ align-items: center;
19
+ appearance: none;
20
+ background: var(--sw-color-surface);
21
+ border: 1px solid var(--sw-color-border);
22
+ border-radius: var(--sw-radius-pill);
23
+ box-sizing: border-box;
24
+ color: var(--sw-color-muted);
25
+ cursor: pointer;
26
+ display: inline-flex;
27
+ font-family: var(--sw-font-sans);
28
+ font-size: ${label.fontSize}px;
29
+ font-weight: 400;
30
+ gap: var(--sw-space-2);
31
+ letter-spacing: ${label.letterSpacing}px;
32
+ line-height: ${label.lineHeight}px;
33
+ margin: 0;
34
+ max-width: 100%;
35
+ min-height: var(--sw-control-sm-min-height);
36
+ min-width: min(${triggerMinWidth}, 100%);
37
+ padding-inline: var(--sw-space-3) var(--sw-space-2);
38
+ text-align: start;
39
+ }
40
+
41
+ .sw-command-palette-trigger-icon {
42
+ display: inline-flex;
43
+ flex: none;
44
+ }
45
+
46
+ .sw-command-palette-trigger-text {
47
+ flex: 1 1 auto;
48
+ min-width: 0;
49
+ overflow: hidden;
50
+ text-overflow: ellipsis;
51
+ white-space: nowrap;
52
+ }
53
+
54
+ @media (hover: hover) {
55
+ ${trigger}:hover { background: ${quietFill}; color: var(--sw-color-text); }
56
+ }
57
+
58
+ ${trigger}:focus-visible {
59
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
60
+ outline-offset: var(--sw-focus-ring-offset);
61
+ }
62
+
63
+ ${compact} {
64
+ background: transparent;
65
+ border-color: transparent;
66
+ justify-content: center;
67
+ min-width: var(--sw-control-sm-min-height);
68
+ padding: 0;
69
+ }
70
+
71
+ @media ${coarsePointerQuery} {
72
+ ${trigger} { min-height: ${touchTarget}; }
73
+ ${compact} { min-width: ${touchTarget}; }
74
+ }
75
+
76
+ @media (forced-colors: active) {
77
+ ${trigger} { color: ButtonText; }
78
+ }`;
79
+ }
80
+ export function commandPaletteTriggerClassCatalog() {
81
+ return [
82
+ 'sw-command-palette-trigger',
83
+ 'sw-command-palette-trigger-compact',
84
+ 'sw-command-palette-trigger-icon',
85
+ 'sw-command-palette-trigger-text',
86
+ 'sw-command-palette-key',
87
+ ];
88
+ }
@@ -0,0 +1,2 @@
1
+ export declare function cssCommandPaletteClasses(): string;
2
+ export declare function commandPaletteClassCatalog(): string[];
@@ -0,0 +1,209 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { breakpointQuery } from './breakpoints.js';
3
+ import { cssCommandPaletteKeyClasses } from './css-command-palette-key.js';
4
+ import { commandPaletteTriggerClassCatalog, cssCommandPaletteTriggerClasses, } from './css-command-palette-trigger.js';
5
+ import { quietFill } from './quiet-fill.js';
6
+ import { coarsePointerQuery, touchTarget } from './touch-target.js';
7
+ const body = typographyVariants.body;
8
+ const caption = typographyVariants.caption;
9
+ const label = typographyVariants.label;
10
+ /* The palette's width, a named geometry of this control. */
11
+ const paletteWidth = '40rem';
12
+ /* Its distance from the window's top: a tenth of the height, at least space-2. */
13
+ const paletteTop = 'max(var(--sw-space-2), 10vh)';
14
+ const fromMd = breakpointQuery('from', 'md');
15
+ /*
16
+ * The palette: a modal dialog on the top layer near the window's top (not
17
+ * centred, so results grow downward), glass like Dialog, 40rem wide (the
18
+ * window less space-2 each side on a phone). Its body is a column: the
19
+ * field, the listbox (the only part that scrolls), the message, the hint.
20
+ */
21
+ function frameRules() {
22
+ return `.sw-command-palette {
23
+ background: var(--sw-glass-fill);
24
+ border: 1px solid var(--sw-glass-border);
25
+ border-radius: var(--sw-radius-lg);
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
+ margin: var(--sw-space-2) auto auto;
32
+ max-height: calc(100dvh - 2 * var(--sw-space-2));
33
+ max-width: calc(100% - 2 * var(--sw-space-2));
34
+ overflow: hidden;
35
+ padding: 0;
36
+ width: ${paletteWidth};
37
+ }
38
+
39
+ @media ${fromMd} {
40
+ .sw-command-palette {
41
+ margin-top: ${paletteTop};
42
+ max-height: calc(100dvh - ${paletteTop} - var(--sw-space-8));
43
+ }
44
+ }
45
+
46
+ .sw-command-palette::backdrop {
47
+ background: color-mix(in srgb, var(--sw-color-text) 28%, transparent);
48
+ }
49
+
50
+ .sw-command-palette-body {
51
+ display: flex;
52
+ flex-direction: column;
53
+ max-height: inherit;
54
+ }
55
+
56
+ [data-theme] input.sw-command-palette-field {
57
+ background: transparent;
58
+ border: 0;
59
+ border-bottom: 1px solid var(--sw-color-border);
60
+ border-radius: 0;
61
+ box-sizing: border-box;
62
+ flex: none;
63
+ font-size: ${body.fontSize}px;
64
+ letter-spacing: ${body.letterSpacing}px;
65
+ line-height: ${body.lineHeight}px;
66
+ min-height: ${touchTarget};
67
+ padding: var(--sw-space-3) var(--sw-space-4);
68
+ width: 100%;
69
+ }
70
+
71
+ [data-theme] input.sw-command-palette-field:focus,
72
+ [data-theme] input.sw-command-palette-field:focus-visible {
73
+ border-bottom-color: var(--sw-color-accent);
74
+ box-shadow: inset 0 -1px 0 var(--sw-color-accent);
75
+ outline: none;
76
+ }
77
+
78
+ .sw-command-palette-list {
79
+ flex: 1 1 auto;
80
+ min-height: 0;
81
+ overflow-y: auto;
82
+ overscroll-behavior: contain;
83
+ padding: var(--sw-space-1) var(--sw-space-2);
84
+ }
85
+
86
+ .sw-command-palette-heading {
87
+ color: var(--sw-color-muted);
88
+ font-family: var(--sw-font-sans);
89
+ font-size: ${caption.fontSize}px;
90
+ font-weight: 600;
91
+ letter-spacing: ${caption.letterSpacing}px;
92
+ line-height: ${caption.lineHeight}px;
93
+ padding: var(--sw-space-2) var(--sw-space-2) var(--sw-space-1);
94
+ }
95
+
96
+ .sw-command-palette-message,
97
+ .sw-command-palette-hint {
98
+ color: var(--sw-color-muted);
99
+ font-family: var(--sw-font-sans);
100
+ font-size: ${caption.fontSize}px;
101
+ letter-spacing: ${caption.letterSpacing}px;
102
+ line-height: ${caption.lineHeight}px;
103
+ margin: 0;
104
+ padding: var(--sw-space-3) var(--sw-space-4);
105
+ }
106
+
107
+ .sw-command-palette-hint {
108
+ border-top: 1px solid var(--sw-color-border);
109
+ flex: none;
110
+ }`;
111
+ }
112
+ /*
113
+ * A result: its mark, its label over its muted detail, and on the active
114
+ * row (`aria-selected`, the arrow keys' or the pointer's) the quiet fill
115
+ * and the Enter key cap at the end. The sm control height, 44 px on a
116
+ * coarse pointer. On the fill the detail moves toward the text colour to
117
+ * keep 4.5:1. Forced colours draw the active row in Highlight.
118
+ */
119
+ function itemRules() {
120
+ return `.sw-command-palette-item {
121
+ align-items: center;
122
+ border-radius: var(--sw-radius-sm);
123
+ box-sizing: border-box;
124
+ cursor: pointer;
125
+ display: flex;
126
+ font-family: var(--sw-font-sans);
127
+ font-size: ${label.fontSize}px;
128
+ gap: var(--sw-space-3);
129
+ letter-spacing: ${label.letterSpacing}px;
130
+ line-height: ${label.lineHeight}px;
131
+ min-height: var(--sw-control-sm-min-height);
132
+ overflow-wrap: anywhere;
133
+ padding: var(--sw-space-1) var(--sw-space-2);
134
+ }
135
+
136
+ .sw-command-palette-item[aria-selected='true'] {
137
+ background: ${quietFill};
138
+ }
139
+
140
+ .sw-command-palette-icon {
141
+ display: inline-flex;
142
+ flex: none;
143
+ }
144
+
145
+ .sw-command-palette-words {
146
+ display: flex;
147
+ flex: 1 1 auto;
148
+ flex-direction: column;
149
+ min-width: 0;
150
+ }
151
+
152
+ .sw-command-palette-label {
153
+ font-weight: 500;
154
+ }
155
+
156
+ .sw-command-palette-detail {
157
+ color: var(--sw-color-muted);
158
+ font-size: ${caption.fontSize}px;
159
+ letter-spacing: ${caption.letterSpacing}px;
160
+ line-height: ${caption.lineHeight}px;
161
+ }
162
+
163
+ .sw-command-palette-item[aria-selected='true'] .sw-command-palette-detail {
164
+ color: color-mix(in srgb, var(--sw-color-muted) 70%, var(--sw-color-text));
165
+ }
166
+
167
+ @media ${coarsePointerQuery} {
168
+ .sw-command-palette-item { min-height: ${touchTarget}; }
169
+ }
170
+
171
+ @media (forced-colors: active) {
172
+ .sw-command-palette { border-color: CanvasText; }
173
+ .sw-command-palette-item[aria-selected='true'] { background: Highlight; color: HighlightText; forced-color-adjust: none; }
174
+ .sw-command-palette-item[aria-selected='true'] .sw-command-palette-detail,
175
+ .sw-command-palette-item[aria-selected='true'] .sw-command-palette-key { border-color: HighlightText; color: HighlightText; }
176
+ [data-theme] input.sw-command-palette-field:focus-visible { border-bottom-color: Highlight; box-shadow: inset 0 -1px 0 Highlight; }
177
+ }`;
178
+ }
179
+ /*
180
+ * CommandPalette and CommandPaletteTrigger (see components/CommandPalette.tsx):
181
+ * the trigger is `css-command-palette-trigger.ts`, the shared key cap
182
+ * `css-command-palette-key.ts`.
183
+ */
184
+ export function cssCommandPaletteClasses() {
185
+ return `${cssCommandPaletteKeyClasses()}
186
+
187
+ ${cssCommandPaletteTriggerClasses()}
188
+
189
+ ${frameRules()}
190
+
191
+ ${itemRules()}`;
192
+ }
193
+ export function commandPaletteClassCatalog() {
194
+ return [
195
+ ...commandPaletteTriggerClassCatalog(),
196
+ 'sw-command-palette',
197
+ 'sw-command-palette-body',
198
+ 'sw-command-palette-field',
199
+ 'sw-command-palette-list',
200
+ 'sw-command-palette-heading',
201
+ 'sw-command-palette-item',
202
+ 'sw-command-palette-icon',
203
+ 'sw-command-palette-words',
204
+ 'sw-command-palette-label',
205
+ 'sw-command-palette-detail',
206
+ 'sw-command-palette-message',
207
+ 'sw-command-palette-hint',
208
+ ];
209
+ }