MatxVerticalNavExpansionPanel.jsx
1 import { useCallback, useEffect, useRef, useState } from "react"; 2 import { ButtonBase, Icon, Box, styled } from "@mui/material"; 3 import { ChevronRight } from "@mui/icons-material"; 4 import { useLocation } from "react-router-dom"; 5 import clsx from "clsx"; 6 7 const NavExpandRoot = styled("div")(({ theme }) => ({ 8 "& .expandIcon": { 9 transition: "transform 0.3s cubic-bezier(0, 0, 0.2, 1) 0ms", 10 transform: "rotate(90deg)" 11 }, 12 "& .collapseIcon": { 13 transition: "transform 0.3s cubic-bezier(0, 0, 0.2, 1) 0ms", 14 transform: "rotate(0deg)" 15 }, 16 "& .expansion-panel": { 17 overflow: "hidden", 18 transition: "max-height 0.3s cubic-bezier(0, 0, 0.2, 1)" 19 }, 20 "& .highlight": { 21 background: theme.palette.primary.main 22 }, 23 "&.compactNavItem": { 24 width: 44, 25 overflow: "hidden", 26 justifyContent: "center !important", 27 "& .itemText": { display: "none" }, 28 "& .itemIcon": { display: "none" } 29 } 30 })); 31 32 const BaseButton = styled(ButtonBase)(({ theme }) => ({ 33 height: 44, 34 width: "100%", 35 whiteSpace: "pre", 36 overflow: "hidden", 37 paddingRight: "16px", 38 borderRadius: "4px", 39 marginBottom: "8px", 40 display: "flex", 41 justifyContent: "space-between !important", 42 color: theme.palette.text.primary, 43 "&:hover": { background: "rgba(255, 255, 255, 0.08)" }, 44 "& .icon": { 45 width: 36, 46 fontSize: "18px", 47 paddingLeft: "16px", 48 paddingRight: "16px", 49 verticalAlign: "middle" 50 } 51 })); 52 53 const BulletIcon = styled("div")(({ theme }) => ({ 54 width: 4, 55 height: 4, 56 color: "inherit", 57 overflow: "hidden", 58 marginLeft: "20px", 59 marginRight: "8px", 60 borderRadius: "300px !important", 61 background: theme.palette.text.primary 62 })); 63 64 const ItemText = styled("span")(() => ({ 65 fontSize: "0.875rem", 66 paddingLeft: "0.8rem", 67 verticalAlign: "middle" 68 })); 69 70 const BadgeValue = styled("div")(() => ({ 71 padding: "1px 4px", 72 overflow: "hidden", 73 borderRadius: "300px" 74 })); 75 76 export default function MatxVerticalNavExpansionPanel({ item, children, mode }) { 77 const [collapsed, setCollapsed] = useState(true); 78 const elementRef = useRef(null); 79 const componentHeight = useRef(0); 80 const { pathname } = useLocation(); 81 const { name, icon, iconText, badge } = item; 82 83 const handleClick = () => { 84 componentHeight.current = 0; 85 calculateHeight(elementRef.current); 86 setCollapsed(!collapsed); 87 }; 88 89 const calculateHeight = useCallback((node) => { 90 if (node.name !== "child") { 91 for (let child of node.children) { 92 calculateHeight(child); 93 } 94 } 95 96 if (node.name === "child") componentHeight.current += node.scrollHeight; 97 else componentHeight.current += 44; 98 return; 99 }, []); 100 101 useEffect(() => { 102 if (!elementRef) return; 103 104 calculateHeight(elementRef.current); 105 106 for (let child of elementRef.current.children) { 107 if (child.getAttribute("href") === pathname) { 108 setCollapsed(false); 109 } 110 } 111 }, [pathname, calculateHeight]); 112 113 return ( 114 <NavExpandRoot> 115 <BaseButton 116 className={clsx({ 117 "has-submenu compactNavItem": true, 118 compactNavItem: mode === "compact", 119 open: !collapsed 120 })} 121 onClick={handleClick}> 122 <Box display="flex" alignItems="center"> 123 {icon && <Icon className="icon">{icon}</Icon>} 124 {iconText && <BulletIcon />} 125 <ItemText className="sidenavHoverShow">{name}</ItemText> 126 </Box> 127 128 {badge && <BadgeValue className="sidenavHoverShow itemIcon">{badge.value}</BadgeValue>} 129 130 <div 131 className={clsx({ 132 sidenavHoverShow: true, 133 collapseIcon: collapsed, 134 expandIcon: !collapsed 135 })}> 136 <ChevronRight fontSize="small" sx={{ verticalAlign: "middle" }} /> 137 </div> 138 </BaseButton> 139 140 <div 141 ref={elementRef} 142 className="expansion-panel submenu" 143 style={collapsed ? { maxHeight: "0px" } : { maxHeight: componentHeight.current + "px" }}> 144 {children} 145 </div> 146 </NavExpandRoot> 147 ); 148 }