Merge branch 'dev' into staging

This commit is contained in:
Maxime Lalo 2024-07-25 13:22:00 +02:00
commit 02f7294bf9
9 changed files with 323 additions and 281 deletions

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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<IBlock | null>(defaultSelectedBlock ?? null);
const [isDropdownOpened, setIsDropdownOpened] = React.useState<boolean>(false);
const rootRef = React.useRef<HTMLDivElement>(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 (
<div className={classes["root"]} data-is-opened={isDropdownOpened} ref={rootRef}>
{selectedBlock && (
<>
<div className={classes["dropdown-header"]} onClick={handleOnClick}>
<div className={classes["text-container"]}>
{selectedBlock.secondaryText && (
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{selectedBlock.secondaryText}
</Typography>
)}
<Typography typo={ETypo.TEXT_MD_BOLD} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{selectedBlock.primaryText}
</Typography>
</div>
<ChevronDownIcon height="24" width="24" color={`var(${ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT})`} />
</div>
{isDropdownOpened && (
<div className={classes["dropdown-content"]}>
{blocks
.filter((block) => block.id !== selectedBlock.id)
.map((block) => (
<div key={block.id} className={classes["dropdown-item"]} onClick={() => selectBlock(block.id)}>
{block.secondaryText && (
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{block.secondaryText}
</Typography>
)}
<Typography typo={ETypo.TEXT_MD_BOLD} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{block.primaryText}
</Typography>
</div>
))}
</div>
)}
</>
)}
</div>
);
}

View File

@ -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;
}
}
}

View File

@ -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<IBlock | null>(null);
const router = useRouter();
const [blocksShowing, setBlocksShowing] = useState<IBlock[]>(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) {
<div className={classes["root"]}>
<div className={classes["searchbar"]} ref={searchBarRef}>
<SearchBar placeholder="Chercher" onChange={handleSearchChange} />
{bottomButton && (
<div className={classes["responsive-button-container"]}>
<Button fullwidth onClick={redirectOnBottomButtonClick}>
{bottomButton.text}
</Button>
</div>
)}
</div>
<div className={classes["block-list"]} style={getHeight()}>
<BlockList blocks={blocksShowing} onSelectedBlock={onSelectedBlock} />
<BlockList blocks={blocksShowing} onSelectedBlock={handleSelectedBlock} />
</div>
<div className={classes["responsive-dropdown"]}>
<DropdownNavigation
blocks={blocksShowing}
onSelectedBlock={handleSelectedBlock}
defaultSelectedBlock={selectedBlock ?? undefined}
/>
</div>
{bottomButton && (
<div className={classes["bottom-container"]}>

View File

@ -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;
}
}
}

View File

@ -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<IProps, IState> {
private onWindowResize = () => {};
public static defaultProps: Partial<IProps> = {
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<OfficeFolder[]>([]);
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<Header isUserConnected={true} />
<div className={classes["content"]}>
{this.state.isLeftSideOpen && <div className={classes["overlay"]} onClick={this.onCloseLeftSide} />}
<div className={classNames(classes["left-side"], this.state.isLeftSideOpen && classes["opened"])}>
{this.props.isArchived && this.state.folders && (
<FolderArchivedListContainer
folders={this.state.folders}
onSelectedFolder={this.props.onSelectedFolder}
onCloseLeftSide={this.onCloseLeftSide}
isArchived={true}
/>
)}
{!this.props.isArchived && this.state.folders && (
<FolderListContainer
folders={this.state.folders}
onSelectedFolder={this.props.onSelectedFolder}
onCloseLeftSide={this.onCloseLeftSide}
isArchived={false}
/>
)}
</div>
<div className={classNames(classes["closable-left-side"])}>
<Image alt="open side menu" src={ChevronIcon} className={classes["chevron-icon"]} onClick={this.onOpenLeftSide} />
</div>
<div className={classes["right-side"]}>
{this.props.hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={this.props.backArrowUrl ?? ""} />
</div>
)}
{this.props.mobileBackText && (
<div className={classes["back-arrow-mobile"]}>
<Button
leftIcon={<ChevronLeftIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
onClick={this.onOpenLeftSide}>
{this.props.mobileBackText ?? "Retour"}
</Button>
</div>
)}
{this.props.children}
</div>
{this.props.image && (
<div className={classes["background-image-container"]}>
<Image alt={"right side image"} src={this.props.image} className={classes["background-image"]} priority />
</div>
)}
</div>
<Version />
</div>
);
}
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<IProps, ISta
},
};
const folders = await Folders.getInstance().get(query);
this.setState({ folders: folders });
}
public override componentWillUnmount() {
this.onWindowResize();
}
Folders.getInstance()
.get(query)
.then((folders) => 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 (
<div className={classes["root"]}>
<Header isUserConnected={true} />
<div className={classes["content"]}>
<FolderListContainer folders={folders} onSelectedFolder={onSelectedFolder} isArchived={isArchived ?? false} />
<div className={classes["right-side"]}>
{hasBackArrow && (
<div className={classes["back-arrow-desktop"]}>
<BackArrow url={backArrowUrl ?? ""} />
</div>
)}
{children}
</div>
</div>
<Version />
</div>
);
}

View File

@ -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() {
<Newsletter isOpen />
<div className={classes["root"]}>
<div className={classes["components"]}>
<Typography typo={ETypo.TEXT_LG_BOLD}>Navigation latérale</Typography>
<SearchBlockList
blocks={[
{
id: "1",
primaryText: "Folder 1",
isActive: true,
secondaryText: "Secondary folder 1",
showAlert: false,
},
{
id: "2",
primaryText: "Folder 2",
isActive: false,
secondaryText: "Secondary folder 2",
showAlert: false,
},
{
id: "3",
primaryText: "Folder 3",
isActive: false,
secondaryText: "Secondary folder 3",
showAlert: false,
},
{
id: "4",
primaryText: "Folder 4",
isActive: false,
secondaryText: "Secondary folder 4",
showAlert: false,
},
]}
onSelectedBlock={() => {}}
bottomButton={{
link: "/",
text: "Créer un dossier",
}}
/>
<Typography typo={ETypo.TEXT_LG_BOLD}>Dropdown navigation</Typography>
<DropdownNavigation
blocks={[
{
id: "1",
primaryText: "Folder 1",
isActive: true,
secondaryText: "Secondary folder 1",
showAlert: false,
},
{
id: "2",
primaryText: "Folder 2",
isActive: false,
secondaryText: "Secondary folder 2",
showAlert: false,
},
{
id: "3",
primaryText: "Folder 3",
isActive: false,
secondaryText: "Secondary folder 3",
showAlert: false,
},
{
id: "4",
primaryText: "Folder 4",
isActive: false,
secondaryText: "Secondary folder 4",
showAlert: false,
},
]}
onSelectedBlock={() => {}}
/>
<Typography typo={ETypo.TEXT_LG_BOLD}>Button icon with menu</Typography>
<Menu
items={[