| 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> |
| ); |
| }; |