Files
dashfront/src/components/FileDropzone.jsx
2026-09-15 16:10:42 +02:00

109 lines
2.6 KiB
JavaScript

import { useState, useRef } from 'react';
import Box from '@mui/material/Box';
/**
* An otherwise invisible dropzone wrapper.
* Renders seamlessly around children without altering layout or style,
* and highlights with an outline only while a file is dragged over it.
*/
export function FileDropzone({
accept = 'application/pdf,.pdf',
disabled = false,
onFileDrop,
children,
sx,
...props
}) {
const [isDragging, setIsDragging] = useState(false);
const dragCounter = useRef(0);
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
if (disabled) return;
dragCounter.current += 1;
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
setIsDragging(true);
}
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter.current -= 1;
if (dragCounter.current <= 0) {
dragCounter.current = 0;
setIsDragging(false);
}
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
if (disabled) {
e.dataTransfer.dropEffect = 'none';
return;
}
e.dataTransfer.dropEffect = 'copy';
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter.current = 0;
setIsDragging(false);
if (disabled) return;
const file = e.dataTransfer.files?.[0];
if (!file) return;
if (accept) {
const allowed = accept
.split(',')
.map((s) => s.trim().toLowerCase())
.filter(Boolean);
const name = (file.name || '').toLowerCase();
const dotIndex = name.lastIndexOf('.');
const ext = dotIndex !== -1 ? name.slice(dotIndex) : '';
const mimeType = (file.type || '').toLowerCase();
const matches = allowed.some((item) => {
if (item.startsWith('.')) {
return ext === item;
}
return mimeType === item;
});
if (!matches) return;
}
onFileDrop?.(file);
};
return (
<Box
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
sx={{
display: 'inline-flex',
position: 'relative',
borderRadius: 1,
flexShrink: 0,
// Keep outline inset so it never affects external geometry or causes scrollbars:
outline: isDragging ? '2px dashed' : 'none',
outlineColor: 'primary.main',
outlineOffset: -2,
transition: 'outline 0.15s ease-in-out',
...sx,
}}
{...props}
>
{typeof children === 'function' ? children({ isDragging }) : children}
</Box>
);
}
export default FileDropzone;