From f39ab33f82cebcb1d51d5110a5057ac4a32ffce6 Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Thu, 25 Jul 2024 13:21:48 +0200 Subject: [PATCH] :sparkles: Responsive notary dashboard --- .../FolderListContainer/classes.module.scss | 16 -- .../BlockList/classes.module.scss | 35 ----- .../DropdownNavigation/classes.module.scss | 60 ++++++++ .../DropdownNavigation/index.tsx | 82 ++++++++++ .../SearchBlockList/classes.module.scss | 48 ++++++ .../DesignSystem/SearchBlockList/index.tsx | 26 +++- .../classes.module.scss | 123 +-------------- .../DefaultNotaryDashboard/index.tsx | 140 ++++-------------- .../Components/Layouts/DesignSystem/index.tsx | 74 +++++++++ 9 files changed, 323 insertions(+), 281 deletions(-) delete mode 100644 src/front/Components/DesignSystem/SearchBlockList/BlockList/classes.module.scss create mode 100644 src/front/Components/DesignSystem/SearchBlockList/DropdownNavigation/classes.module.scss create mode 100644 src/front/Components/DesignSystem/SearchBlockList/DropdownNavigation/index.tsx diff --git a/src/front/Components/DesignSystem/FolderListContainer/classes.module.scss b/src/front/Components/DesignSystem/FolderListContainer/classes.module.scss index deb6e813..560b12a5 100644 --- a/src/front/Components/DesignSystem/FolderListContainer/classes.module.scss +++ b/src/front/Components/DesignSystem/FolderListContainer/classes.module.scss @@ -1,23 +1,7 @@ @import "@Themes/constants.scss"; .root { - width: calc(100vh - 83px); display: flex; flex-direction: column; justify-content: space-between; - - .header { - flex: 1; - } - - .searchbar { - padding: 40px 24px 24px 24px; - } - - .folderlist-container { - max-height: calc(100vh - 290px); - height: calc(100vh - 290px); - overflow: auto; - border-right: 1px solid var(--color-neutral-200); - } } diff --git a/src/front/Components/DesignSystem/SearchBlockList/BlockList/classes.module.scss b/src/front/Components/DesignSystem/SearchBlockList/BlockList/classes.module.scss deleted file mode 100644 index fcf2657b..00000000 --- a/src/front/Components/DesignSystem/SearchBlockList/BlockList/classes.module.scss +++ /dev/null @@ -1,35 +0,0 @@ -@import "@Themes/constants.scss"; - -.root { - display: inline-flex; - justify-content: space-between; - align-items: center; - width: 100%; - padding: 24px; - border: 1px solid var(--color-neutral-200); - cursor: pointer; - - &:hover { - background-color: var(--color-neutral-200); - } - - &[data-selected="true"] { - background-color: var(--color-neutral-200); - } - - .left-side { - display: inline-flex; - justify-content: space-between; - align-items: center; - overflow-wrap: anywhere; - .warning { - margin-left: 32px; - } - } - - .right-side { - display: flex; - align-items: center; - gap: 16px; - } -} diff --git a/src/front/Components/DesignSystem/SearchBlockList/DropdownNavigation/classes.module.scss b/src/front/Components/DesignSystem/SearchBlockList/DropdownNavigation/classes.module.scss new file mode 100644 index 00000000..dc058383 --- /dev/null +++ b/src/front/Components/DesignSystem/SearchBlockList/DropdownNavigation/classes.module.scss @@ -0,0 +1,60 @@ +@import "@Themes/constants.scss"; + +.root { + .dropdown-header { + cursor: pointer; + display: flex; + padding: var(--spacing-2, 16px) var(--spacing-sm, 8px); + align-items: center; + gap: var(--spacing-2, 16px); + + border-radius: var(--input-radius, 0px); + border: 1px solid var(--dropdown-input-border-hovered, #b4bec5); + background: var(--dropdown-input-background, #fff); + + .text-container { + display: flex; + flex-direction: column; + height: var(--spacing-6, 48px); + padding: 0px var(--spacing-2, 16px); + flex: 1; + } + + > svg { + transition: transform 200ms ease-in-out; + } + } + + .dropdown-content { + margin-top: 8px; + display: flex; + padding: var(--spacing-sm, 8px); + flex-direction: column; + align-items: flex-start; + gap: 8px; + align-self: stretch; + + max-height: 200px; + overflow-y: auto; + border-radius: var(--dropdown-radius, 0px); + border: 1px solid var(--dropdown-menu-border-primary, #005bcb); + background: var(--dropdown-menu-background, #fff); + + .dropdown-item { + cursor: pointer; + padding: var(--spacing-1, 8px) var(--spacing-2, 16px); + } + } + + &[data-is-opened="true"] { + .dropdown-header { + border-radius: var(--input-radius, 0px); + border: 1px solid var(--dropdown-input-border-expanded, #005bcb); + background: var(--dropdown-input-background, #fff); + + > svg { + transform: rotate(180deg); + } + } + } +} diff --git a/src/front/Components/DesignSystem/SearchBlockList/DropdownNavigation/index.tsx b/src/front/Components/DesignSystem/SearchBlockList/DropdownNavigation/index.tsx new file mode 100644 index 00000000..50c7ef77 --- /dev/null +++ b/src/front/Components/DesignSystem/SearchBlockList/DropdownNavigation/index.tsx @@ -0,0 +1,82 @@ +import React, { useCallback, useEffect } from "react"; +import { IBlock } from "../BlockList/Block"; +import classes from "./classes.module.scss"; +import Typography, { ETypo, ETypoColor } from "../../Typography"; +import { ChevronDownIcon } from "@heroicons/react/24/outline"; +type IProps = { + blocks: IBlock[]; + onSelectedBlock: (block: IBlock) => void; + defaultSelectedBlock?: IBlock; +}; + +export default function DropdownNavigation({ blocks, onSelectedBlock, defaultSelectedBlock = blocks[0] }: IProps) { + const [selectedBlock, setSelectedBlock] = React.useState(defaultSelectedBlock ?? null); + const [isDropdownOpened, setIsDropdownOpened] = React.useState(false); + const rootRef = React.useRef(null); + + const selectBlock = useCallback( + (id: string) => { + setIsDropdownOpened(false); + setSelectedBlock(blocks.find((folder) => folder.id === id)!); + onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === id)!); + }, + [blocks, onSelectedBlock], + ); + + const handleOnClick = () => setIsDropdownOpened((prev) => !prev); + + useEffect(() => { + // on click outside of root, close dropdown + const handleClickOutside = (event: MouseEvent) => { + if (rootRef.current && !rootRef.current.contains(event.target as Node)) { + setIsDropdownOpened(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + useEffect(() => { + if (defaultSelectedBlock) setSelectedBlock(defaultSelectedBlock); + }, [defaultSelectedBlock]); + return ( +
+ {selectedBlock && ( + <> +
+
+ {selectedBlock.secondaryText && ( + + {selectedBlock.secondaryText} + + )} + + {selectedBlock.primaryText} + +
+ +
+ + {isDropdownOpened && ( +
+ {blocks + .filter((block) => block.id !== selectedBlock.id) + .map((block) => ( +
selectBlock(block.id)}> + {block.secondaryText && ( + + {block.secondaryText} + + )} + + {block.primaryText} + +
+ ))} +
+ )} + + )} +
+ ); +} diff --git a/src/front/Components/DesignSystem/SearchBlockList/classes.module.scss b/src/front/Components/DesignSystem/SearchBlockList/classes.module.scss index e7bcaa27..d7a8ef40 100644 --- a/src/front/Components/DesignSystem/SearchBlockList/classes.module.scss +++ b/src/front/Components/DesignSystem/SearchBlockList/classes.module.scss @@ -1,3 +1,4 @@ +@import "@Themes/constants.scss"; .root { width: 336px; height: 100%; @@ -9,6 +10,11 @@ flex-direction: column; justify-content: flex-start; + @media (max-width: $screen-m) { + gap: 16px; + padding: var(--spacing-lg, 24px); + width: auto; + } .block-list { overflow-y: auto; ::-webkit-scrollbar { @@ -16,13 +22,55 @@ } -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ + + @media (max-width: $screen-m) { + display: none; + } + } + + .responsive-dropdown { + display: none; + + @media (max-width: $screen-m) { + display: block; + } } .searchbar { padding: var(--spacing-md, 16px); + @media (max-width: $screen-m) { + padding: 0px; + display: flex; + gap: var(--spacing-md, 16px); + } + + > label { + flex: 1; + } + + .responsive-button-container { + display: none; + + @media (max-width: $screen-m) { + display: block; + flex: 1; + } + + @media (max-width: $screen-s) { + display: none; + } + } } .bottom-container { margin-top: auto; + + @media (max-width: $screen-m) { + display: none; + } + + @media (max-width: $screen-s) { + display: block; + } } } diff --git a/src/front/Components/DesignSystem/SearchBlockList/index.tsx b/src/front/Components/DesignSystem/SearchBlockList/index.tsx index 3dd43a52..0ed80f61 100644 --- a/src/front/Components/DesignSystem/SearchBlockList/index.tsx +++ b/src/front/Components/DesignSystem/SearchBlockList/index.tsx @@ -5,6 +5,7 @@ import classes from "./classes.module.scss"; import SearchBar from "../SearchBar"; import Button from "../Button"; import { useRouter } from "next/router"; +import DropdownNavigation from "./DropdownNavigation"; type IProps = { blocks: IBlock[]; @@ -17,6 +18,7 @@ type IProps = { export default function SearchBlockList(props: IProps) { const { blocks, onSelectedBlock, bottomButton } = props; + const [selectedBlock, setSelectedBlock] = useState(null); const router = useRouter(); const [blocksShowing, setBlocksShowing] = useState(blocks); @@ -54,6 +56,14 @@ export default function SearchBlockList(props: IProps) { router.push(bottomButton.link); }, [bottomButton, router]); + const handleSelectedBlock = useCallback( + (block: IBlock) => { + setSelectedBlock(block); + onSelectedBlock(block); + }, + [onSelectedBlock], + ); + useEffect(() => { setBlocksShowing(blocks); }, [blocks]); @@ -62,9 +72,23 @@ export default function SearchBlockList(props: IProps) {
+ {bottomButton && ( +
+ +
+ )}
- + +
+
+
{bottomButton && (
diff --git a/src/front/Components/LayoutTemplates/DefaultNotaryDashboard/classes.module.scss b/src/front/Components/LayoutTemplates/DefaultNotaryDashboard/classes.module.scss index 4a8599a1..882a9394 100644 --- a/src/front/Components/LayoutTemplates/DefaultNotaryDashboard/classes.module.scss +++ b/src/front/Components/LayoutTemplates/DefaultNotaryDashboard/classes.module.scss @@ -1,132 +1,23 @@ @import "@Themes/constants.scss"; -@keyframes growWidth { - 0% { - width: 100%; - } - - 100% { - width: 200%; - } -} - .root { position: relative; .content { display: flex; - overflow: hidden; + justify-content: flex-start; height: calc(100vh - var(--header-height)); - .overlay { - position: absolute; - width: 100%; - height: 100%; - background-color: var(--color-generic-white); - opacity: 0.5; - z-index: 2; - transition: all 0.3s $custom-easing; + @media (max-width: $screen-m) { + flex-direction: column; } - - .left-side { - background-color: var(--color-generic-white); - z-index: 3; - display: flex; - width: 336px; - min-width: 336px; - transition: all 0.3s $custom-easing; - overflow: hidden; - - @media (max-width: ($screen-m - 1px)) { - width: 56px; - min-width: 56px; - transform: translateX(-389px); - - &.opened { - transform: translateX(0px); - width: 336px; - min-width: 336px; - } - } - - @media (max-width: $screen-s) { - width: 0px; - min-width: 0px; - - &.opened { - width: 100vw; - min-width: 100vw; - } - } - } - - .closable-left-side { - position: absolute; - background-color: var(--color-generic-white); - z-index: 0; - display: flex; - justify-content: center; - min-width: 56px; - max-width: 56px; - height: calc(100vh - var(--header-height)); - border-right: 1px var(--color-neutral-200) solid; - - @media (min-width: $screen-m) { - display: none; - } - - .chevron-icon { - margin-top: 21px; - transform: rotate(180deg); - cursor: pointer; - } - - @media (max-width: $screen-s) { - display: none; - } - } - .right-side { - min-width: calc(100vw - 389px); - padding: 24px; + min-width: calc(100% - 336px); overflow-y: auto; + padding: var(--spacing-lg, 24px); - @media (max-width: ($screen-m - 1px)) { - min-width: calc(100vw - 56px); + @media (max-width: $screen-m) { + width: 100%; } - - @media (max-width: $screen-s) { - flex: 1; - min-width: unset; - } - - .back-arrow-mobile { - display: none; - @media (max-width: $screen-s) { - display: block; - margin-bottom: 24px; - } - } - - .back-arrow-desktop { - @media (max-width: $screen-s) { - display: none; - } - } - } - } - - .background-image-container { - position: fixed; - top: 0; - right: 0; - @media (max-width: $screen-l) { - display: none; - } - - .background-image { - width: 100%; - height: 100%; - object-fit: cover; } } } diff --git a/src/front/Components/LayoutTemplates/DefaultNotaryDashboard/index.tsx b/src/front/Components/LayoutTemplates/DefaultNotaryDashboard/index.tsx index da2d01dc..5f40199a 100644 --- a/src/front/Components/LayoutTemplates/DefaultNotaryDashboard/index.tsx +++ b/src/front/Components/LayoutTemplates/DefaultNotaryDashboard/index.tsx @@ -1,18 +1,11 @@ -import ChevronIcon from "@Assets/Icons/chevron.svg"; import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; -import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; -import FolderArchivedListContainer from "@Front/Components/DesignSystem/FolderArchivedListContainer"; import FolderListContainer from "@Front/Components/DesignSystem/FolderListContainer"; import Header from "@Front/Components/DesignSystem/Header"; import Version from "@Front/Components/DesignSystem/Version"; import BackArrow from "@Front/Components/Elements/BackArrow"; -import WindowStore from "@Front/Stores/WindowStore"; -import { ChevronLeftIcon } from "@heroicons/react/24/outline"; -import classNames from "classnames"; import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus"; import { OfficeFolder } from "le-coffre-resources/dist/Notary"; -import Image, { StaticImageData } from "next/image"; -import React, { ReactNode } from "react"; +import React, { ReactNode, useEffect } from "react"; import classes from "./classes.module.scss"; @@ -24,94 +17,16 @@ type IProps = { hasBackArrow: boolean; backArrowUrl?: string; mobileBackText?: string; - image?: StaticImageData; -}; -type IState = { - folders: OfficeFolder[] | null; - isLeftSideOpen: boolean; - leftSideCanBeClosed: boolean; }; -export default class DefaultNotaryDashboard extends React.Component { - private onWindowResize = () => {}; - public static defaultProps: Partial = { - hasBackArrow: false, - isArchived: false, - }; +export default function DefaultNotaryDashboard(props: IProps) { + const { hasBackArrow, onSelectedFolder, backArrowUrl, children, isArchived } = props; - public constructor(props: IProps) { - super(props); - this.state = { - folders: null, - isLeftSideOpen: false, - leftSideCanBeClosed: typeof window !== "undefined" ? window.innerWidth < 1024 : false, - }; - this.onOpenLeftSide = this.onOpenLeftSide.bind(this); - this.onCloseLeftSide = this.onCloseLeftSide.bind(this); - } + const [folders, setFolders] = React.useState([]); - public override render(): JSX.Element { - return ( -
-
-
- {this.state.isLeftSideOpen &&
} -
- {this.props.isArchived && this.state.folders && ( - - )} - {!this.props.isArchived && this.state.folders && ( - - )} -
-
- open side menu -
- -
- {this.props.hasBackArrow && ( -
- -
- )} - {this.props.mobileBackText && ( -
- -
- )} - {this.props.children} -
- {this.props.image && ( -
- {"right -
- )} -
- -
- ); - } - - public override async componentDidMount() { - this.onWindowResize = WindowStore.getInstance().onResize((window) => this.onResize(window)); + useEffect(() => { let targetedStatus: EFolderStatus = EFolderStatus["LIVE" as keyof typeof EFolderStatus]; - if (this.props.isArchived) targetedStatus = EFolderStatus.ARCHIVED; + if (isArchived) targetedStatus = EFolderStatus.ARCHIVED; const query: IGetFoldersParams = { q: { where: { status: targetedStatus }, @@ -143,27 +58,26 @@ export default class DefaultNotaryDashboard extends React.Component setFolders(folders)); + }, [isArchived]); - private onOpenLeftSide() { - this.setState({ isLeftSideOpen: true }); - } - - private onCloseLeftSide() { - if (!this.state.leftSideCanBeClosed) return; - this.setState({ isLeftSideOpen: false }); - } - - private onResize(window: Window) { - if (window.innerWidth > 1023) { - if (!this.state.leftSideCanBeClosed) return; - this.setState({ leftSideCanBeClosed: false }); - } - this.setState({ leftSideCanBeClosed: true }); - } + return ( +
+
+
+ +
+ {hasBackArrow && ( +
+ +
+ )} + {children} +
+
+ +
+ ); } diff --git a/src/front/Components/Layouts/DesignSystem/index.tsx b/src/front/Components/Layouts/DesignSystem/index.tsx index 13d4ae9f..276577a6 100644 --- a/src/front/Components/Layouts/DesignSystem/index.tsx +++ b/src/front/Components/Layouts/DesignSystem/index.tsx @@ -28,6 +28,8 @@ import { useCallback, useMemo, useState } from "react"; import classes from "./classes.module.scss"; import Menu from "@Front/Components/DesignSystem/Menu"; +import DropdownNavigation from "@Front/Components/DesignSystem/SearchBlockList/DropdownNavigation"; +import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList"; export default function DesignSystem() { const { isOpen, open, close } = useOpenable(); @@ -79,6 +81,78 @@ export default function DesignSystem() {
+ Navigation latérale + {}} + bottomButton={{ + link: "/", + text: "Créer un dossier", + }} + /> + Dropdown navigation + {}} + /> Button icon with menu