系统、深色、浅色模式切换

问题描述 投票:0回答:1

我在 Next.js Web 应用程序中使用 MUI 在系统、浅色和深色模式之间切换。我通过将所选主题保存在本地存储中来使其在会话之间保持不变。我已经在设置模式的下拉菜单中放置了更改 Web 应用程序主题的功能,因此可以通过

useContext
来更改主题。我遇到的问题是,如果用户选择的主题是“系统”主题(如果您的系统是深色模式)或“深色”主题,则主题在我的所有组件中都不会持久。此外,如果我的主题在初始加载时不是“light”主题或从“light”主题切换到其他主题之一时,我也会收到此错误:

Warning: Prop `className` did not match. Server: "MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit MuiIconButton-edgeStart MuiIconButton-sizeMedium css-134qg7o-MuiButtonBase-root-MuiIconButton-root" Client: "MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit MuiIconButton-edgeStart MuiIconButton-sizeMedium css-6pxnsq-MuiButtonBase-root-MuiIconButton-root

经过一番搜索,我最初以为这是因为我没有在

CssBaseline
标签中使用
ThemeProvider
,但是,添加它后,它似乎只会让事情变得更糟,并且错误仍然存在。我将在下面的屏幕截图中展示不同的行为:

在“系统”或“暗”模式下的预期行为(

CssBaseline
标签之间没有
ThemeProvider

在“系统”或“暗”模式下加载时没有

CssBaseline
的实际行为:

使用

CssBaseline
处于“系统”或“暗”模式时的加载行为:

从“浅色”模式切换到“系统”或“深色”模式时的行为:

下面列出的是我的上下文代码以及如何从系统获取主题、如何将其存储在本地存储中以及如何切换它:

const ThemeContext = createContext()

const useThemeContext = () => useContext(ThemeContext)

export const ThemeModeProviderComponent = ({ children }) => {
  const systemTheme = typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'

  const [selectedTheme, setSelectedTheme] = useState(() =>
    typeof localStorage !== 'undefined' && localStorage.getItem('theme') || 'system'
  )

  const themes = {
    light: createTheme({
      palette: {
        mode: 'light',
        primary: {
          main: '#0065bd'
        },
        secondary: {
          main: '#00b6d3'
        }
      }
    }),
    dark: createTheme({
      palette: {
        mode: 'dark',
        primary: {
          main: '#0065bd'
        },
        secondary: {
          main: '#00b6d3'
        }
      }
    }),
    system: createTheme({
      palette: {
        mode: systemTheme,
        primary: {
          main: '#0065bd'
        },
        secondary: {
          main: '#00b6d3'
        }
      }
    })
  }

  useEffect(() => {
    if (selectedTheme === 'system') {
      const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
      themes.system.palette.mode = systemTheme
    }
  }, [selectedTheme, themes.system])

  const handleThemeChange = (event) => {
    const { value } = event.target

    setSelectedTheme(value)
    localStorage.setItem('theme', value)
  }

  return (
    <ThemeContext.Provider value={{ selectedTheme, handleThemeChange }}>
      <ThemeProvider theme={themes[selectedTheme]}>
          <CssBaseline enableColorScheme/>
          {children}
      </ThemeProvider>
    </ThemeContext.Provider>
  )
}

export const ThemeSelector = () => {
  const { selectedTheme, handleThemeChange } = useThemeContext()

  if (typeof window === 'undefined') return null

  return (
    <FormControl>
      <Select
        value={selectedTheme}
        onChange={handleThemeChange}
      >
        <MenuItem value='system'>System</MenuItem>
        <MenuItem value='dark'>Dark</MenuItem>
        <MenuItem value='light'>Light</MenuItem>
      </Select>
    </FormControl>
  )
}

另一种可能性是,由于我处理抽屉显示和隐藏的方式,它可能会影响风格。我基本上从 MUI 网站here复制了持久抽屉示例,但我认为情况并非如此,正如我们在图像中看到的那样,它在一定程度上工作正常。

javascript reactjs material-ui
1个回答
0
投票

这就是我能够弄清楚的方法。我的问题是我试图在初始加载/安装之前从本地存储太快地设置主题,这给我带来了错误,并且如果最初不是轻型模式,某些组件将无法正确使用主题。以下是更正后的代码:

import { CssBaseline, MenuItem, Select, ThemeProvider, createTheme, useMediaQuery } from "@mui/material";
import { createContext, useContext, useEffect, useMemo, useState } from "react"

const ThemeModeContext = createContext()

const useThemeContext = () => useContext(ThemeModeContext)

export const ThemeModeProvider = ({ children }) => {
  //Check system preference for dark mode
  const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
  const [mode, setMode] = useState('light')

  //Check local storage for theme preference on initial load
  useEffect(() => {
    if (typeof window !== 'undefined') {
      setMode(window.localStorage.getItem('theme') || 'light')
    }
  }, [])

  //Toggle theme mode function
  const toggleThemeMode = (event) => {
    const newMode = event.target.value
    window.localStorage.setItem('theme', newMode)
    setMode(newMode)
  }

  //Create theme based on color mode state
  const theme = useMemo(() => (
    createTheme({
      palette: {
        mode: mode === 'dark' || (mode === 'auto' && isDarkMode) ? 'dark' : 'light'
      }
    })
  ), [mode, isDarkMode])

  //Value for the ThemeModeContext.Provider
  const themeModeValue = useMemo(() => ({ mode, toggleThemeMode }), [mode, toggleThemeMode])

  return (
    <ThemeModeContext.Provider value={themeModeValue}>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        {children}
      </ThemeProvider>
    </ThemeModeContext.Provider>
  )
}

export const ThemeSelector = () => {
  const { mode, toggleThemeMode } = useThemeContext()

  return (
    <Select
      value={mode}
      onChange={(e) => toggleThemeMode(e)}
      size='small'
    >
      <MenuItem value='dark'>Dark</MenuItem>
      <MenuItem value='light'>Light</MenuItem>
    </Select>
  )
}
© www.soinside.com 2019 - 2024. All rights reserved.