dark mode toggle in material ui - reactjs

I am trying to implement a dark toggle in my website. I have gotten the toggle to show up in the correct place. I am using the usestate hook to implement the toggle functionality. However, on clicking the toggle, does not change the theme. I don't understand what is going wrong over here.
Here is the code of the different components.
I have implemented a component for the toggle theme button. Here is the code for togglethemebutton.js
import React, { useState } from "react";
// ----------------------------------------------------------------------
import { Switch } from '#mui/material';
import { createTheme } from '#mui/material/styles';
// ----------------------------------------------------------------------
export default function ToggleThemeButton() {
const [darkState, setDarkState] = useState(false);
const palletType = darkState ? "dark" : "light";
const darkTheme = createTheme({
palette: {
type: palletType,
}
});
const handleThemeChange = () => {
setDarkState(!darkState);
};
return (
<Switch checked={darkState} onChange={handleThemeChange} />
);
}
I am using this component in the Navbar.js component.
import PropTypes from 'prop-types';
// material
import { alpha, styled } from '#mui/material/styles';
import { Box, Stack, AppBar, Toolbar, IconButton } from '#mui/material';
// components
import Iconify from '../../components/Iconify';
//
import Searchbar from './Searchbar';
import AccountPopover from './AccountPopover';
import LanguagePopover from './LanguagePopover';
import NotificationsPopover from './NotificationsPopover';
import ToggleThemeButton from './ToggleThemeButton';
// ----------------------------------------------------------------------
const DRAWER_WIDTH = 280;
const APPBAR_MOBILE = 64;
const APPBAR_DESKTOP = 92;
const RootStyle = styled(AppBar)(({ theme }) => ({
boxShadow: 'none',
backdropFilter: 'blur(6px)',
WebkitBackdropFilter: 'blur(6px)', // Fix on Mobile
backgroundColor: alpha(theme.palette.background.default, 0.72),
[theme.breakpoints.up('lg')]: {
width: `calc(100% - ${DRAWER_WIDTH + 1}px)`
}
}));
const ToolbarStyle = styled(Toolbar)(({ theme }) => ({
minHeight: APPBAR_MOBILE,
[theme.breakpoints.up('lg')]: {
minHeight: APPBAR_DESKTOP,
padding: theme.spacing(0, 5)
}
}));
// ----------------------------------------------------------------------
DashboardNavbar.propTypes = {
onOpenSidebar: PropTypes.func
};
export default function DashboardNavbar({ onOpenSidebar }) {
return (
<RootStyle>
<ToolbarStyle>
<IconButton
onClick={onOpenSidebar}
sx={{ mr: 1, color: 'text.primary', display: { lg: 'none' } }}
>
<Iconify icon="eva:menu-2-fill" />
</IconButton>
<Searchbar />
<Box sx={{ flexGrow: 1 }} />
<Stack direction="row" alignItems="center" spacing={{ xs: 0.5, sm: 1.5 }}>
// here is the toggle button
<ToggleThemeButton/>
<LanguagePopover />
<NotificationsPopover />
<AccountPopover />
</Stack>
</ToolbarStyle>
</RootStyle>
);
}

A posible solution may be to move the logic to change the theme from the ToggleThemButton component to the NavBar and just pass the values needed like this:
ToggleThemeButton:
export default function ToggleThemeButton({handleThemeChange, darkState}) {
return (
<Switch checked={darkState} onChange={handleThemeChange} />
);
}
Then in the NavBar component you could add a theme variable that its updated by the ToggleThemeButton, here i used a new createTheme with the pallete that has a background property just for testing (i don't know much about material ui)
Navbar:
export default function DashboardNavbar({ onOpenSidebar }) {
const [darkState, setDarkState] = useState(false);
const palletType = darkState ? "dark" : "light";
const darkTheme = createTheme({
palette: {
type: palletType,
background: {
default: "#fff"
}
}
});
const [theme, setTheme] = useState(darkTheme);
const handleThemeChange = () => {
setDarkState(!darkState);
setTheme(createTheme({
palette: {
type: palletType,
background: {
default: !darkState? "#000" : "#fff"
}
}
}))
};
return (
<RootStyle theme={theme}>
..... <ToggleThemeButton handleThemeChange={handleThemeChange} darkState={darkState} /> ....
</ToolbarStyle>
</RootStyle>
);
}

Related

passing default theme in material UI4

I'm trying to pass a default theme to makeStyles in material ui 4.
In my component i called my theme, imported of Styled Components, and i called customMaterialStyles and passed to makeStyles;
my root component
import { faTimes } from '#fortawesome/free-solid-svg-icons';
import { Modal } from '#material-ui/core';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Stepper from '#material-ui/core/Stepper';
import { ButtonIcon } from 'commons/ButtonIcon';
import React, { useState } from 'react';
import { useTheme } from 'styled-components';
import { useStyles } from './materialStyles';
import {
StCloseButtonWrapper,
StContainer,
StContentStepper,
StWrapper,
} from './styled';
interface Props {
isVisible: boolean;
onCloseButtonClick: () => void;
}
const CreateEngineSchedule: React.FC<Props> = ({
isVisible,
onCloseButtonClick,
}) => {
const theme = useTheme();
const customMaterialStyles = useStyles(theme.colors);
const [activeStep, setActiveStep] = useState(0);
const steps = ['Nova agenda', 'Escolha a agenda', 'Confirmar'];
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
console.log('steps', steps);
return (
<Modal open={isVisible} className={customMaterialStyles.modal}>
<StContainer>
<StWrapper>
<StCloseButtonWrapper>
<ButtonIcon
icon={faTimes}
onHoverColor='darkBlue'
color='darkGrey'
tooltip
tooltipTitle='FECHAR'
backgroundColor='transparent'
onHoverBackgroundColor='transparent'
onButtonClick={onCloseButtonClick}
/>
</StCloseButtonWrapper>
<StContentStepper>
<Stepper
activeStep={activeStep}
className={customMaterialStyles.stepperRoot}
>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<div>
<button disabled={activeStep === 0} onClick={handleBack}>
Back
</button>
<button onClick={handleNext}>
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
</button>
</div>
</StContentStepper>
</StWrapper>
</StContainer>
</Modal>
);
};
export default CreateEngineSchedule;
Where i'm trying to use the theme of styled components:
import { makeStyles } from '#material-ui/core/styles';
import { DefaultTheme } from 'styled-components';
// import { useTheme } from 'styled-components';
export const useStyles = makeStyles(() => {
// const systemTheme = useTheme();
return {
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
stepperRoot: {
// '& .MuiStep-root:not(.MuiStep-completed)': {
// },
'& .MuiStepConnector-lineHorizontal': {
display: 'none',
},
'& .MuiStepIcon-root': {
fontSize: '3rem',
fill: 'transparent',
border: '1px solid',
borderRadius: '100%',
},
'& .MuiStepIcon-text': {
fill: (props: keyof DefaultTheme['colors']) => props.blue500,
fontSize: '13px',
},
},
};
});
The Error:
I only want use the styled component theme inside the materialUi 4.
you can see the prop here:
I solved this problem typing like this:
fill: (props: DefaultTheme['colors']) => props.blue500,

react mui custom theme issue with styled component and typescript

I'm porting to typescript this awesome project
https://github.com/codingmonk-yt/chat-app
it's a boilerplate starter for a modern chat webapp
built with material ui, where it customize extensively the default material theme
i'm not very familiar with react and material ui, truth be told, so i don't understand what am i doing wrong, i have a typescript issue when i try to migrate a styled component with its typescript version
ERROR :
Property 'theme' is missing in type '{ children: Element[]; sx: { bgcolor?: string | undefined; border?: solid 2px ${string} | undefined; boxShadow?: inset 0 4px 8px 0 ${string} | undefined; }; }' but required in type '{ theme: CustomTheme; }'.ts(2741)
the error suggests that the component can't see that BoxStyle is wrapped around a themeProvider with my custom theme, but i don't understand why
(i omitted to paste SettingsDrawer component since for now it's nothing more than a container for
SettingColorPresets)
Thank you in advance for any pointers
i'm pasting here all the relevant code, please let me know if you need any more information
/*********************************/
//App.tsx:
/*********************************/
function App() {
return (
<ThemeProvider>
<ThemeSettings>
{" "}
<Routes />{" "}
</ThemeSettings>
</ThemeProvider>
);
}
/*********************************/
//theme.d.ts:
/*********************************/
declare module '#mui/material/styles' {
interface CustomTheme extends Theme {
customShadows: CustomShadows;
palette: CustomPalette;
}
// allow configuration using `createTheme`
interface CustomThemeOptions extends ThemeOptions {
customShadows: CustomShadows;
palette: CustomPalette;
}
export function createTheme(options?: CustomThemeOptions): CustomTheme;
}
/*********************************/
//ThemeProvider.tsx:
/*********************************/
import {
createTheme,
ThemeProvider as MUIThemeProvider,
StyledEngineProvider,
CustomThemeOptions,
ThemeOptions,
CustomTheme,
} from "#mui/material/styles";
export default function ThemeProvider({ children }: { children: ReactNode }) {
const { themeMode, themeDirection } = useSettings();
const isLight = themeMode === "light";
const themeOptions = useMemo(
() => ({
palette: isLight ? palette.light : palette.dark,
typography,
breakpoints,
shape: { borderRadius: 8 },
direction: themeDirection,
shadows: isLight ? shadows.light : shadows.dark,
customShadows: isLight ? customShadows.light : customShadows.dark,
} as CustomThemeOptions),
[isLight, themeDirection]
);
const theme = createTheme(themeOptions);
// if i inspect theme in vscode it shows that is instance of CustomTheme, as it should be...
theme.components = componentsOverride(theme);
return (
<StyledEngineProvider injectFirst>
<MUIThemeProvider theme={theme}>
<CssBaseline />
{children}
</MUIThemeProvider>
</StyledEngineProvider>
);
}
/*********************************/
//ThemeSettings.tsx:
/*********************************/
export default function ThemeSettings({ children }: { children: ReactNode }) {
return (
<ThemeColorPresets>
<ThemeContrast>
<ThemeLocalization>
<ThemeRtlLayout>
{children}
<SettingsDrawer />
</ThemeRtlLayout>
</ThemeLocalization>
</ThemeContrast>
</ThemeColorPresets>
);
}
/*********************************/
//ThemeColorPresets.tsx:
/*********************************/
import PropTypes from "prop-types";
import { ReactNode, useMemo } from "react";
// #mui
import {
alpha,
ThemeProvider,
createTheme,
useTheme,
CustomTheme,
} from "#mui/material/styles";
// hooks
import useSettings from "../../hooks/useSettings";
//
import componentsOverride from "../../theme/overrides";
// ----------------------------------------------------------------------
ThemeColorPresets.propTypes = {
children: PropTypes.node,
};
export default function ThemeColorPresets({
children,
}: {
children: ReactNode;
}) {
const defaultTheme = useTheme<CustomTheme>();
const { setColor } = useSettings();
const themeOptions = useMemo(
() => ({
...defaultTheme,
palette: {
...defaultTheme.palette,
primary: setColor,
},
customShadows: {
...defaultTheme.customShadows,
primary: `0 8px 16px 0 ${alpha(setColor.main, 0.24)}`,
},
}),
[setColor, defaultTheme]
);
const theme = createTheme(themeOptions);
// if i inspect theme in vscode it shows that is instance of CustomTheme, as it should be...
theme.components = componentsOverride(theme);
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}
Finally, the part with the error:
/*********************************/
//SettingsColorPresets.tsx (CALLED FROM INSIDE <SettingsDrawer />, from ThemeSettings.tsx:
/*********************************/
const BoxStyle = styled(CardActionArea)(({ theme }: { theme: CustomTheme }) => ({
height: 48,
display: "flex",
alignItems: "center",
justifyContent: "center",
color: theme.palette.text.disabled,
border: `solid 1px ${(theme).palette.grey[500_12]}`,
borderRadius: Number(theme.shape.borderRadius) * 1.25,
}));
export default function SettingColorPresets() {
const { themeColorPresets, onChangeColor, colorOption } = useSettings();
return (
<RadioGroup
name="themeColorPresets"
value={themeColorPresets}
onChange={onChangeColor}
>
<Grid dir="ltr" container spacing={1.5}>
{colorOption.map((color: ColorOption) => {
const colorName = color.name;
const colorValue = color.value;
const isSelected = themeColorPresets === colorName;
return (
<Grid key={colorName} item xs={4}>
<BoxStyle <--- ERROR HERE
sx={{
...(isSelected && {
bgcolor: alpha(colorValue, 0.08),
border: `solid 2px ${colorValue}`,
boxShadow: `inset 0 4px 8px 0 ${alpha(colorValue, 0.24)}`,
}),
}}
>
.....

Toggling button image in React

I'm trying to toggle an icon in a button in react but I've tried everything and nothing works. I can change the background/text color toggling onClick but I'm missing something to change the icon and I don't know what! Any help?
Here's my code:
App.js
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { useState } from "react";
import Layout from "./layout";
import ThemeContext, { themes } from "./theme-context";
import { faSun, faMoon } from '#fortawesome/free-solid-svg-icons'
function App() {
const sun = <FontAwesomeIcon icon={faSun} />
const moon = <FontAwesomeIcon icon={faMoon} />
const [theme, setTheme] = useState(themes.dark)
const toggleTheme = () => theme === themes.dark ? setTheme(themes.light) : setTheme(themes.dark)
return (
<ThemeContext.Provider value={theme}>
<button onClick={toggleTheme}>
{sun}
</button>
<Layout />
</ThemeContext.Provider>
);
}
export default App;
theme-context.js
import React from "react"
export const themes = {
dark: {
color: "white",
background: "black",
padding: "5px",
width: "200px",
textAlign: "center",
},
light: {
color: "black",
background: "white",
padding: "5px",
width: "200px",
textAlign: "center",
}
}
const ThemeContext = React.createContext(themes.dark)
export default ThemeContext;
layout.jsx
import React, { useContext } from "react";
import ThemeContext from "./theme-context";
const Layout = () =>{
const theme = useContext(ThemeContext)
return (
<div style={theme} >
<p>This is your content</p>
</div>
)
}
export default Layout;
I have tried {themes.dark? : {sun} ? {moon}} but it didn't work.
The {themes.dark? : {sun} ? {moon}} was very close to correct (concept-wise, the syntax is not correct though). What you needed to do instead is { theme === themes.dark ? sun : moon } With your original snippet, you were just checking to see if themes.dark was truthy, which will always return true because it is an object. What you needed to do was check to see if the current theme was the same as themes.dark.
Hope this helps.

How to change the color of the primary Material UI

I have theme like this:
export const Original = createMuiTheme({
palette: {
type: 'light',
primary: {
light: '#b2dfdb',
main: '#26a69a',
dark: '#004d40',
}
}
});
And I use it for this:
<ListItem color = 'primary' button >
<img src={APP} alt='' />
</ListItem>
how can I use the primary-light or primary-dark for ListItem
If you read Material UI documentation. You would know that List & ListItem don't have props color. Thus in order for you to add one or apply any other colors as you wish, you can do something like this:-
App.js (require: ThemeProvider & createMuiTheme from #material-ui/core/styles)
import React from "react";
import { ThemeProvider, createMuiTheme } from "#material-ui/core/styles";
import "./style.css";
import Demo from "./Demo";
export default function App() {
const lightTheme = createMuiTheme({
palette: {
type: "light",
primary: {
light: "#b2dfdb",
main: "#26a69a",
dark: "#004d40"
}
}
});
return (
<ThemeProvider theme={lightTheme}>
<Demo />
</ThemeProvider>
);
}
Demo.js (require: makeStyles or 'useTheme' from #material-ui/stlyes):-
import React from "react";
import { makeStyles, useTheme } from "#material-ui/styles";
import { List, ListItem } from "#material-ui/core";
import "./style.css";
const Demo = () => {
const classes = useStyles();
const theme = useTheme();
return (
<>
<List>
<ListItem className={classes.listItem}>
Using useStyles (classes)
</ListItem>
<ListItem style={{ color: theme.palette.primary.dark }}>
Using inline direct theme
</ListItem>
</List>
<List className={classes.list}>
<ListItem>Having List control over all ListItem styles</ListItem>
</List>
</>
);
};
export default Demo;
const useStyles = makeStyles(theme => ({
listItem: {
color: theme.palette.primary.light
},
list: {
color: theme.palette.primary.main
}
}));
Here are the working sandbox code you can refer to.

React: Slider with custom hook not working properly

I am trying to create a custom hook with a slider ui element. My goal is to be able to access the slider value from the parent element so as to update some other ui parts.
However, it seems that the slider values do not update correctly: when the user tries to drag the slider tooltip it only moves one step. It seems like the mouse events stop being tracked after useEffect gets called.
What can I do to fix this and have a smooth dragging behaviour?
Here is my code (sandbox):
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './demo';
ReactDOM.render(<Demo />, document.querySelector('#root'));
demo.js
import React, { useEffect } from "react";
import useSlider from "./slider";
function CustomizedSlider() {
const [CustomSlider, sliderValue] = useSlider("Slider", 50);
useEffect(() => {
console.log("Slider value: " + sliderValue);
}, [sliderValue]);
return <CustomSlider />;
}
export default CustomizedSlider;
slider.js
import React, { useState } from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({...
}));
const PrettoSlider = withStyles({...
})(Slider);
export default function useSlider(label, defaultState) {
const classes = useStyles();
const [state, setState] = useState(defaultState);
const CustomSlider = () => {
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={state}
onChange={(event, v) => {
setState(v);
}}
/>
</Paper>
);
};
return [CustomSlider, state];
}
Thanks a lot for your help!
The issue is that your CustomSlider is a new component type for each render due to it being a unique function each time. This causes it to unmount/remount with each render rather than just re-render which will cause all sorts of issues (as you've seen).
Rather than a custom hook, I think you really just want a custom component. Below is one way you could structure it with only minimal changes to your initial code.
demo.js
import React, { useEffect } from "react";
import CustomSlider from "./CustomSlider";
function CustomizedSlider() {
const [value, setValue] = React.useState(50);
useEffect(() => {
console.log("Slider value: " + value);
}, [value]);
return <CustomSlider label="Slider" value={value} setValue={setValue} />;
}
export default CustomizedSlider;
CustomSlider.js
import React from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({
root: {
width: 300 + 24 * 2,
padding: 24
},
margin: {
height: theme.spacing(1)
}
}));
const PrettoSlider = withStyles({
root: {
color: "#a2df77",
height: 8
},
thumb: {
height: 24,
width: 24,
backgroundColor: "#fff",
border: "2px solid currentColor",
marginTop: -8,
marginLeft: -12,
"&:focus,&:hover,&$active": {
boxShadow: "inherit"
}
},
active: {},
valueLabel: {
left: "calc(-50% + 4px)"
},
track: {
height: 8,
borderRadius: 4
},
rail: {
height: 8,
borderRadius: 4
}
})(Slider);
const CustomSlider = ({ label, value, setValue }) => {
const classes = useStyles();
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={value}
onChange={(event, v) => {
setValue(v);
}}
/>
</Paper>
);
};
export default CustomSlider;

Resources