✨ drag and drop
This commit is contained in:
parent
a813a4e57e
commit
b6565833b1
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -2,22 +2,57 @@
|
|||||||
|
|
||||||
.root {
|
.root {
|
||||||
display: inline-flex;
|
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);
|
padding: var(--spacing-2, 16px) var(--Radius-2xl, 32px) var(--spacing-2, 16px) var(--spacing-xl, 32px);
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-4, 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 {
|
.content {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
gap: var(--spacing-sm, 8px);
|
gap: var(--spacing-4, 32px);
|
||||||
|
|
||||||
.deposit-document {
|
.browse-document-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
gap: var(--spacing-sm, 8px);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
svg{
|
.documents {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
min-width: var(--spacing-3, 24px);
|
min-width: var(--spacing-3, 24px);
|
||||||
min-height: var(--spacing-3, 24px);
|
min-height: var(--spacing-3, 24px);
|
||||||
width: var(--spacing-3, 24px);
|
width: var(--spacing-3, 24px);
|
||||||
|
@ -1,35 +1,161 @@
|
|||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
import React from "react";
|
|
||||||
import { DocumentPlusIcon } from "@heroicons/react/24/outline";
|
import { DocumentPlusIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import Separator, { ESeperatorColor, ESeperatorDirection } from "../Separator";
|
import React, { useCallback, useRef, useState } from "react";
|
||||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "../Button";
|
|
||||||
|
|
||||||
type IProps = {};
|
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) {
|
export default function DragAndDrop(props: IProps) {
|
||||||
const {} = props;
|
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 (
|
return (
|
||||||
<div className={classNames(classes["root"])}>
|
<div className={classNames(classes["root"])} onDrop={handleDrop} onDragOver={(e) => e.preventDefault()}>
|
||||||
<DocumentPlusIcon />
|
|
||||||
<Separator direction={ESeperatorDirection.VERTICAL} color={ESeperatorColor.STRONG} size={64} />
|
|
||||||
<div className={classes["content"]}>
|
<div className={classes["content"]}>
|
||||||
<div className={classes["deposit-document"]}>
|
<DocumentPlusIcon />
|
||||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_PRIMARY}>
|
<Separator direction={ESeperatorDirection.VERTICAL} color={ESeperatorColor.STRONG} size={64} />
|
||||||
Drag and Drop ou
|
<div className={classes["browse-document-container"]}>
|
||||||
</Typography>
|
<div className={classNames(classes["browse-document"], classes["desktop"])}>
|
||||||
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.TEXT} size={EButtonSize.SM}>
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
parcourir
|
{title}
|
||||||
</Button>
|
</Typography>
|
||||||
</div>
|
<Button
|
||||||
|
variant={EButtonVariant.PRIMARY}
|
||||||
|
styletype={EButtonstyletype.TEXT}
|
||||||
|
size={EButtonSize.SM}
|
||||||
|
onClick={triggerFileInput}>
|
||||||
|
{inputFile()}
|
||||||
|
parcourir
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.TEXT_SECONDARY}>
|
<div className={classNames(classes["browse-document"], classes["mobile"])}>
|
||||||
Description
|
<Button
|
||||||
</Typography>
|
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>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function inputFile() {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
name={name}
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
accept={Object.keys(mimeTypesAccepted).join(",")}
|
||||||
|
onChange={handleBrowse}
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ export default function DesignSystem() {
|
|||||||
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
|
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
|
||||||
<div className={classes["components"]}>
|
<div className={classes["components"]}>
|
||||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Drag and Drop</Typography>
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Drag and Drop</Typography>
|
||||||
<DragAndDrop />
|
<DragAndDrop name="test" title="Upload de document" description="Description" />
|
||||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Separators</Typography>
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Separators</Typography>
|
||||||
<div className={classes["rows"]}>
|
<div className={classes["rows"]}>
|
||||||
<Separator color={ESeperatorColor.DEFAULT} direction={ESeperatorDirection.HORIZONTAL} />
|
<Separator color={ESeperatorColor.DEFAULT} direction={ESeperatorDirection.HORIZONTAL} />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user