New nav without responsive

This commit is contained in:
Maxime Lalo 2024-07-24 14:54:32 +02:00
parent 4960b51ee3
commit 8ff373271b
12 changed files with 233 additions and 87 deletions

View File

@ -1,48 +0,0 @@
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo } from "../Typography";
import ChevronIcon from "@Assets/Icons/chevron.svg";
import Image from "next/image";
import WarningBadge from "../WarningBadge";
export type IBlock = {
name: string;
id: string;
selected: boolean;
rightIcon?: JSX.Element;
hasFlag?: boolean;
};
type IProps = {
blocks: IBlock[];
onSelectedBlock: (block: IBlock) => void;
};
export default function BlockList({ blocks, onSelectedBlock }: IProps) {
const selectBlock = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === e.currentTarget.id)!);
},
[blocks, onSelectedBlock],
);
return (
<div>
{blocks.map((folder) => {
return (
<div onClick={selectBlock} key={folder.id} id={folder.id}>
<div className={classes["root"]} data-selected={folder.selected.toString()}>
<div className={classes["left-side"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR}>{folder.name}</Typography>
</div>
<div className={classes["right-side"]}>
{folder.hasFlag && <WarningBadge />}
{folder.rightIcon}
<Image alt="chevron" src={ChevronIcon} />
</div>
</div>
</div>
);
})}
</div>
);
}

View File

@ -5,12 +5,13 @@ import Link from "next/link";
import { NextRouter, useRouter } from "next/router"; import { NextRouter, useRouter } from "next/router";
import React from "react"; import React from "react";
import BlockList, { IBlock } from "../BlockList";
import Button from "../Button"; import Button from "../Button";
import SearchBar from "../SearchBar"; import SearchBar from "../SearchBar";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules"; import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule"; import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
import { IBlock } from "../SearchBlockList/BlockList/Block";
import BlockList from "../SearchBlockList/BlockList";
type IProps = { type IProps = {
folders: OfficeFolder[]; folders: OfficeFolder[];
@ -106,9 +107,10 @@ class FolderArchivedListContainerClass extends React.Component<IPropsClass, ISta
return [...pendingFolders, ...otherFolders].map((folder) => { return [...pendingFolders, ...otherFolders].map((folder) => {
return { return {
id: folder.uid!, id: folder.uid!,
name: folder.folder_number! + " - " + folder.name!, primaryText: folder.name,
selected: this.props.selectedFolder === folder.uid, isActive: this.props.selectedFolder === folder.uid,
hasFlag: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED), secondaryText: folder.folder_number,
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
}; };
}); });
} }

View File

@ -5,12 +5,14 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect } from "react";
import BlockList, { IBlock } from "../BlockList";
import Button from "../Button"; import Button from "../Button";
import SearchBar from "../SearchBar"; import SearchBar from "../SearchBar";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules"; import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule"; import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
import { IBlock } from "../SearchBlockList/BlockList/Block";
import BlockList from "../SearchBlockList/BlockList";
import SearchBlockList from "../SearchBlockList";
type IProps = { type IProps = {
folders: OfficeFolder[]; folders: OfficeFolder[];
@ -76,9 +78,10 @@ export default function FolderListContainer(props: IProps) {
return [...pendingFolders, ...otherFolders].map((folder) => { return [...pendingFolders, ...otherFolders].map((folder) => {
return { return {
id: folder.uid!, id: folder.uid!,
name: folder.folder_number! + " - " + folder.name!, primaryText: folder.name,
selected: folderUid === folder.uid, secondaryText: folder.folder_number,
hasFlag: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED), isActive: folderUid === folder.uid,
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
}; };
}); });
}, },
@ -99,18 +102,18 @@ export default function FolderListContainer(props: IProps) {
setBlocks(getBlocks(folders)); setBlocks(getBlocks(folders));
}, [folders, getBlocks]); }, [folders, getBlocks]);
const navigatePath = Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path; //const navigatePath = Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path;
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["header"]}> <SearchBlockList
<div className={classes["searchbar"]}> blocks={blocks}
<SearchBar onChange={filterFolders} placeholder="Chercher un dossier" /> onSelectedBlock={onSelectedFolder}
</div> bottomButton={{
<div className={classes["folderlist-container"]}> link: Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path,
<BlockList blocks={blocks} onSelectedBlock={onSelectedFolder} /> text: "Créer un dossier",
</div> }}
</div> />
{!isArchived && ( {/* {!isArchived && (
<div> <div>
<Rules <Rules
mode={RulesMode.NECESSARY} mode={RulesMode.NECESSARY}
@ -125,7 +128,7 @@ export default function FolderListContainer(props: IProps) {
</Link> </Link>
</Rules> </Rules>
</div> </div>
)} )} */}
</div> </div>
); );
} }

View File

@ -47,6 +47,7 @@
font-weight: var(--font-text-weight-semibold, 600); font-weight: var(--font-text-weight-semibold, 600);
line-height: normal; line-height: normal;
letter-spacing: 0.08px; letter-spacing: 0.08px;
width: 100%;
&::placeholder { &::placeholder {
color: var(--input-placeholder-empty, #6d7e8a); color: var(--input-placeholder-empty, #6d7e8a);

View File

@ -12,26 +12,18 @@ export default function SearchBar({ onChange, placeholder = "Rechercher" }: IPro
const [isFocused, setIsFocused] = React.useState(false); const [isFocused, setIsFocused] = React.useState(false);
const [value, setValue] = React.useState(""); const [value, setValue] = React.useState("");
const handleOnChange = useCallback( const changeValue = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => { (value: string) => {
setValue(event.target.value); setValue(value);
onChange && onChange(event.target.value); onChange && onChange(value);
}, },
[onChange], [onChange],
); );
const handleFocus = useCallback(() => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value);
setIsFocused(true); const handleFocus = () => setIsFocused(true);
}, []); const handleBlur = () => setIsFocused(false);
const clearValue = () => changeValue("");
const handleBlur = useCallback(() => {
setIsFocused(false);
}, []);
const clearValue = useCallback(() => {
setValue("");
onChange && onChange("");
}, [onChange]);
return ( return (
<label className={classes["root"]} data-is-focused={isFocused} data-has-value={value !== ""}> <label className={classes["root"]} data-is-focused={isFocused} data-has-value={value !== ""}>

View File

@ -0,0 +1,29 @@
.root {
background: var(--navigation-button-background-default, #f7f8f8);
cursor: pointer;
&:hover {
background: var(--navigation-button-background-actived, #edeff1);
}
&[data-is-active="true"] {
border-left: 3px solid var(--navigation-border, #005bcb);
background: var(--navigation-button-background-actived, #edeff1);
}
.content {
display: flex;
padding: var(--spacing-md, 16px) var(--spacing-lg, 24px);
align-items: center;
gap: var(--spacing-sm, 8px);
.text-container {
flex: 1;
}
}
.separator {
width: 100%;
height: 1px;
background: var(--separator-stroke-light, #d7dce0);
}
}

View File

@ -0,0 +1,44 @@
import { ChevronRightIcon } from "@heroicons/react/24/outline";
import classes from "./classes.module.scss";
import { useCallback } from "react";
import { ExclamationCircleIcon } from "@heroicons/react/20/solid";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
export type IBlock = {
id: string;
primaryText: string;
secondaryText?: string;
showAlert?: boolean;
isActive?: boolean;
};
type IProps = IBlock & {
hasSeparator?: boolean;
onClick?: (id: string) => void;
};
export default function Block(props: IProps) {
const { primaryText, secondaryText, showAlert = false, hasSeparator = true, isActive = false, onClick } = props;
const handleOnClick = useCallback(() => onClick && onClick(props.id), [onClick, props.id]);
return (
<div className={classes["root"]} data-is-active={isActive} onClick={handleOnClick}>
<div className={classes["content"]}>
<div className={classes["text-container"]}>
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{secondaryText}
</Typography>
<Typography typo={ETypo.TEXT_MD_BOLD} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
{primaryText}
</Typography>
</div>
{showAlert && (
<ExclamationCircleIcon width="24" height="24" className={classes["icon"]} color={"var(--warning-default-base)"} />
)}
<ChevronRightIcon width="24" height="24" className={classes["icon"]} color={"var(--button-icon-button-default-default)"} />
</div>
{hasSeparator && <div className={classes["separator"]} />}
</div>
);
}

View File

@ -0,0 +1,23 @@
import React, { useCallback } from "react";
import Block, { IBlock } from "./Block";
type IProps = {
blocks: IBlock[];
onSelectedBlock: (block: IBlock) => void;
};
export default function BlockList({ blocks, onSelectedBlock }: IProps) {
const selectBlock = useCallback(
(id: string) => {
onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === id)!);
},
[blocks, onSelectedBlock],
);
return (
<div>
{blocks.map((block) => {
return <Block {...block} hasSeparator onClick={selectBlock} key={block.id} />;
})}
</div>
);
}

View File

@ -0,0 +1,30 @@
.root {
width: 336px;
height: 100%;
max-height: 100%;
border-radius: var(--navigation-radius, 0px);
background: var(--navigation-button-background-default, #f7f8f8);
display: flex;
flex-direction: column;
justify-content: flex-start;
.block-list {
overflow-y: auto;
::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.searchbar {
padding: var(--spacing-md, 16px);
}
.bottom-container {
position: fixed;
bottom: 0;
width: 336px;
}
}

View File

@ -0,0 +1,69 @@
import { useCallback, useEffect, useRef, useState } from "react";
import BlockList from "./BlockList";
import { IBlock } from "./BlockList/Block";
import classes from "./classes.module.scss";
import SearchBar from "../SearchBar";
import Button from "../Button";
import Link from "next/link";
type IProps = {
blocks: IBlock[];
onSelectedBlock: (block: IBlock) => void;
bottomButton?: {
text: string;
link: string;
};
};
export default function SearchBlockList(props: IProps) {
const { blocks, onSelectedBlock, bottomButton } = props;
const [blocksShowing, setBlocksShowing] = useState<IBlock[]>(blocks);
const bottomButtonRef = useRef<HTMLAnchorElement>(null);
const searchBarRef = useRef<HTMLDivElement>(null);
const handleSearchChange = useCallback(
(value: string) => {
const filteredBlocks = blocks.filter((block) => {
const name = block.primaryText.toLowerCase();
const number = block.secondaryText?.toLowerCase();
value = value.toLowerCase();
return name.includes(value) || number?.includes(value);
});
setBlocksShowing(filteredBlocks);
},
[blocks],
);
const getHeight = useCallback(() => {
let heightToSubstract = 0;
if (bottomButton && bottomButtonRef.current) {
heightToSubstract += bottomButtonRef.current.clientHeight;
}
if (searchBarRef.current) {
heightToSubstract += searchBarRef.current.clientHeight;
}
return {
maxHeight: `calc(100% - ${heightToSubstract}px)`,
};
}, []);
useEffect(() => {
setBlocksShowing(blocks);
}, [blocks]);
return (
<div className={classes["root"]}>
<div className={classes["searchbar"]} ref={searchBarRef}>
<SearchBar placeholder="Chercher" onChange={handleSearchChange} />
</div>
<div className={classes["block-list"]} style={getHeight()}>
<BlockList blocks={blocksShowing} onSelectedBlock={onSelectedBlock} />
</div>
{bottomButton && (
<Link href={bottomButton.link} className={classes["bottom-container"]} ref={bottomButtonRef}>
<Button fullwidth>{bottomButton.text}</Button>
</Link>
)}
</div>
);
}

View File

@ -28,25 +28,25 @@ export enum ETypo {
TEXT_LG_SEMIBOLD = "text-lg-semibold", TEXT_LG_SEMIBOLD = "text-lg-semibold",
TEXT_LG_REGULAR = "text-lg-regular", TEXT_LG_REGULAR = "text-lg-regular",
TEXT_LG_UPPERCASE = "text-lg-uppercase", TEXT_LG_UPPERCASE = "text-lg-uppercase",
TEXT_LG_light = "text-lg-light", TEXT_LG_LIGHT = "text-lg-light",
TEXT_MD_BOLD = "text-md-bold", TEXT_MD_BOLD = "text-md-bold",
TEXT_MD_SEMIBOLD = "text-md-semibold", TEXT_MD_SEMIBOLD = "text-md-semibold",
TEXT_MD_REGULAR = "text-md-regular", TEXT_MD_REGULAR = "text-md-regular",
TEXT_MD_UPPERCASE = "text-md-uppercase", TEXT_MD_UPPERCASE = "text-md-uppercase",
TEXT_MD_light = "text-md-light", TEXT_MD_LIGHT = "text-md-light",
TEXT_SM_BOLD = "text-sm-bold", TEXT_SM_BOLD = "text-sm-bold",
TEXT_SM_SEMIBOLD = "text-sm-semibold", TEXT_SM_SEMIBOLD = "text-sm-semibold",
TEXT_SM_REGULAR = "text-sm-regular", TEXT_SM_REGULAR = "text-sm-regular",
TEXT_SM_UPPERCASE = "text-sm-uppercase", TEXT_SM_UPPERCASE = "text-sm-uppercase",
TEXT_SM_light = "text-sm-light", TEXT_SM_LIGHT = "text-sm-light",
TEXT_XS_BOLD = "text-xs-bold", TEXT_XS_BOLD = "text-xs-bold",
TEXT_XS_SEMIBOLD = "text-xs-semibold", TEXT_XS_SEMIBOLD = "text-xs-semibold",
TEXT_XS_REGULAR = "text-xs-regular", TEXT_XS_REGULAR = "text-xs-regular",
TEXT_XS_UPPERCASE = "text-xs-uppercase", TEXT_XS_UPPERCASE = "text-xs-uppercase",
TEXT_XS_light = "text-xs-light", TEXT_XS_LIGHT = "text-xs-light",
CAPTION_BOLD = "caption-bold", CAPTION_BOLD = "caption-bold",
CAPTION_SEMIBOLD = "caption-semibold", CAPTION_SEMIBOLD = "caption-semibold",
@ -153,6 +153,7 @@ export enum ETypoColor {
CONTRAST_DEFAULT = "--contrast-default", CONTRAST_DEFAULT = "--contrast-default",
CONTRAST_HOVERED = "--contrast-hovered", CONTRAST_HOVERED = "--contrast-hovered",
ERROR_WEAK_CONTRAST = "--error-weak-contrast", ERROR_WEAK_CONTRAST = "--error-weak-contrast",
NAVIGATION_BUTTON_CONTRAST_DEFAULT = "--navigation-button-contrast-default",
} }
export default function Typography(props: IProps) { export default function Typography(props: IProps) {