import { PlusCircle as PlusIcon, Upload as UploadIcon } from '@transferwise/icons'; import { clsx } from 'clsx'; import { ChangeEvent, DragEvent, useRef, useState, forwardRef, useImperativeHandle, ForwardedRef, } from 'react'; import { useIntl } from 'react-intl'; import Body from '../../body'; import { FileType, Typography } from '../../common'; import MESSAGES from './UploadButton.messages'; import { DEFAULT_SIZE_LIMIT, imageFileTypes } from './defaults'; import getAllowedFileTypes from './getAllowedFileTypes'; type AllowedFileTypes = string | readonly string[] | readonly FileType[]; export type UploadButtonProps = { /** * Disable the upload button if your app is not yet ready to accept uploads */ disabled?: boolean; /** * Should be true, if the UploadInput has at least 1 * file (valid or invalid) listed. */ withEntries?: boolean; /** * Allow multiple file uploads */ multiple?: boolean; /** * List of allowed filetypes, eg. '*' | '.zip,application/zip' | ['.jpg,.jpeg,image/jpeg', '.png,image/png'] (default: image files + PDF) */ fileTypes?: AllowedFileTypes; /** * Size limit in KBs (1000 KB = 1 MB). * If set to `null`, no size limit will be applied. * @default 5000 */ sizeLimit?: number | null; /** * Description for the upload button */ description?: string | undefined; /** * Maximum number of files allowed, if provided, shows error below file item */ maxFiles?: number; /** * Called when some files were successfully selected * * @param files */ onChange: (files: FileList) => void; /** * Id for the upload input */ id?: string; /** * Title for the upload button */ uploadButtonTitle?: string; }; export enum TEST_IDS { uploadInput = 'uploadInput', mediaBody = 'mediaBody', } const onDragOver = (event: DragEvent): void => { event.preventDefault(); }; const DEFAULT_FILE_INPUT_ID = 'np-upload-button'; const UploadButton = forwardRef( ( { disabled, withEntries, multiple, description, fileTypes = imageFileTypes, sizeLimit = DEFAULT_SIZE_LIMIT, maxFiles, onChange, id = DEFAULT_FILE_INPUT_ID, uploadButtonTitle, }, ref: ForwardedRef, ) => { const { formatMessage } = useIntl(); const inputRef = useRef(null); useImperativeHandle(ref, () => { if (!inputRef.current) { throw new Error('inputRef.current is null'); } return inputRef.current; }, []); const [isDropping, setIsDropping] = useState(false); const dragCounter = useRef(0); const reset = (): void => { dragCounter.current = 0; setIsDropping(false); }; const onDragLeave = (event: DragEvent): void => { event.preventDefault(); dragCounter.current -= 1; if (dragCounter.current === 0) { setIsDropping(false); } }; const onDragEnter = (event: DragEvent): void => { event.preventDefault(); dragCounter.current += 1; if (dragCounter.current === 1) { setIsDropping(true); } }; const onDrop = (event: DragEvent): void => { event.preventDefault(); reset(); if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) { onChange(event.dataTransfer.files); } }; const filesSelected = (event: ChangeEvent): void => { const { files } = event.target; if (files) { onChange(files); if (inputRef.current) { inputRef.current.value = ''; } } }; const getFileTypesDescription = (): string => { if (fileTypes === '*') { return fileTypes; } return getAllowedFileTypes(Array.isArray(fileTypes) ? fileTypes : [fileTypes]).join(', '); }; function getDescription() { if (description) { return description; } const fileTypesDescription = getFileTypesDescription(); const derivedFileDescription = fileTypesDescription === '*' ? formatMessage(MESSAGES.allFileTypes) : fileTypesDescription; if (typeof sizeLimit === 'number') { return formatMessage(MESSAGES.instructions, { fileTypes: derivedFileDescription, size: Math.round(sizeLimit / 1000), }); } return derivedFileDescription; } function getAcceptedTypes(): Pick, 'accept'> { const areAllFilesAllowed = getFileTypesDescription() === '*'; if (areAllFilesAllowed) { return {}; // file input by default allows all files } if (Array.isArray(fileTypes)) { return { accept: fileTypes.join(',') }; } return { accept: fileTypes as string }; } function renderDescription() { return ( {getDescription()} {maxFiles && ( <>
{formatMessage(MESSAGES.maximumFiles, { maxFiles })} )} ); } function renderButtonTitle() { if (uploadButtonTitle) { return uploadButtonTitle; } return formatMessage(multiple ? MESSAGES.uploadFiles : MESSAGES.uploadFile); } return ( ); }, ); UploadButton.displayName = 'UploadButton'; export default UploadButton;