@astronox/core 0.2.3 → 0.2.4
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/package.json +1 -1
- package/src/components/actions/Button/Button.css +1 -1
- package/src/components/actions/CardAction/CardAction.css +3 -3
- package/src/components/actions/ThemeToggle/ThemeToggle.astro +59 -0
- package/src/components/actions/ThemeToggle/ThemeToggle.css +83 -0
- package/src/components/actions/ThemeToggle/themeToggleController.ts +111 -0
- package/src/components/feedback/ThemeBootstrap/ThemeBootstrap.astro +31 -0
- package/src/components/forms/ChoiceGroup/ChoiceGroup.css +1 -1
- package/src/components/icons/CircleSymbolIcon.css +3 -3
- package/src/components/index.ts +2 -0
- package/src/components/navigation/DesktopNavigation.css +4 -1
- package/src/components/navigation/Header.astro +20 -2
- package/src/components/navigation/MobileNavigation.astro +107 -43
- package/src/components/navigation/MobileNavigation.css +109 -24
- package/src/components/navigation/NavigationBrand.css +1 -1
- package/src/components/navigation/NavigationControl.css +16 -2
- package/src/components/navigation/NavigationItemContent.astro +6 -1
- package/src/components/navigation/mobileNavigationController.ts +0 -16
- package/src/components/navigation/navigationTypes.ts +9 -1
- package/src/display/contracts.ts +1 -0
- package/src/layouts/BaseLayout.astro +5 -0
- package/src/patterns/MapViewport/MapViewport.css +3 -3
- package/src/styles/style.css +1 -0
- package/src/styles/tokens.css +55 -15
- package/src/theme/contracts.ts +2 -0
- package/src/theme/index.ts +1 -0
- package/src/theme/storage.ts +1 -0
package/package.json
CHANGED
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
background: color-mix(
|
|
131
131
|
in srgb,
|
|
132
132
|
var(--card-action-hover-seed) 12%,
|
|
133
|
-
var(--nox-
|
|
133
|
+
var(--nox-theme-surface-base)
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
background: color-mix(
|
|
146
146
|
in srgb,
|
|
147
147
|
var(--card-action-hover-seed) 12%,
|
|
148
|
-
var(--nox-
|
|
148
|
+
var(--nox-theme-surface-base)
|
|
149
149
|
);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
background: color-mix(
|
|
155
155
|
in srgb,
|
|
156
156
|
var(--card-action-hover-seed) 18%,
|
|
157
|
-
var(--nox-
|
|
157
|
+
var(--nox-theme-surface-base)
|
|
158
158
|
);
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
+
import DayIcon from '../../icons/DayIcon.astro';
|
|
4
|
+
import NightIcon from '../../icons/NightIcon.astro';
|
|
5
|
+
|
|
6
|
+
type Props = HTMLAttributes<'button'> & {
|
|
7
|
+
appearance?: 'circle' | 'utility';
|
|
8
|
+
darkLabel: string;
|
|
9
|
+
lightLabel: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
appearance = 'circle',
|
|
14
|
+
class: className,
|
|
15
|
+
darkLabel,
|
|
16
|
+
lightLabel,
|
|
17
|
+
type = 'button',
|
|
18
|
+
...attrs
|
|
19
|
+
}: Props = Astro.props;
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<button
|
|
23
|
+
{...attrs}
|
|
24
|
+
class:list={['theme-toggle', className]}
|
|
25
|
+
type={type}
|
|
26
|
+
aria-label={darkLabel}
|
|
27
|
+
title={darkLabel}
|
|
28
|
+
data-theme-toggle
|
|
29
|
+
data-theme-toggle-appearance={appearance}
|
|
30
|
+
data-theme-toggle-dark-label={darkLabel}
|
|
31
|
+
data-theme-toggle-light-label={lightLabel}
|
|
32
|
+
data-navigation-control={appearance === 'circle' ? '' : undefined}
|
|
33
|
+
data-navigation-control-appearance={
|
|
34
|
+
appearance === 'circle' ? 'bar' : undefined
|
|
35
|
+
}
|
|
36
|
+
>
|
|
37
|
+
<span
|
|
38
|
+
class="theme-toggle__icon"
|
|
39
|
+
data-theme-toggle-target="dark"
|
|
40
|
+
aria-hidden="true"
|
|
41
|
+
>
|
|
42
|
+
<NightIcon />
|
|
43
|
+
</span>
|
|
44
|
+
<span
|
|
45
|
+
class="theme-toggle__icon"
|
|
46
|
+
data-theme-toggle-target="light"
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
>
|
|
49
|
+
<DayIcon />
|
|
50
|
+
</span>
|
|
51
|
+
</button>
|
|
52
|
+
|
|
53
|
+
<script>
|
|
54
|
+
import { initThemeToggle } from './themeToggleController';
|
|
55
|
+
|
|
56
|
+
for (const toggle of document.querySelectorAll('[data-theme-toggle]')) {
|
|
57
|
+
if (toggle instanceof HTMLElement) initThemeToggle(toggle);
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
.theme-toggle {
|
|
2
|
+
display: grid;
|
|
3
|
+
flex: 0 0 auto;
|
|
4
|
+
place-items: center;
|
|
5
|
+
padding: 0;
|
|
6
|
+
border: var(--nox-border-width) solid var(--nox-border);
|
|
7
|
+
background: var(--nox-surface);
|
|
8
|
+
color: var(--nox-text-muted);
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.theme-toggle[data-theme-toggle-appearance='circle'] {
|
|
13
|
+
min-inline-size: 4.75rem;
|
|
14
|
+
block-size: 100%;
|
|
15
|
+
border: 0;
|
|
16
|
+
border-radius: 0;
|
|
17
|
+
background: transparent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.theme-toggle[data-theme-toggle-appearance='utility'] {
|
|
21
|
+
inline-size: 100%;
|
|
22
|
+
min-block-size: var(--nox-navigation-block-size);
|
|
23
|
+
border-radius: var(--nox-radius-md);
|
|
24
|
+
background: var(--nox-surface-muted);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.theme-toggle__icon {
|
|
28
|
+
display: grid;
|
|
29
|
+
inline-size: 2rem;
|
|
30
|
+
block-size: 2rem;
|
|
31
|
+
grid-area: 1 / 1;
|
|
32
|
+
place-items: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.theme-toggle[data-theme-toggle-appearance='utility'] .theme-toggle__icon {
|
|
36
|
+
inline-size: 2.125rem;
|
|
37
|
+
block-size: 2.125rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.theme-toggle[data-theme-toggle-appearance='circle'] .theme-toggle__icon {
|
|
41
|
+
border: var(--nox-border-width) solid var(--nox-border);
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
background: var(--nox-surface);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.theme-toggle__icon > svg {
|
|
47
|
+
display: block;
|
|
48
|
+
inline-size: 100%;
|
|
49
|
+
block-size: 100%;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.theme-toggle [data-theme-toggle-target='light'] {
|
|
53
|
+
display: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
:root[data-theme='dark'] .theme-toggle [data-theme-toggle-target='dark'] {
|
|
57
|
+
display: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
:root[data-theme='dark'] .theme-toggle [data-theme-toggle-target='light'] {
|
|
61
|
+
display: grid;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.theme-toggle:focus-visible {
|
|
65
|
+
outline: var(--nox-focus-ring-width) solid var(--nox-focus-ring);
|
|
66
|
+
outline-offset: var(--nox-focus-ring-offset);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media (hover: hover) {
|
|
70
|
+
.theme-toggle:hover {
|
|
71
|
+
background: var(--nox-surface-strong);
|
|
72
|
+
color: var(--nox-link-hover);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
77
|
+
.theme-toggle {
|
|
78
|
+
transition:
|
|
79
|
+
background-color var(--nox-motion-duration-sm)
|
|
80
|
+
var(--nox-motion-easing-standard),
|
|
81
|
+
color var(--nox-motion-duration-sm) var(--nox-motion-easing-standard);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ColorTheme } from '../../../theme/contracts';
|
|
2
|
+
import { defaultThemeStorageKey } from '../../../theme/storage';
|
|
3
|
+
|
|
4
|
+
const initializedToggles = new WeakSet<HTMLElement>();
|
|
5
|
+
const systemThemeQuery = '(prefers-color-scheme: dark)';
|
|
6
|
+
let globalListenersInitialized = false;
|
|
7
|
+
|
|
8
|
+
function isColorTheme(value: string | null): value is ColorTheme {
|
|
9
|
+
return value === 'light' || value === 'dark';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getStorageKey(): string {
|
|
13
|
+
return (
|
|
14
|
+
document.documentElement.dataset.themeStorageKey ??
|
|
15
|
+
defaultThemeStorageKey
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readStoredTheme(): ColorTheme | null {
|
|
20
|
+
try {
|
|
21
|
+
const value = window.localStorage.getItem(getStorageKey());
|
|
22
|
+
return isColorTheme(value) ? value : null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getSystemTheme(): ColorTheme {
|
|
29
|
+
return window.matchMedia(systemThemeQuery).matches ? 'dark' : 'light';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getCurrentTheme(): ColorTheme {
|
|
33
|
+
const value = document.documentElement.dataset.theme ?? null;
|
|
34
|
+
return isColorTheme(value) ? value : readStoredTheme() ?? getSystemTheme();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function updateToggle(toggle: HTMLElement, theme: ColorTheme): void {
|
|
38
|
+
const label =
|
|
39
|
+
theme === 'light'
|
|
40
|
+
? toggle.dataset.themeToggleDarkLabel
|
|
41
|
+
: toggle.dataset.themeToggleLightLabel;
|
|
42
|
+
|
|
43
|
+
if (label) {
|
|
44
|
+
toggle.setAttribute('aria-label', label);
|
|
45
|
+
toggle.setAttribute('title', label);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function updateToggles(theme: ColorTheme): void {
|
|
50
|
+
for (const toggle of document.querySelectorAll<HTMLElement>(
|
|
51
|
+
'[data-theme-toggle]',
|
|
52
|
+
)) {
|
|
53
|
+
updateToggle(toggle, theme);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function applyTheme(theme: ColorTheme, persist: boolean): void {
|
|
58
|
+
document.documentElement.dataset.theme = theme;
|
|
59
|
+
|
|
60
|
+
if (persist) {
|
|
61
|
+
try {
|
|
62
|
+
window.localStorage.setItem(getStorageKey(), theme);
|
|
63
|
+
} catch {
|
|
64
|
+
// The selected theme remains active for the current document.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
updateToggles(theme);
|
|
69
|
+
document.dispatchEvent(
|
|
70
|
+
new CustomEvent('astronox:theme-change', { detail: { theme } }),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function initializeGlobalListeners(): void {
|
|
75
|
+
if (globalListenersInitialized) return;
|
|
76
|
+
globalListenersInitialized = true;
|
|
77
|
+
|
|
78
|
+
const media = window.matchMedia(systemThemeQuery);
|
|
79
|
+
media.addEventListener('change', (event) => {
|
|
80
|
+
if (readStoredTheme() === null) {
|
|
81
|
+
applyTheme(event.matches ? 'dark' : 'light', false);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
window.addEventListener('storage', (event) => {
|
|
86
|
+
if (event.key !== getStorageKey()) return;
|
|
87
|
+
|
|
88
|
+
const theme = isColorTheme(event.newValue)
|
|
89
|
+
? event.newValue
|
|
90
|
+
: getSystemTheme();
|
|
91
|
+
applyTheme(theme, false);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function initThemeToggle(toggle: HTMLElement): () => void {
|
|
96
|
+
if (initializedToggles.has(toggle)) return () => {};
|
|
97
|
+
initializedToggles.add(toggle);
|
|
98
|
+
initializeGlobalListeners();
|
|
99
|
+
updateToggle(toggle, getCurrentTheme());
|
|
100
|
+
|
|
101
|
+
const handleClick = (): void => {
|
|
102
|
+
applyTheme(getCurrentTheme() === 'light' ? 'dark' : 'light', true);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
toggle.addEventListener('click', handleClick);
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
toggle.removeEventListener('click', handleClick);
|
|
109
|
+
initializedToggles.delete(toggle);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { defaultThemeStorageKey } from '../../../theme/storage';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
storageKey?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const { storageKey = defaultThemeStorageKey }: Props = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<script is:inline define:vars={{ storageKey }}>
|
|
12
|
+
(() => {
|
|
13
|
+
const root = document.documentElement;
|
|
14
|
+
let storedTheme = null;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const value = window.localStorage.getItem(storageKey);
|
|
18
|
+
if (value === 'light' || value === 'dark') storedTheme = value;
|
|
19
|
+
} catch {
|
|
20
|
+
// The system preference remains available without local storage.
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const systemTheme = window.matchMedia?.('(prefers-color-scheme: dark)')
|
|
24
|
+
.matches
|
|
25
|
+
? 'dark'
|
|
26
|
+
: 'light';
|
|
27
|
+
|
|
28
|
+
root.dataset.theme = storedTheme ?? systemTheme;
|
|
29
|
+
root.dataset.themeStorageKey = storageKey;
|
|
30
|
+
})();
|
|
31
|
+
</script>
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
--circle-symbol-icon-surface: color-mix(
|
|
4
4
|
in srgb,
|
|
5
5
|
var(--circle-symbol-icon-seed) 32%,
|
|
6
|
-
var(--nox-
|
|
6
|
+
var(--nox-theme-surface-base)
|
|
7
7
|
);
|
|
8
8
|
--circle-symbol-icon-border: color-mix(
|
|
9
9
|
in srgb,
|
|
10
10
|
var(--circle-symbol-icon-seed) 74%,
|
|
11
|
-
var(--nox-
|
|
11
|
+
var(--nox-theme-text-base)
|
|
12
12
|
);
|
|
13
13
|
--circle-symbol-icon-mark: color-mix(
|
|
14
14
|
in srgb,
|
|
15
15
|
var(--circle-symbol-icon-seed) 82%,
|
|
16
|
-
var(--nox-
|
|
16
|
+
var(--nox-theme-text-base)
|
|
17
17
|
);
|
|
18
18
|
|
|
19
19
|
color: var(--circle-symbol-icon-mark);
|
package/src/components/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as ActionListItem } from './actions/ActionListItem/ActionListItem.astro';
|
|
2
2
|
export { default as Button } from './actions/Button/Button.astro';
|
|
3
|
+
export { default as ThemeToggle } from './actions/ThemeToggle/ThemeToggle.astro';
|
|
3
4
|
export { default as CardAction } from './actions/CardAction/CardAction.astro';
|
|
4
5
|
export { default as IdentityProviderAction } from './actions/IdentityProviderAction/IdentityProviderAction.astro';
|
|
5
6
|
export { default as MediaTrigger } from './actions/MediaTrigger/MediaTrigger.astro';
|
|
@@ -33,6 +34,7 @@ export { default as TextInput } from './forms/TextInput/TextInput.astro';
|
|
|
33
34
|
export { default as Textarea } from './forms/Textarea/Textarea.astro';
|
|
34
35
|
export { default as Spinner } from './feedback/Spinner/Spinner.astro';
|
|
35
36
|
export { default as PreferenceBootstrap } from './feedback/PreferenceBootstrap/PreferenceBootstrap.astro';
|
|
37
|
+
export { default as ThemeBootstrap } from './feedback/ThemeBootstrap/ThemeBootstrap.astro';
|
|
36
38
|
export { default as Tooltip } from './feedback/Tooltip/Tooltip.astro';
|
|
37
39
|
export { default as CanvasFrame } from './media/CanvasFrame/CanvasFrame.astro';
|
|
38
40
|
export { default as EmbedFrame } from './media/EmbedFrame/EmbedFrame.astro';
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
3
|
import type { SiteIdentity } from '../../config/siteConfig';
|
|
4
|
-
import type {
|
|
5
|
-
|
|
4
|
+
import type {
|
|
5
|
+
MobileNavigationControlItem,
|
|
6
|
+
MobileNavigationLabels,
|
|
7
|
+
} from './navigationTypes';
|
|
8
|
+
import type {
|
|
9
|
+
NavigationItem,
|
|
10
|
+
NavigationLinkItem,
|
|
11
|
+
} from '../../display/contracts';
|
|
6
12
|
import type { Tone } from '../../theme/contracts';
|
|
7
13
|
import DesktopNavigation from './DesktopNavigation.astro';
|
|
8
14
|
import MobileNavigation from './MobileNavigation.astro';
|
|
@@ -15,6 +21,9 @@ type Props = HTMLAttributes<'header'> & {
|
|
|
15
21
|
identity: SiteIdentity;
|
|
16
22
|
leftItems?: readonly NavigationItem[];
|
|
17
23
|
menu: MobileNavigationLabels;
|
|
24
|
+
mobileFooterItems?: readonly MobileNavigationControlItem[];
|
|
25
|
+
mobileItems?: readonly NavigationLinkItem[];
|
|
26
|
+
mobileUtilityItems?: readonly MobileNavigationControlItem[];
|
|
18
27
|
rightItems?: readonly NavigationItem[];
|
|
19
28
|
sticky?: boolean;
|
|
20
29
|
tone?: Tone;
|
|
@@ -30,12 +39,16 @@ const {
|
|
|
30
39
|
identity,
|
|
31
40
|
leftItems = [],
|
|
32
41
|
menu,
|
|
42
|
+
mobileFooterItems = [],
|
|
43
|
+
mobileItems,
|
|
44
|
+
mobileUtilityItems = [],
|
|
33
45
|
rightItems = [],
|
|
34
46
|
sticky = false,
|
|
35
47
|
tone,
|
|
36
48
|
...attrs
|
|
37
49
|
} = Astro.props;
|
|
38
50
|
const resolvedTone = tone ?? dataTone;
|
|
51
|
+
const hasMobileUtilityActions = Astro.slots.has('mobile-utility-actions');
|
|
39
52
|
---
|
|
40
53
|
|
|
41
54
|
<header
|
|
@@ -52,10 +65,15 @@ const resolvedTone = tone ?? dataTone;
|
|
|
52
65
|
homeLabel={homeLabel}
|
|
53
66
|
leftItems={leftItems}
|
|
54
67
|
menu={menu}
|
|
68
|
+
mobileFooterItems={mobileFooterItems}
|
|
69
|
+
mobileItems={mobileItems}
|
|
70
|
+
mobileUtilityItems={mobileUtilityItems}
|
|
71
|
+
hasUtilityActions={hasMobileUtilityActions}
|
|
55
72
|
rightItems={rightItems}
|
|
56
73
|
currentPath={currentPath}
|
|
57
74
|
>
|
|
58
75
|
<slot name="mobile-actions" slot="actions" />
|
|
76
|
+
<slot name="mobile-utility-actions" slot="utility-actions" />
|
|
59
77
|
</MobileNavigation>
|
|
60
78
|
|
|
61
79
|
<DesktopNavigation
|
|
@@ -1,52 +1,56 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { SiteIdentity } from '../../config/siteConfig';
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
NavigationItem,
|
|
5
|
+
NavigationLinkItem,
|
|
6
|
+
} from '../../display/contracts';
|
|
7
|
+
import type {
|
|
8
|
+
MobileNavigationControlItem,
|
|
9
|
+
MobileNavigationLabels,
|
|
10
|
+
} from './navigationTypes';
|
|
11
|
+
import CardLink from '../content/CardLink/CardLink.astro';
|
|
5
12
|
import NavigationBrand from './NavigationBrand.astro';
|
|
6
13
|
import NavigationLink from './NavigationLink.astro';
|
|
7
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
isNavigationGroupItem,
|
|
16
|
+
isNavigationItemActive,
|
|
17
|
+
} from './navigationState';
|
|
8
18
|
|
|
9
19
|
type Props = {
|
|
10
20
|
currentPath: string;
|
|
21
|
+
hasUtilityActions?: boolean;
|
|
11
22
|
homeHref: string;
|
|
12
23
|
homeLabel: string;
|
|
13
24
|
identity: SiteIdentity;
|
|
14
25
|
leftItems: readonly NavigationItem[];
|
|
15
26
|
menu: MobileNavigationLabels;
|
|
27
|
+
mobileFooterItems: readonly MobileNavigationControlItem[];
|
|
28
|
+
mobileItems?: readonly NavigationLinkItem[];
|
|
29
|
+
mobileUtilityItems: readonly MobileNavigationControlItem[];
|
|
16
30
|
rightItems: readonly NavigationItem[];
|
|
17
31
|
};
|
|
18
32
|
|
|
19
|
-
type MobileNavigationGroup = {
|
|
20
|
-
items: readonly NavigationItem[];
|
|
21
|
-
label?: string;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
33
|
const {
|
|
25
34
|
currentPath,
|
|
35
|
+
hasUtilityActions,
|
|
26
36
|
homeHref,
|
|
27
37
|
homeLabel,
|
|
28
38
|
identity,
|
|
29
39
|
leftItems,
|
|
30
40
|
menu,
|
|
41
|
+
mobileFooterItems,
|
|
42
|
+
mobileItems,
|
|
43
|
+
mobileUtilityItems,
|
|
31
44
|
rightItems,
|
|
32
45
|
} = Astro.props;
|
|
33
46
|
const panelId = 'site-nav-mobile-panel';
|
|
34
47
|
const closeLabel = menu.closeLabel ?? menu.openLabel;
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
? {
|
|
42
|
-
label: item.label,
|
|
43
|
-
items: item.items,
|
|
44
|
-
}
|
|
45
|
-
: {
|
|
46
|
-
items: [item],
|
|
47
|
-
},
|
|
48
|
-
),
|
|
49
|
-
];
|
|
48
|
+
const fallbackItems = [...leftItems, ...rightItems].flatMap((item) =>
|
|
49
|
+
isNavigationGroupItem(item) ? [...item.items] : [item],
|
|
50
|
+
);
|
|
51
|
+
const resolvedMobileItems = mobileItems ?? fallbackItems;
|
|
52
|
+
const resolvedHasUtilityActions =
|
|
53
|
+
hasUtilityActions ?? Astro.slots.has('utility-actions');
|
|
50
54
|
---
|
|
51
55
|
|
|
52
56
|
<div class="site-nav__mobile" data-mobile-navigation>
|
|
@@ -88,28 +92,88 @@ const mobileGroups: MobileNavigationGroup[] = [
|
|
|
88
92
|
>
|
|
89
93
|
<ul class="site-nav__mobile-list">
|
|
90
94
|
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
resolvedMobileItems.map((item) => {
|
|
96
|
+
const active = isNavigationItemActive(item, currentPath);
|
|
97
|
+
const rel =
|
|
98
|
+
item.rel ??
|
|
99
|
+
(item.target === '_blank' ? 'noopener noreferrer' : undefined);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<li>
|
|
103
|
+
<CardLink
|
|
104
|
+
class="site-nav__mobile-link"
|
|
105
|
+
disabled={item.disabled}
|
|
106
|
+
href={item.href}
|
|
107
|
+
media={item.media}
|
|
108
|
+
mediaSize="compact"
|
|
109
|
+
rel={rel}
|
|
110
|
+
target={item.target}
|
|
111
|
+
textTreatment="heading"
|
|
112
|
+
title={item.label}
|
|
113
|
+
aria-current={active ? 'page' : undefined}
|
|
114
|
+
/>
|
|
115
|
+
</li>
|
|
116
|
+
);
|
|
117
|
+
})
|
|
111
118
|
}
|
|
112
119
|
</ul>
|
|
120
|
+
|
|
121
|
+
{
|
|
122
|
+
mobileUtilityItems.length > 0 ||
|
|
123
|
+
resolvedHasUtilityActions ||
|
|
124
|
+
mobileFooterItems.length > 0 ? (
|
|
125
|
+
<div class="site-nav__mobile-controls">
|
|
126
|
+
{mobileUtilityItems.length > 0 || resolvedHasUtilityActions ? (
|
|
127
|
+
<ul class="site-nav__mobile-utility">
|
|
128
|
+
{mobileUtilityItems.map((item) => (
|
|
129
|
+
<li>
|
|
130
|
+
<NavigationLink
|
|
131
|
+
item={item}
|
|
132
|
+
currentPath={currentPath}
|
|
133
|
+
appearance="menu"
|
|
134
|
+
class="site-nav__mobile-utility-link"
|
|
135
|
+
data-mobile-navigation-label-visibility={
|
|
136
|
+
item.labelVisibility
|
|
137
|
+
}
|
|
138
|
+
/>
|
|
139
|
+
</li>
|
|
140
|
+
))}
|
|
141
|
+
{resolvedHasUtilityActions ? (
|
|
142
|
+
<li>
|
|
143
|
+
<slot name="utility-actions" />
|
|
144
|
+
</li>
|
|
145
|
+
) : null}
|
|
146
|
+
</ul>
|
|
147
|
+
) : null}
|
|
148
|
+
|
|
149
|
+
{mobileFooterItems.length > 0 ? (
|
|
150
|
+
<ul class="site-nav__mobile-footer">
|
|
151
|
+
{mobileFooterItems.map((item) => (
|
|
152
|
+
<li>
|
|
153
|
+
<NavigationLink
|
|
154
|
+
item={item}
|
|
155
|
+
currentPath={currentPath}
|
|
156
|
+
appearance="menu"
|
|
157
|
+
class="site-nav__mobile-footer-link"
|
|
158
|
+
data-mobile-navigation-accent={
|
|
159
|
+
item.accentColor ? true : undefined
|
|
160
|
+
}
|
|
161
|
+
data-mobile-navigation-label-visibility={
|
|
162
|
+
item.labelVisibility
|
|
163
|
+
}
|
|
164
|
+
style={
|
|
165
|
+
item.accentColor
|
|
166
|
+
? `--mobile-navigation-accent: ${item.accentColor};`
|
|
167
|
+
: undefined
|
|
168
|
+
}
|
|
169
|
+
/>
|
|
170
|
+
</li>
|
|
171
|
+
))}
|
|
172
|
+
</ul>
|
|
173
|
+
) : null}
|
|
174
|
+
</div>
|
|
175
|
+
) : null
|
|
176
|
+
}
|
|
113
177
|
</div>
|
|
114
178
|
</div>
|
|
115
179
|
|
|
@@ -4,14 +4,18 @@
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
.site-nav__mobile-bar {
|
|
7
|
+
--site-nav-mobile-side-size: 4.75rem;
|
|
8
|
+
|
|
7
9
|
position: relative;
|
|
8
10
|
display: grid;
|
|
9
|
-
grid-template-columns:
|
|
11
|
+
grid-template-columns:
|
|
12
|
+
var(--site-nav-mobile-side-size)
|
|
13
|
+
minmax(0, 1fr)
|
|
14
|
+
var(--site-nav-mobile-side-size);
|
|
10
15
|
align-items: center;
|
|
11
|
-
width:
|
|
16
|
+
width: 100%;
|
|
12
17
|
min-height: var(--nox-site-header-compact-height);
|
|
13
|
-
|
|
14
|
-
gap: clamp(var(--nox-space-1), 2vw, var(--nox-space-3));
|
|
18
|
+
gap: 0;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
.site-nav__menu-button {
|
|
@@ -29,6 +33,7 @@
|
|
|
29
33
|
background: var(--nox-surface);
|
|
30
34
|
color: var(--nox-text);
|
|
31
35
|
cursor: pointer;
|
|
36
|
+
justify-self: center;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
.site-nav__menu-icon {
|
|
@@ -72,12 +77,16 @@
|
|
|
72
77
|
|
|
73
78
|
.site-nav__brand--mobile {
|
|
74
79
|
z-index: 1;
|
|
75
|
-
|
|
80
|
+
padding-inline: var(--nox-page-gutter);
|
|
81
|
+
justify-self: center;
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
.site-nav__mobile-action {
|
|
79
85
|
z-index: 2;
|
|
80
|
-
|
|
86
|
+
display: flex;
|
|
87
|
+
width: 100%;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
justify-self: center;
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
.site-nav__mobile-action > * {
|
|
@@ -101,13 +110,15 @@
|
|
|
101
110
|
.site-nav__mobile-panel {
|
|
102
111
|
position: absolute;
|
|
103
112
|
z-index: 2;
|
|
113
|
+
display: grid;
|
|
104
114
|
inset-inline: 0;
|
|
105
115
|
top: 100%;
|
|
106
116
|
width: 100%;
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
height: calc(100vh - var(--nox-site-header-compact-height));
|
|
118
|
+
height: calc(100dvh - var(--nox-site-header-compact-height));
|
|
119
|
+
grid-template-rows: minmax(0, 1fr) auto;
|
|
109
120
|
overflow-x: hidden;
|
|
110
|
-
overflow-y:
|
|
121
|
+
overflow-y: hidden;
|
|
111
122
|
padding: var(--nox-space-3);
|
|
112
123
|
border-top: 3px solid var(--nox-link-active);
|
|
113
124
|
border-bottom: var(--nox-border-width) solid var(--nox-border);
|
|
@@ -125,30 +136,90 @@
|
|
|
125
136
|
.site-nav__mobile-list {
|
|
126
137
|
display: grid;
|
|
127
138
|
width: 100%;
|
|
139
|
+
min-height: 0;
|
|
128
140
|
margin: 0;
|
|
129
141
|
padding: 0;
|
|
130
|
-
|
|
142
|
+
align-content: start;
|
|
143
|
+
gap: var(--nox-space-3);
|
|
131
144
|
list-style: none;
|
|
145
|
+
overflow-x: hidden;
|
|
146
|
+
overflow-y: auto;
|
|
132
147
|
}
|
|
133
148
|
|
|
134
149
|
.site-nav__mobile-link {
|
|
135
|
-
|
|
136
|
-
padding-block: 0;
|
|
137
|
-
border-radius: var(--nox-radius-sm);
|
|
150
|
+
--surface-radius: var(--nox-radius-md);
|
|
138
151
|
}
|
|
139
152
|
|
|
140
|
-
.site-nav__mobile-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
.site-nav__mobile-link[aria-current='page'] .text-pair__primary {
|
|
154
|
+
position: relative;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.site-nav__mobile-link[aria-current='page'] .text-pair__primary::after {
|
|
158
|
+
position: absolute;
|
|
159
|
+
right: 0;
|
|
160
|
+
bottom: calc(0px - var(--nox-space-2));
|
|
161
|
+
left: 0;
|
|
162
|
+
height: 2px;
|
|
163
|
+
border-radius: var(--nox-radius-pill);
|
|
164
|
+
background: var(--nox-source-tone-success);
|
|
165
|
+
content: '';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.site-nav__mobile-controls {
|
|
169
|
+
display: grid;
|
|
144
170
|
margin-top: var(--nox-space-2);
|
|
145
|
-
padding: var(--nox-space-2)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
171
|
+
padding: var(--nox-space-2) 0 0;
|
|
172
|
+
gap: var(--nox-space-3);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.site-nav__mobile-utility,
|
|
176
|
+
.site-nav__mobile-footer {
|
|
177
|
+
display: grid;
|
|
178
|
+
margin: 0;
|
|
179
|
+
padding: 0;
|
|
180
|
+
list-style: none;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.site-nav__mobile-utility {
|
|
184
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
185
|
+
gap: var(--nox-space-3);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.site-nav__mobile-footer {
|
|
189
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
190
|
+
gap: var(--nox-space-3);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.site-nav__mobile-utility-link,
|
|
194
|
+
.site-nav__mobile-footer-link {
|
|
195
|
+
min-height: var(--nox-navigation-block-size);
|
|
196
|
+
justify-content: center;
|
|
197
|
+
padding: 0;
|
|
198
|
+
border: var(--nox-border-width) solid var(--nox-border);
|
|
199
|
+
border-radius: var(--nox-radius-md);
|
|
200
|
+
background: var(--nox-surface-muted);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.site-nav__mobile-footer-link[data-mobile-navigation-accent] {
|
|
204
|
+
border-color: var(--mobile-navigation-accent);
|
|
205
|
+
background: var(--mobile-navigation-accent);
|
|
206
|
+
color: #fff;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
[data-mobile-navigation-label-visibility='accessible']
|
|
210
|
+
[data-navigation-control-label] {
|
|
211
|
+
position: absolute;
|
|
212
|
+
overflow: hidden;
|
|
213
|
+
width: 1px;
|
|
214
|
+
height: 1px;
|
|
215
|
+
clip: rect(0 0 0 0);
|
|
216
|
+
white-space: nowrap;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.site-nav__mobile-footer-link [data-navigation-control-media] {
|
|
220
|
+
inline-size: 1.5rem;
|
|
221
|
+
block-size: 1.5rem;
|
|
222
|
+
border-radius: 0;
|
|
152
223
|
}
|
|
153
224
|
|
|
154
225
|
html[data-mobile-navigation-open],
|
|
@@ -166,6 +237,20 @@ html[data-mobile-navigation-open] body {
|
|
|
166
237
|
.site-nav__menu-button:hover {
|
|
167
238
|
background: var(--nox-surface-muted);
|
|
168
239
|
}
|
|
240
|
+
|
|
241
|
+
.site-nav__mobile-footer-link[data-mobile-navigation-accent]:hover {
|
|
242
|
+
border-color: color-mix(
|
|
243
|
+
in srgb,
|
|
244
|
+
var(--mobile-navigation-accent) 88%,
|
|
245
|
+
#000
|
|
246
|
+
);
|
|
247
|
+
background: color-mix(
|
|
248
|
+
in srgb,
|
|
249
|
+
var(--mobile-navigation-accent) 88%,
|
|
250
|
+
#000
|
|
251
|
+
);
|
|
252
|
+
color: #fff;
|
|
253
|
+
}
|
|
169
254
|
}
|
|
170
255
|
|
|
171
256
|
@media (prefers-reduced-motion: no-preference) {
|
|
@@ -39,6 +39,20 @@
|
|
|
39
39
|
block-size: 100%;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
[data-navigation-control-icon] {
|
|
43
|
+
display: grid;
|
|
44
|
+
inline-size: 1.5rem;
|
|
45
|
+
block-size: 1.5rem;
|
|
46
|
+
flex: 0 0 auto;
|
|
47
|
+
place-items: center;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-navigation-control-icon] > svg {
|
|
51
|
+
display: block;
|
|
52
|
+
inline-size: 100%;
|
|
53
|
+
block-size: 100%;
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
[data-navigation-control-media-frame='bordered'] {
|
|
43
57
|
border: var(--nox-border-width) solid var(--nox-border);
|
|
44
58
|
}
|
|
@@ -92,11 +106,11 @@
|
|
|
92
106
|
[data-navigation-control][data-navigation-control-appearance='bar']::after {
|
|
93
107
|
position: absolute;
|
|
94
108
|
right: var(--nox-space-4);
|
|
95
|
-
bottom: var(--nox-space-
|
|
109
|
+
bottom: var(--nox-space-4);
|
|
96
110
|
left: var(--nox-space-4);
|
|
97
111
|
height: 3px;
|
|
98
112
|
border-radius: var(--nox-radius-pill);
|
|
99
|
-
background: var(--nox-
|
|
113
|
+
background: var(--nox-source-tone-success);
|
|
100
114
|
content: '';
|
|
101
115
|
opacity: 0;
|
|
102
116
|
transform: translateY(var(--nox-space-1));
|
|
@@ -7,10 +7,15 @@ type Props = {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
const { item } = Astro.props;
|
|
10
|
+
const Icon = item.icon;
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
{
|
|
13
|
-
|
|
14
|
+
Icon ? (
|
|
15
|
+
<span data-navigation-control-icon aria-hidden="true">
|
|
16
|
+
<Icon />
|
|
17
|
+
</span>
|
|
18
|
+
) : item.media ? (
|
|
14
19
|
<span
|
|
15
20
|
data-navigation-control-media
|
|
16
21
|
data-navigation-control-media-frame={item.mediaFrame}
|
|
@@ -82,20 +82,6 @@ export function initMobileNavigation(root: HTMLElement): () => void {
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
const handlePanelClick = (event: MouseEvent): void => {
|
|
86
|
-
const target = event.target;
|
|
87
|
-
|
|
88
|
-
if (!(target instanceof Element)) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const link = target.closest('a[href]');
|
|
93
|
-
|
|
94
|
-
if (link && panel.contains(link)) {
|
|
95
|
-
close(true);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
85
|
const handleDesktopMediaChange = (): void => {
|
|
100
86
|
if (desktopMedia.matches) {
|
|
101
87
|
close(false);
|
|
@@ -105,7 +91,6 @@ export function initMobileNavigation(root: HTMLElement): () => void {
|
|
|
105
91
|
setOpen(isOpen);
|
|
106
92
|
|
|
107
93
|
trigger.addEventListener('click', handleTriggerClick);
|
|
108
|
-
panel.addEventListener('click', handlePanelClick);
|
|
109
94
|
document.addEventListener('click', handleDocumentClick);
|
|
110
95
|
document.addEventListener('keydown', handleDocumentKeydown);
|
|
111
96
|
desktopMedia.addEventListener('change', handleDesktopMediaChange);
|
|
@@ -113,7 +98,6 @@ export function initMobileNavigation(root: HTMLElement): () => void {
|
|
|
113
98
|
return () => {
|
|
114
99
|
setOpen(false);
|
|
115
100
|
trigger.removeEventListener('click', handleTriggerClick);
|
|
116
|
-
panel.removeEventListener('click', handlePanelClick);
|
|
117
101
|
document.removeEventListener('click', handleDocumentClick);
|
|
118
102
|
document.removeEventListener('keydown', handleDocumentKeydown);
|
|
119
103
|
desktopMedia.removeEventListener('change', handleDesktopMediaChange);
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type { AstroComponent } from '../widgetTypes';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
DisplayMedia,
|
|
4
|
+
NavigationLinkItem,
|
|
5
|
+
} from '../../display/contracts';
|
|
3
6
|
|
|
4
7
|
export type MobileNavigationLabels = {
|
|
5
8
|
closeLabel?: string;
|
|
6
9
|
openLabel: string;
|
|
7
10
|
};
|
|
8
11
|
|
|
12
|
+
export type MobileNavigationControlItem = NavigationLinkItem & {
|
|
13
|
+
accentColor?: string;
|
|
14
|
+
labelVisibility?: 'accessible' | 'visible';
|
|
15
|
+
};
|
|
16
|
+
|
|
9
17
|
type SubNavigationVisual =
|
|
10
18
|
| { icon?: AstroComponent; media?: never }
|
|
11
19
|
| { icon?: never; media: DisplayMedia };
|
package/src/display/contracts.ts
CHANGED
|
@@ -9,6 +9,8 @@ import type { Tone } from '../theme/contracts';
|
|
|
9
9
|
import PageSurface from './PageSurface.astro';
|
|
10
10
|
import Breadcrumbs from '../components/navigation/Breadcrumbs/Breadcrumbs.astro';
|
|
11
11
|
import ScrollToTop from '../components/actions/ScrollToTop/ScrollToTop.astro';
|
|
12
|
+
import ThemeBootstrap from '../components/feedback/ThemeBootstrap/ThemeBootstrap.astro';
|
|
13
|
+
import { defaultThemeStorageKey } from '../theme/storage';
|
|
12
14
|
|
|
13
15
|
type Props = HTMLAttributes<'html'> & {
|
|
14
16
|
meta: PageMeta;
|
|
@@ -20,6 +22,7 @@ type Props = HTMLAttributes<'html'> & {
|
|
|
20
22
|
scrollToTopLabel?: string;
|
|
21
23
|
structuredData?: readonly JsonLdNode[];
|
|
22
24
|
tone?: Tone;
|
|
25
|
+
themeStorageKey?: string;
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
const {
|
|
@@ -33,6 +36,7 @@ const {
|
|
|
33
36
|
site,
|
|
34
37
|
structuredData,
|
|
35
38
|
tone,
|
|
39
|
+
themeStorageKey = defaultThemeStorageKey,
|
|
36
40
|
...htmlAttrs
|
|
37
41
|
} = Astro.props;
|
|
38
42
|
const lang = meta.lang ?? site.defaultMeta.lang;
|
|
@@ -44,6 +48,7 @@ const resolvedTone = tone ?? dataTone;
|
|
|
44
48
|
<head>
|
|
45
49
|
<meta charset="utf-8" />
|
|
46
50
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
51
|
+
<ThemeBootstrap storageKey={themeStorageKey} />
|
|
47
52
|
<SeoHead
|
|
48
53
|
breadcrumbs={breadcrumbs}
|
|
49
54
|
meta={meta}
|
|
@@ -155,12 +155,12 @@
|
|
|
155
155
|
--map-viewport-cell-surface: color-mix(
|
|
156
156
|
in srgb,
|
|
157
157
|
var(--map-viewport-cell-seed) 38%,
|
|
158
|
-
var(--nox-
|
|
158
|
+
var(--nox-theme-surface-base)
|
|
159
159
|
);
|
|
160
160
|
--map-viewport-cell-surface-hover: color-mix(
|
|
161
161
|
in srgb,
|
|
162
162
|
var(--map-viewport-cell-seed) 52%,
|
|
163
|
-
var(--nox-
|
|
163
|
+
var(--nox-theme-surface-base)
|
|
164
164
|
);
|
|
165
165
|
--map-viewport-cell-background: color-mix(
|
|
166
166
|
in srgb,
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
--map-viewport-cell-border: color-mix(
|
|
176
176
|
in srgb,
|
|
177
177
|
var(--map-viewport-cell-seed) 74%,
|
|
178
|
-
var(--nox-
|
|
178
|
+
var(--nox-theme-text-base)
|
|
179
179
|
);
|
|
180
180
|
|
|
181
181
|
fill: var(--map-viewport-cell-background);
|
package/src/styles/style.css
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
@import '../components/actions/ActionListItem/ActionListItem.css'
|
|
18
18
|
layer(components);
|
|
19
19
|
@import '../components/actions/Button/Button.css' layer(components);
|
|
20
|
+
@import '../components/actions/ThemeToggle/ThemeToggle.css' layer(components);
|
|
20
21
|
@import '../components/actions/CardAction/CardAction.css'
|
|
21
22
|
layer(components);
|
|
22
23
|
@import '../components/actions/ScrollToTop/ScrollToTop.css'
|
package/src/styles/tokens.css
CHANGED
|
@@ -27,6 +27,42 @@
|
|
|
27
27
|
--nox-mix-light: #ffffff;
|
|
28
28
|
--nox-mix-dark: #020617;
|
|
29
29
|
--nox-tone-seed: var(--nox-source-tone-neutral);
|
|
30
|
+
|
|
31
|
+
/* Theme recipe anchors. Light mode preserves the original palette. */
|
|
32
|
+
--nox-theme-page-base: var(--nox-mix-light);
|
|
33
|
+
--nox-theme-surface-base: var(--nox-mix-light);
|
|
34
|
+
--nox-theme-text-base: var(--nox-mix-dark);
|
|
35
|
+
--nox-theme-text-disabled-base: var(--nox-mix-light);
|
|
36
|
+
--nox-theme-border-base: var(--nox-mix-light);
|
|
37
|
+
--nox-theme-inverse-base: var(--nox-mix-light);
|
|
38
|
+
--nox-theme-shadow-base: var(--nox-mix-dark);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:root[data-theme='dark'] {
|
|
42
|
+
color-scheme: dark;
|
|
43
|
+
|
|
44
|
+
--nox-source-tone-neutral: #8f9bad;
|
|
45
|
+
--nox-source-tone-primary: #a78bfa;
|
|
46
|
+
--nox-source-tone-info: #73a4ff;
|
|
47
|
+
--nox-source-tone-success: #42d392;
|
|
48
|
+
--nox-source-tone-warning: #f0ad55;
|
|
49
|
+
--nox-source-tone-danger: #f47777;
|
|
50
|
+
|
|
51
|
+
--nox-source-map-available: #42d392;
|
|
52
|
+
--nox-source-map-unavailable: #fb7185;
|
|
53
|
+
--nox-source-map-field: #fbbf24;
|
|
54
|
+
--nox-source-map-grass: #84cc16;
|
|
55
|
+
--nox-source-map-mountain: #94a3b8;
|
|
56
|
+
--nox-source-map-water: #38bdf8;
|
|
57
|
+
--nox-source-map-range: #60a5fa;
|
|
58
|
+
|
|
59
|
+
--nox-theme-page-base: #0f1218;
|
|
60
|
+
--nox-theme-surface-base: #171b23;
|
|
61
|
+
--nox-theme-text-base: #f8fafc;
|
|
62
|
+
--nox-theme-text-disabled-base: #657184;
|
|
63
|
+
--nox-theme-border-base: #252b35;
|
|
64
|
+
--nox-theme-inverse-base: #0f1218;
|
|
65
|
+
--nox-theme-shadow-base: #000000;
|
|
30
66
|
}
|
|
31
67
|
|
|
32
68
|
:root,
|
|
@@ -35,51 +71,55 @@
|
|
|
35
71
|
--nox-page-background: color-mix(
|
|
36
72
|
in srgb,
|
|
37
73
|
var(--nox-tone-seed) 11%,
|
|
38
|
-
var(--nox-
|
|
74
|
+
var(--nox-theme-page-base)
|
|
39
75
|
);
|
|
40
76
|
--nox-surface: color-mix(
|
|
41
77
|
in srgb,
|
|
42
78
|
var(--nox-tone-seed) 2%,
|
|
43
|
-
var(--nox-
|
|
79
|
+
var(--nox-theme-surface-base)
|
|
44
80
|
);
|
|
45
81
|
--nox-surface-muted: color-mix(
|
|
46
82
|
in srgb,
|
|
47
83
|
var(--nox-tone-seed) 8%,
|
|
48
|
-
var(--nox-
|
|
84
|
+
var(--nox-theme-surface-base)
|
|
49
85
|
);
|
|
50
86
|
--nox-surface-strong: color-mix(
|
|
51
87
|
in srgb,
|
|
52
88
|
var(--nox-tone-seed) 14%,
|
|
53
|
-
var(--nox-
|
|
89
|
+
var(--nox-theme-surface-base)
|
|
54
90
|
);
|
|
55
91
|
|
|
56
|
-
--nox-text: color-mix(
|
|
92
|
+
--nox-text: color-mix(
|
|
93
|
+
in srgb,
|
|
94
|
+
var(--nox-tone-seed) 18%,
|
|
95
|
+
var(--nox-theme-text-base)
|
|
96
|
+
);
|
|
57
97
|
--nox-text-muted: color-mix(
|
|
58
98
|
in srgb,
|
|
59
99
|
var(--nox-tone-seed) 52%,
|
|
60
|
-
var(--nox-
|
|
100
|
+
var(--nox-theme-text-base)
|
|
61
101
|
);
|
|
62
102
|
--nox-text-subtle: color-mix(
|
|
63
103
|
in srgb,
|
|
64
104
|
var(--nox-tone-seed) 68%,
|
|
65
|
-
var(--nox-
|
|
105
|
+
var(--nox-theme-text-base)
|
|
66
106
|
);
|
|
67
107
|
--nox-text-disabled: color-mix(
|
|
68
108
|
in srgb,
|
|
69
109
|
var(--nox-tone-seed) 36%,
|
|
70
|
-
var(--nox-
|
|
110
|
+
var(--nox-theme-text-disabled-base)
|
|
71
111
|
);
|
|
72
|
-
--nox-text-inverse: var(--nox-
|
|
112
|
+
--nox-text-inverse: var(--nox-theme-inverse-base);
|
|
73
113
|
|
|
74
114
|
--nox-border: color-mix(
|
|
75
115
|
in srgb,
|
|
76
116
|
var(--nox-tone-seed) 28%,
|
|
77
|
-
var(--nox-
|
|
117
|
+
var(--nox-theme-border-base)
|
|
78
118
|
);
|
|
79
119
|
--nox-border-muted: color-mix(
|
|
80
120
|
in srgb,
|
|
81
121
|
var(--nox-tone-seed) 18%,
|
|
82
|
-
var(--nox-
|
|
122
|
+
var(--nox-theme-border-base)
|
|
83
123
|
);
|
|
84
124
|
|
|
85
125
|
/* Tone interaction slots. */
|
|
@@ -89,12 +129,12 @@
|
|
|
89
129
|
--nox-tone-hover-background: color-mix(
|
|
90
130
|
in srgb,
|
|
91
131
|
var(--nox-tone-seed) 12%,
|
|
92
|
-
var(--nox-
|
|
132
|
+
var(--nox-theme-surface-base)
|
|
93
133
|
);
|
|
94
134
|
--nox-tone-active-background: color-mix(
|
|
95
135
|
in srgb,
|
|
96
136
|
var(--nox-tone-seed) 20%,
|
|
97
|
-
var(--nox-
|
|
137
|
+
var(--nox-theme-surface-base)
|
|
98
138
|
);
|
|
99
139
|
--nox-tone-border: color-mix(
|
|
100
140
|
in srgb,
|
|
@@ -104,13 +144,13 @@
|
|
|
104
144
|
--nox-tone-text: color-mix(
|
|
105
145
|
in srgb,
|
|
106
146
|
var(--nox-tone-seed) 72%,
|
|
107
|
-
var(--nox-
|
|
147
|
+
var(--nox-theme-text-base)
|
|
108
148
|
);
|
|
109
149
|
--nox-tone-focus-ring: var(--nox-tone-seed);
|
|
110
150
|
--nox-tone-shadow: color-mix(
|
|
111
151
|
in srgb,
|
|
112
152
|
var(--nox-tone-seed) 24%,
|
|
113
|
-
var(--nox-
|
|
153
|
+
var(--nox-theme-shadow-base)
|
|
114
154
|
);
|
|
115
155
|
|
|
116
156
|
/* Shared framed-data surface slots. */
|
package/src/theme/contracts.ts
CHANGED
package/src/theme/index.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const defaultThemeStorageKey = 'astronox:theme';
|