Some checks failed
Build & Deploy / build-and-deploy (push) Failing after 8s
Includes React PWA frontend, WebSocket signaling server, shared types, K8s manifests, Gitea CI/CD workflow, nginx config, and Dockerfiles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
102 lines
2.7 KiB
TypeScript
102 lines
2.7 KiB
TypeScript
import { useState, useRef, useCallback } from "react";
|
|
|
|
interface DropZoneProps {
|
|
onFilesSelected: (files: File[]) => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export default function DropZone({ onFilesSelected, disabled }: DropZoneProps) {
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const dragCounter = useRef(0);
|
|
|
|
const handleDragEnter = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
dragCounter.current++;
|
|
if (e.dataTransfer.items.length > 0) {
|
|
setIsDragging(true);
|
|
}
|
|
}, []);
|
|
|
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
dragCounter.current--;
|
|
if (dragCounter.current === 0) {
|
|
setIsDragging(false);
|
|
}
|
|
}, []);
|
|
|
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
}, []);
|
|
|
|
const handleDrop = useCallback(
|
|
(e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragging(false);
|
|
dragCounter.current = 0;
|
|
if (disabled) return;
|
|
|
|
const files = Array.from(e.dataTransfer.files);
|
|
if (files.length > 0) {
|
|
onFilesSelected(files);
|
|
}
|
|
},
|
|
[onFilesSelected, disabled],
|
|
);
|
|
|
|
const handleClick = () => {
|
|
if (!disabled) {
|
|
inputRef.current?.click();
|
|
}
|
|
};
|
|
|
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const files = Array.from(e.target.files || []);
|
|
if (files.length > 0) {
|
|
onFilesSelected(files);
|
|
}
|
|
// Reset to allow re-selecting the same file
|
|
e.target.value = "";
|
|
};
|
|
|
|
return (
|
|
<div
|
|
onDragEnter={handleDragEnter}
|
|
onDragLeave={handleDragLeave}
|
|
onDragOver={handleDragOver}
|
|
onDrop={handleDrop}
|
|
onClick={handleClick}
|
|
className={`
|
|
border-2 border-dashed rounded-2xl p-8
|
|
flex flex-col items-center justify-center gap-3
|
|
transition-all duration-200 cursor-pointer
|
|
min-h-[160px]
|
|
${isDragging
|
|
? "border-brand-400 bg-brand-500/10 scale-[1.02]"
|
|
: disabled
|
|
? "border-slate-700 bg-slate-900/50 cursor-not-allowed opacity-50"
|
|
: "border-slate-700 hover:border-slate-500 bg-slate-900/30 hover:bg-slate-900/50"
|
|
}
|
|
`}
|
|
>
|
|
<div className="text-4xl">{isDragging ? "📥" : "📁"}</div>
|
|
<p className="text-slate-400 text-sm text-center">
|
|
{disabled
|
|
? "Sélectionnez d'abord un appareil"
|
|
: isDragging
|
|
? "Déposez vos fichiers ici"
|
|
: "Glissez des fichiers ici ou cliquez pour sélectionner"
|
|
}
|
|
</p>
|
|
<input
|
|
ref={inputRef}
|
|
type="file"
|
|
multiple
|
|
className="hidden"
|
|
onChange={handleFileChange}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|