@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
@@ -0,0 +1,62 @@
1
+ import { useEffect } from 'react';
2
+ import { breakpointQuery } from '../../css/breakpoints.js';
3
+ /** From here the bar shows its row of destinations and hides the menu. */
4
+ const wideQuery = breakpointQuery('from', 'lg');
5
+ /**
6
+ * What script adds to the menu, a native `details` that opens and closes
7
+ * without it: Escape closes it and puts focus back on its button; choosing
8
+ * a link in it, focus leaving it, a press outside it, or the window
9
+ * widening to the row of destinations closes it. Nothing traps focus.
10
+ */
11
+ export function useMenuDismiss(menuRef) {
12
+ useEffect(() => {
13
+ const menu = menuRef.current;
14
+ if (!menu)
15
+ return;
16
+ const close = () => {
17
+ menu.open = false;
18
+ };
19
+ const onKeyDown = (event) => {
20
+ if (event.key !== 'Escape' || !menu.open)
21
+ return;
22
+ event.preventDefault();
23
+ close();
24
+ menu.querySelector('summary')?.focus();
25
+ };
26
+ const onClick = (event) => {
27
+ if (event.target instanceof Element && event.target.closest('a'))
28
+ close();
29
+ };
30
+ const onFocusOut = (event) => {
31
+ const next = event.relatedTarget;
32
+ if (next instanceof Node && menu.contains(next))
33
+ return;
34
+ if (next !== null)
35
+ close();
36
+ };
37
+ const onPointerDown = (event) => {
38
+ if (event.target instanceof Node && menu.contains(event.target))
39
+ return;
40
+ close();
41
+ };
42
+ const wide = typeof window.matchMedia === 'function'
43
+ ? window.matchMedia(wideQuery)
44
+ : undefined;
45
+ const onWiden = () => {
46
+ if (wide?.matches)
47
+ close();
48
+ };
49
+ menu.addEventListener('keydown', onKeyDown);
50
+ menu.addEventListener('click', onClick);
51
+ menu.addEventListener('focusout', onFocusOut);
52
+ document.addEventListener('pointerdown', onPointerDown);
53
+ wide?.addEventListener('change', onWiden);
54
+ return () => {
55
+ menu.removeEventListener('keydown', onKeyDown);
56
+ menu.removeEventListener('click', onClick);
57
+ menu.removeEventListener('focusout', onFocusOut);
58
+ document.removeEventListener('pointerdown', onPointerDown);
59
+ wide?.removeEventListener('change', onWiden);
60
+ };
61
+ }, [menuRef]);
62
+ }
@@ -0,0 +1,7 @@
1
+ import { type SiteHeaderProps } from './types.js';
2
+ /**
3
+ * Fails closed on the header's words and destinations: no items, blank or
4
+ * repeated ids, blank labels or hrefs, and a blank navigation name or menu
5
+ * button name.
6
+ */
7
+ export declare function assertSiteHeaderProps({ items, labels, navLabel, }: Pick<SiteHeaderProps, 'items' | 'labels' | 'navLabel'>): void;
@@ -0,0 +1,17 @@
1
+ import { assertNavItems } from '../nav-items.js';
2
+ /**
3
+ * Fails closed on the header's words and destinations: no items, blank or
4
+ * repeated ids, blank labels or hrefs, and a blank navigation name or menu
5
+ * button name.
6
+ */
7
+ export function assertSiteHeaderProps({ items, labels, navLabel, }) {
8
+ if (items.length === 0)
9
+ throw new RangeError('SiteHeader needs at least one item');
10
+ assertNavItems('SiteHeader', items);
11
+ if (items.some((item) => !item.href.trim()))
12
+ throw new RangeError('SiteHeader items need nonempty hrefs');
13
+ if (!navLabel.trim())
14
+ throw new RangeError('SiteHeader needs a nonempty navLabel');
15
+ if (!labels.openMenu.trim() || !labels.closeMenu.trim())
16
+ throw new RangeError('SiteHeader labels need nonempty openMenu and closeMenu');
17
+ }
@@ -0,0 +1,10 @@
1
+ import { type RefObject } from 'react';
2
+ /**
3
+ * Follows the height of an element's border box, in CSS pixels: on
4
+ * mount, before the first paint, and whenever its box changes size. It is
5
+ * `null` before that, on the server, while `enabled` is false and while the
6
+ * element draws no box (`display: none`), so a caller can fall back to a
7
+ * value of its own. A browser with no ResizeObserver measures on window
8
+ * resizes instead.
9
+ */
10
+ export declare function useElementHeight(ref: RefObject<HTMLElement | null>, enabled?: boolean): number | null;
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+ import { useLayoutEffect, useState } from 'react';
3
+ /**
4
+ * Follows the height of an element's border box, in CSS pixels: on
5
+ * mount, before the first paint, and whenever its box changes size. It is
6
+ * `null` before that, on the server, while `enabled` is false and while the
7
+ * element draws no box (`display: none`), so a caller can fall back to a
8
+ * value of its own. A browser with no ResizeObserver measures on window
9
+ * resizes instead.
10
+ */
11
+ export function useElementHeight(ref, enabled = true) {
12
+ const [height, setHeight] = useState(null);
13
+ useLayoutEffect(() => {
14
+ const box = ref.current;
15
+ if (!box || !enabled) {
16
+ setHeight(null);
17
+ return;
18
+ }
19
+ function measure() {
20
+ if (!box)
21
+ return;
22
+ const next = box.getBoundingClientRect().height;
23
+ setHeight(next > 0 ? next : null);
24
+ }
25
+ measure();
26
+ if (typeof ResizeObserver === 'undefined') {
27
+ window.addEventListener('resize', measure);
28
+ return () => window.removeEventListener('resize', measure);
29
+ }
30
+ const observer = new ResizeObserver(measure);
31
+ observer.observe(box);
32
+ return () => observer.disconnect();
33
+ }, [ref, enabled]);
34
+ return height;
35
+ }
@@ -0,0 +1,9 @@
1
+ import { type RenderResult } from '@testing-library/react';
2
+ import { type Mock } from 'vitest';
3
+ import { type ContextSwitcherGroup, type ContextSwitcherProps } from './components/ContextSwitcher.js';
4
+ /** Field stations: a bare group, then a labelled one with badges and counts. */
5
+ export declare const stations: ContextSwitcherGroup[];
6
+ /** The switcher with its required props, and any prop overridden. */
7
+ export declare function renderSwitcher(props?: Partial<ContextSwitcherProps>): RenderResult & {
8
+ onSelect: Mock;
9
+ };
@@ -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,2 @@
1
+ export declare function cssAppShellDrawerClasses(): string;
2
+ export declare function appShellDrawerClassCatalog(): string[];
@@ -0,0 +1,91 @@
1
+ import { iconButtonRules } from './css-icon-button.js';
2
+ /* The drawer's width, a named geometry of this frame; a phone keeps a strip of backdrop. */
3
+ const drawerWidth = '20rem';
4
+ const drawer = '.sw-app-shell-drawer';
5
+ /*
6
+ * AppShell's menu below `lg`: a modal `dialog` on the top layer, docked to
7
+ * the inline start (the right in right-to-left) at the viewport's full
8
+ * height on the solid surface, a hairline at its inline end, clearing the
9
+ * safe areas, a space-8 strip of backdrop left in view on a phone. It
10
+ * slides in from its edge unless reduced motion is asked for. Its column
11
+ * is the top row (the close button where the menu button was, then the
12
+ * header), the nav, which alone scrolls, and the footer. The backdrop is
13
+ * Dialog's.
14
+ */
15
+ export function cssAppShellDrawerClasses() {
16
+ return `${drawer} {
17
+ background: var(--sw-color-surface);
18
+ border: 0;
19
+ border-inline-end: 1px solid var(--sw-color-border);
20
+ box-sizing: border-box;
21
+ color: var(--sw-color-text);
22
+ height: 100%;
23
+ inset-block: 0;
24
+ inset-inline: 0 auto;
25
+ margin: 0;
26
+ max-height: none;
27
+ max-width: calc(100% - var(--sw-space-8));
28
+ overflow: hidden;
29
+ padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px);
30
+ padding-inline-start: env(safe-area-inset-left, 0px);
31
+ width: ${drawerWidth};
32
+ }
33
+
34
+ ${drawer}:dir(rtl) {
35
+ padding-inline-start: env(safe-area-inset-right, 0px);
36
+ }
37
+
38
+ ${drawer}[open] {
39
+ animation: sw-app-shell-drawer-in var(--sw-motion-default) var(--sw-motion-easing);
40
+ display: flex;
41
+ flex-direction: column;
42
+ }
43
+
44
+ ${drawer}[open]:dir(rtl) {
45
+ animation-name: sw-app-shell-drawer-in-rtl;
46
+ }
47
+
48
+ @keyframes sw-app-shell-drawer-in {
49
+ from { translate: -100% 0; }
50
+ }
51
+
52
+ @keyframes sw-app-shell-drawer-in-rtl {
53
+ from { translate: 100% 0; }
54
+ }
55
+
56
+ ${drawer}::backdrop {
57
+ background: color-mix(in srgb, var(--sw-color-text) 28%, transparent);
58
+ }
59
+
60
+ .sw-app-shell-drawer-top {
61
+ align-items: center;
62
+ display: flex;
63
+ flex: none;
64
+ gap: var(--sw-space-2);
65
+ padding: var(--sw-space-3) var(--sw-space-3) 0;
66
+ }
67
+
68
+ .sw-app-shell-drawer-top .sw-app-shell-header {
69
+ flex: 1 1 auto;
70
+ min-width: 0;
71
+ padding: 0;
72
+ }
73
+
74
+ ${iconButtonRules('sw-app-shell-close')}
75
+
76
+ @media (prefers-reduced-motion: reduce) {
77
+ ${drawer}[open],
78
+ ${drawer}[open]:dir(rtl) { animation: none; }
79
+ }
80
+
81
+ @media (forced-colors: active) {
82
+ ${drawer} { border-inline-end-color: CanvasText; }
83
+ }`;
84
+ }
85
+ export function appShellDrawerClassCatalog() {
86
+ return [
87
+ 'sw-app-shell-drawer',
88
+ 'sw-app-shell-drawer-top',
89
+ 'sw-app-shell-close',
90
+ ];
91
+ }
@@ -0,0 +1,3 @@
1
+ export declare function cssAppShellNavClasses(): string;
2
+ export declare function cssAppShellRailNavRules(indent: string): string;
3
+ export declare function appShellNavClassCatalog(): string[];
@@ -0,0 +1,167 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { visuallyHiddenDeclarations } from './css-utilities.js';
3
+ import { coarsePointerQuery, touchTarget } from './touch-target.js';
4
+ const caption = typographyVariants.caption;
5
+ const label = typographyVariants.label;
6
+ /*
7
+ * The shell's destinations, in the full sidebar and the drawer alike: label
8
+ * size in the text colour at weight 500, the glyph before the label and the
9
+ * count pill (the shared rule, `css-count-pill.ts`) at the row's end. The
10
+ * current one takes the quiet muted fill in the text colour at the label
11
+ * weight, as SectionNav \`filled\` marks it; hover a lighter fill. Each is
12
+ * the sm control and the 44 px target on a coarse pointer. A 2 px
13
+ * transparent border keeps room for forced colours, where the current
14
+ * link's turns Highlight. A group's label is a muted caption at weight 600
15
+ * (sentence case, never uppercase). The link rules start at the canvas
16
+ * (\`[data-theme]\`) to outrank its accent links.
17
+ */
18
+ export function cssAppShellNavClasses() {
19
+ return `.sw-app-shell-nav {
20
+ box-sizing: border-box;
21
+ display: flex;
22
+ flex: 1 1 auto;
23
+ flex-direction: column;
24
+ gap: var(--sw-space-4);
25
+ min-height: 0;
26
+ overflow-y: auto;
27
+ overscroll-behavior: contain;
28
+ padding: var(--sw-space-2) var(--sw-space-3);
29
+ }
30
+
31
+ .sw-app-shell-group {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: var(--sw-space-1);
35
+ }
36
+
37
+ .sw-app-shell-group-label {
38
+ color: var(--sw-color-muted);
39
+ font-family: var(--sw-font-sans);
40
+ font-size: ${caption.fontSize}px;
41
+ font-weight: 600;
42
+ letter-spacing: ${caption.letterSpacing}px;
43
+ line-height: ${caption.lineHeight}px;
44
+ margin: 0;
45
+ padding-inline: var(--sw-space-2);
46
+ }
47
+
48
+ .sw-app-shell-list {
49
+ display: flex;
50
+ flex-direction: column;
51
+ gap: var(--sw-space-1);
52
+ list-style: none;
53
+ margin: 0;
54
+ padding: 0;
55
+ }
56
+
57
+ .sw-app-shell-item > .sw-tooltip-anchor {
58
+ display: flex;
59
+ }
60
+
61
+ [data-theme] .sw-app-shell-link {
62
+ align-items: center;
63
+ border: 2px solid transparent;
64
+ border-radius: var(--sw-radius-sm);
65
+ box-sizing: border-box;
66
+ color: var(--sw-color-text);
67
+ display: flex;
68
+ flex: 1 1 auto;
69
+ font-family: var(--sw-font-sans);
70
+ font-size: ${label.fontSize}px;
71
+ font-weight: 500;
72
+ gap: var(--sw-space-3);
73
+ letter-spacing: ${label.letterSpacing}px;
74
+ line-height: ${label.lineHeight}px;
75
+ min-height: var(--sw-control-sm-min-height);
76
+ padding: var(--sw-space-1) var(--sw-space-2);
77
+ text-decoration: none;
78
+ }
79
+
80
+ [data-theme] .sw-app-shell-link:hover {
81
+ background: color-mix(in srgb, var(--sw-color-muted) 12%, transparent);
82
+ }
83
+
84
+ [data-theme] .sw-app-shell-link:focus-visible {
85
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
86
+ outline-offset: calc(-1 * var(--sw-focus-ring-width));
87
+ }
88
+
89
+ [data-theme] .sw-app-shell-link[aria-current] {
90
+ background: color-mix(in srgb, var(--sw-color-muted) 18%, transparent);
91
+ font-weight: ${label.fontWeight};
92
+ }
93
+
94
+ .sw-app-shell-icon {
95
+ display: inline-flex;
96
+ flex: none;
97
+ }
98
+
99
+ .sw-app-shell-label {
100
+ flex: 1 1 auto;
101
+ min-width: 0;
102
+ overflow-wrap: anywhere;
103
+ }
104
+
105
+ .sw-app-shell-count {
106
+ flex: none;
107
+ }
108
+
109
+ @media ${coarsePointerQuery} {
110
+ [data-theme] .sw-app-shell-link { min-height: ${touchTarget}; }
111
+ }
112
+
113
+ @media (forced-colors: active) {
114
+ [data-theme] .sw-app-shell-link { color: LinkText; }
115
+ [data-theme] .sw-app-shell-link[aria-current] { border-color: Highlight; color: CanvasText; }
116
+ }`;
117
+ }
118
+ /*
119
+ * The rail (from \`lg\` to 75rem, inside the sidebar's media queries): each
120
+ * link the 44 px target holding its glyph alone, the label visually hidden
121
+ * but still its name, the count pill at the link's top end corner. A
122
+ * group's label is hidden too (it still names its list) and every group
123
+ * after the first starts under a hairline.
124
+ */
125
+ export function cssAppShellRailNavRules(indent) {
126
+ const rules = `.sw-app-shell-sidebar .sw-app-shell-label,
127
+ .sw-app-shell-sidebar .sw-app-shell-group-label { ${visuallyHiddenDeclarations} }
128
+
129
+ .sw-app-shell-sidebar .sw-app-shell-nav {
130
+ gap: var(--sw-space-2);
131
+ }
132
+
133
+ .sw-app-shell-sidebar .sw-app-shell-group + .sw-app-shell-group {
134
+ border-top: 1px solid var(--sw-color-border);
135
+ padding-top: var(--sw-space-2);
136
+ }
137
+
138
+ [data-theme] .sw-app-shell-sidebar .sw-app-shell-link {
139
+ justify-content: center;
140
+ min-height: ${touchTarget};
141
+ padding: 0;
142
+ position: relative;
143
+ }
144
+
145
+ .sw-app-shell-sidebar .sw-app-shell-count {
146
+ inset-block-start: 0;
147
+ inset-inline-end: 0;
148
+ position: absolute;
149
+ }`;
150
+ return rules
151
+ .split('\n')
152
+ .map((line) => (line ? `${indent}${line}` : line))
153
+ .join('\n');
154
+ }
155
+ export function appShellNavClassCatalog() {
156
+ return [
157
+ 'sw-app-shell-nav',
158
+ 'sw-app-shell-group',
159
+ 'sw-app-shell-group-label',
160
+ 'sw-app-shell-list',
161
+ 'sw-app-shell-item',
162
+ 'sw-app-shell-link',
163
+ 'sw-app-shell-icon',
164
+ 'sw-app-shell-label',
165
+ // sw-app-shell-count is the shared pill's (css-count-pill.ts).
166
+ ];
167
+ }
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ /** From this width up AppShell draws its full sidebar; script reads it too. */
2
+ export declare const appShellWideQuery = "(min-width: 75rem)";
3
+ export declare function cssAppShellClasses(): string;
4
+ export declare function appShellClassCatalog(): string[];