/** * DropZone — full-window drag-and-drop file upload overlay. * * Detects when files are dragged over the browser window and shows a * customizable overlay. Products provide the onDrop handler and optional * custom overlay content. * * Usage: * uploadToR2(files)} accept=".pdf,.csv"> * * */ import { useCallback, useRef, useState, type DragEvent, type ReactNode } from "react"; import { cn } from "../lib/utils"; export interface DropZoneProps { /** Called with dropped files */ onDrop: (files: File[]) => void; /** Accepted file types (e.g. ".pdf,.csv,.xlsx") */ accept?: string; /** Whether drop zone is active */ disabled?: boolean; /** Custom overlay content (replaces default) */ overlay?: ReactNode; /** Overlay title */ title?: string; /** Overlay description */ description?: string; /** Overlay icon (Material Symbols name or ReactNode) */ icon?: string | ReactNode; /** Children wrapped by the drop zone */ children: ReactNode; className?: string; } export function DropZone({ onDrop, accept, disabled, overlay, title = "Drop files to upload", description = "Your files will be securely stored in the workspace.", icon = "cloud_upload", children, className, }: DropZoneProps) { const [dragOver, setDragOver] = useState(false); const counter = useRef(0); const isAccepted = useCallback( (file: File) => { if (!accept) return true; const extensions = accept.split(",").map((ext) => ext.trim().toLowerCase()); const fileName = file.name.toLowerCase(); return extensions.some((ext) => fileName.endsWith(ext)); }, [accept], ); const handleDragEnter = useCallback( (e: DragEvent) => { e.preventDefault(); if (disabled) return; counter.current++; if (e.dataTransfer?.types.includes("Files")) setDragOver(true); }, [disabled], ); const handleDragLeave = useCallback((e: DragEvent) => { e.preventDefault(); counter.current--; if (counter.current === 0) setDragOver(false); }, []); const handleDragOver = useCallback( (e: DragEvent) => { e.preventDefault(); if (!disabled) e.dataTransfer.dropEffect = "copy"; }, [disabled], ); const handleDrop = useCallback( (e: DragEvent) => { e.preventDefault(); counter.current = 0; setDragOver(false); if (disabled) return; const allFiles = Array.from(e.dataTransfer?.files || []); const accepted = accept ? allFiles.filter(isAccepted) : allFiles; if (accepted.length > 0) onDrop(accepted); }, [disabled, accept, isAccepted, onDrop], ); return (
{dragOver && (overlay || (
{typeof icon === "string" ? ( Upload ) : ( icon )}

{title}

{description}

))} {children}
); }