I would like to set up a sidebar with the possibility to collapse it to have only the sidebar icons.
I use Material-UI, I managed to set up the basic sidebar (Mini Variant Drawer) and it works fine. However, I can't keep the state (boolean) when I change the page or when I refresh the page I'm on.
Does anyone know how I can do this?
function:
const [open, setOpen] = useState(true)
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
Full code:
import React, { useState } from "react";
import clsx from "clsx";
import { makeStyles } from "@material-ui/core/styles";
import Drawer from "@material-ui/core/Drawer";
// import List from '@material-ui/core/List';
import Divider from "@material-ui/core/Divider";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import { Link as Route, NavLink, withRouter } from "react-router-dom";
import styled, { createGlobalStyle } from "styled-components/macro";
import {
Chip,
Collapse,
List as MuiList,
ListItemIcon,
Tooltip,
Typography,
} from "@material-ui/core";
import { darken, rgba } from "polished";
import { ExpandLess, ExpandMore } from "@material-ui/icons";
import PropTypes from "prop-types";
import {
sidebarMainRoutes as mainRoutes,
sidebarShortRoutes as shortRoutes,
} from "../../routes";
import ReactRouterPropTypes from "react-router-prop-types";
import desktopLogo from "../../assets/images/logos/data_is_logo.png";
import mobileLogo from "../../assets/images/logos/data_is_logo_icon.png";
import { CertificationIcon, LogOutIcon } from "../Icons/Icons";
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import CertificationModal from "../../pages/CertificationPage/CertificationModal";
import LogOutModal from "../../pages/AuthPages/components/LogOutModal";
const drawerWidth = 240;
const GlobalStyle = createGlobalStyle`
.MuiListItem-gutters {
padding-left: 8px !important;
padding-right: 10px !important;
}
`;
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginRight: 36,
},
hide: {
display: "none",
},
drawer: {
width: drawerWidth,
flexShrink: 0,
whiteSpace: "nowrap",
borderRight: "1px solid #F4F4F4",
},
drawerOpen: {
width: drawerWidth,
borderRight: "1px solid #F4F4F4",
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerClose: {
borderRight: "1px solid #F4F4F4",
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: "hidden",
width: theme.spacing(7) + 1,
[theme.breakpoints.up("sm")]: {
width: theme.spacing(9) + 20,
},
},
toolbar: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
},
}));
const List = styled(MuiList)`
background-color: ${(props) => props.theme.sidebar.background};
padding: ${(props) => props.theme.spacing(2)}px !important;
`;
const Items = styled.div`
padding-top: ${(props) => props.theme.spacing(2.5)}px;
padding-bottom: ${(props) => props.theme.spacing(2.5)}px;
`;
const Category = styled(ListItem)`
padding-top: ${(props) => props.theme.spacing(3)}px;
padding-bottom: ${(props) => props.theme.spacing(3)}px;
padding-left: ${(props) => props.theme.spacing(8)}px;
padding-right: ${(props) => props.theme.spacing(7)}px;
font-weight: ${(props) => props.theme.typography.fontWeightRegular};
border-radius: 10px !important;
svg {
color: ${(props) => props.theme.sidebar.color};
font-size: 20px;
width: 20px;
height: 20px;
}
&:hover {
background-color: ${(props) =>
props.theme.palette.customer.blue} !important;
color: ${(props) => props.theme.palette.primary.contrastText} !important;
border-radius: 10px !important;
svg {
color: ${(props) => props.theme.palette.common.white};
}
span {
color: ${(props) => props.theme.palette.common.white};
}
}
&.${(props) => props.activeClassName} {
background-color: ${(props) =>
darken(0.03, props.theme.palette.customer.blue)};
color: ${(props) => props.theme.palette.primary.contrastText} !important;
svg {
color: ${(props) => props.theme.palette.common.white};
}
span {
color: ${(props) => props.theme.palette.common.white};
}
}
`;
const CategoryText = styled(ListItemText)`
margin: 0;
span {
color: ${(props) => props.theme.sidebar.color};
font-size: ${(props) => props.theme.typography.body1.fontSize}px;
}
`;
const CategoryIconLess = styled(ExpandLess)`
color: ${(props) => rgba(props.theme.sidebar.color, 0.5)};
`;
const CategoryIconMore = styled(ExpandMore)`
color: ${(props) => rgba(props.theme.sidebar.color, 0.5)};
`;
const Link = styled(ListItem)`
padding-left: ${(props) => props.theme.spacing(17.5)}px;
padding-top: ${(props) => props.theme.spacing(2)}px;
padding-bottom: ${(props) => props.theme.spacing(2)}px;
span {
color: ${(props) => rgba(props.theme.sidebar.color, 0.7)};
}
&:hover span {
color: ${(props) => rgba(props.theme.sidebar.color, 0.9)};
}
&:hover {
background-color: ${(props) =>
darken(0.015, props.theme.sidebar.background)};
}
&.${(props) => props.activeClassName} {
background-color: ${(props) =>
darken(0.03, props.theme.sidebar.background)};
span {
color: ${(props) => props.theme.sidebar.color};
}
}
`;
const LinkText = styled(ListItemText)`
color: ${(props) => props.theme.sidebar.color};
span {
font-size: ${(props) => props.theme.typography.body1.fontSize}px;
}
margin-top: 0;
margin-bottom: 0;
`;
const LinkIcon = styled(ListItemIcon)`
color: ${(props) => props.theme.sidebar.color};
`;
const LinkBadge = styled(Chip)`
font-size: 11px;
font-weight: ${(props) => props.theme.typography.fontWeightBold};
height: 20px;
position: absolute;
right: 28px;
top: 8px;
background: ${(props) => props.theme.sidebar.badge.background};
span.MuiChip-label,
span.MuiChip-label:hover {
cursor: pointer;
color: ${(props) => props.theme.sidebar.badge.color};
padding-left: ${(props) => props.theme.spacing(2)}px;
padding-right: ${(props) => props.theme.spacing(2)}px;
}
`;
const CategoryBadge = styled(LinkBadge)`
top: 12px;
`;
const SidebarSection = styled(Typography)`
color: ${(props) => props.theme.sidebar.color};
padding: ${(props) => props.theme.spacing(4)}px
${(props) => props.theme.spacing(4)}px
${(props) => props.theme.spacing(2)}px;
opacity: 0.9;
display: block;
font-size: 20px !important;
`;
const Brand = styled(ListItem)`
font-size: ${(props) => props.theme.typography.h5.fontSize};
font-weight: ${(props) => props.theme.typography.fontWeightMedium};
color: ${(props) => props.theme.sidebar.header.color};
background-color: ${(props) => props.theme.sidebar.header.background};
font-family: ${(props) => props.theme.typography.fontFamily};
min-height: 56px;
padding-left: ${(props) => props.theme.spacing(6)}px;
padding-right: ${(props) => props.theme.spacing(6)}px;
justify-content: center;
cursor: pointer;
${(props) => props.theme.breakpoints.up("sm")} {
min-height: 64px;
}
&:hover {
background-color: ${(props) => props.theme.sidebar.header.background};
}
`;
const StyledDrawer = styled(Drawer)`
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
::-webkit-scrollbar-thumb {
background-color: ${(props) => props.theme.palette.primary.main};
outline: 1px solid ${(props) => props.theme.palette.primary.main};
}
`;
const SidebarCategory = (props) => {
const { name, icon, isOpen, isCollapsable, badge, ...rest } = props;
return (
<Category {...rest}>
<Tooltip title={name} placement="right">
<ListItemIcon>{icon}</ListItemIcon>
</Tooltip>
<CategoryText>{name}</CategoryText>
{isCollapsable ? (
isOpen ? (
<CategoryIconMore />
) : (
<CategoryIconLess />
)
) : null}
{badge ? <CategoryBadge label={badge} /> : ""}
</Category>
);
};
const SidebarLink = ({ name, to, badge, icon }) => {
return (
<Link
button
dense
component={NavLink}
exact
to={to}
activeClassName="active"
>
<Tooltip title={name} placement="right">
<LinkIcon>{icon}</LinkIcon>
</Tooltip>
<LinkText>{name}</LinkText>
{badge ? <LinkBadge label={badge} /> : ""}
</Link>
);
};
const Sidebar = ({
onDrawerChange,
staticContext,
location,
addModal,
addLogOutModal,
...rest
}) => {
const initOpenRoutes = () => {
/* Open collapse element that matches current url */
const pathName = location.pathname;
let _routes = {};
mainRoutes.forEach((route, index) => {
const isActive = pathName.indexOf(route.path) === 0;
const isOpen = route.open;
const isHome = route.containsHome && pathName === "/";
_routes = Object.assign({}, _routes, {
[index]: isActive || isOpen || isHome,
});
});
return _routes;
};
const [openRoutes, setOpenRoutes] = useState(() => initOpenRoutes());
const classes = useStyles();
const [open, setOpen] = React.useState(true);
const toggle = (index) => {
// Collapse all elements
Object.keys(openRoutes).forEach(
(item) =>
openRoutes[index] ||
setOpenRoutes((openRoutes) =>
Object.assign({}, openRoutes, { [item]: false })
)
);
// Toggle selected element
setOpenRoutes((openRoutes) =>
Object.assign({}, openRoutes, { [index]: !openRoutes[index] })
);
};
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
const [isOpen, setIsOpen] = useState(false);
const [isOpenLogoutDialog, setIsOpenLogoutDialog] = useState(false);
const handleDialogCertificationOpen = () => {
setIsOpen(true);
};
const handleDialogCertificationClose = () => {
setIsOpen(false);
};
const handleDialogLogOutOpen = () => {
setIsOpenLogoutDialog(true);
};
const handleDialogLogOutClose = () => {
setIsOpenLogoutDialog(false);
};
return (
<React.Fragment>
<GlobalStyle />
<CertificationModal
isOpen={isOpen}
handleClose={handleDialogCertificationClose}
handleModalAction={() => console.log("Handle Modal Action")}
/>
<LogOutModal
isOpen={isOpenLogoutDialog}
handleClose={handleDialogLogOutClose}
handleModalAction={() => console.log("Déconnexion")}
/>
<StyledDrawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
}),
}}
{...rest}
>
<Brand>
{open === true ? (
<Route to="/">
<img src={desktopLogo} alt="" width={180} />
</Route>
) : (
<Route to="/">
<img src={mobileLogo} alt="" width={35} />
</Route>
)}
</Brand>
<Divider />
<List disablePadding>
<Items>
{mainRoutes.map((category, index) => (
<React.Fragment key={index}>
{category.children && category.icon ? (
<React.Fragment key={index}>
<SidebarCategory
isOpen={!openRoutes[index]}
isCollapsable={true}
name={category.id}
icon={category.icon}
button={true}
onClick={() => toggle(index)}
/>
<Collapse
in={openRoutes[index]}
timeout="auto"
unmountOnExit
>
{category.children.map((route, index) => (
<SidebarLink
key={index}
name={route.name}
to={route.path}
icon={route.icon}
badge={route.badge}
/>
))}
</Collapse>
</React.Fragment>
) : category.icon ? (
<SidebarCategory
isCollapsable={false}
name={category.id}
to={category.path}
activeClassName="active"
component={NavLink}
icon={category.icon}
exact
button
badge={category.badge}
/>
) : null}
</React.Fragment>
))}
</Items>
</List>
<Divider />
{open && <SidebarSection>Raccourcis</SidebarSection>}
<List disablePadding>
<Items>
{shortRoutes.map((category, index) => (
<React.Fragment key={index}>
{category.children && category.icon ? (
<React.Fragment key={index}>
<SidebarCategory
isOpen={!openRoutes[index]}
isCollapsable={true}
name={category.id}
icon={category.icon}
button={true}
onClick={() => toggle(index)}
/>
<Collapse
in={openRoutes[index]}
timeout="auto"
unmountOnExit
>
{category.children.map((route, index) => (
<SidebarLink
key={index}
name={route.name}
to={route.path}
icon={route.icon}
badge={route.badge}
/>
))}
</Collapse>
</React.Fragment>
) : category.icon ? (
<SidebarCategory
isCollapsable={false}
name={category.id}
to={category.path}
activeClassName="active"
component={NavLink}
icon={category.icon}
exact
button
badge={category.badge}
/>
) : null}
</React.Fragment>
))}
<SidebarCategory
icon={<CertificationIcon />}
name="Ajouter attestation"
onClick={handleDialogCertificationOpen}
/>
<SidebarCategory
icon={<LogOutIcon />}
name="Déconnexion"
onClick={handleDialogLogOutOpen}
/>
{open === true ? (
<SidebarCategory
icon={<ChevronLeftIcon />}
name="Réduire le menu"
onClick={handleDrawerClose}
/>
) : (
<SidebarCategory
icon={<ChevronRightIcon />}
name="Agrandir le menu"
onClick={handleDrawerOpen}
/>
)}
</Items>
</List>
</StyledDrawer>
</React.Fragment>
);
};
export default withRouter(Sidebar);
SidebarCategory.propTypes = {
name: PropTypes.string,
icon: PropTypes.node,
isOpen: PropTypes.bool,
isCollapsable: PropTypes.bool,
badge: PropTypes.node,
};
SidebarLink.propTypes = {
name: PropTypes.string,
to: PropTypes.string,
badge: PropTypes.node,
icon: PropTypes.node,
};
Sidebar.propTypes = {
onDrawerChange: PropTypes.func,
classes: PropTypes.node,
staticContext: PropTypes.node,
location: ReactRouterPropTypes.location,
addModal: PropTypes.func,
addLogOutModal: PropTypes.func,
};