diff --git a/src/ThemeProvider.tsx b/src/ThemeProvider.tsx index f637c08..fab7627 100644 --- a/src/ThemeProvider.tsx +++ b/src/ThemeProvider.tsx @@ -1,12 +1,13 @@ -import React, { createContext, FC, useMemo } from "react" +import React, { createContext, FC, useEffect, useMemo, useState } from "react" import { ThemeProvider as BaseThemeProvider } from "styled-components" import { darkTheme, lightTheme } from "./design/themes" import { useLocalStorage } from "./hooks/useLocalStorage" +type ThemeType = "dark" | "light" + export interface IThemeContext { - theme: "dark" | "light" - setTheme: () => void + theme: ThemeType toggleTheme: () => void } @@ -17,19 +18,47 @@ interface IScopedDownChildren { export const ThemeContext = createContext(null) const ThemeProvider: FC = ({ children }) => { - const [theme, setTheme] = useLocalStorage("theme", "dark") + // User's explicitly set theme + const [localTheme, setLocalTheme] = useLocalStorage( + "theme", + "unset" + ) + + // App's current theme + const [theme, setTheme] = useState( + localTheme === "unset" ? "dark" : localTheme + ) const themeObject = useMemo( () => (theme === "dark" ? darkTheme : lightTheme), [theme] ) + useEffect(() => { + if (localTheme === "unset") { + const prefersLightTheme = window.matchMedia( + "(prefers-color-scheme: light)" + ) + + setTheme(prefersLightTheme.matches ? "light" : "dark") + + prefersLightTheme.onchange = ({ matches }) => + setTheme(matches ? "light" : "dark") + + return () => { + prefersLightTheme.onchange = null + } + } + }, []) + const contextValue = useMemo( () => ({ theme, - setTheme, + toggleTheme: () => { - setTheme(theme === "light" ? "dark" : "light") + const newTheme = theme === "light" ? "dark" : "light" + setTheme(newTheme) + setLocalTheme(newTheme) }, }), [theme, setTheme] diff --git a/src/hooks/useLocalStorage.tsx b/src/hooks/useLocalStorage.tsx index 8cea92a..849eb33 100644 --- a/src/hooks/useLocalStorage.tsx +++ b/src/hooks/useLocalStorage.tsx @@ -1,9 +1,13 @@ /* globals window */ import { useEffect, useState } from "react" -export const useLocalStorage = (name: string, initialValue: string) => { +export const useLocalStorage = ( + name: string, + initialValue: Value +) => { const windowGlobal = typeof window !== "undefined" && window - const [value, setValue] = useState(() => { + + const [value, setValue] = useState(() => { if (windowGlobal) { const currentValue = windowGlobal.localStorage.getItem(name) return currentValue ? JSON.parse(currentValue) : initialValue @@ -17,5 +21,5 @@ export const useLocalStorage = (name: string, initialValue: string) => { } }, [name, value, windowGlobal]) - return [value, setValue] + return [value, setValue] as const }