submenu on folder

This commit is contained in:
Maxime Lalo 2024-07-23 15:54:57 +02:00
parent 7530435acc
commit d9183701ff
4 changed files with 85 additions and 48 deletions

View File

@ -1,20 +1,26 @@
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { ISubElementWithLink, ISubElementWithOnClick } from "..";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import useHoverable from "@Front/Hooks/useHoverable"; import useHoverable from "@Front/Hooks/useHoverable";
import { ISubElement } from "..";
type IProps = { type IProps = {
element: ISubElementWithLink | ISubElementWithOnClick; element: ISubElement;
closeMenuCb: () => void;
}; };
export default function SubMenuItem(props: IProps) { export default function SubMenuItem(props: IProps) {
const { element } = props; const { element, closeMenuCb } = props;
const router = useRouter(); const router = useRouter();
const handleClickElement = (e: React.MouseEvent<HTMLDivElement>) => { const handleClickElement = useCallback(
const link = e.currentTarget.getAttribute("data-link"); (e: React.MouseEvent<HTMLDivElement>) => {
if (link) router.push(link); closeMenuCb();
}; const link = e.currentTarget.getAttribute("data-link");
if (link) router.push(link);
if (element.onClick) element.onClick();
},
[closeMenuCb, element, router],
);
const { handleMouseEnter, handleMouseLeave, isHovered } = useHoverable(); const { handleMouseEnter, handleMouseLeave, isHovered } = useHoverable();
@ -28,7 +34,7 @@ export default function SubMenuItem(props: IProps) {
return ( return (
<div <div
className={classes["menu-item-wrapper"]} className={classes["menu-item-wrapper"]}
onClick={element.onClick ?? handleClickElement} onClick={handleClickElement}
data-link={element.link} data-link={element.link}
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}> onMouseLeave={handleMouseLeave}>

View File

@ -5,7 +5,6 @@
.sub-menu { .sub-menu {
position: absolute; position: absolute;
top: 48px; top: 48px;
left: 0px;
display: inline-flex; display: inline-flex;
padding: var(--spacing-05, 4px) var(--spacing-2, 16px); padding: var(--spacing-05, 4px) var(--spacing-2, 16px);
flex-direction: column; flex-direction: column;
@ -16,5 +15,15 @@
text-wrap: nowrap; text-wrap: nowrap;
/* shadow/sm */ /* shadow/sm */
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1); box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1);
z-index: 2;
&[data-opening-side="left"] {
left: auto;
right: 0px;
}
&[data-opening-side="right"] {
left: 0px;
right: auto;
}
} }
} }

View File

@ -11,23 +11,28 @@ type ISubElementBase = {
color?: ETypoColor; color?: ETypoColor;
}; };
export type ISubElementWithLink = ISubElementBase & { type ISubElementWithLink = ISubElementBase & {
link: string; link: string;
onClick?: never; onClick?: never;
}; };
export type ISubElementWithOnClick = ISubElementBase & { type ISubElementWithOnClick = ISubElementBase & {
onClick: () => void; onClick: () => void;
link?: never; link?: never;
}; };
export type ISubElement = ISubElementWithLink | ISubElementWithOnClick;
type IProps = { type IProps = {
icon: React.ReactNode; icon: React.ReactNode;
text?: string; text?: string;
subElements: (ISubElementWithLink | ISubElementWithOnClick)[]; subElements: ISubElement[];
openingSide?: "left" | "right";
}; };
export default function ButtonWithSubMenu(props: IProps) { export default function ButtonWithSubMenu(props: IProps) {
const { openingSide = "left" } = props;
const [isSubMenuOpened, setIsSubMenuOpened] = useState(false); const [isSubMenuOpened, setIsSubMenuOpened] = useState(false);
const subMenuRef = useRef<HTMLDivElement>(null); const subMenuRef = useRef<HTMLDivElement>(null);
@ -37,6 +42,10 @@ export default function ButtonWithSubMenu(props: IProps) {
setIsSubMenuOpened((prev) => !prev); setIsSubMenuOpened((prev) => !prev);
}; };
const closeMenu = () => {
setIsSubMenuOpened(false);
};
useEffect(() => { useEffect(() => {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
if ( if (
@ -58,9 +67,9 @@ export default function ButtonWithSubMenu(props: IProps) {
</div> </div>
{isSubMenuOpened && ( {isSubMenuOpened && (
<div className={classes["sub-menu"]} ref={subMenuRef}> <div className={classes["sub-menu"]} ref={subMenuRef} data-opening-side={openingSide}>
{props.subElements.map((element, index) => { {props.subElements.map((element, index) => {
return <SubMenuItem element={element} key={index} />; return <SubMenuItem element={element} key={index} closeMenuCb={closeMenu} />;
})} })}
</div> </div>
)} )}

View File

@ -1,14 +1,14 @@
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress"; import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag"; import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { ArchiveBoxIcon, PencilSquareIcon, UserGroupIcon } from "@heroicons/react/24/outline"; import { ArchiveBoxIcon, EllipsisHorizontalIcon, PencilSquareIcon, UsersIcon } from "@heroicons/react/24/outline";
import { OfficeFolder } from "le-coffre-resources/dist/Notary"; import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import Link from "next/link";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { AnchorStatus } from ".."; import { AnchorStatus } from "..";
import ButtonWithSubMenu, { ISubElement } from "@Front/Components/Elements/ButtonWithSubMenu";
import { useCallback } from "react";
type IProps = { type IProps = {
folder: OfficeFolder | null; folder: OfficeFolder | null;
@ -21,6 +21,49 @@ type IProps = {
export default function InformationSection(props: IProps) { export default function InformationSection(props: IProps) {
const { folder, progress, onArchive, anchorStatus, isArchived } = props; const { folder, progress, onArchive, anchorStatus, isArchived } = props;
const getSubMenuElement = useCallback(() => {
let elements: ISubElement[] = [];
// Creating the three elements and adding them conditionnally
const modifyCollaboratorsElement = {
icon: <UsersIcon />,
text: "Modifier les collaborateurs",
link: Module.getInstance()
.get()
.modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", folder?.uid ?? ""),
hasSeparator: true,
};
const modifyInformationsElement = {
icon: <PencilSquareIcon />,
text: "Modifier les informations du dossier",
link: Module.getInstance()
.get()
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""),
hasSeparator: true,
};
const archiveElement = {
icon: <ArchiveBoxIcon />,
text: "Archiver le dossier",
onClick: onArchive,
color: ETypoColor.ERROR_WEAK_CONTRAST,
};
// If the folder is not anchored, we can modify the collaborators and the informations
if (anchorStatus === AnchorStatus.NOT_ANCHORED) {
elements.push(modifyCollaboratorsElement);
// Remove the separator if it's the last item (if the folder is not archived)
if (isArchived) modifyInformationsElement.hasSeparator = false;
elements.push(modifyInformationsElement);
}
// If the folder is not archived, we can archive it
if (!isArchived) {
elements.push(archiveElement);
}
return elements;
}, [anchorStatus, folder?.uid, isArchived, onArchive]);
return ( return (
<section className={classes["root"]}> <section className={classes["root"]}>
<div className={classes["info-box1"]}> <div className={classes["info-box1"]}>
@ -43,37 +86,7 @@ export default function InformationSection(props: IProps) {
<div className={classes["progress-container"]}> <div className={classes["progress-container"]}>
<CircleProgress percentage={progress} /> <CircleProgress percentage={progress} />
<div className={classes["icon-container"]}> <div className={classes["icon-container"]}>
{anchorStatus === AnchorStatus.NOT_ANCHORED && ( <ButtonWithSubMenu icon={<EllipsisHorizontalIcon />} subElements={getSubMenuElement()} />
<>
<Link
href={Module.getInstance()
.get()
.modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", folder?.uid ?? "")}
title="Modifier les collaborateurs">
<IconButton
icon={<UserGroupIcon title="Modifier les collaborateurs" />}
variant={EIconButtonVariant.NEUTRAL}
/>
</Link>
<Link
href={Module.getInstance()
.get()
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? "")}
title="Modifier les informations du dossiers">
<IconButton
icon={<PencilSquareIcon title="Modifier les informations du dossiers" />}
variant={EIconButtonVariant.NEUTRAL}
/>
</Link>
</>
)}
{!isArchived && (
<IconButton
onClick={onArchive}
icon={<ArchiveBoxIcon title="Archiver le dossier" />}
variant={EIconButtonVariant.ERROR}
/>
)}
</div> </div>
</div> </div>
<div className={classes["description-container"]}> <div className={classes["description-container"]}>