Last active
January 8, 2025 11:41
-
-
Save cmnstmntmn/6126e37aa016092712594c2cc354fe83 to your computer and use it in GitHub Desktop.
Qwick initial theme + a hook to be used inside Qwick components
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { component$, useServerData } from "@builder.io/qwik"; | |
import { isDev } from "@builder.io/qwik/build"; | |
import { | |
QwikCityProvider, | |
RouterOutlet, | |
ServiceWorkerRegister, | |
} from "@builder.io/qwik-city"; | |
import { RouterHead } from "~/components/router-head/router-head"; | |
import setInitialThemeAsString from "~/utils/set-initial-theme?raw"; | |
import "./global.css"; | |
export default component$(() => { | |
/** | |
* The root of a QwikCity site always start with the <QwikCityProvider> component, | |
* immediately followed by the document's <head> and <body>. | |
* | |
* Don't remove the `<head>` and `<body>` elements. | |
*/ | |
const nonce = useServerData<string | undefined>("nonce"); | |
return ( | |
<QwikCityProvider> | |
<head> | |
<meta charset="utf-8" /> | |
{!isDev && ( | |
<link | |
rel="manifest" | |
href={`${import.meta.env.BASE_URL}manifest.json`} | |
/> | |
)} | |
<RouterHead /> | |
<script | |
nonce={nonce} | |
dangerouslySetInnerHTML={setInitialThemeAsString} | |
></script> | |
{!isDev && <ServiceWorkerRegister nonce={nonce} />} | |
</head> | |
<body lang="en" class="antialiased"> | |
<RouterOutlet /> | |
</body> | |
</QwikCityProvider> | |
); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const LOCAL_STORAGE_KEY = "theme"; | |
const SYSTEM = "system"; | |
const DARK = "dark"; | |
const LIGHT = "light"; | |
const watcher = window.matchMedia("(prefers-color-scheme: dark)"); | |
const setSystemTheme = () => { | |
const theme = watcher.matches ? DARK : LIGHT; | |
document.documentElement.classList.toggle(DARK, theme === DARK); | |
document.documentElement.classList.toggle(LIGHT, theme === LIGHT); | |
localStorage.setItem(LOCAL_STORAGE_KEY, theme); | |
}; | |
// Listen for system theme changes | |
watcher.onchange = setSystemTheme; | |
// Listen for localStorage changes | |
window.addEventListener("storage", (event) => { | |
if (event.key === LOCAL_STORAGE_KEY && event.oldValue !== event.newValue) { | |
setTheme(); | |
} | |
}); | |
// Set initial theme based on localStorage or system preference | |
const setTheme = () => { | |
const theme = localStorage.getItem(LOCAL_STORAGE_KEY) || SYSTEM; | |
if (theme === SYSTEM) { | |
setSystemTheme(); | |
} else { | |
document.documentElement.classList.toggle(DARK, theme === DARK); | |
document.documentElement.classList.toggle(LIGHT, theme === LIGHT); | |
} | |
}; | |
setTheme(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { $, useSignal, useVisibleTask$, useOnWindow } from "@builder.io/qwik"; | |
export const LOCAL_STORAGE_KEY = "theme"; | |
export enum themes { | |
DARK = "dark", | |
LIGHT = "light", | |
SYSTEM = "system", | |
} | |
export type ThemeOption = themes.DARK | themes.LIGHT | themes.SYSTEM; | |
export function useThemeProvider() { | |
// Signal to store the current theme value | |
const currentTheme = useSignal<ThemeOption>(themes.SYSTEM); | |
const isDarkMode = currentTheme.value === themes.DARK; | |
// Apply the correct theme based on the theme value | |
const applyTheme = $((theme: ThemeOption) => { | |
const isDark = | |
theme === themes.DARK || | |
(theme === themes.SYSTEM && | |
window.matchMedia("(prefers-color-scheme: dark)").matches); | |
document.documentElement.classList.toggle(themes.DARK, isDark); | |
document.documentElement.classList.toggle(themes.LIGHT, !isDark); | |
// Update current theme signal | |
currentTheme.value = theme; | |
}); | |
// Track system theme changes | |
// eslint-disable-next-line qwik/no-use-visible-task | |
useVisibleTask$(() => { | |
const watcher = window.matchMedia("(prefers-color-scheme: dark)"); | |
const handleSystemThemeChange = () => { | |
const theme = localStorage.getItem( | |
LOCAL_STORAGE_KEY, | |
) as ThemeOption | null; | |
if (theme) applyTheme(theme); | |
}; | |
watcher.addEventListener("change", handleSystemThemeChange); | |
return () => watcher.removeEventListener("change", handleSystemThemeChange); | |
}); | |
// Initialize theme on mount | |
// eslint-disable-next-line qwik/no-use-visible-task | |
useVisibleTask$(() => { | |
const storedTheme = localStorage.getItem( | |
LOCAL_STORAGE_KEY, | |
) as ThemeOption | null; | |
applyTheme(storedTheme || themes.SYSTEM); | |
}); | |
// Track localStorage changes using `useOnWindow` | |
useOnWindow( | |
"storage", | |
$((event: StorageEvent) => { | |
if (event.key === LOCAL_STORAGE_KEY) { | |
const theme = event.newValue as ThemeOption | null; | |
if (theme) applyTheme(theme); | |
} | |
}), | |
); | |
// Function to set the theme and update localStorage | |
const setTheme = $((theme: ThemeOption) => { | |
localStorage.setItem(LOCAL_STORAGE_KEY, theme); | |
applyTheme(theme); | |
}); | |
// Function to toggleTheme | |
const toggleTheme = $(() => { | |
const theme = | |
currentTheme.value === themes.LIGHT ? themes.DARK : themes.LIGHT; | |
localStorage.setItem(LOCAL_STORAGE_KEY, theme); | |
applyTheme(theme); | |
}); | |
return { | |
currentTheme, | |
setTheme, | |
toggleTheme, | |
isDarkMode, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment