ui-nyla/src/components/UiComponents/DragDropOverlay/DragDropOverlay.tsx
2026-04-08 20:28:44 +02:00

195 lines
6 KiB
TypeScript

import React, { useState, useCallback } from 'react';
import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from './DragDropOverlay.module.css';
import { IoFolderOpen } from 'react-icons/io5';
export interface DragDropConfig {
enabled: boolean;
onDrop?: (files: File[]) => Promise<void> | void;
accept?: string; // MIME types or file extensions
multiple?: boolean;
disabled?: boolean;
overlayText?: string;
overlaySubtext?: string;
}
interface DragDropOverlayProps {
config: DragDropConfig;
children: React.ReactNode;
className?: string;
}
export function DragDropOverlay({
config,
children,
className = ''
}: DragDropOverlayProps) {
const { t } = useLanguage();
const [isDragOver, setIsDragOver] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const handleDragEnter = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (config.disabled || !config.enabled) return;
setIsDragOver(true);
}, [config.disabled, config.enabled]);
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
// Only hide overlay if we're leaving the container entirely
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
setIsDragOver(false);
}
}, []);
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
}, []);
const handleDrop = useCallback(async (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (config.disabled || !config.enabled) return;
setIsDragOver(false);
setIsProcessing(true);
try {
const files = Array.from(e.dataTransfer.files);
// Filter files by accept type if specified
let filteredFiles = files;
if (config.accept && config.accept !== '*/*') {
filteredFiles = files.filter(file => {
const acceptTypes = config.accept!.split(',').map(type => type.trim());
return acceptTypes.some(acceptType => {
// Handle MIME types
if (acceptType.startsWith('.')) {
return file.name.toLowerCase().endsWith(acceptType.toLowerCase());
}
// Handle MIME types like "image/*" or "application/pdf"
if (acceptType.includes('*')) {
const baseType = acceptType.split('/')[0];
return file.type.startsWith(baseType);
}
return file.type === acceptType;
});
});
}
console.log('🔍 DragDropOverlay file filtering:', {
originalFiles: files.length,
filteredFiles: filteredFiles.length,
accept: config.accept,
fileDetails: files.map(f => ({ name: f.name, type: f.type }))
});
// Respect multiple setting
const filesToProcess = config.multiple ? filteredFiles : filteredFiles.slice(0, 1);
if (filesToProcess.length > 0 && config.onDrop) {
console.log('🎯 DragDropOverlay calling onDrop with files:', filesToProcess);
await config.onDrop(filesToProcess);
console.log('✅ DragDropOverlay onDrop completed');
} else {
console.log('⚠️ DragDropOverlay: No files to process or no onDrop handler');
console.log('Files to process:', filesToProcess.length);
console.log('Has onDrop:', !!config.onDrop);
}
} catch (error) {
console.error(t('Fehler beim Verarbeiten der Dateien'), error);
} finally {
setIsProcessing(false);
}
}, [config, t]);
const handleFileInputChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
if (config.disabled || !config.enabled) return;
const files = Array.from(e.target.files || []);
console.log('🔍 DragDropOverlay file input:', {
fileCount: files.length,
accept: config.accept,
fileDetails: files.map(f => ({ name: f.name, type: f.type }))
});
if (files.length > 0 && config.onDrop) {
setIsProcessing(true);
try {
await config.onDrop(files);
} catch (error) {
console.error(t('Fehler beim Verarbeiten der Dateien'), error);
} finally {
setIsProcessing(false);
// Reset input
e.target.value = '';
}
}
}, [config, t]);
if (!config.enabled) {
return <>{children}</>;
}
return (
<div
className={`${styles.dragDropContainer} ${className}`}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
{children}
{/* Hidden file input for click-to-upload */}
<input
type="file"
accept={config.accept}
multiple={config.multiple}
onChange={handleFileInputChange}
className={styles.hiddenFileInput}
disabled={config.disabled}
/>
{/* Drag overlay */}
{isDragOver && (
<div className={styles.dragOverlay}>
<div className={styles.dragOverlayContent}>
<div className={styles.dragIcon}>
<IoFolderOpen />
</div>
<div className={styles.dragText}>
{config.overlayText || t('Dateien hier ablegen')}
</div>
{(config.overlaySubtext || !config.overlayText) && (
<div className={styles.dragSubtext}>
{config.overlaySubtext || t('Sie können auch auf den Upload-Button klicken')}
</div>
)}
</div>
</div>
)}
{/* Processing overlay */}
{isProcessing && (
<div className={styles.processingOverlay}>
<div className={styles.processingContent}>
<div className={styles.spinner}></div>
<div className={styles.processingText}>
{t('Dateien werden verarbeitet...')}
</div>
</div>
</div>
)}
</div>
);
}
export default DragDropOverlay;