Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions public/docs/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -706,26 +706,33 @@ nav.skip-links a:focus {
z-index: 2;
}

.theme-switcher.dark-mode .switch-slider {
html[data-theme='dark'] .theme-switcher .switch-slider {
transform: translateX(100%);
}

.theme-switcher.dark-mode .theme-switcher__icon--light {
html[data-theme='dark'] .theme-switcher__icon--light {
color: #7c98b4;
}

.theme-switcher.dark-mode .theme-switcher__icon--light svg path {
html[data-theme='dark'] .theme-switcher__icon--light svg path {
fill: #7c98b4;
}

.theme-switcher:not(.dark-mode) .theme-switcher__icon--dark {
html[data-theme='light'] .theme-switcher__icon--dark {
color: #557999;
}

.theme-switcher:not(.dark-mode) .theme-switcher__icon--dark svg path {
html[data-theme='light'] .theme-switcher__icon--dark svg path {
fill: #557999;
}

/* The input is visually hidden but still the focusable control, so the ring
has to be drawn on the label. */
.theme-switcher input:focus-visible + .theme-switcher__label {
outline: 0.125rem solid var(--octo-blue);
outline-offset: 0.125rem;
}

.theme-switcher input:disabled + .theme-switcher__label {
opacity: 0.5;
cursor: not-allowed;
Expand Down
49 changes: 0 additions & 49 deletions public/docs/js/modules/theme-switcher.js

This file was deleted.

41 changes: 2 additions & 39 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { SITE } from '@config';
import Search from '../themes/octopus/components/Search.astro';
import MobileMenu from './MobileMenu.astro';
import OctopusLogo from '../../public/docs/img/OctopusLogo.astro';
import SunIcon from '../../public/docs/img/SunIcon.astro';
import MoonIcon from '../../public/docs/img/MoonIcon.astro';
import ThemeSwitcher from './ThemeSwitcher.astro';
const stats = new accelerator.statistics('components/Header.astro');
stats.start();
Expand All @@ -34,34 +33,7 @@ stats.stop();
<OctopusLogo />
</a>
<span class="badge badge--brand"> Docs </span>
<div class="theme-switcher" data-theme-toggle>
<input
id="theme-switcher"
type="checkbox"
aria-checked="false"
role="switch"
aria-label="Toggle theme between light and dark mode"
tabindex="-1"
data-theme-toggle-checkbox
/>
<label for="theme-switcher" class="theme-switcher__label" tabindex="0">
<span
class="theme-switcher__icon theme-switcher__icon--light"
aria-hidden="true"
>
<SunIcon />
Light
</span>
<span class="switch-slider"></span>
<span
class="theme-switcher__icon theme-switcher__icon--dark"
aria-hidden="true"
>
<MoonIcon />
Dark
</span>
</label>
</div>
<ThemeSwitcher id="theme-switcher" />
</div>
{showSearch && <Search lang={lang} />}
<MobileMenu lang={lang} />
Expand All @@ -71,12 +43,3 @@ stats.stop();
>
</div>
</header>

<script type="module" is:inline>
import { ThemeSwitcher } from '/docs/js/modules/theme-switcher.js';

document.addEventListener('DOMContentLoaded', function () {
const themeSwitchers = document.querySelectorAll('[data-theme-toggle]');
themeSwitchers.forEach((switcher) => new ThemeSwitcher(switcher));
});
</script>
36 changes: 2 additions & 34 deletions src/components/MobileMenu.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
// Drawer body is populated client-side by nav-mobile.js cloning #site-nav.
import SunIcon from '../../public/docs/img/SunIcon.astro';
import MoonIcon from '../../public/docs/img/MoonIcon.astro';
import ThemeSwitcher from './ThemeSwitcher.astro';
---

<div
Expand All @@ -21,38 +20,7 @@ import MoonIcon from '../../public/docs/img/MoonIcon.astro';
<span class="circle"></span>
</a>
<div class="site-nav site-nav__mobile" data-mobile-menu>
<div class="theme-switcher" data-theme-toggle>
<input
id="theme-switcher-mobile"
type="checkbox"
aria-checked="false"
role="switch"
aria-label="Toggle theme between light and dark mode"
tabindex="-1"
data-theme-toggle-checkbox
/>
<label
for="theme-switcher-mobile"
class="theme-switcher__label"
tabindex="0"
>
<span
class="theme-switcher__icon theme-switcher__icon--light"
aria-hidden="true"
>
<SunIcon />
Light
</span>
<span class="switch-slider"></span>
<span
class="theme-switcher__icon theme-switcher__icon--dark"
aria-hidden="true"
>
<MoonIcon />
Dark
</span>
</label>
</div>
<ThemeSwitcher id="theme-switcher-mobile" />
<ul class="site-nav__list" data-mobile-menu-list></ul>
</div>
</div>
44 changes: 44 additions & 0 deletions src/components/ThemeSwitcher.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
import SunIcon from '../../public/docs/img/SunIcon.astro';
import MoonIcon from '../../public/docs/img/MoonIcon.astro';

type Props = {
/** Unique per instance - the label needs its own `for` target. */
id: string;
};

const { id } = Astro.props satisfies Props;
---

<div class="theme-switcher">
<input
id={id}
type="checkbox"
role="switch"
aria-label="Dark mode"
data-theme-toggle-checkbox
/>
<label for={id} class="theme-switcher__label">
<span
class="theme-switcher__icon theme-switcher__icon--light"
aria-hidden="true"
>
<SunIcon />
Light
</span>
<span class="switch-slider"></span>
<span
class="theme-switcher__icon theme-switcher__icon--dark"
aria-hidden="true"
>
<MoonIcon />
Dark
</span>
</label>
</div>

<script>
// Astro bundles and dedupes this, so rendering the switcher more than once
// still loads the controller exactly once.
import '../scripts/theme-switcher';
</script>
8 changes: 0 additions & 8 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,6 @@ export const HEADER_SCRIPTS = `
<link href="/docs/css/fa/css/solid.css" rel="stylesheet">
<meta name="google-site-verification" content="nIbWsTU_ELqMnLNxIexH_s6ch3m-s_MaFnl5u8WoaRM" />
<!-- Inline Script to set the initial theme -->
<script>
(() => {
const theme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<script defer>
const whenActivated = new Promise((resolve) => {
if (document.prerendering) {
Expand Down
40 changes: 40 additions & 0 deletions src/lib/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Shared contract for theme handling. These names are used by the pre-paint
* inline script (ThemeScript.astro), the client controller
* (src/scripts/theme-switcher.ts) and the stylesheets, so they live in one
* place to stop the three drifting apart.
*
* `data-theme` on <html> is always exactly 'light' or 'dark' - never absent,
* never 'system'. The design token stylesheets key off [data-theme='light'] and
* [data-theme='dark'] and define nothing at :root, so the attribute has to be
* there unconditionally: layouts/Default.astro renders 'light' as the server
* default, and ThemeScript.astro resolves the real value before first paint.
* With scripting disabled the default stands and the tokens still resolve.
*
* This holds for anything rendered through layouts/Default.astro; the
* src/pages/report/*.astro pages build their own <html> and sit outside it.
*
* `data-theme-preference` records what the user actually chose, including
* 'system'. Nothing styles off it today; it exists so a future three-state
* control can render the right position.
*/

export const THEME_STORAGE_KEY = 'theme';
export const THEME_ATTRIBUTE = 'data-theme';
export const THEME_PREFERENCE_ATTRIBUTE = 'data-theme-preference';
export const COLOR_SCHEME_QUERY = '(prefers-color-scheme: dark)';

/**
* Fired on <html> after the theme changes. detail: { theme, preference }
*
* Does not fire at boot - the pre-paint script sets the attribute directly.
* Read `data-theme` for the current value and listen for later changes.
*/
export const THEME_CHANGE_EVENT = 'theme:change';

export type Theme = 'light' | 'dark';
export type ThemePreference = Theme | 'system';

export function isTheme(value: unknown): value is Theme {
return value === 'light' || value === 'dark';
}
Loading