TryGhost/Ghost

View on GitHub
apps/admin-x-design-system/src/global/form/FileUpload.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
import React, {CSSProperties, ChangeEvent, useState} from 'react';
import clsx from 'clsx';

export interface FileUploadProps {
    id: string;

    /**
     * Can be string or any component that has no default onClick eventh handline.
     * E.g. buttons and links won't work. If it's text then it's styled as the
     * default button.
     */
    children?: string | React.ReactNode;
    className?: string;
    dragIndicatorClassName?: string;
    onUpload: (file: File) => void;
    style?: CSSProperties;
    unstyled?: boolean;
    inputRef?: React.RefObject<HTMLInputElement>;
}

const FileUpload: React.FC<FileUploadProps> = ({id, onUpload, children, style, unstyled = false, inputRef, className, dragIndicatorClassName, ...props}) => {
    const [fileKey, setFileKey] = useState<number>(Date.now());
    const [isDragging, setIsDragging] = useState(false);

    const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
        const selectedFile = event.target.files?.[0];
        if (selectedFile) {
            onUpload?.(selectedFile);
        }
        setFileKey(Date.now());
    };

    const handleDrop = (event: React.DragEvent<HTMLLabelElement>) => {
        handleStopDragging(event);
        const selectedFile = event.dataTransfer.files?.[0];
        if (selectedFile) {
            onUpload?.(selectedFile);
        }
        setFileKey(Date.now());
    };

    const handleDragging = (event: React.DragEvent<HTMLLabelElement>) => {
        event.preventDefault();
        setIsDragging(true);
    };

    const handleStopDragging = (event: React.DragEvent<HTMLLabelElement>) => {
        event.preventDefault();
        setIsDragging(false);
    };

    return (
        <label className={clsx('relative', className)} htmlFor={id} style={style} onDragEnter={handleDragging} onDragLeave={handleStopDragging} onDragOver={handleDragging} onDrop={handleDrop} {...props}>
            {isDragging && <div className={clsx('absolute inset-1 rounded border-2 border-dashed border-grey-400/25', dragIndicatorClassName)} />}
            <input key={fileKey} ref={inputRef || null} id={id} type="file" hidden onChange={handleFileChange} />
            {(typeof children === 'string') ?
                <div className={!unstyled ? `inline-flex h-[34px] cursor-pointer items-center justify-center rounded px-4 text-sm font-semibold hover:bg-grey-100 dark:text-white dark:hover:bg-grey-900` : ''}>
                    {children}
                </div>
                :
                children}
        </label>
    );
};

export default FileUpload;