UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

372 lines (319 loc) 9.73 kB
# Dropzone Import: `import { Dropzone } from '@neo4j-ndl/react'` ## Props ### Dropzone | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `acceptedFileExtensions` | `string[]` | | | The accepted file extensions for the Drag-and-Drop. | | `dropZoneOptions` | `DropzoneOptions` | | | Props for the underlying `react-dropzone` component. Don't use `accept`. If using custom file extensions as this option is broken. Instead use the `acceptedFileExtensions` property. | | `heading` | `ReactNode` | | | Use when wanting a custom heading. | | `loadingElement` | `ReactNode` | | | Element to display when uploading the file. | | `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. | | `rejectedText` | `ReactNode` | | | Message to be shown when file is rejected. | | `supportedFilesDescription` | `ReactNode` | | | Informational text that highlights the supported file types, will be displayed in the footer. | Where `DropzoneOptions` is the props for the `react-dropzone` library. For more information, see the [react-dropzone documentation](https://react-dropzone.js.org/#src). ### Dropzone.LoadingProgressBar | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `progressBarMinute` | `number` | | | The time in minute left to upload the file. | | `progressBarPrecentage` | `number` | | | The percentage of the progress bar. | | `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. | ## Examples ### Default ```tsx import { Dropzone } from '@neo4j-ndl/react'; import { type DropzoneOptions } from 'react-dropzone'; const Component = () => { const onDrop: DropzoneOptions['onDrop'] = (files) => { alert( `attempted to upload ${ files.length } file(s). Check console logs for more info`, ); }; return ( <Dropzone dropZoneOptions={{ onDrop, }} /> ); }; export default Component; ``` ### Csv Files ```tsx import { Dropzone } from '@neo4j-ndl/react'; import { type DropzoneOptions } from 'react-dropzone'; const Component = () => { const onDrop: DropzoneOptions['onDrop'] = (files) => { alert( `attempted to upload ${ files.length } file(s). Check console logs for more info`, ); }; return ( <Dropzone supportedFilesDescription="Supports: CSV" dropZoneOptions={{ accept: { 'text/csv': ['.csv'] }, onDrop, }} /> ); }; export default Component; ``` ### Custom Extensions ```tsx import { Dropzone } from '@neo4j-ndl/react'; import { type DropzoneOptions } from 'react-dropzone'; const Component = () => { const onDrop: DropzoneOptions['onDrop'] = (files) => { alert( `attempted to upload ${ files.length } file(s). Check console logs for more info`, ); }; return ( <Dropzone supportedFilesDescription="Supports: .dump" acceptedFileExtensions={['.dump']} dropZoneOptions={{ onDrop, }} /> ); }; export default Component; ``` ### Disabled ```tsx import { Dropzone } from '@neo4j-ndl/react'; import { type DropzoneOptions } from 'react-dropzone'; const Component = () => { const onDrop: DropzoneOptions['onDrop'] = (files) => { alert( `attempted to upload ${ files.length } file(s). Check console logs for more info`, ); }; return ( <Dropzone supportedFilesDescription="Supports: CSV Files" dropZoneOptions={{ accept: { 'text/csv': ['.csv'] }, disabled: true, onDrop, }} /> ); }; export default Component; ``` ### Full ```tsx import { XMarkIcon } from '@heroicons/react/24/solid'; import { Dropzone, FilledButton, Typography } from '@neo4j-ndl/react'; import { useCallback, useState } from 'react'; import { type DropEvent, type DropzoneOptions, type FileRejection, } from 'react-dropzone'; // Configuration constants const CONFIG = { MAX_FILES: 3, MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB PROGRESS_INCREMENT: 5, UPDATE_INTERVAL: 100, ACCEPTED_EXTENSIONS: ['.txt', '.json'] as string[], } as const; enum UploadState { Idle = 'idle', Uploading = 'uploading', Success = 'success', Error = 'error', } interface UploadData { state: UploadState; progress: number; files: File[]; errors: string[]; } const Component = () => { const [uploadData, setUploadData] = useState<UploadData>({ errors: [], files: [], progress: 0, state: UploadState.Idle, }); const resetUpload = useCallback(() => { setUploadData({ errors: [], files: [], progress: 0, state: UploadState.Idle, }); }, []); const simulateUpload = useCallback((files: File[]) => { setUploadData((prev) => ({ ...prev, state: UploadState.Uploading, files, progress: 0, })); const interval = setInterval(() => { setUploadData((prev) => { const newProgress = prev.progress + CONFIG.PROGRESS_INCREMENT; if (newProgress >= 100) { clearInterval(interval); setTimeout( () => setUploadData((current) => ({ ...current, state: UploadState.Success, })), 500, ); return { ...prev, progress: 100 }; } return { ...prev, progress: newProgress }; }); }, CONFIG.UPDATE_INTERVAL); }, []); const handleDrop: DropzoneOptions['onDrop'] = useCallback( ( acceptedFiles: File[], rejectedFiles: FileRejection[], event: DropEvent, ) => { console.info('Drop event:', { acceptedFiles, event, rejectedFiles }); if (acceptedFiles.length > 0) { simulateUpload(acceptedFiles); } }, [simulateUpload], ); const handleDropRejected: DropzoneOptions['onDropRejected'] = useCallback( (rejectedFiles: FileRejection[]) => { const errors = rejectedFiles.map( (file) => `${file.file.name}: ${file.errors.map((e) => e.message).join(', ')}`, ); setUploadData((prev) => ({ ...prev, state: UploadState.Error, errors })); }, [], ); const handleError = useCallback((error: Error) => { console.error('Dropzone error:', error); setUploadData((prev) => ({ ...prev, state: UploadState.Error, errors: [error.message], })); }, []); const renderStatusContent = () => { const { state, progress, files, errors } = uploadData; switch (state) { case UploadState.Uploading: return ( <Dropzone.LoadingProgressBar progressBarPrecentage={Math.round(progress)} /> ); case UploadState.Success: return ( <div className="n-flex n-flex-col n-justify-center n-items-center n-gap-y-token-32"> <Typography variant="title-4" className="n-text-success-text n-font-semibold" > Upload Complete! </Typography> <Typography variant="body-small" className="n-text-neutral-text-weak" > Successfully uploaded: {files.map((file) => file.name).join(', ')} </Typography> <FilledButton onClick={resetUpload}>Upload More Files</FilledButton> </div> ); case UploadState.Error: return ( <div className="n-flex n-flex-col n-justify-center n-items-center n-gap-y-token-32"> <XMarkIcon className="n-w-token-32 n-h-token-32 n-text-danger-text" /> <div className="n-flex n-flex-col n-gap-y-token-2"> {errors.map((message, index) => ( <Typography key={index} variant="label" className="n-text-danger-text n-font-semibold" htmlAttributes={{ role: 'alert' }} > {message} </Typography> ))} </div> <FilledButton onClick={resetUpload}>Try Again</FilledButton> </div> ); default: return undefined; } }; return ( <Dropzone dropZoneOptions={{ disabled: uploadData.state === UploadState.Uploading, maxFiles: CONFIG.MAX_FILES, maxSize: CONFIG.MAX_FILE_SIZE, multiple: true, onDrop: handleDrop, onDropRejected: handleDropRejected, onError: handleError, }} supportedFilesDescription={ <div className="n-text-center n-flex n-flex-col n-gap-y-token-2"> <Typography variant="body-small" className="n-text-neutral-text-weak"> Supports: Text files (.txt, .json) only </Typography> <Typography variant="body-small" className="n-text-neutral-text-weak"> Max {CONFIG.MAX_FILES} files {' '} {CONFIG.MAX_FILE_SIZE / (1024 * 1024)}MB per file </Typography> </div> } acceptedFileExtensions={CONFIG.ACCEPTED_EXTENSIONS} loadingElement={renderStatusContent()} rejectedText="Error: Please make sure to only upload .json or .txt files" heading="Drag & Drop your files" /> ); }; export default Component; ``` ### Loading Progress ```tsx import { Dropzone } from '@neo4j-ndl/react'; const Component = () => { return ( <Dropzone loadingElement={ <Dropzone.LoadingProgressBar progressBarPrecentage={50} progressBarMinute={5} /> } dropZoneOptions={{ onDrop: () => void 0, }} /> ); }; export default Component; ```