Add theme toggle functionality with ThemeProvider and ModeToggle component

This commit is contained in:
ecwu
2024-12-20 18:28:33 +08:00
parent 368d9810c2
commit 5ecfe13234
4 changed files with 121 additions and 5 deletions

View File

@@ -0,0 +1,37 @@
import { Moon, Sun } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useTheme } from "@/components/theme-provider";
export function ModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -0,0 +1,74 @@
import { createContext } from "preact";
import { useContext, useEffect, useState } from "preact/hooks";
type Theme = "dark" | "light" | "system";
type ThemeProviderProps = {
children: preact.VNode;
defaultTheme?: Theme;
storageKey?: string;
};
type ThemeProviderState = {
theme: Theme;
setTheme: (theme: Theme) => void;
};
const initialState: ThemeProviderState = {
theme: "system",
setTheme: () => null,
};
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
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 value = {
theme,
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme);
setTheme(theme);
},
};
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
);
}
export const useTheme = () => {
const context = useContext(ThemeProviderContext);
if (context === undefined)
throw new Error("useTheme must be used within a ThemeProvider");
return context;
};

View File

@@ -5,6 +5,7 @@ import { App } from "@/pages/App";
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate"; import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import { SidebarProvider } from "@/components/ui/sidebar"; import { SidebarProvider } from "@/components/ui/sidebar";
import { Toaster } from "@/components/ui/toaster"; import { Toaster } from "@/components/ui/toaster";
import { ThemeProvider } from "@/components/theme-provider";
function Base() { function Base() {
const [langCode, _setLangCode] = useState("en-US"); const [langCode, _setLangCode] = useState("en-US");
@@ -47,11 +48,14 @@ function Base() {
return ( return (
/* @ts-ignore */ /* @ts-ignore */
<langCodeContext.Provider value={{ langCode, setLangCode }}> <langCodeContext.Provider value={{ langCode, setLangCode }}>
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
<SidebarProvider> <SidebarProvider>
<App /> <App />
<Toaster /> <Toaster />
</SidebarProvider> </SidebarProvider>
</ThemeProvider>
</langCodeContext.Provider> </langCodeContext.Provider>
); );
} }

View File

@@ -71,6 +71,7 @@ import {
CogIcon, CogIcon,
} from "lucide-react"; } from "lucide-react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { ModeToggle } from "@/components/mode-toggle";
export function App() { export function App() {
// init selected index // init selected index
@@ -396,7 +397,7 @@ export function App() {
</Menubar> </Menubar>
</div> </div>
</div> </div>
<CogIcon /> <ModeToggle />
</div> </div>
</div> </div>
</header> </header>