import { useCallback, useRef, useState, type ReactNode } from "react"; interface DropZoneProps { accept?: string; multiple?: boolean; onFiles: (files: File[]) => void; children?: ReactNode; className?: string; label?: string; hint?: string; disabled?: boolean; } export default function DropZone({ accept = "image/*", multiple = false, onFiles, children, className = "", label = "拖入文件或点击上传", hint, disabled = false, }: DropZoneProps) { const [isDragging, setIsDragging] = useState(false); const inputRef = useRef(null); const dragCounter = useRef(0); const handleDragEnter = useCallback((e: React.DragEvent) => { e.preventDefault(); dragCounter.current += 1; setIsDragging(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); dragCounter.current -= 1; if (dragCounter.current <= 0) { dragCounter.current = 0; setIsDragging(false); } }, []); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); dragCounter.current = 0; setIsDragging(false); if (disabled) return; const acceptTypes = accept.split(",").map((t) => t.trim()); const files = Array.from(e.dataTransfer.files).filter((f) => acceptTypes.some((t) => { if (t.endsWith("/*")) return f.type.startsWith(t.replace("/*", "/")); return f.type === t || f.name.endsWith(t); }), ); if (files.length) onFiles(multiple ? files : files.slice(0, 1)); }, [accept, disabled, multiple, onFiles], ); const handleChange = useCallback( (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); if (files.length) onFiles(multiple ? files : files.slice(0, 1)); e.target.value = ""; }, [multiple, onFiles], ); return (
e.preventDefault()} onDragLeave={handleDragLeave} onDrop={handleDrop} onClick={() => !disabled && inputRef.current?.click()} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") inputRef.current?.click(); }} > {children || ( <> {label} {hint && {hint}} )}
); }