Merge branch 'dev' into staging

This commit is contained in:
Max S 2024-08-08 17:49:18 +02:00
commit dc17446248
9 changed files with 450 additions and 72 deletions

View File

@ -0,0 +1,39 @@
@import "@Themes/constants.scss";
.root {
display: flex;
max-width: 357px;
width: 100%;
gap: var(--spacing-sm, 8px);
justify-content: space-between;
align-items: center;
.content {
display: flex;
gap: var(--spacing-sm, 8px);
align-items: center;
.file-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 277px;
}
}
svg {
min-width: var(--spacing-3, 24px);
min-height: var(--spacing-3, 24px);
width: var(--spacing-3, 24px);
height: var(--spacing-3, 24px);
stroke: var(--color-primary-500);
}
.error{
min-width: var(--spacing-3, 24px);
min-height: var(--spacing-3, 24px);
width: var(--spacing-3, 24px);
height: var(--spacing-3, 24px);
stroke: var(--color-error-500);
}
}

View File

@ -0,0 +1,37 @@
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { CheckCircleIcon, XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
import React from "react";
import IconButton, { EIconButtonVariant } from "../../IconButton";
import Loader from "../../Loader";
import classes from "./classes.module.scss";
type IProps = {
isLoading: boolean;
file: File | null;
onRemove: () => void;
error?: string;
};
export default function DocumentElement(props: IProps) {
const { isLoading, onRemove, file, error } = props;
return (
<div className={classes["root"]}>
<div className={classes["content"]}>
{isLoading ? <Loader /> : !error ? <CheckCircleIcon /> : <XCircleIcon className={classes["error"]} />}
{error && (
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.COLOR_ERROR_500} className={classes["file-name"]}>
{error}
</Typography>
)}
{file && !error && (
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.TEXT_SECONDARY} className={classes["file-name"]}>
{file.name}
</Typography>
)}
</div>
<IconButton onClick={onRemove} icon={<XMarkIcon />} variant={error ? EIconButtonVariant.ERROR : EIconButtonVariant.NEUTRAL} />
</div>
);
}

View File

@ -0,0 +1,62 @@
@import "@Themes/constants.scss";
.root {
display: inline-flex;
flex-direction: column;
gap: var(--spacing-3, 24px);
width: fit-content;
padding: var(--spacing-2, 16px) var(--Radius-2xl, 32px) var(--spacing-2, 16px) var(--spacing-xl, 32px);
border-radius: var(--Radius-md, 8px);
border: 1px dashed var(--dropdown-input-border-hovered, #b4bec5);
&:hover {
border-color: var(--dropdown-input-border-expanded);
background: var(--primary-weak-higlight, #e5eefa);
}
.content {
display: inline-flex;
align-items: center;
gap: var(--spacing-4, 32px);
.browse-document-container {
display: flex;
flex-direction: column;
gap: var(--spacing-sm, 8px);
.browse-document {
display: flex;
gap: var(--spacing-sm, 8px);
&.desktop {
@media screen and (max-width: $screen-s) {
display: none;
}
}
&.mobile {
display: none;
@media screen and (max-width: $screen-s) {
display: flex;
}
}
}
}
}
.documents {
display: flex;
flex-direction: column;
gap: var(--spacing-sm, 8px);
}
svg {
min-width: var(--spacing-3, 24px);
min-height: var(--spacing-3, 24px);
width: var(--spacing-3, 24px);
height: var(--spacing-3, 24px);
stroke: var(--color-primary-500);
}
}

View File

@ -0,0 +1,161 @@
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { DocumentPlusIcon } from "@heroicons/react/24/outline";
import classNames from "classnames";
import React, { useCallback, useRef, useState } from "react";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "../Button";
import Separator, { ESeperatorColor, ESeperatorDirection } from "../Separator";
import classes from "./classes.module.scss";
import DocumentElement from "./DocumentElement";
type IProps = {
name?: string;
title: string;
description: string;
};
type IMimeTypes = {
extension: string;
size: number;
};
const mimeTypesAccepted: { [key: string]: IMimeTypes } = {
"application/pdf": {
extension: "pdf",
size: 41943040,
},
"image/jpeg": {
extension: "jpeg",
size: 41943040,
},
"image/png": {
extension: "png",
size: 41943040,
},
"image/jpg": {
extension: "jpg",
size: 41943040,
},
};
type IDocument = {
id: string;
file: File | null;
isLoading: boolean;
error?: string;
};
export default function DragAndDrop(props: IProps) {
const { name, title, description } = props;
const fileInputRef = useRef<HTMLInputElement>(null);
const [documents, setDocuments] = useState<IDocument[]>([]);
const handleFiles = useCallback((files: File[]) => {
files.forEach((file) => {
setDocuments((prevDocs) => [...prevDocs, { id: file.name, file: file, isLoading: true }]);
setTimeout(() => {
if (mimeTypesAccepted[file.type]) {
const newDoc: IDocument = { id: file.name, file, isLoading: false };
setDocuments((prevDocs) => prevDocs.map((doc) => (doc.id === newDoc.id ? newDoc : doc)));
} else {
const errorDoc: IDocument = { id: file.name, file: null, isLoading: false, error: "Type de fichier non accepté" };
setDocuments((prevDocs) => prevDocs.map((doc) => (doc.id === errorDoc.id ? errorDoc : doc)));
}
}, 1000);
});
}, []);
const handleDrop = useCallback(
(event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
const files = Array.from(event.dataTransfer.files);
handleFiles(files);
},
[handleFiles],
);
const handleRemove = useCallback((id: string) => {
setDocuments((prevDocs) => prevDocs.filter((doc) => doc.id !== id));
}, []);
const handleBrowse = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
handleFiles(files);
},
[handleFiles],
);
const triggerFileInput = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};
return (
<div className={classNames(classes["root"])} onDrop={handleDrop} onDragOver={(e) => e.preventDefault()}>
<div className={classes["content"]}>
<DocumentPlusIcon />
<Separator direction={ESeperatorDirection.VERTICAL} color={ESeperatorColor.STRONG} size={64} />
<div className={classes["browse-document-container"]}>
<div className={classNames(classes["browse-document"], classes["desktop"])}>
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_PRIMARY}>
{title}
</Typography>
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}
onClick={triggerFileInput}>
{inputFile()}
parcourir
</Button>
</div>
<div className={classNames(classes["browse-document"], classes["mobile"])}>
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}
onClick={triggerFileInput}>
{inputFile()}
Ajouter un document
</Button>
</div>
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.TEXT_SECONDARY}>
{description}
</Typography>
</div>
</div>
{documents.length > 0 && (
<div className={classes["documents"]}>
{documents.map((doc) => (
<DocumentElement
key={doc.id}
isLoading={doc.isLoading}
file={doc.file}
onRemove={() => handleRemove(doc.id)}
error={doc.error}
/>
))}
</div>
)}
</div>
);
function inputFile() {
return (
<input
ref={fileInputRef}
name={name}
type="file"
multiple
accept={Object.keys(mimeTypesAccepted).join(",")}
onChange={handleBrowse}
hidden
/>
);
}
}

View File

@ -0,0 +1,24 @@
@import "@Themes/constants.scss";
.root {
height: 2px;
width: 100%;
background-color: var(--separator-stroke-default);
&.vertical {
width: 2px;
height: 100%;
}
&.light {
background-color: var(--separator-stroke-light);
}
&.strong {
background-color: var(--separator-stroke-strong);
}
&.contrast {
background-color: var(--separator-stroke-contrast);
}
}

View File

@ -0,0 +1,33 @@
import React from "react";
import classes from "./classes.module.scss";
import classNames from "classnames";
export enum ESeperatorColor {
LIGHT = "light",
DEFAULT = "default",
STRONG = "strong",
CONTRAST = "contrast",
}
export enum ESeperatorDirection {
HORIZONTAL = "horizontal",
VERTICAL = "vertical",
}
type IProps = {
color?: ESeperatorColor;
direction?: ESeperatorDirection;
size?: number;
};
export default function Separator(props: IProps) {
const { color = ESeperatorColor.DEFAULT, direction = ESeperatorDirection.HORIZONTAL, size } = props;
return (
<div
className={classNames(classes["root"], classes[color], classes[direction])}
style={direction === ESeperatorDirection.HORIZONTAL ? { width: size } : { height: size }}
/>
);
}

View File

@ -149,6 +149,8 @@ export enum ETypoColor {
INPUT_ERROR = "--input-error",
TEXT_ACCENT = "--text-accent",
TEXT_PRIMARY = "--text-primary",
TEXT_SECONDARY = "--text-secondary",
CONTRAST_DEFAULT = "--contrast-default",
CONTRAST_HOVERED = "--contrast-hovered",

View File

@ -3,15 +3,16 @@
flex-direction: column;
gap: 32px;
.components {
display: flex;
flex-direction: column;
gap: 32px;
max-width: 600px;
.inputs {
display: flex;
flex-direction: column;
gap: 24px;
}
display: flex;
flex-direction: column;
gap: 24px;
.rows {
display: flex;

View File

@ -36,6 +36,8 @@ import classes from "./classes.module.scss";
import CheckboxesInputElement from "@Front/Components/DesignSystem/CheckBox";
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
import Toggle, { EToggleSize } from "@Front/Components/DesignSystem/Toggle";
import Separator, { ESeperatorColor, ESeperatorDirection } from "@Front/Components/DesignSystem/Separator";
import DragAndDrop from "@Front/Components/DesignSystem/DragAndDrop";
export default function DesignSystem() {
const { isOpen, open, close } = useOpenable();
@ -83,78 +85,95 @@ export default function DesignSystem() {
return (
<DefaultTemplate title={"DesignSystem"}>
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
<Newsletter isOpen={false} />
<div className={classes["root"]}>
<div />
<div className={classes["rows"]}>
<CheckboxesInputElement
option={{
label: "Default",
value: "all",
description: "Description",
}}
toolTip="test"
/>
<CheckboxesInputElement
option={{
label: "Checked",
value: "all",
description: "Description",
}}
toolTip="test"
checked={true}
/>
<CheckboxesInputElement
option={{
label: "Disabled",
value: "all",
description: "Description",
}}
toolTip="test"
disabled={true}
/>
<CheckboxesInputElement
option={{
label: "Checked & Disabled",
value: "all",
description: "Description",
}}
toolTip="test"
checked={true}
disabled={true}
/>
</div>
<div className={classes["rows"]}>
<RadioBox name="document" value={"new client"} description="Test" label="Créer un document" toolTip="test" />
<RadioBox
name="document"
value={"new client"}
description="Test"
label="Créer un document"
toolTip="test"
defaultChecked={true}
/>
<RadioBox
name="document"
value={"new client"}
description="Test"
label="Créer un document"
toolTip="test"
disabled={true}
/>
<RadioBox
name="document"
value={"new client"}
description="Test"
label="Créer un document"
toolTip="test"
checked={true}
disabled={true}
/>
</div>
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
<div className={classes["components"]}>
<Typography typo={ETypo.TEXT_LG_BOLD}>Drag and Drop</Typography>
<DragAndDrop name="test" title="Upload de document" description="Description" />
<Typography typo={ETypo.TEXT_LG_BOLD}>Separators</Typography>
<div className={classes["rows"]}>
<Separator color={ESeperatorColor.DEFAULT} direction={ESeperatorDirection.HORIZONTAL} />
<Separator color={ESeperatorColor.LIGHT} direction={ESeperatorDirection.HORIZONTAL} />
<Separator color={ESeperatorColor.STRONG} direction={ESeperatorDirection.HORIZONTAL} />
<Separator color={ESeperatorColor.CONTRAST} direction={ESeperatorDirection.HORIZONTAL} />
</div>
<div className={classes["rows"]} style={{ height: 70, justifyContent: "space-around" }}>
<Separator color={ESeperatorColor.DEFAULT} direction={ESeperatorDirection.VERTICAL} />
<Separator color={ESeperatorColor.LIGHT} direction={ESeperatorDirection.VERTICAL} />
<Separator color={ESeperatorColor.STRONG} direction={ESeperatorDirection.VERTICAL} />
<Separator color={ESeperatorColor.CONTRAST} direction={ESeperatorDirection.VERTICAL} />
</div>
<Typography typo={ETypo.TEXT_LG_BOLD}>Checkboxes</Typography>
<div className={classes["rows"]}>
<CheckboxesInputElement
option={{
label: "Default",
value: "all",
description: "Description",
}}
toolTip="test"
/>
<CheckboxesInputElement
option={{
label: "Checked",
value: "all",
description: "Description",
}}
toolTip="test"
checked={true}
/>
<CheckboxesInputElement
option={{
label: "Disabled",
value: "all",
description: "Description",
}}
toolTip="test"
disabled={true}
/>
<CheckboxesInputElement
option={{
label: "Checked & Disabled",
value: "all",
description: "Description",
}}
toolTip="test"
checked={true}
disabled={true}
/>
</div>
<Typography typo={ETypo.TEXT_LG_BOLD}>Radio boxes</Typography>
<div className={classes["rows"]}>
<RadioBox name="document" value={"new client"} description="Test" label="Créer un document" toolTip="test" />
<RadioBox
name="document"
value={"new client"}
description="Test"
label="Créer un document"
toolTip="test"
defaultChecked={true}
/>
<RadioBox
name="document"
value={"new client"}
description="Test"
label="Créer un document"
toolTip="test"
disabled={true}
/>
<RadioBox
name="document"
value={"new client"}
description="Test"
label="Créer un document"
toolTip="test"
checked={true}
disabled={true}
/>
</div>
<Typography typo={ETypo.TEXT_LG_BOLD}>Toggle</Typography>
<div className={classes["rows"]}>
<Toggle size={EToggleSize.MD} />