From d9183701ffee9939050878c692435aeff569c3da Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Tue, 23 Jul 2024 15:54:57 +0200 Subject: [PATCH] :sparkles: submenu on folder --- .../ButtonWithSubMenu/SubMenuItem/index.tsx | 22 +++-- .../ButtonWithSubMenu/classes.module.scss | 11 ++- .../Elements/ButtonWithSubMenu/index.tsx | 19 +++-- .../InformationSection/index.tsx | 81 +++++++++++-------- 4 files changed, 85 insertions(+), 48 deletions(-) diff --git a/src/front/Components/Elements/ButtonWithSubMenu/SubMenuItem/index.tsx b/src/front/Components/Elements/ButtonWithSubMenu/SubMenuItem/index.tsx index 83ccdc75..81071a54 100644 --- a/src/front/Components/Elements/ButtonWithSubMenu/SubMenuItem/index.tsx +++ b/src/front/Components/Elements/ButtonWithSubMenu/SubMenuItem/index.tsx @@ -1,20 +1,26 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import classes from "./classes.module.scss"; import { useRouter } from "next/router"; -import { ISubElementWithLink, ISubElementWithOnClick } from ".."; import React, { useCallback } from "react"; import useHoverable from "@Front/Hooks/useHoverable"; +import { ISubElement } from ".."; type IProps = { - element: ISubElementWithLink | ISubElementWithOnClick; + element: ISubElement; + closeMenuCb: () => void; }; export default function SubMenuItem(props: IProps) { - const { element } = props; + const { element, closeMenuCb } = props; const router = useRouter(); - const handleClickElement = (e: React.MouseEvent) => { - const link = e.currentTarget.getAttribute("data-link"); - if (link) router.push(link); - }; + const handleClickElement = useCallback( + (e: React.MouseEvent) => { + 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(); @@ -28,7 +34,7 @@ export default function SubMenuItem(props: IProps) { return (
diff --git a/src/front/Components/Elements/ButtonWithSubMenu/classes.module.scss b/src/front/Components/Elements/ButtonWithSubMenu/classes.module.scss index bbd8ced7..28ddf217 100644 --- a/src/front/Components/Elements/ButtonWithSubMenu/classes.module.scss +++ b/src/front/Components/Elements/ButtonWithSubMenu/classes.module.scss @@ -5,7 +5,6 @@ .sub-menu { position: absolute; top: 48px; - left: 0px; display: inline-flex; padding: var(--spacing-05, 4px) var(--spacing-2, 16px); flex-direction: column; @@ -16,5 +15,15 @@ text-wrap: nowrap; /* shadow/sm */ 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; + } } } diff --git a/src/front/Components/Elements/ButtonWithSubMenu/index.tsx b/src/front/Components/Elements/ButtonWithSubMenu/index.tsx index 7d9bf5e3..1ef1a471 100644 --- a/src/front/Components/Elements/ButtonWithSubMenu/index.tsx +++ b/src/front/Components/Elements/ButtonWithSubMenu/index.tsx @@ -11,23 +11,28 @@ type ISubElementBase = { color?: ETypoColor; }; -export type ISubElementWithLink = ISubElementBase & { +type ISubElementWithLink = ISubElementBase & { link: string; onClick?: never; }; -export type ISubElementWithOnClick = ISubElementBase & { +type ISubElementWithOnClick = ISubElementBase & { onClick: () => void; link?: never; }; +export type ISubElement = ISubElementWithLink | ISubElementWithOnClick; + type IProps = { icon: React.ReactNode; text?: string; - subElements: (ISubElementWithLink | ISubElementWithOnClick)[]; + subElements: ISubElement[]; + openingSide?: "left" | "right"; }; export default function ButtonWithSubMenu(props: IProps) { + const { openingSide = "left" } = props; + const [isSubMenuOpened, setIsSubMenuOpened] = useState(false); const subMenuRef = useRef(null); @@ -37,6 +42,10 @@ export default function ButtonWithSubMenu(props: IProps) { setIsSubMenuOpened((prev) => !prev); }; + const closeMenu = () => { + setIsSubMenuOpened(false); + }; + useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if ( @@ -58,9 +67,9 @@ export default function ButtonWithSubMenu(props: IProps) {
{isSubMenuOpened && ( -
+
{props.subElements.map((element, index) => { - return ; + return ; })}
)} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/index.tsx index 39a6e2c8..4e60f216 100644 --- a/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/index.tsx +++ b/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/index.tsx @@ -1,14 +1,14 @@ import CircleProgress from "@Front/Components/DesignSystem/CircleProgress"; -import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton"; import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; 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 Link from "next/link"; import classes from "./classes.module.scss"; import { AnchorStatus } from ".."; +import ButtonWithSubMenu, { ISubElement } from "@Front/Components/Elements/ButtonWithSubMenu"; +import { useCallback } from "react"; type IProps = { folder: OfficeFolder | null; @@ -21,6 +21,49 @@ type IProps = { export default function InformationSection(props: IProps) { const { folder, progress, onArchive, anchorStatus, isArchived } = props; + const getSubMenuElement = useCallback(() => { + let elements: ISubElement[] = []; + + // Creating the three elements and adding them conditionnally + const modifyCollaboratorsElement = { + icon: , + text: "Modifier les collaborateurs", + link: Module.getInstance() + .get() + .modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", folder?.uid ?? ""), + hasSeparator: true, + }; + const modifyInformationsElement = { + icon: , + 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: , + 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 (
@@ -43,37 +86,7 @@ export default function InformationSection(props: IProps) {
- {anchorStatus === AnchorStatus.NOT_ANCHORED && ( - <> - - } - variant={EIconButtonVariant.NEUTRAL} - /> - - - } - variant={EIconButtonVariant.NEUTRAL} - /> - - - )} - {!isArchived && ( - } - variant={EIconButtonVariant.ERROR} - /> - )} + } subElements={getSubMenuElement()} />