2024-07-25 12:30:02 +02:00

139 lines
3.9 KiB
TypeScript

import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
import { useRouter } from "next/router";
import React, { useCallback, useEffect } from "react";
import useHoverable from "@Front/Hooks/useHoverable";
import classNames from "classnames";
import { IAppRule } from "@Front/Api/Entities/rule";
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import useOpenable from "@Front/Hooks/useOpenable";
type IProps = {
item: IItem;
};
type IItemBase = {
text: string;
icon?: JSX.Element;
hasSeparator?: boolean;
color?: ETypoColor;
onClose?: () => void;
};
type IItemWithLink = IItemBase & {
link: string;
rules?: IAppRule[];
routesActive?: string[];
onClick?: never;
dropdown?: never;
target?: "_blank";
};
type IItemWithOnClick = IItemBase & {
onClick: () => void;
link?: never;
rules?: never;
routesActive?: never;
dropdown?: never;
target?: never;
};
type IItemWithDropdown = IItemBase & {
dropdown: {
items: IItem[];
};
routesActive?: never;
link?: never;
rules?: never;
onClick?: never;
target?: never;
};
export type IItem = IItemWithLink | IItemWithOnClick | IItemWithDropdown;
export default function MenuItem(props: IProps) {
const { item } = props;
const router = useRouter();
const { pathname } = router;
const [isActive, setIsActive] = React.useState(item.link === pathname);
const { isOpen, toggle, open } = useOpenable();
const handleClickElement = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
item.onClose?.();
const link = e.currentTarget.getAttribute("data-link");
if (item.target === "_blank") window.open(item.link, "_blank");
if (link) router.push(link);
if (item.onClick) item.onClick();
},
[item, router],
);
const { handleMouseEnter, handleMouseLeave, isHovered } = useHoverable();
const getColor = useCallback(() => {
if (isActive) return ETypoColor.CONTRAST_ACTIVED;
if (isHovered && item.color !== ETypoColor.ERROR_WEAK_CONTRAST) return ETypoColor.CONTRAST_HOVERED;
if (item.color) return item.color;
if (isHovered) return ETypoColor.CONTRAST_DEFAULT;
return ETypoColor.CONTRAST_DEFAULT;
}, [isActive, isHovered, item.color]);
useEffect(() => {
if (item.link === pathname) setIsActive(true);
if (item.routesActive) {
for (const routeActive of item.routesActive) {
if (isActive) break;
if (pathname.includes(routeActive)) setIsActive(true);
}
}
if (item.dropdown) {
for (const subItem of item.dropdown.items) {
if (isActive) break;
if (subItem.link === pathname) {
!isOpen && open();
setIsActive(true);
}
if (subItem.routesActive) {
for (const routeActive of subItem.routesActive) {
if (isActive) break;
if (pathname.includes(routeActive)) {
!isOpen && open();
setIsActive(true);
}
}
}
}
}
}, [isActive, isOpen, item.dropdown, item.link, item.routesActive, open, pathname]);
return (
<div
className={classNames(classes["root"], isActive && classes["active"])}
onClick={handleClickElement}
data-link={item.link}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}>
<div className={classes["menu-item"]} onClick={item.dropdown && toggle}>
{item.icon && React.cloneElement(item.icon, { color: `var(${getColor()})` })}
<Typography typo={ETypo.TEXT_LG_REGULAR} color={getColor()}>
{item.text}
</Typography>
{item.dropdown &&
React.cloneElement(<ChevronDownIcon className={classNames(classes["chevron"], isOpen && [classes["open"]])} />, {
color: `var(${getColor()})`,
})}
</div>
{item.dropdown && (
<div className={classNames(classes["dropdown"], isOpen && [classes["open"]])}>
{item.dropdown.items.map((subItem, index) => (
<MenuItem key={index} item={subItem} />
))}
</div>
)}
{item.hasSeparator && <div className={classes["separator"]} />}
</div>
);
}