Spaces:
Running
Running
| import { useState, useRef, useCallback } from "react"; | |
| interface UploadZoneProps { | |
| onFile: (file: File) => void; | |
| disabled?: boolean; | |
| } | |
| const ACCEPTED = ".wav,.mp3,.flac,.ogg,.m4a,.aac,.wma,.opus"; | |
| const MAX_SIZE = 100 * 1024 * 1024; | |
| export function UploadZone({ onFile, disabled }: UploadZoneProps) { | |
| const [isDragOver, setIsDragOver] = useState(false); | |
| const inputRef = useRef<HTMLInputElement>(null); | |
| const handleFile = useCallback( | |
| (file: File) => { | |
| if (file.size > MAX_SIZE) { | |
| alert("File too large (max 100MB)"); | |
| return; | |
| } | |
| onFile(file); | |
| }, | |
| [onFile] | |
| ); | |
| const handleDrop = useCallback( | |
| (e: React.DragEvent) => { | |
| e.preventDefault(); | |
| setIsDragOver(false); | |
| const file = e.dataTransfer.files[0]; | |
| if (file) handleFile(file); | |
| }, | |
| [handleFile] | |
| ); | |
| const handleChange = useCallback( | |
| (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const file = e.target.files?.[0]; | |
| if (file) handleFile(file); | |
| }, | |
| [handleFile] | |
| ); | |
| return ( | |
| <div | |
| className={` | |
| relative rounded-xl border-2 border-dashed p-8 md:p-12 | |
| text-center cursor-pointer transition-all duration-200 | |
| ${ | |
| isDragOver | |
| ? "border-accent bg-accent/10" | |
| : "border-border hover:border-text-secondary hover:bg-bg-hover/50" | |
| } | |
| ${disabled ? "opacity-50 pointer-events-none" : ""} | |
| `} | |
| onDragOver={(e) => { | |
| e.preventDefault(); | |
| setIsDragOver(true); | |
| }} | |
| onDragLeave={() => setIsDragOver(false)} | |
| onDrop={handleDrop} | |
| onClick={() => inputRef.current?.click()} | |
| > | |
| <input | |
| ref={inputRef} | |
| type="file" | |
| accept={ACCEPTED} | |
| className="hidden" | |
| onChange={handleChange} | |
| /> | |
| <div className="flex flex-col items-center gap-3"> | |
| <svg | |
| className="w-12 h-12 text-text-secondary" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke="currentColor" | |
| strokeWidth={1.5} | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| d="M9 8.25H7.5a2.25 2.25 0 00-2.25 2.25v9a2.25 2.25 0 002.25 2.25h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25H15m0-3l-3-3m0 0l-3 3m3-3v11.25" | |
| /> | |
| </svg> | |
| <div> | |
| <p className="text-base md:text-lg font-medium text-text-primary"> | |
| Drop audio file here or click to browse | |
| </p> | |
| <p className="text-sm text-text-secondary mt-1"> | |
| WAV, MP3, FLAC, OGG, M4A, AAC (max 100MB) | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |