Last active
May 27, 2023 08:42
-
-
Save alexcmgit/af6fc4f067063fc18111cceb9a4bc353 to your computer and use it in GitHub Desktop.
This file contains 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 { 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]; | |
} |
This file contains 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 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