From 7577942308be0af83d68dbdc64aeebdd577f8002 Mon Sep 17 00:00:00 2001 From: Josh Wooding <12938082+joshwooding@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:20:56 +0000 Subject: [PATCH] Add example --- .../file-upload/file-upload.stories.tsx | 372 ++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 packages/core/stories/patterns/file-upload/file-upload.stories.tsx diff --git a/packages/core/stories/patterns/file-upload/file-upload.stories.tsx b/packages/core/stories/patterns/file-upload/file-upload.stories.tsx new file mode 100644 index 0000000000..18af1cc98f --- /dev/null +++ b/packages/core/stories/patterns/file-upload/file-upload.stories.tsx @@ -0,0 +1,372 @@ +import { faker } from "@faker-js/faker"; +import { + Button, + Divider, + FileDropZone, + FileDropZoneIcon, + FileDropZoneTrigger, + Spinner, + StackLayout, + StatusIndicator, + Text, + Tooltip, + useId, +} from "@salt-ds/core"; +import { + DeleteIcon, + PauseIcon, + PlayIcon, + ProgressOnholdIcon, + RefreshIcon, +} from "@salt-ds/icons"; +import { + StaticList, + StaticListItem, + StaticListItemContent, +} from "@salt-ds/lab"; +import type { Meta } from "@storybook/react"; +import { clsx } from "clsx"; +import { + Fragment, + type SyntheticEvent, + useCallback, + useEffect, + useState, +} from "react"; + +export default { + title: "Patterns/File Upload", +} as Meta; + +type FileItemStatus = + | "initial" + | "failedValidation" + | "success" + | "uploading" + | "paused" + | "failedConnection"; + +function getStatusDecoration(status: FileItemStatus) { + switch (status) { + case "failedValidation": + case "failedConnection": + return ; + case "uploading": + return ; + case "success": + return ; + case "paused": + return ; + } +} + +function fileSize(bytes: number) { + const sizeFormatter = new Intl.NumberFormat([], { + style: "unit", + unit: "byte", + notation: "compact", + unitDisplay: "narrow", + }); + return sizeFormatter.format(bytes); +} + +function getDescription({ + totalSize, + currentSize = 0, +}: { totalSize: number; currentSize?: number }) { + const downloadCompleted = currentSize === totalSize; + if (totalSize && !downloadCompleted) { + return `${fileSize(currentSize)} of ${fileSize(totalSize)}`; + } + return `${fileSize(currentSize)}`; +} + +const updateInterval = 500; + +const FileItem = ({ + file, + handleDelete, + badConnection, +}: { + file: File; + handleDelete: (file: File) => void; + badConnection?: boolean; +}) => { + const id = useId(); + const [status, setStatus] = useState("initial"); + const [errorMessage, setErrorMessage] = useState(""); + const [currentSize, setCurrentSize] = useState(0); + + const validFile = useCallback((file: File) => { + if (file.type !== "application/pdf") { + setErrorMessage("Wrong format. Files must be in .PDF format"); + return false; + } + + if (file.size > 100_000) { + setErrorMessage("Exceeds file size. 100KB file size limit."); + return false; + } + + return true; + }, []); + + useEffect(() => { + if (file && validFile(file)) { + setTimeout(() => { + // emulate upload + setStatus("uploading"); + }, 700); + } else { + setStatus("failedValidation"); + } + }, [file, validFile]); + + useEffect(() => { + let timeout: ReturnType; + + if (status === "uploading") { + const update = () => { + setCurrentSize((old) => { + if (old !== file.size) { + timeout = setTimeout(update, updateInterval); + + if (badConnection && Math.random() > 0.7) { + setStatus("failedConnection"); + setErrorMessage("Connection failed"); + return old; + } + + return Math.min(old + Math.random() * 1000, file.size); + } + + setStatus("success"); + return old; + }); + }; + + timeout = setTimeout(update, updateInterval); + } + + return () => { + clearTimeout(timeout); + }; + }, [status, file.size, badConnection]); + + return ( + +
+ {getStatusDecoration(status)} +
+ + + + {file.name} + + + {errorMessage === "" + ? getDescription({ totalSize: file.size, currentSize }) + : errorMessage} + + + + {status === "failedConnection" && ( + + + + )} + {status === "paused" && ( + + + + )} + {status === "uploading" && ( + + + + )} + {status !== "uploading" && ( + + + + )} +
+ ); +}; + +export const FileUploadExample = () => { + const [files, setFiles] = useState<{ file: File; badConnection?: boolean }[]>( + [], + ); + + const handleFiles = (_: SyntheticEvent, files: File[]) => { + setFiles((old) => + old.concat( + files.map((file) => ({ + file, + })), + ), + ); + }; + + const handleDelete = (fileToRemove: File) => { + setFiles((old) => old.filter((file) => file.file !== fileToRemove)); + }; + + return ( + + + + + + + + + Upload files + + Please drag and drop file(s) in the below area; or browse files by + using the button. + + + + + Drop files here or + + Files must be in .PDF format. 100KB file size limit. + + + {files.map(({ file, badConnection }, index) => ( + + + {index < files.length - 1 && ( + + )} + + ))} + + + ); +};