- Create ChatFileDropZone component with drag-and-drop state and overlay - Add onFilesPicked/pendingFiles/onRemoveFile props to ChatInput - Wrap form in ChatFileDropZone for drag-and-drop support - Add handlePaste for clipboard image paste (clipboardData.files) - Add Paperclip icon button with hidden file input for file picker - Show pending file chips above textarea with progress and remove button - Add tests: renders file attach button, calls onFilesPicked, shows pending chips
67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
import { useState } from "react";
|
|
import { cn } from "../lib/utils";
|
|
|
|
interface ChatFileDropZoneProps {
|
|
onFilesDropped: (files: File[]) => void;
|
|
disabled?: boolean;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export function ChatFileDropZone({ onFilesDropped, disabled = false, children }: ChatFileDropZoneProps) {
|
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
|
|
function handleDragEnter(e: React.DragEvent) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (!disabled) setIsDragOver(true);
|
|
}
|
|
|
|
function handleDragLeave(e: React.DragEvent) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
// Only set false if leaving the outer element (not entering a child)
|
|
if (e.currentTarget === e.target || !e.currentTarget.contains(e.relatedTarget as Node)) {
|
|
setIsDragOver(false);
|
|
}
|
|
}
|
|
|
|
function handleDragOver(e: React.DragEvent) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (!disabled) setIsDragOver(true);
|
|
}
|
|
|
|
function handleDrop(e: React.DragEvent) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
setIsDragOver(false);
|
|
if (disabled) return;
|
|
const files = Array.from(e.dataTransfer.files);
|
|
if (files.length > 0) {
|
|
onFilesDropped(files);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="relative"
|
|
onDragEnter={handleDragEnter}
|
|
onDragLeave={handleDragLeave}
|
|
onDragOver={handleDragOver}
|
|
onDrop={handleDrop}
|
|
>
|
|
{children}
|
|
{isDragOver && (
|
|
<div
|
|
className={cn(
|
|
"absolute inset-0 z-10 flex items-center justify-center rounded-md",
|
|
"bg-primary/10 border-2 border-dashed border-primary",
|
|
"pointer-events-none",
|
|
)}
|
|
>
|
|
<span className="text-sm font-medium text-primary">Drop files here</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|