|
|
| import { createContext, ReactNode, useState, useEffect, useCallback, useMemo } from "react"; |
|
|
| export type Theme = "dark" | "light" | "system"; |
|
|
| export interface ThemeProviderState { |
| theme: Theme; |
| setTheme: (theme: Theme) => void; |
| } |
|
|
| const initialState: ThemeProviderState = { |
| theme: "system", |
| setTheme: () => null, |
| }; |
|
|
| export const ThemeProviderContext = |
| createContext<ThemeProviderState>(initialState); |
|
|
| interface ThemeProviderProps { |
| children: ReactNode; |
| defaultTheme?: Theme; |
| storageKey?: string; |
| } |
|
|
| export function ThemeProvider({ |
| children, |
| defaultTheme = "system", |
| storageKey = "vite-ui-theme", |
| ...props |
| }: ThemeProviderProps) { |
| const [theme, setTheme] = useState<Theme>( |
| () => (localStorage.getItem(storageKey) as Theme) || defaultTheme |
| ); |
|
|
| useEffect(() => { |
| const root = window.document.documentElement; |
| root.classList.remove("light", "dark"); |
|
|
| if (theme === "system") { |
| const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") |
| .matches |
| ? "dark" |
| : "light"; |
| root.classList.add(systemTheme); |
| return; |
| } |
|
|
| root.classList.add(theme); |
| }, [theme]); |
|
|
| const updateTheme = useCallback( |
| (newTheme: Theme) => { |
| localStorage.setItem(storageKey, newTheme); |
| setTheme(newTheme); |
| }, |
| [storageKey] |
| ); |
|
|
| const value = useMemo( |
| () => ({ theme, setTheme: updateTheme }), |
| [theme, updateTheme] |
| ); |
|
|
| return ( |
| <ThemeProviderContext.Provider {...props} value={value}> |
| {children} |
| </ThemeProviderContext.Provider> |
| ); |
| } |
|
|