Spaces:
Sleeping
Sleeping
Download frontend/src/components/FileUploadPanel.tsx from Hamza4100/cookie-har-analyzer: direct link, hf CLI and curl.
- Browser
- Download file 3.9 kB
-
https://huggingface.co/spaces/Hamza4100/cookie-har-analyzer/resolve/main/frontend/src/components/FileUploadPanel.tsx
- Command line
-
hf download hf://spaces/Hamza4100/cookie-har-analyzer/frontend/src/components/FileUploadPanel.tsx
-
curl -L -o FileUploadPanel.tsx https://huggingface.co/spaces/Hamza4100/cookie-har-analyzer/resolve/main/frontend/src/components/FileUploadPanel.tsx
3.9 kB
| import { useCallback, useRef, useState } from 'react'; | |
| import { Upload, X, FileText, AlertCircle, CheckCircle2 } from 'lucide-react'; | |
| interface FileUploadProps { | |
| label: string; | |
| accept: string; | |
| icon?: React.ReactNode; | |
| file: File | null; | |
| onFile: (file: File | null) => void; | |
| description?: string; | |
| id: string; | |
| } | |
| function formatSize(bytes: number): string { | |
| if (bytes < 1024) return `${bytes} B`; | |
| if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; | |
| return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; | |
| } | |
| export function FileUploadPanel({ label, accept, icon, file, onFile, description, id }: FileUploadProps) { | |
| const [isDragging, setIsDragging] = useState(false); | |
| const inputRef = useRef<HTMLInputElement>(null); | |
| const handleDrop = useCallback( | |
| (e: React.DragEvent) => { | |
| e.preventDefault(); | |
| setIsDragging(false); | |
| const dropped = e.dataTransfer.files[0]; | |
| if (dropped) onFile(dropped); | |
| }, | |
| [onFile] | |
| ); | |
| const handleDragOver = useCallback((e: React.DragEvent) => { | |
| e.preventDefault(); | |
| setIsDragging(true); | |
| }, []); | |
| const handleDragLeave = useCallback(() => setIsDragging(false), []); | |
| const handleChange = useCallback( | |
| (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const selected = e.target.files?.[0]; | |
| if (selected) onFile(selected); | |
| }, | |
| [onFile] | |
| ); | |
| return ( | |
| <div className="flex flex-col gap-2"> | |
| <label className="text-xs font-semibold text-gray-400 uppercase tracking-wider">{label}</label> | |
| {file ? ( | |
| <div className="card flex items-center gap-3 px-4 py-3 animate-fade-in"> | |
| <div className="flex-shrink-0 w-9 h-9 rounded-lg bg-green-500/15 border border-green-500/30 flex items-center justify-center"> | |
| <CheckCircle2 className="w-4 h-4 text-green-400" /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <p className="text-sm font-medium text-gray-200 truncate">{file.name}</p> | |
| <p className="text-xs text-gray-500">{formatSize(file.size)}</p> | |
| </div> | |
| <button | |
| id={`${id}-remove`} | |
| onClick={() => { | |
| onFile(null); | |
| if (inputRef.current) inputRef.current.value = ''; | |
| }} | |
| className="flex-shrink-0 p-1.5 rounded-lg text-gray-500 hover:text-red-400 hover:bg-red-500/10 transition-all" | |
| title="Remove file" | |
| > | |
| <X className="w-4 h-4" /> | |
| </button> | |
| </div> | |
| ) : ( | |
| <div | |
| className={`drop-zone p-6 text-center ${isDragging ? 'drop-zone-active' : ''}`} | |
| onDrop={handleDrop} | |
| onDragOver={handleDragOver} | |
| onDragLeave={handleDragLeave} | |
| onClick={() => inputRef.current?.click()} | |
| role="button" | |
| tabIndex={0} | |
| id={id} | |
| onKeyDown={(e) => e.key === 'Enter' && inputRef.current?.click()} | |
| > | |
| <div className={`mb-3 flex justify-center transition-transform duration-200 ${isDragging ? 'scale-110' : ''}`}> | |
| <div className="w-12 h-12 rounded-xl bg-blue-500/10 border border-blue-500/20 flex items-center justify-center"> | |
| {icon || <Upload className="w-5 h-5 text-blue-400" />} | |
| </div> | |
| </div> | |
| <p className="text-sm font-medium text-gray-300 mb-1"> | |
| {isDragging ? 'Drop it here' : 'Drop file here'} | |
| </p> | |
| <p className="text-xs text-gray-500 mb-2"> | |
| or <span className="text-blue-400 hover:text-blue-300 font-medium">browse</span> | |
| </p> | |
| {description && <p className="text-xs text-gray-600">{description}</p>} | |
| <input | |
| ref={inputRef} | |
| type="file" | |
| accept={accept} | |
| className="sr-only" | |
| onChange={handleChange} | |
| id={`${id}-input`} | |
| /> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |