Files
multica/packages/views/editor/use-file-drop-zone.ts
Naiyuan Qing 66cb5d924a fix(editor): lift drag-drop overlay to outer container for better UX
When editors are empty, the internal drop overlay was too small to be
useful. Move the overlay to the parent container with a lighter style
so the drop target covers the full input area regardless of content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:18:38 +08:00

70 lines
1.8 KiB
TypeScript

import { useState, useEffect, useCallback, useRef, type DragEvent } from "react";
interface UseFileDropZoneOptions {
onDrop: (files: File[]) => void;
enabled?: boolean;
}
function useFileDropZone({ onDrop, enabled = true }: UseFileDropZoneOptions) {
const [isDragOver, setIsDragOver] = useState(false);
const onDropRef = useRef(onDrop);
onDropRef.current = onDrop;
// Clear on any document-level drop or dragend (e.g. user drops outside the zone)
useEffect(() => {
if (!enabled) return;
const clear = () => setIsDragOver(false);
document.addEventListener("drop", clear);
document.addEventListener("dragend", clear);
return () => {
document.removeEventListener("drop", clear);
document.removeEventListener("dragend", clear);
};
}, [enabled]);
const handleDragEnter = useCallback(
(e: DragEvent) => {
e.preventDefault();
if (enabled && e.dataTransfer.types.includes("Files")) {
setIsDragOver(true);
}
},
[enabled],
);
const handleDragOver = useCallback((e: DragEvent) => {
e.preventDefault();
}, []);
const handleDragLeave = useCallback((e: DragEvent) => {
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
setIsDragOver(false);
}
}, []);
const handleDrop = useCallback(
(e: DragEvent) => {
const alreadyHandled = e.nativeEvent.defaultPrevented;
e.preventDefault();
setIsDragOver(false);
if (alreadyHandled || !enabled) return;
const files = e.dataTransfer?.files;
if (files?.length) {
onDropRef.current(Array.from(files));
}
},
[enabled],
);
const dropZoneProps = {
onDragEnter: handleDragEnter,
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onDrop: handleDrop,
};
return { isDragOver: enabled && isDragOver, dropZoneProps };
}
export { useFileDropZone };