diff --git a/app/src/components/ThemeProvider.tsx b/app/src/components/ThemeProvider.tsx index 8fe9949..42e35db 100644 --- a/app/src/components/ThemeProvider.tsx +++ b/app/src/components/ThemeProvider.tsx @@ -1,11 +1,11 @@ import { createContext, useContext, useEffect, useState } from "react"; -import { Moon, Sun } from "lucide-react"; +import { Moon, Sun, Monitor } from "lucide-react"; import { Button } from "./ui/button"; import { getMemory, setMemory } from "@/utils/memory.ts"; import { themeEvent } from "@/events/theme.ts"; -const defaultTheme: Theme = "dark"; +const defaultTheme: Theme = "system"; export type Theme = "dark" | "light" | "system"; @@ -15,7 +15,6 @@ type ThemeProviderProps = { type ThemeProviderState = { theme: Theme; - setTheme: (theme: Theme) => void; toggleTheme?: () => void; }; @@ -23,13 +22,15 @@ export function activeTheme(theme: Theme) { const root = window.document.documentElement; root.classList.remove("light", "dark"); - if (theme === "system") + + if (theme === "system") { theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + } + + root.classList.add(`${theme}`); - root.classList.add(theme); - setMemory("theme", theme); themeEvent.emit(theme); } @@ -38,52 +39,58 @@ export function getTheme() { } const initialState: ThemeProviderState = { - theme: "system", - setTheme: (theme: Theme) => { - activeTheme(theme); - }, + theme: defaultTheme, toggleTheme: () => { - const key = getMemory("theme"); - const theme = (key.length > 0 ? key : defaultTheme) as Theme; + const currentTheme = getMemory("theme"); + let newTheme: Theme; + const root = window.document.documentElement; + + + root.classList.remove("dark", "light","system"); + + // dark -> light -> system -> dark + if (currentTheme === "dark") { + newTheme = "light"; + root.classList.add(`${newTheme}`); + + } else if (currentTheme === "light") { + newTheme = "system"; + + } else { + newTheme = "dark"; + root.classList.add(`${newTheme}`); + + } + + + activeTheme(newTheme); + setMemory("theme", newTheme); - activeTheme(theme === "dark" ? "light" : "dark"); }, }; const ThemeProviderContext = createContext(initialState); export function ThemeProvider({ - defaultTheme = "dark", + defaultTheme = "system", ...props }: ThemeProviderProps) { - const [theme, setTheme] = useState( - () => (getMemory("theme") as Theme) || defaultTheme, - ); + const { theme } = useTheme(); + 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); + const savedTheme = getTheme(); + + activeTheme(savedTheme); }, [theme]); const value = { theme, setTheme: (theme: Theme) => { setMemory("theme", theme); - setTheme(theme); }, + toggleTheme: initialState.toggleTheme, }; return ; @@ -99,16 +106,61 @@ export const useTheme = () => { }; export function ModeToggle() { - const { toggleTheme } = useTheme(); + const { theme, toggleTheme } = useTheme(); + const [isAnimating, setIsAnimating] = useState(false); + const [currentIconIndex, setCurrentIconIndex] = useState(0); + + const icons = [ + , + , + , + ]; + + + useEffect(() => { + const savedTheme = getTheme(); + const index = icons.findIndex(icon => icon.key === savedTheme); + + setCurrentIconIndex(index); + }, [theme]); + + const handleClick = () => { + if (isAnimating) return; // 防止重复点击 + setIsAnimating(true); + + + toggleTheme?.(); + + setTimeout(() => { + + const nextIconIndex = (currentIconIndex + 1) % icons.length; + + setCurrentIconIndex(nextIconIndex); + setIsAnimating(false); + }, 500); + }; return ( - ); }