Skip to content

Instantly share code, notes, and snippets.

@segunadebayo
Created December 31, 2019 13:50
Show Gist options
  • Save segunadebayo/a144442ef14b5fb41bd9f5612b7b5b57 to your computer and use it in GitHub Desktop.
Save segunadebayo/a144442ef14b5fb41bd9f5612b7b5b57 to your computer and use it in GitHub Desktop.
Color Mode
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