import { useState, useRef, useCallback } from "react"; import { Button } from "@storyteller/ui-button"; import { twMerge } from "tailwind-merge"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faUpload, faImages, faTrash } from "@fortawesome/pro-solid-svg-icons"; import { toast } from "react-hot-toast"; export interface ImageFile { id: string; url: string; file: File; mediaToken?: string; } export interface ImageInputProps { /** Selected image */ value?: ImageFile | null; /** Callback when image is selected or removed */ onChange: (image: ImageFile | null) => void; /** Handler for opening gallery modal */ onGalleryOpen?: () => void; /** Show gallery button */ showGalleryButton?: boolean; /** Custom placeholder text */ placeholderText?: string; /** Additional class names */ className?: string; } export function ImageInput({ value, onChange, onGalleryOpen, showGalleryButton = true, placeholderText = "Drag and drop an image here", className, }: ImageInputProps) { const [isDragging, setIsDragging] = useState(false); const fileInputRef = useRef(null); const processImageFile = (file: File) => { // Only accept image files if (!file.type.startsWith("image/")) { toast.error("Please upload an image file"); return; } const reader = new FileReader(); reader.onloadend = () => { const newImage: ImageFile = { id: Math.random().toString(36).substring(7), url: reader.result as string, file: file, }; onChange(newImage); }; reader.readAsDataURL(file); }; const handleFileUpload = (event: React.ChangeEvent) => { const files = event.target.files; if (files && files[0]) { const file = files[0]; processImageFile(file); } }; const handleUploadClick = () => { fileInputRef.current?.click(); }; // Drag and drop handlers // Enhanced drag and drop handlers for better desktop compatibility const handleDragEnter = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }, []); const handleDragOver = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (!isDragging) setIsDragging(true); // Explicitly set effectAllowed to make sure the browser shows the correct drop effect if (e.dataTransfer) { e.dataTransfer.dropEffect = "copy"; } }, [isDragging] ); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); // Only set isDragging to false if we're leaving the drop target // and not entering a child element const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect(); const x = e.clientX; const y = e.clientY; if (x < rect.left || x >= rect.right || y < rect.top || y >= rect.bottom) { setIsDragging(false); } }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); // Debug logging console.log("Files dropped:", e.dataTransfer.files); console.log("DataTransfer types:", e.dataTransfer.types); try { // First try the files collection if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { const file = e.dataTransfer.files[0]; // Just take the first file console.log("Processing file:", file.name, file.type, file.size); processImageFile(file); return; } // Then try the items collection const items = e.dataTransfer.items; if (items && items.length > 0) { for (let i = 0; i < items.length; i++) { if (items[i].kind === "file") { const file = items[i].getAsFile(); if (file) { console.log("Processing item as file:", file.name); processImageFile(file); return; } } } } // If we got here, we couldn't process the drop console.log("Could not process dropped content"); toast.error( "Could not process the dropped file. Please try uploading instead." ); } catch (error) { console.error("Error processing drop:", error); toast.error( "Error processing the dropped file. Please try uploading instead." ); } }, []); return (
{/* Drag & drop area or image preview */}
e.preventDefault()} > {value ? (
Selected image
) : (

{placeholderText}

or

{showGalleryButton && onGalleryOpen && ( )}
)}
{/* Hidden file input */} {/* Action buttons when image is selected */} {value && (
{showGalleryButton && onGalleryOpen && ( )}
)}
); } export default ImageInput;