| "use client"; |
|
|
| import { createContext, useContext, useEffect, useState, type MouseEvent, type ReactNode } from "react"; |
|
|
| type Theme = "light" | "dark"; |
|
|
| interface ThemeContextType { |
| theme: Theme; |
| toggleTheme: (event?: MouseEvent) => void; |
| } |
|
|
| const ThemeContext = createContext<ThemeContextType | undefined>(undefined); |
|
|
| export function ThemeProvider({ children }: { children: ReactNode }) { |
| const [theme, setTheme] = useState<Theme>("light"); |
| useEffect(() => { |
| const savedTheme = localStorage.getItem("theme") as Theme | null; |
| if (savedTheme) { |
| |
| setTheme(savedTheme); |
| document.documentElement.classList.toggle("dark", savedTheme === "dark"); |
| } else { |
| |
| const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; |
| const initialTheme = prefersDark ? "dark" : "light"; |
| setTheme(initialTheme); |
| document.documentElement.classList.toggle("dark", initialTheme === "dark"); |
| } |
| }, []); |
|
|
| const toggleTheme = (event?: React.MouseEvent) => { |
| const newTheme = theme === "light" ? "dark" : "light"; |
| |
| |
| |
| if (event && (document as any).startViewTransition) { |
| const x = event.clientX; |
| const y = event.clientY; |
| const endRadius = Math.hypot( |
| Math.max(x, window.innerWidth - x), |
| Math.max(y, window.innerHeight - y) |
| ); |
|
|
| |
| const transition = (document as any).startViewTransition(() => { |
| setTheme(newTheme); |
| localStorage.setItem("theme", newTheme); |
| document.documentElement.classList.toggle("dark", newTheme === "dark"); |
| }); |
|
|
| transition.ready.then(() => { |
| const clipPath = [ |
| `circle(0px at ${x}px ${y}px)`, |
| `circle(${endRadius}px at ${x}px ${y}px)` |
| ]; |
| document.documentElement.animate( |
| { clipPath }, |
| { |
| duration: 500, |
| easing: "ease-in-out", |
| pseudoElement: "::view-transition-new(root)" |
| } |
| ); |
| }); |
| } else { |
| setTheme(newTheme); |
| localStorage.setItem("theme", newTheme); |
| document.documentElement.classList.toggle("dark", newTheme === "dark"); |
| } |
| }; |
|
|
| return ( |
| <ThemeContext.Provider value={{ theme, toggleTheme }}> |
| {children} |
| </ThemeContext.Provider> |
| ); |
| } |
|
|
| export function useTheme() { |
| const context = useContext(ThemeContext); |
| if (context === undefined) { |
| throw new Error("useTheme must be used within a ThemeProvider"); |
| } |
| return context; |
| } |
|
|