theme switcher

This commit is contained in:
2026-03-09 07:48:39 -04:00
parent 0070ed1c19
commit 173b5ba9f4
6 changed files with 124 additions and 3 deletions

23
src/components/Icon.astro Normal file
View File

@@ -0,0 +1,23 @@
---
export interface Props {
name: string,
};
const { name } = Astro.props;
const icons = import.meta.glob<{string: string}>('@components/icons/*.svg', { query: '?raw', import: 'default' });
const path = `/src/components/icons/${name}.svg`;
if (icons[path] === undefined) {
throw new Error(`Icon ${name} does not exist.`);
}
const icon = await icons[path]();
---
<Fragment set:html={icon} />
<style>
svg {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,73 @@
---
import Icon from '@components/Icon.astro';
---
<div class="theme-switcher">
<button id="switch-to-dark">
<Icon name="sun" />
</button>
<button id="switch-to-light">
<Icon name="moon" />
</button>
</div>
<style>
.theme-switcher {
position: relative;
isolation: isolate;
width: 1.5rem;
height: 1.5rem;
transform: translateY(0.1rem);
}
button {
position: absolute;
inset: 0;
background-color: transparent;
padding: 0;
color: var(--nav-link-color);
border: none;
&:hover {
cursor: pointer;
color: var(--accent-color);
}
/* hide by default, i.e. if JS isn't enabled and the data-theme attribute didn't get set, */
visibility: hidden;
opacity: 0;
transition:
color 0.2s ease,
opacity 0.5s ease,
transform 0.5s ease;
}
:global(html[data-theme="light"]) button#switch-to-dark {
opacity: 1;
visibility: visible;
transform: rotate(360deg);
/* whichever one is currently active should be on top */
z-index: 10;
}
:global(html[data-theme="dark"]) button#switch-to-light {
opacity: 1;
visibility: visible;
transform: rotate(-360deg);
z-index: 10;
}
</style>
<script>
document.getElementById('switch-to-dark')?.addEventListener('click', () => {
localStorage.setItem('theme-preference', 'dark');
document.documentElement.dataset.theme = 'dark';
});
document.getElementById('switch-to-light')?.addEventListener('click', () => {
localStorage.setItem('theme-preference', 'light');
document.documentElement.dataset.theme = 'light';
})
</script>

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
</svg>
<style>
svg {
width: 100%;
height: 100%;
}
</style>

After

Width:  |  Height:  |  Size: 425 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 377 B