| import React from 'react'; | |
| import { FileItem, UploadStatus } from '../types'; | |
| import { FileText, Loader2, CheckCircle2, AlertTriangle, ExternalLink, X, Edit2 } from 'lucide-react'; | |
| interface UploadListProps { | |
| files: FileItem[]; | |
| onRemove: (id: string) => void; | |
| onPathChange: (id: string, newPath: string) => void; | |
| } | |
| export const UploadList: React.FC<UploadListProps> = ({ files, onRemove, onPathChange }) => { | |
| if (files.length === 0) return null; | |
| return ( | |
| <div className="bg-white/60 backdrop-blur-md rounded-2xl shadow-sm border border-gray-100 overflow-hidden ring-1 ring-gray-200/50"> | |
| <div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50"> | |
| <h3 className="font-semibold text-gray-700 flex items-center gap-2"> | |
| <div className="w-2 h-2 rounded-full bg-indigo-500" /> | |
| Upload Queue | |
| <span className="bg-gray-200 text-gray-600 text-xs py-0.5 px-2 rounded-full ml-1">{files.length}</span> | |
| </h3> | |
| <span className="text-xs text-gray-400 font-medium">Auto-saving path changes</span> | |
| </div> | |
| <div className="max-h-[400px] overflow-y-auto custom-scrollbar divide-y divide-gray-100"> | |
| {files.map((item) => ( | |
| <div key={item.id} className="p-4 hover:bg-white transition-colors flex items-center gap-4 group relative"> | |
| {/* Icon Box */} | |
| <div className={` | |
| p-3 rounded-xl flex-shrink-0 transition-colors | |
| ${item.status === UploadStatus.SUCCESS ? 'bg-green-50 text-green-600' : | |
| item.status === UploadStatus.ERROR ? 'bg-red-50 text-red-600' : 'bg-indigo-50 text-indigo-600'} | |
| `}> | |
| <FileText className="w-5 h-5" /> | |
| </div> | |
| {/* File Info & Input */} | |
| <div className="flex-1 min-w-0 space-y-1"> | |
| <div className="flex items-center gap-2"> | |
| <span className="font-medium text-sm text-gray-700 truncate max-w-[180px] md:max-w-xs" title={item.file.name}> | |
| {item.file.name} | |
| </span> | |
| <span className="text-[10px] bg-gray-100 text-gray-500 px-1.5 py-0.5 rounded"> | |
| {(item.file.size / 1024).toFixed(1)} KB | |
| </span> | |
| </div> | |
| {item.status === UploadStatus.IDLE && ( | |
| <div className="relative max-w-sm"> | |
| <input | |
| type="text" | |
| value={item.path} | |
| onChange={(e) => onPathChange(item.id, e.target.value)} | |
| className="w-full text-xs py-1.5 pl-2 pr-7 border border-transparent hover:border-gray-200 focus:border-indigo-400 focus:bg-white rounded bg-transparent transition-all outline-none text-gray-600 font-mono" | |
| placeholder="path/to/file.ext" | |
| /> | |
| <Edit2 className="w-3 h-3 text-gray-300 absolute right-2 top-2 pointer-events-none" /> | |
| </div> | |
| )} | |
| {item.status === UploadStatus.UPLOADING && ( | |
| <div className="w-full bg-gray-100 rounded-full h-1.5 mt-2 overflow-hidden"> | |
| <div className="bg-indigo-500 h-1.5 rounded-full animate-[progress_1s_ease-in-out_infinite]" style={{width: '70%'}}></div> | |
| </div> | |
| )} | |
| {item.status === UploadStatus.SUCCESS && ( | |
| <a | |
| href={item.url} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="inline-flex items-center gap-1 text-xs font-medium text-green-600 hover:text-green-700 hover:underline" | |
| > | |
| View on Hub <ExternalLink className="w-3 h-3" /> | |
| </a> | |
| )} | |
| {item.status === UploadStatus.ERROR && ( | |
| <span className="text-xs font-medium text-red-500 truncate block" title={item.error}> | |
| Error: {item.error} | |
| </span> | |
| )} | |
| </div> | |
| {/* Actions / Status Icons */} | |
| <div className="flex items-center gap-2 pl-2 border-l border-gray-50"> | |
| {item.status === UploadStatus.UPLOADING && ( | |
| <Loader2 className="w-5 h-5 text-indigo-500 animate-spin" /> | |
| )} | |
| {item.status === UploadStatus.SUCCESS && ( | |
| <div className="flex items-center gap-1 text-green-600 bg-green-50 px-2 py-1 rounded-lg text-xs font-medium"> | |
| <CheckCircle2 className="w-4 h-4" /> | |
| <span>Done</span> | |
| </div> | |
| )} | |
| {item.status === UploadStatus.ERROR && ( | |
| <div className="flex items-center gap-1 text-red-600 bg-red-50 px-2 py-1 rounded-lg text-xs font-medium"> | |
| <AlertTriangle className="w-4 h-4" /> | |
| <span>Failed</span> | |
| </div> | |
| )} | |
| {item.status !== UploadStatus.UPLOADING && item.status !== UploadStatus.SUCCESS && ( | |
| <button | |
| onClick={() => onRemove(item.id)} | |
| className="p-2 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors" | |
| title="Remove file" | |
| > | |
| <X className="w-4 h-4" /> | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| }; |