为你的 Vite 应用添加暗黑模式。

暗黑模式

创建主题提供者

components/theme-provider.tsx

  1. import { createContext, useContext, useEffect, useState } from "react"
  2. type Theme = "dark" | "light" | "system"
  3. type ThemeProviderProps = {
  4. children: React.ReactNode
  5. defaultTheme?: Theme
  6. storageKey?: string
  7. }
  8. type ThemeProviderState = {
  9. theme: Theme
  10. setTheme: (theme: Theme) => void
  11. }
  12. const initialState: ThemeProviderState = {
  13. theme: "system",
  14. setTheme: () => null,
  15. }
  16. const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
  17. export function ThemeProvider({
  18. children,
  19. defaultTheme = "system",
  20. storageKey = "vite-ui-theme",
  21. ...props
  22. }: ThemeProviderProps) {
  23. const [theme, setTheme] = useState<Theme>(
  24. () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
  25. )
  26. useEffect(() => {
  27. const root = window.document.documentElement
  28. root.classList.remove("light", "dark")
  29. if (theme === "system") {
  30. const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
  31. .matches
  32. ? "dark"
  33. : "light"
  34. root.classList.add(systemTheme)
  35. return
  36. }
  37. root.classList.add(theme)
  38. }, [theme])
  39. const value = {
  40. theme,
  41. setTheme: (theme: Theme) => {
  42. localStorage.setItem(storageKey, theme)
  43. setTheme(theme)
  44. },
  45. }
  46. return (
  47. <ThemeProviderContext.Provider {...props} value={value}>
  48. {children}
  49. </ThemeProviderContext.Provider>
  50. )
  51. }
  52. export const useTheme = () => {
  53. const context = useContext(ThemeProviderContext)
  54. if (context === undefined)
  55. throw new Error("useTheme 必须在 ThemeProvider 内部使用")
  56. return context
  57. }

包装你的根布局

ThemeProvider 添加到你的根布局。

App.tsx

  1. import { ThemeProvider } from "@/components/theme-provider"
  2. function App() {
  3. return (
  4. <ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
  5. {children}
  6. </ThemeProvider>
  7. )
  8. }
  9. export default App

添加模式切换

在你的站点上添加一个模式切换按钮,以在光明模式和暗黑模式之间切换。

components/mode-toggle.tsx

  1. import { Moon, Sun } from "lucide-react"
  2. import { Button } from "@/components/ui/button"
  3. import {
  4. DropdownMenu,
  5. DropdownMenuContent,
  6. DropdownMenuItem,
  7. DropdownMenuTrigger,
  8. } from "@/components/ui/dropdown-menu"
  9. import { useTheme } from "@/components/theme-provider"
  10. export function ModeToggle() {
  11. const { setTheme } = useTheme()
  12. return (
  13. <DropdownMenu>
  14. <DropdownMenuTrigger asChild>
  15. <Button variant="outline" size="icon">
  16. <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
  17. <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
  18. <span className="sr-only">切换主题</span>
  19. </Button>
  20. </DropdownMenuTrigger>
  21. <DropdownMenuContent align="end">
  22. <DropdownMenuItem onClick={() => setTheme("light")}>
  23. 光明模式
  24. </DropdownMenuItem>
  25. <DropdownMenuItem onClick={() => setTheme("dark")}>
  26. 暗黑模式
  27. </DropdownMenuItem>
  28. <DropdownMenuItem onClick={() => setTheme("system")}>
  29. 系统模式
  30. </DropdownMenuItem>
  31. </DropdownMenuContent>
  32. </DropdownMenu>
  33. )
  34. }