|
| 1 | +import { Fragment, useState } from 'react'; |
| 2 | +import { useNavigate, useLocation } from 'react-router-dom'; |
| 3 | +import ListSubheader from '@mui/material/ListSubheader'; |
| 4 | +import List from '@mui/material/List'; |
| 5 | +import ListItemButton from '@mui/material/ListItemButton'; |
| 6 | +import ListItemText from '@mui/material/ListItemText'; |
| 7 | +import { Collapse } from '@mui/material'; |
| 8 | +import ExpandLess from '@mui/icons-material/ExpandLess'; |
| 9 | +import ExpandMore from '@mui/icons-material/ExpandMore'; |
| 10 | + |
| 11 | +interface NavInfo { |
| 12 | + name: string; |
| 13 | + path: string; |
| 14 | +} |
| 15 | + |
| 16 | +type NavItem = NavInfo & { |
| 17 | + subs: NavInfo[]; |
| 18 | +}; |
| 19 | + |
| 20 | +export interface SidebarProps { |
| 21 | + navs: NavItem[]; |
| 22 | +} |
| 23 | + |
| 24 | +const Sidebar = ({ navs }: SidebarProps) => { |
| 25 | + const navigate = useNavigate(); |
| 26 | + const location = useLocation(); |
| 27 | + |
| 28 | + const isCurrentNavSubOpen = (path: string, subs: NavInfo[]) => |
| 29 | + subs.some((s) => `${path}${s.path}` === location.pathname); |
| 30 | + |
| 31 | + const getOpenedNav = (): string => { |
| 32 | + const nav = navs.find((n) => isCurrentNavSubOpen(n.path, n.subs)); |
| 33 | + |
| 34 | + return nav ? nav.path : navs.length ? navs[0].path : ''; |
| 35 | + }; |
| 36 | + |
| 37 | + const [open, setOpen] = useState(getOpenedNav()); |
| 38 | + const isCurrentNav = (path: string) => open === path; |
| 39 | + |
| 40 | + const handleOpenNavGroup = (path: string) => () => { |
| 41 | + setOpen((prev) => (prev !== path ? path : '')); |
| 42 | + }; |
| 43 | + |
| 44 | + const handleGoToNav = (path: string) => () => navigate(path); |
| 45 | + |
| 46 | + return ( |
| 47 | + <List |
| 48 | + sx={{ width: '100%', height: '100%', bgcolor: '#f9f7f4' }} |
| 49 | + component="nav" |
| 50 | + aria-labelledby="nested-list-subheader" |
| 51 | + subheader={ |
| 52 | + <ListSubheader sx={{ position: 'inherit', bgcolor: '#f9f7f4' }} component="div" id="nested-list-subheader"> |
| 53 | + Examples |
| 54 | + </ListSubheader> |
| 55 | + } |
| 56 | + > |
| 57 | + {navs.map((n) => ( |
| 58 | + <Fragment key={n.path}> |
| 59 | + <ListItemButton |
| 60 | + onClick={handleOpenNavGroup(n.path)} |
| 61 | + selected={!isCurrentNav(n.path) && isCurrentNavSubOpen(n.path, n.subs)} |
| 62 | + > |
| 63 | + <ListItemText primary={n.name} primaryTypographyProps={{ variant: 'body2' }} /> |
| 64 | + {isCurrentNav(n.path) ? <ExpandLess fontSize="small" /> : <ExpandMore fontSize="small" />} |
| 65 | + </ListItemButton> |
| 66 | + <Collapse in={isCurrentNav(n.path)} timeout="auto" unmountOnExit> |
| 67 | + <List component="div" disablePadding> |
| 68 | + {n.subs.map((s) => ( |
| 69 | + <ListItemButton |
| 70 | + key={s.path} |
| 71 | + sx={{ pl: 4 }} |
| 72 | + onClick={handleGoToNav(`${n.path}${s.path}`)} |
| 73 | + selected={isCurrentNavSubOpen(n.path, [s])} |
| 74 | + > |
| 75 | + <ListItemText primary={s.name} primaryTypographyProps={{ variant: 'body2' }} /> |
| 76 | + </ListItemButton> |
| 77 | + ))} |
| 78 | + </List> |
| 79 | + </Collapse> |
| 80 | + </Fragment> |
| 81 | + ))} |
| 82 | + </List> |
| 83 | + ); |
| 84 | +}; |
| 85 | + |
| 86 | +export default Sidebar; |
0 commit comments