201 lines
6.9 KiB
JavaScript
201 lines
6.9 KiB
JavaScript
import { useState } from 'react';
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
import {
|
|
Drawer,
|
|
Box,
|
|
List,
|
|
ListItemButton,
|
|
ListItemIcon,
|
|
ListItemText,
|
|
Typography,
|
|
Collapse,
|
|
Tooltip,
|
|
Toolbar
|
|
} from '@mui/material';
|
|
import ExpandLess from '@mui/icons-material/ExpandLess';
|
|
import ExpandMore from '@mui/icons-material/ExpandMore';
|
|
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
|
|
|
|
import navItems from '@/menu/navItems';
|
|
import Logo from '@/components/Logo';
|
|
|
|
export const DRAWER_WIDTH = 264;
|
|
export const MINI_WIDTH = 78;
|
|
|
|
const RED = '#C01227';
|
|
|
|
function NavLeaf({ item, open, active, depth = 0, onClick }) {
|
|
const Icon = item.icon;
|
|
const button = (
|
|
<ListItemButton
|
|
selected={active}
|
|
onClick={onClick}
|
|
sx={{
|
|
minHeight: 44,
|
|
my: 0.25,
|
|
mx: open ? 1 : 0.75,
|
|
px: open ? 1.5 : 0,
|
|
justifyContent: open ? 'flex-start' : 'center',
|
|
borderRadius: 2,
|
|
color: 'rgba(255,255,255,0.78)',
|
|
'& .MuiListItemIcon-root': { color: 'inherit' },
|
|
'&:hover': { bgcolor: 'rgba(255,255,255,0.12)', color: '#fff' },
|
|
'&.Mui-selected': {
|
|
bgcolor: 'rgba(255,255,255,0.18)',
|
|
color: '#fff',
|
|
'&:hover': { bgcolor: 'rgba(255,255,255,0.22)' }
|
|
}
|
|
}}
|
|
>
|
|
<ListItemIcon sx={{ minWidth: open ? 34 : 'auto', justifyContent: 'center' }}>
|
|
{depth > 0 && !Icon ? <FiberManualRecordIcon sx={{ fontSize: 8 }} /> : Icon ? <Icon fontSize="small" /> : null}
|
|
</ListItemIcon>
|
|
{open && (
|
|
<ListItemText
|
|
primary={item.title}
|
|
primaryTypographyProps={{ fontSize: '0.875rem', fontWeight: active ? 700 : 500 }}
|
|
/>
|
|
)}
|
|
</ListItemButton>
|
|
);
|
|
|
|
return open ? button : <Tooltip title={item.title} placement="right">{button}</Tooltip>;
|
|
}
|
|
|
|
export default function Sidebar({ open, mobileOpen, onMobileClose, isMobile }) {
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const isActive = (url) => url && location.pathname.startsWith(url);
|
|
const expanded = open || isMobile;
|
|
|
|
const initialOpen = navItems
|
|
.flatMap((g) => g.items)
|
|
.filter((i) => i.children && i.children.some((c) => isActive(c.url)))
|
|
.map((i) => i.id);
|
|
const [collapse, setCollapse] = useState(initialOpen);
|
|
|
|
const go = (url) => {
|
|
navigate(url);
|
|
if (isMobile) onMobileClose();
|
|
};
|
|
|
|
const content = (
|
|
<Box sx={{ bgcolor: RED, height: '100%', color: '#fff', display: 'flex', flexDirection: 'column' }}>
|
|
<Toolbar sx={{ px: expanded ? 2.5 : 0, justifyContent: expanded ? 'flex-start' : 'center', minHeight: 64 }}>
|
|
<Logo onDark compact={!expanded} />
|
|
</Toolbar>
|
|
<Box sx={{ overflowY: 'auto', overflowX: 'hidden', flexGrow: 1, pb: 2 }}>
|
|
{navItems.map((grp) => (
|
|
<Box key={grp.group} sx={{ mt: 1 }}>
|
|
{expanded && (
|
|
<Typography
|
|
variant="overline"
|
|
sx={{ px: 2.5, color: 'rgba(255,255,255,0.55)', fontSize: '0.6875rem', letterSpacing: '0.08em' }}
|
|
>
|
|
{grp.group}
|
|
</Typography>
|
|
)}
|
|
<List disablePadding sx={{ mt: 0.5 }}>
|
|
{grp.items.map((item) => {
|
|
if (item.children) {
|
|
const opened = collapse.includes(item.id);
|
|
const childActive = item.children.some((c) => isActive(c.url));
|
|
const Icon = item.icon;
|
|
const head = (
|
|
<ListItemButton
|
|
onClick={() =>
|
|
expanded
|
|
? setCollapse((p) => (p.includes(item.id) ? p.filter((x) => x !== item.id) : [...p, item.id]))
|
|
: go(item.children[0].url)
|
|
}
|
|
sx={{
|
|
minHeight: 44,
|
|
my: 0.25,
|
|
mx: expanded ? 1 : 0.75,
|
|
px: expanded ? 1.5 : 0,
|
|
justifyContent: expanded ? 'flex-start' : 'center',
|
|
borderRadius: 2,
|
|
color: childActive ? '#fff' : 'rgba(255,255,255,0.78)',
|
|
bgcolor: childActive && !opened ? 'rgba(255,255,255,0.12)' : 'transparent',
|
|
'&:hover': { bgcolor: 'rgba(255,255,255,0.12)', color: '#fff' }
|
|
}}
|
|
>
|
|
<ListItemIcon sx={{ minWidth: expanded ? 34 : 'auto', justifyContent: 'center', color: 'inherit' }}>
|
|
<Icon fontSize="small" />
|
|
</ListItemIcon>
|
|
{expanded && (
|
|
<>
|
|
<ListItemText primary={item.title} primaryTypographyProps={{ fontSize: '0.875rem', fontWeight: 500 }} />
|
|
{opened ? <ExpandLess fontSize="small" /> : <ExpandMore fontSize="small" />}
|
|
</>
|
|
)}
|
|
</ListItemButton>
|
|
);
|
|
return (
|
|
<Box key={item.id}>
|
|
{expanded ? head : <Tooltip title={item.title} placement="right">{head}</Tooltip>}
|
|
{expanded && (
|
|
<Collapse in={opened} timeout="auto" unmountOnExit>
|
|
<Box sx={{ pl: 1.5 }}>
|
|
{item.children.map((c) => (
|
|
<NavLeaf key={c.id} item={c} open depth={1} active={isActive(c.url)} onClick={() => go(c.url)} />
|
|
))}
|
|
</Box>
|
|
</Collapse>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|
|
return (
|
|
<NavLeaf key={item.id} item={item} open={expanded} active={isActive(item.url)} onClick={() => go(item.url)} />
|
|
);
|
|
})}
|
|
</List>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
{expanded && (
|
|
<Box sx={{ p: 2, borderTop: '1px solid rgba(255,255,255,0.12)' }}>
|
|
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.55)' }}>
|
|
Doormile CRM v1.0
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
);
|
|
|
|
if (isMobile) {
|
|
return (
|
|
<Drawer
|
|
variant="temporary"
|
|
open={mobileOpen}
|
|
onClose={onMobileClose}
|
|
ModalProps={{ keepMounted: true }}
|
|
sx={{ '& .MuiDrawer-paper': { width: DRAWER_WIDTH, border: 'none' } }}
|
|
>
|
|
{content}
|
|
</Drawer>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Drawer
|
|
variant="permanent"
|
|
sx={{
|
|
width: open ? DRAWER_WIDTH : MINI_WIDTH,
|
|
flexShrink: 0,
|
|
whiteSpace: 'nowrap',
|
|
'& .MuiDrawer-paper': {
|
|
width: open ? DRAWER_WIDTH : MINI_WIDTH,
|
|
border: 'none',
|
|
overflowX: 'hidden',
|
|
transition: (t) => t.transitions.create('width', { duration: t.transitions.duration.standard })
|
|
}
|
|
}}
|
|
open={open}
|
|
>
|
|
{content}
|
|
</Drawer>
|
|
);
|
|
}
|