import type { Icon, IconProps } from '@phosphor-icons/react'; import type { MIMETypeValue } from '@shared/types/MIMEType'; import type { ThemeColor } from '@shared/types/ThemeColors'; import { getThemeColorHexByName, getThemeColorRgbByName } from '@shared/util/themeColors'; import Text from '@views/components/common/Text/Text'; import clsx from 'clsx'; import React, { useEffect, useRef, useState } from 'react'; interface Props { className?: string; style?: React.CSSProperties; variant?: 'filled' | 'outline' | 'minimal'; size?: 'regular' | 'small' | 'mini'; onChange?: (event: React.ChangeEvent) => void; icon?: Icon; iconProps?: IconProps; disabled?: boolean; title?: string; color: ThemeColor; accept?: MIMETypeValue | MIMETypeValue[]; } /** * A reusable input button component that follows Button.tsx consistency. * Now supports drag-and-drop file uploads (issue #446). */ export default function FileUpload({ className, style, variant = 'filled', size = 'regular', onChange, icon, iconProps, disabled, title, color, accept, children, }: React.PropsWithChildren): JSX.Element { const Icon = icon; const isIconOnly = !children && !!icon; const colorHex = getThemeColorHexByName(color); const colorRgb = getThemeColorRgbByName(color)?.join(' '); const inputRef = useRef(null); const [isDragging, setIsDragging] = useState(false); // Convert accept array to comma-separated list const acceptValue = Array.isArray(accept) ? accept.join(',') : accept; // --- Prevent Chrome from opening the file on drop anywhere else --- useEffect(() => { const preventDefault = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); }; window.addEventListener('dragover', preventDefault); window.addEventListener('drop', preventDefault); return () => { window.removeEventListener('dragover', preventDefault); window.removeEventListener('drop', preventDefault); }; }, []); // ------------------------------------------------------------------ // --- Local drag and drop handlers for this button only ------------- const handleDrop = (event: React.DragEvent) => { event.preventDefault(); event.stopPropagation(); setIsDragging(false); if (disabled) return; const file = event.dataTransfer.files?.[0]; if (file && inputRef.current && onChange) { const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); inputRef.current.files = dataTransfer.files; // Trigger change event manually onChange({ target: inputRef.current } as React.ChangeEvent); } }; const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); event.stopPropagation(); if (!disabled) setIsDragging(true); }; const handleDragLeave = (event: React.DragEvent) => { event.preventDefault(); event.stopPropagation(); setIsDragging(false); }; // ------------------------------------------------------------------ return ( ); }