import { i18nMessage } from "../i18n/LanguageContext";import { useRef, useState, type CSSProperties, type DragEvent as ReactDragEvent, type ReactNode } from "react"; import { UploadCloud } from "lucide-react"; import { resolveDroppedFiles } from "./resolveDroppedFiles"; type RejectedDropReason = "disabled" | "empty" | "unreadable"; export type FileDropZoneProps = { accept?: string; multiple?: boolean; disabled?: boolean; busy?: boolean; progress?: number | null; label?: ReactNode; actionLabel?: ReactNode; busyLabel?: ReactNode; progressLabel?: ReactNode; note?: ReactNode; className?: string; inputLabel?: string; onRejectedDrop?: (reason: RejectedDropReason) => void; onFiles: (files: File[]) => void | Promise; }; export default function FileDropZone({ accept, multiple = true, disabled = false, busy = false, progress = null, label = "i18n:govoplan-core.drop_files_here.77348907", actionLabel = "i18n:govoplan-core.or_click_to_select_files.91b05dc1", busyLabel = "i18n:govoplan-core.uploading_files.6536791d", progressLabel, note, className = "", inputLabel = "i18n:govoplan-core.drop_files_here_or_click_to_select_files.7eda8608", onRejectedDrop, onFiles }: FileDropZoneProps) { const inputRef = useRef(null); const [dragActive, setDragActive] = useState(false); const progressValue = typeof progress === "number" && Number.isFinite(progress) ? Math.max(0, Math.min(100, progress)) : null; const showProgress = busy || progressValue !== null; const interactionDisabled = disabled || busy; const zoneClassName = `file-drop-zone ${dragActive ? "is-active" : ""} ${showProgress ? "is-busy" : ""} ${className}`.trim(); const roundedProgress = progressValue === null ? null : Math.round(progressValue); const progressStyle = progressValue === null ? undefined : { "--file-drop-progress": `${progressValue}%` } as CSSProperties; async function handleFiles(fileList: FileList | File[]) { if (interactionDisabled) return; const files = Array.from(fileList); if (files.length === 0) return; try { await onFiles(files); } finally { if (inputRef.current) inputRef.current.value = ""; } } function prepareFileDrag(event: ReactDragEvent) { event.preventDefault(); event.stopPropagation(); event.dataTransfer.dropEffect = interactionDisabled ? "none" : "copy"; if (!interactionDisabled) setDragActive(true); } return ( <>
{ if (!interactionDisabled) inputRef.current?.click(); }} onKeyDown={(event) => { if ((event.key === "Enter" || event.key === " ") && !interactionDisabled) { event.preventDefault(); inputRef.current?.click(); } }} onDragEnter={prepareFileDrag} onDragOver={prepareFileDrag} onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)) return; setDragActive(false); }} onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setDragActive(false); if (interactionDisabled) { onRejectedDrop?.("disabled"); return; } void resolveDroppedFiles(event.dataTransfer).then((files) => { if (files.length === 0) { onRejectedDrop?.("empty"); return; } void handleFiles(files); }).catch(() => onRejectedDrop?.("unreadable")); }}> {showProgress ? {roundedProgress === null ? "" : i18nMessage("i18n:govoplan-core.value.8c5f2c0e", { value0: roundedProgress })} :
event.target.files && void handleFiles(event.target.files)} /> ); }