| import { useRef, useState, type DragEvent } from "react"; |
| import { Upload } from "lucide-react"; |
| import { ACCEPTED_DATA_EXTS, MAX_DATA_FILE_SIZE } from "../utils/data-files"; |
|
|
| interface UploadZoneProps { |
| onFiles: (files: FileList | File[]) => void | Promise<void>; |
| disabled?: boolean; |
| compact?: boolean; |
| } |
|
|
| const ACCEPT_ATTR = ACCEPTED_DATA_EXTS.map((e) => `.${e}`).join(","); |
| const MAX_MB = (MAX_DATA_FILE_SIZE / (1024 * 1024)).toFixed(0); |
|
|
| export function UploadZone({ onFiles, disabled, compact }: UploadZoneProps) { |
| const inputRef = useRef<HTMLInputElement>(null); |
| const [isDragging, setIsDragging] = useState(false); |
|
|
| const handleDrop = (e: DragEvent<HTMLDivElement>) => { |
| e.preventDefault(); |
| setIsDragging(false); |
| if (disabled) return; |
| if (e.dataTransfer.files?.length) void onFiles(e.dataTransfer.files); |
| }; |
|
|
| const handleDragOver = (e: DragEvent<HTMLDivElement>) => { |
| e.preventDefault(); |
| if (!disabled) setIsDragging(true); |
| }; |
|
|
| const handleDragLeave = () => setIsDragging(false); |
|
|
| return ( |
| <div |
| className={`es-upload-zone ${isDragging ? "es-upload-zone--active" : ""} ${compact ? "es-upload-zone--compact" : ""}`} |
| onDrop={handleDrop} |
| onDragOver={handleDragOver} |
| onDragLeave={handleDragLeave} |
| onClick={() => !disabled && inputRef.current?.click()} |
| role="button" |
| tabIndex={disabled ? -1 : 0} |
| aria-label="Upload data file" |
| > |
| <input |
| ref={inputRef} |
| type="file" |
| accept={ACCEPT_ATTR} |
| multiple |
| onChange={(e) => { |
| if (e.target.files?.length) void onFiles(e.target.files); |
| e.target.value = ""; |
| }} |
| style={{ display: "none" }} |
| /> |
| <Upload size={compact ? 14 : 18} /> |
| {compact ? ( |
| <span>Add data file</span> |
| ) : ( |
| <div className="es-upload-zone__text"> |
| <strong>Drop a data file</strong> |
| <span>CSV, TSV, JSON, NDJSON, TXT - up to {MAX_MB} MB</span> |
| </div> |
| )} |
| </div> |
| ); |
| } |
|
|