import React, { useEffect, useRef, useState } from 'react' import { type Meta, type StoryObj } from '@storybook/react-vite' import { DocsTemplate } from '../../../.storybook' import FileUploader, { type FileType, MIMETypes } from './FileUploader' import Alert from '../Alerts/Alert' import { toast } from '../Toast/Toast' const meta: Meta = { title: 'Components/FileUploader', component: FileUploader, parameters: { design: { type: 'figma', url: 'https://www.figma.com/design/RvhKD82948FMQnh5MyCi0o/Web-Design-System?node-id=121-338&t=jsd8kEmb1sJfCa2m-0', }, docs: { page: () => ( The FileUploader component is a user-friendly solution designed to facilitate the uploading of files. It provides both a drag-and-drop interface and a button to open a file manager, making the upload process straightforward and accessible for users. This also supports folder uploads, allowing users to select entire folders for upload, with each file in the folder processed individually. } infoBullets={[ Use the FileUploader component to enable users to upload files easily, whether through drag-and-drop or by opening a file manager. , Integrate the component in sections of the application where file uploads are required, such as data import, bulk updates, or reporting. , To quickly access pre-defined types for the{' '} acceptedFileTypes prop, use the{' '} MIMETypes enum. , Users can select entire folders for upload, with each file processed individually. , ]} /> ), }, }, } export default meta type Story = StoryObj const Template: Story = { render: function Render(args) { const [files, setFiles] = useState(args.files ?? []) const handleUpload = (updatedFiles: File[]) => { if (updatedFiles) { setFiles(updatedFiles) } } //// The following code is for the purpose of simulating file uploads //// const progressTimers = useRef<{ [key: string]: ReturnType }>({}) const simulateFileUpload = (file: FileType) => { const fileId = `${file.name}-${Date.now()}` let progress = 0 // Random chance of failure (20%) const willFail = Math.random() < 0.2 const timer = setInterval(() => { progress += 2 // Will complete in 5 seconds (50 intervals of 2%) setFiles((prev) => prev.map((f) => f.name === file.name ? { ...f, progress, status: progress >= 100 ? 'success' : 'uploading', } : f, ), ) if (progress >= 100 || (willFail && progress >= 60)) { clearInterval(progressTimers.current[fileId]) delete progressTimers.current[fileId] setFiles((prev) => prev.map((f) => f.name === file.name ? { ...f, progress: willFail ? progress : 100, status: willFail ? 'error' : 'success', } : f, ), ) } }, 100) progressTimers.current[fileId] = timer } const handleAsyncUpload = (newFiles: File[]) => { const newFilesWithStatus: FileType[] = newFiles.map((file) => ({ ...file, name: file.name, progress: 0, status: 'uploading', })) setFiles((prev) => [...(prev ?? []), ...newFilesWithStatus]) // Start upload simulation for new files only newFilesWithStatus.forEach((file) => { simulateFileUpload(file) }) } // Cleanup timers on unmount useEffect(() => { const currentTimers = progressTimers.current return () => { Object.values(currentTimers).forEach((timer) => clearInterval(timer)) } }, []) //// End of file upload simulation code //// return (
{args.acceptedFileTypes ? ( The accepted file types for this story are{' '} {args.acceptedFileTypes ?.map((type) => type .split(',') .map((t) => t.trim()) .join(', '), ) .join(', ')} } type='info' customClass='mb-5' /> ) : null} { setFiles((prev) => prev.filter((f) => f.name !== file.name)) }} />
) }, } const genericArgs = { formLabelProps: { label: 'Upload File' }, } export const Basic: Story = { ...Template, args: genericArgs, } export const DownloadCallout: Story = { ...Template, args: { formLabelProps: genericArgs.formLabelProps, downloadCallout: () => { toast({ message: 'Download callout clicked', type: 'success' }) }, }, } export const WithoutLabel: Story = { ...Template, args: {}, } export const WithAcceptedFileTypes: Story = { ...Template, args: { formLabelProps: genericArgs.formLabelProps, acceptedFileTypes: [MIMETypes.Media], }, } export const Disabled: Story = { ...Template, args: { formLabelProps: genericArgs.formLabelProps, disabled: true, }, } export const MultipleFiles: Story = { ...Template, args: { formLabelProps: genericArgs.formLabelProps, acceptMultiple: true, }, } export const MultipleFilesWithAcceptedTypes: Story = { ...Template, args: { formLabelProps: genericArgs.formLabelProps, acceptMultiple: true, acceptedFileTypes: [MIMETypes.Spreadsheets, MIMETypes.Images], }, } export const FilesPopulated: Story = { ...Template, args: { ...genericArgs, acceptMultiple: true, files: [ new File(['This is a test text file'], 'test1.txt', { type: 'text/plain', }), new File(['This is a test text file'], 'test2.txt', { type: 'text/plain', }), new File(['This is a test text file'], 'test3.txt', { type: 'text/plain', }), ], }, } // TODO: This story will be changed to simulate the "status" of file uploads instead of showcasing the "controlled" state. Once all implementations are "controlled", this story will be updated. export const Controlled: Story = { ...Template, args: { ...genericArgs, acceptMultiple: true, controlled: true, }, } export const noFileRemoval: Story = { ...Template, args: { ...genericArgs, noFileRemoval: true, }, }