mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
94.04k stars 32.31k forks source link

[material-ui][pigment-css] How to toggle between color modes #43655

Open shjacobs303 opened 2 months ago

shjacobs303 commented 2 months ago

Steps to reproduce

Link to live example: (required) codesandbox

Steps:

  1. Deploy https://github.com/mui/material-ui/blob/master/examples/material-ui-pigment-css-nextjs-ts/src/app/page.tsx
  2. Add getSelector: (colorScheme) => colorScheme ? .theme-${colorScheme} : ":root" to the theme file
  3. Add a toggle button with logic like document.documentElement.classList.toggle("theme-light"); from the pigment css documentation

Current behavior

adds class="theme-light" or "theme-dark" Color scheme does not change after button click ### Expected behavior adds class="theme-light" or "theme-dark" Color scheme changes on button click ### Context I'm trying to use mui v6 with pigment css with the ability to select a particular color scheme, overriding the system defaults. You'll also notice I had to comment out a Grid component in the codesandbox example. The nextjs pigment css example does not currently render with Grid/> without changing to a client component. ### Your environment
npx @mui/envinfo ``` Replicated in Chrome System: OS: Linux 6.1 Ubuntu 20.04.6 LTS (Focal Fossa) Binaries: Node: 20.12.1 - /home/codespace/nvm/current/bin/node npm: 10.5.0 - /home/codespace/nvm/current/bin/npm pnpm: 8.15.6 - /home/codespace/nvm/current/bin/pnpm Browsers: Chrome: Not Found npmPackages: @emotion/react: 11.13.3 @emotion/styled: 11.13.0 @mui/core-downloads-tracker: 6.0.2 @mui/material: 6.0.2 => 6.0.2 @mui/material-pigment-css: 6.0.2 => 6.0.2 @mui/private-theming: 6.0.2 @mui/styled-engine: 6.0.2 @mui/system: 6.0.2 @mui/types: 7.2.16 @mui/utils: 6.0.2 @pigment-css/nextjs-plugin: latest => 0.0.22 @pigment-css/react: 0.0.21 @pigment-css/unplugin: 0.0.22 @types/react: latest => 18.3.4 react: latest => 18.3.1 react-dom: latest => 18.3.1 typescript: latest => 5.5.4 ```
**Search keywords**: pigment-css v6
samuelgoldenbaum commented 2 months ago

I battled this issue this morning...

What I did to resolve in the interim:

  1. specify colorSchemeSelector to be class in `next.config
    -   cssVariables: true,
    +   cssVariables: {
    +      colorSchemeSelector: "class"
    +   },
  2. toggle() the class on root/html element

    const ToggleColorScheme = () => {
    const handleClick = () => {
    document.documentElement.classList.toggle("dark");
    };
    
    return (
    <Button onClick={handleClick} color="primary" variant="contained">
      Toggle color scheme
    </Button>
    );
    };
  3. remove the getSelector in next.config as it seems superfluous...

PS Add suppressHydrationWarning to your html and body tags to avoid hydration errors in console

sandbox

shjacobs303 commented 2 months ago

This at least gets it to toggle but it loses system preference. Would really like to have an opinionated way to allow system preference, but then to select an override if desired.

And like other frameworks, be able to avoid the first time load flicker while not causing all routes to become dynamic by using async in the root layout.

siriwatknp commented 2 months ago

@shjacobs303 Pigment does not have a built-in way to handle system preferences yet. Meanwhile, you can use https://github.com/pacocoursey/next-themes for Next.js

shjacobs303 commented 2 months ago

next-themes looks to be ideal, I can see you're planning to add some examples using V6. I can wait and follow your pattern, looking forward to it.

brijeshb42 commented 1 month ago

As @samuelgoldenbaum has stated, if you are using createTheme from @mui/material, that's the way to go to have manual toggle of theme which will give you dark and light classes. As for the system preference, there's no built-in way right now. It's either manual or system, not both. But you can use a bit of JS with manual classes to achieve it. A reference snippet -

const matcher = window.matchMedia('(prefers-color-scheme: dark)');
if (matcher.matches) {
    document.documentElement.classList.remove('light');
    document.documentElement.classList.add('dark');
} else {
    document.documentElement.classList.remove('dark');
    document.documentElement.classList.add('light');
}
matcher.addEventListener('change', event => {
    const newColorScheme = event.matches ? "dark" : "light";
    document.documentElement.classList.remove('light', 'dark');
    document.documentElement.classList.add(newColorScheme);
});