/** * SidebarDropZone — compact scoped drop zone for sidebar panels and narrow containers. * * Unlike the full-window DropZone, this renders as an inline element * that can be placed inside sidebar panels, file trees, or any container. * Shows a subtle drop target when files are dragged over it. * * Usage: * * * * */ import { useCallback, useRef, useState, type DragEvent, type ReactNode } from "react"; import { Upload } from "lucide-react"; import { cn } from "../lib/utils"; export interface SidebarDropZoneProps { /** Called with dropped files */ onDrop: (files: File[]) => void; /** Accepted file types (e.g. ".pdf,.csv") */ accept?: string; /** Whether drop zone is active */ disabled?: boolean; /** Title shown in the drop zone */ title?: string; /** Description shown below the title */ description?: string; /** Custom icon (replaces default upload icon) */ icon?: ReactNode; /** Always visible (not just on drag). Useful as a persistent upload area. */ persistent?: boolean; className?: string; } export function SidebarDropZone({ onDrop, accept, disabled, title = "Drop files here", description, icon, persistent = false, className, }: SidebarDropZoneProps) { 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(); e.stopPropagation(); if (disabled) return; counter.current++; if (e.dataTransfer?.types.includes("Files")) setDragOver(true); }, [disabled], ); const handleDragLeave = useCallback((e: DragEvent) => { e.preventDefault(); e.stopPropagation(); counter.current--; if (counter.current === 0) setDragOver(false); }, []); const handleDragOver = useCallback( (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); if (!disabled) e.dataTransfer.dropEffect = "copy"; }, [disabled], ); const handleDrop = useCallback( (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); 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], ); const isVisible = persistent || dragOver; return (
{isVisible && (
{icon ?? }

{title}

{description && (

{description}

)}
)}
); }