diff --git a/.prettierignore b/.prettierignore index 6056347..d84f1a6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,6 +3,7 @@ # Except these files & folders !/src +!/public !/.github !tsconfig.json !astro.config.mjs diff --git a/public/toggle-theme.js b/public/toggle-theme.js new file mode 100644 index 0000000..7baaa00 --- /dev/null +++ b/public/toggle-theme.js @@ -0,0 +1,52 @@ +const primaryColorScheme = ""; // "light" | "dark" + +// Get theme data from local storage +const currentTheme = localStorage.getItem("theme"); + +function getPreferTheme() { + // return theme value in local storage if it is set + if (currentTheme) return currentTheme; + + // return primary color scheme if it is set + if (primaryColorScheme) return primaryColorScheme; + + // return user device's prefer color scheme + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; +} + +let themeValue = getPreferTheme(); + +function setPreference() { + localStorage.setItem("theme", themeValue); + reflectPreference(); +} + +function reflectPreference() { + document.firstElementChild.setAttribute("data-theme", themeValue); + + document.querySelector("#theme-btn")?.setAttribute("aria-label", themeValue); +} + +// set early so no page flashes / CSS is made aware +reflectPreference(); + +window.onload = () => { + // set on load so screen readers can get the latest value on the button + reflectPreference(); + + // now this script can find and listen for clicks on the control + document.querySelector("#theme-btn").addEventListener("click", () => { + themeValue = themeValue === "light" ? "dark" : "light"; + setPreference(); + }); +}; + +// sync with system changes +window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", ({ matches: isDark }) => { + themeValue = isDark ? "dark" : "light"; + setPreference(); + }); diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 2e7dfd6..3b5dca7 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -61,45 +61,7 @@ const socialImageURL = new URL( onload="this.onload=null;this.rel='stylesheet'" /> - + diff --git a/src/styles/base.css b/src/styles/base.css index e7e719c..131f57b 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -3,7 +3,8 @@ @tailwind utilities; @layer base { - :root { + :root, + html[data-theme="light"] { --color-fill: 251, 254, 251; --color-text-base: 40, 39, 40; --color-accent: 0, 108, 172; @@ -11,7 +12,7 @@ --color-card-muted: 205, 205, 205; --color-border: 236, 233, 233; } - .theme-dark { + html[data-theme="dark"] { --color-fill: 47, 55, 65; --color-text-base: 230, 230, 230; --color-accent: 26, 217, 217; @@ -20,11 +21,11 @@ --color-border: 59, 70, 85; } #sun-svg, - .theme-dark #moon-svg { + html[data-theme="dark"] #moon-svg { display: none; } #moon-svg, - .theme-dark #sun-svg { + html[data-theme="dark"] #sun-svg { display: block; } body { @@ -36,7 +37,8 @@ @apply max-w-3xl mx-auto px-4; } a { - @apply outline-offset-1 outline-skin-fill outline-2 focus-visible:outline-dashed focus-visible:no-underline; + @apply outline-offset-1 outline-skin-fill outline-2 + focus-visible:outline-dashed focus-visible:no-underline; } svg { @apply w-6 h-6 inline-block fill-skin-base group-hover:fill-skin-accent;