@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.
- package/CHANGELOG.md +82 -0
- package/README.md +215 -6
- package/dist/command-palette.test-support.d.ts +21 -0
- package/dist/command-palette.test-support.js +55 -0
- package/dist/components/AppHeader.d.ts +3 -0
- package/dist/components/AppShell.d.ts +4 -3
- package/dist/components/AppShell.js +24 -7
- package/dist/components/Band.d.ts +30 -0
- package/dist/components/Band.js +23 -0
- package/dist/components/ButtonLink.d.ts +33 -0
- package/dist/components/ButtonLink.js +27 -0
- package/dist/components/CommandPalette.d.ts +12 -0
- package/dist/components/CommandPalette.js +143 -0
- package/dist/components/CommandPaletteTrigger.d.ts +9 -0
- package/dist/components/CommandPaletteTrigger.js +16 -0
- package/dist/components/ContextSwitcher.d.ts +12 -0
- package/dist/components/ContextSwitcher.js +62 -0
- package/dist/components/EmptyState.d.ts +23 -0
- package/dist/components/EmptyState.js +40 -0
- package/dist/components/FixtureGroup.d.ts +9 -0
- package/dist/components/FixtureGroup.js +16 -0
- package/dist/components/FixtureRow.d.ts +10 -0
- package/dist/components/FixtureRow.js +46 -0
- package/dist/components/GlyphTile.d.ts +9 -3
- package/dist/components/GlyphTile.js +15 -4
- package/dist/components/Grid.d.ts +11 -3
- package/dist/components/Grid.js +13 -4
- package/dist/components/Hero.d.ts +26 -0
- package/dist/components/Hero.js +23 -0
- package/dist/components/SiteFooter.d.ts +12 -0
- package/dist/components/SiteFooter.js +23 -0
- package/dist/components/SiteHeader.d.ts +14 -0
- package/dist/components/SiteHeader.js +24 -0
- package/dist/components/Skeleton.d.ts +22 -0
- package/dist/components/Skeleton.js +30 -0
- package/dist/components/Stepper.d.ts +11 -1
- package/dist/components/Stepper.js +10 -8
- package/dist/components/Text.js +3 -0
- package/dist/components/app-shell/AppShellBar.d.ts +23 -0
- package/dist/components/app-shell/AppShellBar.js +25 -0
- package/dist/components/app-shell/types.d.ts +36 -1
- package/dist/components/app-shell/validate.d.ts +4 -3
- package/dist/components/app-shell/validate.js +21 -3
- package/dist/components/command-palette/PaletteList.d.ts +20 -0
- package/dist/components/command-palette/PaletteList.js +21 -0
- package/dist/components/command-palette/shortcut.d.ts +19 -0
- package/dist/components/command-palette/shortcut.js +75 -0
- package/dist/components/command-palette/types.d.ts +72 -0
- package/dist/components/command-palette/types.js +2 -0
- package/dist/components/command-palette/validate.d.ts +10 -0
- package/dist/components/command-palette/validate.js +50 -0
- package/dist/components/context-switcher/SwitcherOption.d.ts +17 -0
- package/dist/components/context-switcher/SwitcherOption.js +20 -0
- package/dist/components/context-switcher/SwitcherPopup.d.ts +31 -0
- package/dist/components/context-switcher/SwitcherPopup.js +111 -0
- package/dist/components/context-switcher/options.d.ts +33 -0
- package/dist/components/context-switcher/options.js +60 -0
- package/dist/components/context-switcher/types.d.ts +96 -0
- package/dist/components/context-switcher/types.js +1 -0
- package/dist/components/context-switcher/use-switcher-list.d.ts +29 -0
- package/dist/components/context-switcher/use-switcher-list.js +93 -0
- package/dist/components/context-switcher/validate.d.ts +8 -0
- package/dist/components/context-switcher/validate.js +94 -0
- package/dist/components/fixture/fixture-list-context.d.ts +4 -0
- package/dist/components/fixture/fixture-list-context.js +9 -0
- package/dist/components/fixture/types.d.ts +50 -0
- package/dist/components/fixture/types.js +1 -0
- package/dist/components/fixture/validate.d.ts +8 -0
- package/dist/components/fixture/validate.js +36 -0
- package/dist/components/site-footer/types.d.ts +31 -0
- package/dist/components/site-footer/types.js +1 -0
- package/dist/components/site-footer/validate.d.ts +7 -0
- package/dist/components/site-footer/validate.js +22 -0
- package/dist/components/site-header/SiteHeaderLinks.d.ts +7 -0
- package/dist/components/site-header/SiteHeaderLinks.js +7 -0
- package/dist/components/site-header/types.d.ts +38 -0
- package/dist/components/site-header/types.js +1 -0
- package/dist/components/site-header/use-menu-dismiss.d.ts +8 -0
- package/dist/components/site-header/use-menu-dismiss.js +62 -0
- package/dist/components/site-header/validate.d.ts +7 -0
- package/dist/components/site-header/validate.js +17 -0
- package/dist/components/stepper/StepperCountFrame.d.ts +15 -0
- package/dist/components/stepper/StepperCountFrame.js +25 -0
- package/dist/components/use-element-height.d.ts +10 -0
- package/dist/components/use-element-height.js +35 -0
- package/dist/context-switcher.test-support.d.ts +9 -0
- package/dist/context-switcher.test-support.js +41 -0
- package/dist/css/css-app-shell-page-bar.d.ts +3 -0
- package/dist/css/css-app-shell-page-bar.js +81 -0
- package/dist/css/css-app-shell.js +6 -1
- package/dist/css/css-band.d.ts +10 -0
- package/dist/css/css-band.js +101 -0
- package/dist/css/css-button-link.d.ts +3 -0
- package/dist/css/css-button-link.js +48 -0
- package/dist/css/css-command-palette-key.d.ts +1 -0
- package/dist/css/css-command-palette-key.js +33 -0
- package/dist/css/css-command-palette-trigger.d.ts +2 -0
- package/dist/css/css-command-palette-trigger.js +88 -0
- package/dist/css/css-command-palette.d.ts +2 -0
- package/dist/css/css-command-palette.js +209 -0
- package/dist/css/css-components.js +30 -0
- package/dist/css/css-context-switcher-trigger.d.ts +15 -0
- package/dist/css/css-context-switcher-trigger.js +175 -0
- package/dist/css/css-context-switcher.d.ts +4 -0
- package/dist/css/css-context-switcher.js +219 -0
- package/dist/css/css-count-pill.d.ts +2 -2
- package/dist/css/css-count-pill.js +11 -3
- package/dist/css/css-empty-state.d.ts +2 -0
- package/dist/css/css-empty-state.js +77 -0
- package/dist/css/css-fixture.d.ts +5 -0
- package/dist/css/css-fixture.js +260 -0
- package/dist/css/css-glyph-tile.d.ts +2 -0
- package/dist/css/css-glyph-tile.js +29 -1
- package/dist/css/css-grid-span.js +5 -2
- package/dist/css/css-grid.d.ts +13 -0
- package/dist/css/css-grid.js +17 -1
- package/dist/css/css-hero.d.ts +2 -0
- package/dist/css/css-hero.js +62 -0
- package/dist/css/css-site-footer.d.ts +2 -0
- package/dist/css/css-site-footer.js +152 -0
- package/dist/css/css-site-header.d.ts +2 -0
- package/dist/css/css-site-header.js +262 -0
- package/dist/css/css-skeleton.d.ts +10 -0
- package/dist/css/css-skeleton.js +110 -0
- package/dist/css/css-stepper-unit.d.ts +9 -0
- package/dist/css/css-stepper-unit.js +102 -0
- package/dist/css/css-stepper.js +5 -1
- package/dist/css/stacking.js +7 -4
- package/dist/css/stylesheet.js +20 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.js +13 -1
- package/dist/stepper-count.d.ts +8 -0
- package/dist/stepper-count.js +17 -0
- package/dist/styles.css +2202 -702
- 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,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
|
|
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,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,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,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
|
+
}
|