1- "use client"
1+ import {
2+ createContext ,
3+ useCallback ,
4+ useContext ,
5+ useEffect ,
6+ useState ,
7+ } from "react"
28
3- import * as React from "react"
4- import { ThemeProvider as NextThemesProvider } from "next-themes"
9+ export type Theme = "dark" | "light" | "system"
10+
11+ type ThemeProviderProps = {
12+ children : React . ReactNode
13+ defaultTheme ?: Theme
14+ storageKey ?: string
15+ }
16+
17+ type ThemeProviderState = {
18+ theme : Theme
19+ resolvedTheme : "dark" | "light"
20+ setTheme : ( theme : Theme ) => void
21+ }
22+
23+ const initialState : ThemeProviderState = {
24+ theme : "system" ,
25+ resolvedTheme : "light" ,
26+ setTheme : ( ) => null ,
27+ }
28+
29+ const ThemeProviderContext = createContext < ThemeProviderState > ( initialState )
530
631export function ThemeProvider ( {
7- children,
8- ...props
9- } : React . ComponentProps < typeof NextThemesProvider > ) {
10- return < NextThemesProvider { ...props } > { children } </ NextThemesProvider >
11- }
32+ children,
33+ defaultTheme = "system" ,
34+ storageKey = "vite-ui-theme" ,
35+ ...props
36+ } : ThemeProviderProps ) {
37+ const [ theme , setTheme ] = useState < Theme > (
38+ ( ) => ( localStorage . getItem ( storageKey ) as Theme ) || defaultTheme ,
39+ )
40+
41+ const getResolvedTheme = useCallback ( ( theme : Theme ) : "dark" | "light" => {
42+ if ( theme === "system" ) {
43+ return window . matchMedia ( "(prefers-color-scheme: dark)" ) . matches
44+ ? "dark"
45+ : "light"
46+ }
47+ return theme
48+ } , [ ] )
49+
50+ const [ resolvedTheme , setResolvedTheme ] = useState < "dark" | "light" > ( ( ) =>
51+ getResolvedTheme ( theme ) ,
52+ )
53+
54+ const updateTheme = useCallback ( ( newTheme : Theme ) => {
55+ const root = window . document . documentElement
56+
57+ root . classList . remove ( "light" , "dark" )
58+
59+ if ( newTheme === "system" ) {
60+ const systemTheme = window . matchMedia ( "(prefers-color-scheme: dark)" )
61+ . matches
62+ ? "dark"
63+ : "light"
64+
65+ root . classList . add ( systemTheme )
66+ return
67+ }
68+
69+ root . classList . add ( newTheme )
70+ } , [ ] )
71+
72+ useEffect ( ( ) => {
73+ updateTheme ( theme )
74+ setResolvedTheme ( getResolvedTheme ( theme ) )
75+
76+ const mediaQuery = window . matchMedia ( "(prefers-color-scheme: dark)" )
77+
78+ const handleChange = ( ) => {
79+ if ( theme === "system" ) {
80+ updateTheme ( "system" )
81+ setResolvedTheme ( getResolvedTheme ( "system" ) )
82+ }
83+ }
84+
85+ mediaQuery . addEventListener ( "change" , handleChange )
86+
87+ return ( ) => {
88+ mediaQuery . removeEventListener ( "change" , handleChange )
89+ }
90+ } , [ theme , updateTheme , getResolvedTheme ] )
91+
92+ const value = {
93+ theme,
94+ resolvedTheme,
95+ setTheme : ( theme : Theme ) => {
96+ localStorage . setItem ( storageKey , theme )
97+ setTheme ( theme )
98+ } ,
99+ }
100+
101+ return (
102+ < ThemeProviderContext . Provider { ...props } value = { value } >
103+ { children }
104+ </ ThemeProviderContext . Provider >
105+ )
106+ }
107+
108+ export const useTheme = ( ) => {
109+ const context = useContext ( ThemeProviderContext )
110+
111+ if ( context === undefined )
112+ throw new Error ( "useTheme must be used within a ThemeProvider" )
113+
114+ return context
115+ }
0 commit comments