// @flow strict import * as React from 'react'; // $FlowFixMe[untyped-import] -- this should be fixed soon import { type UseFileUploadReturnProps, useFileUpload, } from '../../hooks/useFileUpload'; import classify from '../../utils/classify'; import {UnstyledButton} from '../Button'; import {Truncate} from '../Truncate'; import {FileBlock} from './FileBlock'; import css from './FileUpload.module.css'; type ClassNames = $ReadOnly<{ wrapper?: string, instruction?: string, secondaryInstruction?: string, dropZone?: string, files?: string, }>; export type FileProgress = number | 'indeterminate'; export type FileObject = { file: File, id: string, reject?: boolean, rejectReason?: string, progress?: FileProgress, success?: boolean, successMessage?: string, // This is a flag that is used to show/hide re-upload button showReUpload?: boolean, }; // This is a file error object that is passed to onRejectedFilesDrop callback in useFileUpload hook export type FileError = { code: string, }; // This is a file rejection object that is passed to handleDropRejected function in useFileUpload hook export type FileRejection = { errors: Array, file: File, ... }; // This is a ref object that is passed to FileUpload component for managing state of a single file export type FileUploadRef = { moveFileToProgress: (id: string, progress: FileProgress) => mixed, moveFileToSuccess: (id: string, successMessage?: string) => mixed, moveFileToReject: (id: string, rejectReason?: string) => mixed, setShowReUpload: (id: string, showReUpload?: boolean) => mixed, handleFileClear: (id: string) => mixed, validFiles: Array, rejectedFiles: Array, files: Array, }; // These props are shared between FileUpload component and useFileUpload hook export type FileUploadBaseProps = { maxFiles?: number, maxSize?: number, accept?: {[string]: string[]}, disabled?: boolean, // File drop callbacks onValidFilesDrop?: (acceptedFiles: Array) => mixed, onRejectedFilesDrop?: (fileRejections: Array) => mixed, // File clear callbacks onFileClear?: (id: string) => mixed, onClear?: () => mixed, }; export type FileUploadProps = { ...FileUploadBaseProps, classNames?: ClassNames, label?: React.Node, instruction?: React.Node, draggingInstruction?: React.Node, secondaryInstruction?: React.Node, required?: boolean, handleFileDeletionExternally?: boolean, // File refresh callback onFileRefreshClick?: (file: FileObject) => mixed, }; const FileUploadBase = (props: FileUploadProps, ref) => { const { classNames, label = 'Upload File', disabled = false, instruction = 'Drag and drop or click to upload', draggingInstruction = 'Drop here to start uploading..', error = false, required = false, secondaryInstruction = '', maxSize, accept, onValidFilesDrop, onRejectedFilesDrop, onFileClear, onFileRefreshClick, maxFiles = 1, handleFileDeletionExternally, } = props; // Get file upload state from useFileUpload hook const { validFiles, rejectedFiles, isDragActive, getRootProps, shouldAcceptFiles, getInputProps, handleFileClear, moveFileToProgress, moveFileToSuccess, moveFileToReject, setShowReUpload, }: UseFileUploadReturnProps = useFileUpload({ maxFiles, maxSize, accept, disabled, onValidFilesDrop, onRejectedFilesDrop, onFileClear, }); // Expose file upload actions to parent component React.useImperativeHandle(ref, () => ({ moveFileToProgress, moveFileToSuccess, moveFileToReject, handleFileClear, setShowReUpload, validFiles, rejectedFiles, files: [...validFiles, ...rejectedFiles], })); // Merge valid and rejected files const files = [...validFiles, ...rejectedFiles]; return (
{Boolean(label) && (
{label} {required && {'*'}}
)}
{isDragActive ? draggingInstruction : instruction}
{secondaryInstruction}
{files.length > 0 && (
{files.map((fileObject: FileObject) => ( ))}
)}
); }; export const FileUpload: React.AbstractComponent< FileUploadProps, FileUploadRef, > = React.forwardRef(FileUploadBase);