Skip to content

Instantly share code, notes, and snippets.

@alexrintt
Last active May 27, 2023 08:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexrintt/af6fc4f067063fc18111cceb9a4bc353 to your computer and use it in GitHub Desktop.
Save alexrintt/af6fc4f067063fc18111cceb9a4bc353 to your computer and use it in GitHub Desktop.
import { useState, useEffect, useCallback } from "react";
import {
COLOR_SCHEME_PREFERENCE_CHANGE_EVENT,
ColorSchemePreference,
THEME_CHANGE_EVENT,
ThemeKey,
getColorSchemePreference,
getCurrentTheme,
setColorSchemePreference,
} from "../theme";
import log from "loglevel";
export function useTheme() {
const [localThemeState, setLocalThemeState] = useState<ThemeKey>(
getCurrentTheme()
);
const setGlobalColorSchemePreference = useCallback(
(colorSchemePreference: ColorSchemePreference) => {
setColorSchemePreference(colorSchemePreference);
},
[]
);
const onThemeChange = useCallback(
() => setLocalThemeState(getCurrentTheme()),
[]
);
useEffect(() => {
window.addEventListener(THEME_CHANGE_EVENT, onThemeChange);
return () => window.removeEventListener(THEME_CHANGE_EVENT, onThemeChange);
}, []);
return [localThemeState, setGlobalColorSchemePreference];
}
export type StateDispatcherArray<T> = [T, (state: T) => void];
export function useColorSchemePreference(): StateDispatcherArray<ColorSchemePreference> {
const [localColorSchemePreferenceState, setLocalColorSchemePreferenceState] =
useState<ColorSchemePreference>(getColorSchemePreference());
const setGlobalColorSchemePreference = useCallback(
(colorSchemePreference: ColorSchemePreference) => {
setColorSchemePreference(colorSchemePreference);
},
[]
);
const onColorSchemePreferenceChange = useCallback(() => {
log.info("[useTheme] hook", `Color scheme preference changed`);
setLocalColorSchemePreferenceState(getColorSchemePreference());
}, []);
useEffect(() => {
window.addEventListener(
COLOR_SCHEME_PREFERENCE_CHANGE_EVENT,
onColorSchemePreferenceChange
);
return () =>
window.removeEventListener(
COLOR_SCHEME_PREFERENCE_CHANGE_EVENT,
onColorSchemePreferenceChange
);
}, []);
return [localColorSchemePreferenceState, setGlobalColorSchemePreference];
}
import log from "loglevel";
export enum ColorSchemePreference {
dark = "dark",
light = "light",
followSystem = "followSystem",
}
export enum ThemeKey {
"dark" = "dark",
"light" = "light",
}
export const THEME_CHANGE_EVENT = "themechange";
export const COLOR_SCHEME_PREFERENCE_CHANGE_EVENT =
"colorschemepreferencechange";
export const DEFAULT_COLOR_SCHEME_PREFERENCE: ColorSchemePreference =
ColorSchemePreference.followSystem;
const COLOR_SCHEME_PREFERENCE_STORAGE_KEY = "colorpreferencestoragekey";
export function initThemeAndListenForSystemChanges() {
log.info("Theme", `Initializing theme as ${getColorSchemePreference()}`);
const setTheme = () => {
log.info("Theme", `System them changed: ${getColorSchemePreference()}`);
setColorSchemePreference(getColorSchemePreference());
};
setTheme();
listenForSystemThemeChanges(setTheme);
}
export function stringIsColorSchemePreferenceEnum(
source?: string | null
): source is ColorSchemePreference {
return (
typeof source === "string" &&
Object.values(ColorSchemePreference)
.map((e) => e.toString())
.includes(source)
);
}
export function getColorSchemePreference(): ColorSchemePreference {
const colorSchemePreference = window.localStorage.getItem(
COLOR_SCHEME_PREFERENCE_STORAGE_KEY
);
if (stringIsColorSchemePreferenceEnum(colorSchemePreference)) {
return colorSchemePreference;
} else {
return DEFAULT_COLOR_SCHEME_PREFERENCE;
}
}
export function getCurrentTheme(
colorSchemePreference: ColorSchemePreference = getColorSchemePreference()
): ThemeKey {
switch (colorSchemePreference) {
case ColorSchemePreference.dark:
return ThemeKey.dark;
case ColorSchemePreference.light:
return ThemeKey.light;
case ColorSchemePreference.followSystem:
default:
return resolveSystemPreferredTheme();
}
}
export function setColorSchemePreference(
colorSchemePreference: ColorSchemePreference
): void {
log.info(
"Theme",
`Changing manually the preferred color scheme to ${colorSchemePreference}`
);
setBodyClassListTheme(getCurrentTheme(colorSchemePreference));
window.localStorage.setItem(
COLOR_SCHEME_PREFERENCE_STORAGE_KEY,
colorSchemePreference
);
const colorSchemePreferenceChangeEvent = new Event(
COLOR_SCHEME_PREFERENCE_CHANGE_EVENT
);
window.dispatchEvent(colorSchemePreferenceChangeEvent);
}
export const DARK_THEME_IS_PREFERRED_MEDIA_QUERY = `(prefers-color-scheme: dark)`;
function darkThemeIsPreferred(): boolean {
return window.matchMedia(DARK_THEME_IS_PREFERRED_MEDIA_QUERY).matches;
}
export type UnsubscribeFn = () => void;
export function listenForSystemThemeChanges(
callback: (darkThemeIsPreferred: boolean) => void
): UnsubscribeFn {
const mediaQuery = window.matchMedia(DARK_THEME_IS_PREFERRED_MEDIA_QUERY);
callback(mediaQuery.matches);
const mediaQueryEventListener = (e: MediaQueryListEvent) =>
callback(e.matches);
mediaQuery.addEventListener("change", mediaQueryEventListener);
return () =>
mediaQuery.removeEventListener("change", mediaQueryEventListener);
}
function resolveSystemPreferredTheme(): ThemeKey {
return darkThemeIsPreferred() ? ThemeKey.dark : ThemeKey.light;
}
function setBodyClassListTheme(theme: ThemeKey): void {
for (const className of Object.values(ThemeKey)) {
window.document.body.classList.remove(className);
}
window.document.body.classList.add(theme);
const themeChangeEvent = new Event(THEME_CHANGE_EVENT);
window.dispatchEvent(themeChangeEvent);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment