1

Trying to create a Toggle to switch from dark mode to light mode has been quite difficult for me in v5.

Using the code directly from MUI Sandbox MUI darkmode, I attempted to separate the code to work in the app.js and my Navbarpractice.js.

App.js

import React from "react";
import useMediaQuery from '@mui/material/useMediaQuery';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { Paper } from "@mui/material";
import BasicCard from "./components /Card.js";
import Navbarpractice from "./components /Navbar/Navbarpractice"

const ColorModeContext = React.createContext({ toggleColorMode: () => {} });


function App() {
  const [mode, setMode] = React.useState('light');
  const colorMode = React.useMemo(
    () => ({
      toggleColorMode: () => {
        setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
      },
    }),
    [],
  );

  const theme = React.useMemo(
    () =>
      createTheme({
        palette: {
          mode,
        },
      }),
    [mode],
  );

  return (
    <ColorModeContext.Provider value={colorMode}>
    <ThemeProvider theme={theme}>
    <div className="App">
    <Navbarpractice/>
      <BasicCard/>
    </div>
    </ThemeProvider>
    </ColorModeContext.Provider>
  );
}

export default App;

Navbarpractice.js

import React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import { useTheme, ThemeProvider, createTheme } from '@mui/material/styles';
import { teal } from '@mui/material/colors';
import { withStyles } from '@mui/styles';
import { Switch } from '@mui/material';
import Brightness4Icon from '@mui/icons-material/Brightness4';
import Brightness7Icon from '@mui/icons-material/Brightness7';


const label = { inputProps: { 'aria-label': 'Switch' } };

const ColorModeContext = React.createContext({ toggleColorMode: () => {} });

const theme = createTheme({
  
    Navbar: {
      primary: {
        // Purple and green play nicely together.
        main: teal[500],
      },
      secondary: {
        // This is green.A700 as hex.
        main: '#11cb5f',
      },
    },
  });

  const TealTextTypography = withStyles({
    root: {
      color: "#008080"
    }
  })(Typography);

function Navbar() {

  const theme = useTheme();
  const colorMode = React.useContext(ColorModeContext);

    return (
      <ColorModeContext.Provider value={colorMode}>
    <ThemeProvider theme={theme}>\
      <Box sx={{ flexGrow: 1 }}>
        <AppBar position="static"
        style={{ background: 'transparent', boxShadow: 'none'}}>
          <Toolbar>
            <IconButton
              size="large"
              edge="start"
            
              aria-label="menu"
              sx={{ mr: 2 }}
            >
              <MenuIcon />
            </IconButton>
            <TealTextTypography variant="h6" component="div" sx={{ flexGrow: 1 }}>
              Mentors
            </TealTextTypography>
            <TealTextTypography variant="h6" component="div" sx={{ flexGrow: 1 }}>
              Mentees
            </TealTextTypography>

            <Box
            sx={{
            display: 'flex',
            width: '100%',
            alignItems: 'center',
            justifyContent: 'center',
            bgcolor: 'background.default',
            color: 'text.primary',
            borderRadius: 1,
            p: 3,
            }}
            >
            <IconButton sx={{ ml: 1 }} onClick={colorMode.toggleColorMode} color="inherit">
            { theme.palette.mode === 'dark' ? <Brightness7Icon /> : <Brightness4Icon />}
            </IconButton>

            </Box>
          </Toolbar>
        </AppBar>
      </Box>
      </ThemeProvider>
      </ColorModeContext.Provider>
    );
  }

export default Navbar;

I am certain I am mixing up my const. and placing them in the wrong places. Although I am new to react and Mui I did manage to get it to work statically, however, the toggle is proving to be difficult.

Amila Senadheera
  • 12,229
  • 15
  • 27
  • 43
Adnan Said
  • 23
  • 1
  • 4
  • Do you try to read this part of the MUI doc https://mui.com/customization/dark-mode/#main-content ? It's more efficient than the codebox – LutherW Feb 13 '22 at 20:12

2 Answers2

6

This seem to work for me

App.js

import React from 'react';
import {
  ThemeProvider,
  createTheme,
  responsiveFontSizes,
} from '@mui/material/styles';
import { deepmerge } from '@mui/utils';
import useMediaQuery from '@mui/material/useMediaQuery';
import { getDesignTokens, getThemedComponents } from 'theme/Theme';
import { ColorModeContext } from 'config/color-context';

export default function App() {
  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
  const [mode, setMode] = React.useState();

  React.useEffect(() => {
    setMode(prefersDarkMode ? 'dark' : 'light');
  }, [prefersDarkMode]);

  const colorMode = React.useMemo(
    () => ({
      toggleColorMode: () => {
        setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
      },
    }),
    []
  );

  let theme = React.useMemo(
    () =>
      createTheme(deepmerge(getDesignTokens(mode), getThemedComponents(mode))),
    [mode]
  );

  theme = responsiveFontSizes(theme);

  return (
    <ColorModeContext.Provider value={colorMode}>
      <ThemeProvider theme={theme}>
       ...         
      </ThemeProvider>
    </ColorModeContext.Provider>
  );
}

Theme.js

import { amber, deepOrange, grey, blue, common } from '@mui/material/colors';

const palette = {
  light: {
    primary: {
      main: '#34C0AC',
      light: '#B1DED3',
      dark: '#00765A',
    },
  },
};

export const getDesignTokens = (mode) => ({
  palette: {
    mode,
    ...(mode === 'light'
      ? {
          primary: {
            main: palette.light.primary.main,
            light: palette.light.primary.light,
            dark: palette.light.primary.dark,
          },

          divider: amber[200],
          text: {
            primary: grey[900],
            secondary: grey[800],
          },
        }
      : {
          primary: deepOrange,
          divider: deepOrange[700],
          background: {
            default: deepOrange[900],
            paper: deepOrange[900],
          },
          text: {
            primary: '#fff',
            secondary: grey[500],
          },
        }),
  },
  typography: {
    fontFamily: [
      'Oswald',
      'Roboto',
      '"Helvetica Neue"',
      'Arial',
      'sans-serif',
    ].join(','),
    body1: {
      fontFamily: 'Poppins, Arial, sans-serif',
    },
  },
});

export const getThemedComponents = (mode) => ({
  components: {
    ...(mode === 'light'
      ? {
          MuiAppBar: {
            styleOverrides: {
              colorPrimary: {
                backgroundColor: grey[800],
              },
            },
          },
          MuiLink: {
            variant: 'h3',
          },
          MuiButton: {
            styleOverrides: {
              root: {
                borderRadius: 0,
                color: common.white,
                fontFamily:
                  "Oswald, Roboto, 'Helvetica Neue', Arial, sans-serif",
                fontSize: 20,
                borderWidth: 2,
                '&:hover': {
                  borderWidth: 2,
                },
              },
            },
            variants: [
              {
                props: { variant: 'contained' },
                style: {
                  fontFamily:
                    "Oswald, Roboto, 'Helvetica Neue', Arial, sans-serif",
                },
              },
              {
                props: { variant: 'outlined' },
                style: {
                  color: palette.light.primary.main,
                },
              },
              {
                props: { variant: 'primary', color: 'primary' },
                style: {
                  border: '4px dashed blue',
                },
              },
            ],
          },
          MuiList: {
            styleOverrides: {
              root: {},
            },
          },
          MuiMenuItem: {
            styleOverrides: {
              root: {
                color: common.white,
                alignItems: 'stretch',
                fontFamily:
                  "Oswald, Roboto, 'Helvetica Neue', Arial, sans-serif",
              },
            },
          },
          MuiAccordion: {
            styleOverrides: {
              root: {
                color: common.white,
                fontFamily:
                  "Oswald, Roboto, 'Helvetica Neue', Arial, sans-serif",
              },
            },
          },
        }
      : {
          MuiAppBar: {
            styleOverrides: {
              colorPrimary: {
                backgroundColor: blue[800],
              },
            },
          },
        }),
  },
});

color-context.js

import React from 'react';

export const ColorModeContext = React.createContext({
  toggleColorMode: () => {
    // This is intentional
  },
});

ThemeToggler.js

import React from 'react';
import { IconButton, Box } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import Brightness4Icon from '@mui/icons-material/Brightness4';
import Brightness7Icon from '@mui/icons-material/Brightness7';
import { ColorModeContext } from 'config/color-context';

export default function SubHeaderNavigation() {
  const theme = useTheme();
  const colorMode = React.useContext(ColorModeContext);

  return (
    <Box
      sx={{
        display: 'flex',
        width: '100%',
        alignItems: 'center',
        justifyContent: 'center',
        bgcolor: 'background.default',
        color: 'text.primary',
        borderRadius: 1,
        p: 3,
      }}
    >
      {theme.palette.mode} mode
      <IconButton
        sx={{ ml: 1 }}
        onClick={colorMode.toggleColorMode}
        color="inherit"
      >
        {theme.palette.mode === 'dark' ? (
          <Brightness7Icon />
        ) : (
          <Brightness4Icon />
        )}
      </IconButton>
    </Box>
  );
}
atazmin
  • 4,757
  • 1
  • 32
  • 23
  • Could you tell me where I can use the SubHeaderNavigation please? – MoonKnight Apr 13 '22 at 14:20
  • @MoonKnight I used it in App.js, the part https://github.com/atazmin/atazmin-react/blob/master/src/App.js Url: https://atazmin-react.herokuapp.com (top right corner toggle and should default to OS theme mode, ex: https://support.apple.com/library/content/dam/edam/applecare/images/en_US/macos/Big-Sur/macos-big-sur-system-prefs-general-dark.jpg) – atazmin Apr 14 '22 at 05:07
  • Thank you for coming back to me, your time is most appreciated. That is awesome. I have chosen to only display this on my login screen and then store the selection for future sessions, but I have an issue - I have asked a question here https://stackoverflow.com/questions/71869200/theme-button-requires-two-clicks-the-first-time-it-is-used hopefully you won't mind taking a look for me... Thanks again for your time. – MoonKnight Apr 14 '22 at 09:14
  • There is an issue with this, when you navigate back to the root "/" the theme switches back to the default. Any idea how this could be prevented - I guess a sessionStorage entry... – MoonKnight Apr 15 '22 at 09:08
  • @MoonKnight Sorry you have problems, maybe something needs to be stored in localStorage and checked? On my site I have added pages and it seems to work: https://atazmin-react.herokuapp.com I am also new to React/MUI this is my first site with MUI and second with React, I am still learning. I used code (you might already seen this page) from https://mui.com/material-ui/customization/dark-mode/ – atazmin Apr 20 '22 at 21:08
  • I have done this a different way now using a complete context with state. This is working well. I may post an answer showing my approach. – MoonKnight Apr 20 '22 at 21:14
  • @MoonKnight :thumbsup: – atazmin Apr 22 '22 at 22:57
0

Make sure you are not nesting your Themes or both could be negated.

For example:

App.tsx

<>
  <ThemeProvider theme={!isDark ? lightTheme : darkTheme}>
      <CssBaseline />
      <Dashboard />
  </ThemeProvider>
</>

Dashboard.tsx taken from here

function Dashboard() {
...
  return (
    <ThemeProvider theme={mdTheme}>
      <Box sx={{ display: 'flex' }}>
        <CssBaseline />
        ...
    </ThemeProvider>
  );
}
export default function Dashboard() {
  return <DashboardContent />;
}

The effect of this will negate the Themes in the App.tsx config.

Michael Nelles
  • 5,426
  • 8
  • 41
  • 57