/ frontend / src / app / components / MatxVerticalNav / MatxVerticalNavExpansionPanel.jsx
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  }