Spaces:
Configuration error
Configuration error
File size: 3,130 Bytes
a8f12e6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | import { useRef, useState } from "react";
const ACCEPTED = ["image/jpeg", "image/png", "image/webp"];
const MAX_BYTES = 10 * 1024 * 1024;
export default function UploadZone({ onFileSelected, disabled = false }) {
const inputRef = useRef(null);
const [dragActive, setDragActive] = useState(false);
const validate = (file) => {
if (!file) return "No file selected.";
if (!ACCEPTED.includes(file.type)) {
return "Unsupported format. Use JPG, PNG, or WEBP.";
}
if (file.size > MAX_BYTES) {
return "File exceeds 10 MB limit.";
}
return null;
};
const handleFile = (file) => {
const error = validate(file);
onFileSelected(file ?? null, error);
};
const onDrop = (e) => {
e.preventDefault();
setDragActive(false);
if (disabled) return;
const file = e.dataTransfer?.files?.[0];
if (file) handleFile(file);
};
const onDragOver = (e) => {
e.preventDefault();
if (!disabled) setDragActive(true);
};
const onDragLeave = (e) => {
e.preventDefault();
setDragActive(false);
};
const onChange = (e) => {
const file = e.target.files?.[0];
if (file) handleFile(file);
e.target.value = "";
};
return (
<div
onDrop={onDrop}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onClick={() => !disabled && inputRef.current?.click()}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if ((e.key === "Enter" || e.key === " ") && !disabled) {
e.preventDefault();
inputRef.current?.click();
}
}}
className={[
"relative w-full rounded-2xl border border-dashed transition-all duration-200 select-none",
"flex flex-col items-center justify-center text-center px-6 py-16",
disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer",
dragActive
? "border-violet-400/80 bg-violet-500/10 shadow-[0_0_0_4px_rgba(139,92,246,0.12)]"
: "border-white/15 bg-white/[0.02] hover:border-white/25 hover:bg-white/[0.04]",
].join(" ")}
>
<input
ref={inputRef}
type="file"
accept={ACCEPTED.join(",")}
onChange={onChange}
className="hidden"
disabled={disabled}
/>
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-violet-500/20 to-fuchsia-500/20 border border-white/10 flex items-center justify-center mb-4">
<svg
viewBox="0 0 24 24"
className="w-7 h-7 text-violet-300"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
</div>
<p className="text-white/90 text-base font-medium">
{dragActive ? "Drop to upload" : "Drag & drop an image"}
</p>
<p className="text-white/40 text-sm mt-1">
or click to browse — JPG, PNG, WEBP · max 10 MB
</p>
</div>
);
}
|