Skip to content

Instantly share code, notes, and snippets.

@cmnstmntmn
Last active January 8, 2025 11:41
Show Gist options
  • Save cmnstmntmn/6126e37aa016092712594c2cc354fe83 to your computer and use it in GitHub Desktop.
Save cmnstmntmn/6126e37aa016092712594c2cc354fe83 to your computer and use it in GitHub Desktop.
Qwick initial theme + a hook to be used inside Qwick components
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>
);
});
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();
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