Skip to content

Commit

Permalink
theme provider add listener for preference change
Browse files Browse the repository at this point in the history
  • Loading branch information
nickzoum committed Aug 5, 2022
1 parent aef3264 commit 7c12378
Showing 1 changed file with 115 additions and 0 deletions.
115 changes: 115 additions & 0 deletions src/lib/themeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
useEffect,
useState,
useContext,
createContext,
useCallback,
} from 'react';

export type ThemeMode = 'light' | 'dark';
export type SavedThemeMode = ThemeMode | null;

const storageName = 'themeMode';
const attributeName = 'data-theme-mode';
// name of css property indicating mode
const themeProperty = '--default-theme';

interface ThemeContextType {
setThemeMode: (theme: SavedThemeMode) => void;
toggleThemeMode: () => void;
themeMode: ThemeMode;
}

const ThemeContext = createContext<ThemeContextType>({
toggleThemeMode: () => void 0,
setThemeMode: () => void 0,
themeMode: getTheme(),
});

const storedMode = getSavedTheme();
if (storedMode) {
document.documentElement.setAttribute(attributeName, storedMode);
}

export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [defaultTheme, setDefaultTheme] = useState(getDefaultBrowserTheme);
const [savedTheme, setSavedTheme] = useState(getSavedTheme);
const themeMode = savedTheme ?? defaultTheme;

const toggleThemeMode = useCallback(
function () {
setSavedTheme(themeMode === 'dark' ? 'light' : 'dark');
},
[themeMode]
);

// local storage change (if the theme was changed from another tab)
useEffect(() => {
window.addEventListener('storage', onStorageChange, false);
return () => window.removeEventListener('storage', onStorageChange, false);

function onStorageChange() {
setSavedTheme(getSavedTheme());
}
}, []);

// preference change (if the browser settings were changed)
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', onPreferenceChange, false);
return () =>
mediaQuery.removeEventListener('change', onPreferenceChange, false);

function onPreferenceChange() {
setDefaultTheme(getDefaultBrowserTheme());
}
}, []);

// When the mode gets updated then update the document attribute
useEffect(() => {
if (savedTheme) {
document.documentElement.setAttribute(attributeName, savedTheme);
} else {
document.documentElement.removeAttribute(attributeName);
}
}, [savedTheme]);

// When the mode gets updated then update the persisted preference
useEffect(() => {
if (savedTheme) {
if (localStorage.getItem(storageName) !== savedTheme) {
localStorage.setItem(storageName, savedTheme);
}
} else {
if (localStorage.getItem(storageName)) {
localStorage.removeItem(storageName);
}
}
}, [savedTheme]);

return (
<ThemeContext.Provider
value={{ themeMode, setThemeMode: setSavedTheme, toggleThemeMode }}
>
{children}
</ThemeContext.Provider>
);
}

export function useThemeMode() {
return useContext(ThemeContext);
}

function getSavedTheme(): SavedThemeMode {
return localStorage.getItem(storageName) as SavedThemeMode;
}

export function getTheme(): ThemeMode {
return getSavedTheme() ?? getDefaultBrowserTheme();
}

export function getDefaultBrowserTheme(): ThemeMode {
return getComputedStyle(document.documentElement)
.getPropertyValue(themeProperty)
.trim() as ThemeMode;
}

0 comments on commit 7c12378

Please sign in to comment.