Created
December 31, 2019 13:50
-
-
Save segunadebayo/a144442ef14b5fb41bd9f5612b7b5b57 to your computer and use it in GitHub Desktop.
Color Mode
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 * as React from "react"; | |
const storageKey = "chakra-ui-color-mode"; | |
const cx = (mode: ColorMode) => `chakra-ui-${mode}`; | |
const supportsLocalStorage = typeof Storage !== "undefined"; | |
const getBodyElement = () => { | |
// for SSR | |
const mockBody = { | |
classList: { | |
add: (token: string) => {}, | |
remove: (token: string) => {}, | |
}, | |
}; | |
return window.document?.body ?? mockBody; | |
}; | |
type ColorMode = "light" | "dark"; | |
const storage = { | |
get: (init?: ColorMode) => | |
((supportsLocalStorage && window.localStorage.getItem(storageKey)) || | |
init) as ColorMode | undefined, | |
set: (value: ColorMode) => | |
supportsLocalStorage && window.localStorage.setItem(storageKey, value), | |
}; | |
function getMediaQuery() { | |
const preferDarkQuery = "(prefers-color-scheme: dark)"; | |
const queryList: MediaQueryList = window.matchMedia?.(preferDarkQuery); | |
const isQuerySupported = queryList.media === preferDarkQuery; | |
const isDark = isQuerySupported && queryList.matches; | |
return { isQuerySupported, isDark, queryList }; | |
} | |
type ContextType = [ColorMode, React.Dispatch<React.SetStateAction<ColorMode>>]; | |
const ColorModeContext = React.createContext<ContextType | undefined>( | |
undefined, | |
); | |
export const useColorMode = () => | |
React.useContext(ColorModeContext) as ContextType; | |
// Gets the color mode based on OS time | |
function syncWithLocalTime(callback: Function, shouldRun?: boolean) { | |
if (!shouldRun) return; | |
const date = new Date(); | |
const hour = date.getHours(); | |
if (hour > 20 || hour < 5) callback("dark"); | |
else callback("light"); | |
} | |
function syncBodyClassName(isDark: boolean) { | |
const body = getBodyElement(); | |
body.classList.add(isDark ? cx("dark") : cx("light")); | |
body.classList.remove(isDark ? cx("light") : cx("dark")); | |
} | |
export const ColorModeProvider: React.FC = ({ | |
children, | |
mode, | |
}: { | |
mode?: ColorMode; | |
children?: React.ReactNode; | |
}) => { | |
const [colorMode, setColorMode] = React.useState<ColorMode>(() => { | |
const mode = storage.get(); | |
if (mode) { | |
syncBodyClassName(mode === "dark"); | |
return mode; | |
} else { | |
const { isDark } = getMediaQuery(); | |
syncBodyClassName(isDark); | |
return isDark ? "dark" : "light"; | |
} | |
}); | |
// Sync color mode between tabs | |
React.useEffect(() => { | |
const handleStorage = (event: StorageEvent) => { | |
if (event.key === storageKey) { | |
if (!event.newValue) return; | |
setColorMode(event.newValue as ColorMode); | |
} | |
}; | |
window.addEventListener("storage", handleStorage); | |
return () => { | |
window.removeEventListener("storage", handleStorage); | |
}; | |
}, []); | |
// Sync color mode when user changes OS preferences | |
React.useEffect(() => { | |
const { queryList } = getMediaQuery(); | |
const onChangePreference = (event: MediaQueryListEvent) => { | |
setColorMode(event.matches ? "dark" : "light"); | |
}; | |
queryList.addEventListener("change", onChangePreference); | |
return () => { | |
queryList.removeEventListener("change", onChangePreference); | |
}; | |
}, []); | |
React.useEffect(() => { | |
if (!colorMode) return; | |
storage.set(colorMode); | |
syncBodyClassName(colorMode === "dark"); | |
}, [colorMode]); | |
const context = [colorMode, setColorMode] as const; | |
return ( | |
<ColorModeContext.Provider value={context as any}> | |
{children} | |
</ColorModeContext.Provider> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment