Add hover popover to nav download indicator

Shows each active download (title + progress bar) and background task
(label, phase, done/total + bar) on hover. Pure CSS group-hover, no JS state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 23:33:26 +02:00
parent 3a557a1d24
commit e02ea12494

View File

@@ -114,10 +114,10 @@ function DownloadIndicator() {
} }
return ( return (
<div className="relative group shrink-0">
<Link <Link
to="/downloads" to="/downloads"
className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700 transition-colors text-xs text-zinc-300 shrink-0" className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700 transition-colors text-xs text-zinc-300"
title={`${totalActive} task${totalActive > 1 ? "s" : ""} in progress`}
> >
<svg className="w-3 h-3 animate-spin text-zinc-400 shrink-0" fill="none" viewBox="0 0 24 24"> <svg className="w-3 h-3 animate-spin text-zinc-400 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-20" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" /> <circle className="opacity-20" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
@@ -128,6 +128,43 @@ function DownloadIndicator() {
<span className="hidden sm:inline text-zinc-500">+{totalActive - 1}</span> <span className="hidden sm:inline text-zinc-500">+{totalActive - 1}</span>
)} )}
</Link> </Link>
{/* Hover popover */}
<div className="absolute top-[calc(100%+6px)] right-0 z-50
invisible opacity-0 group-hover:visible group-hover:opacity-100
transition-all duration-100 pointer-events-none">
<div className="bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl p-3 min-w-[220px] max-w-[280px] flex flex-col gap-2">
{activeDownloads.map((d) => (
<div key={d.id}>
<div className="flex items-center justify-between gap-2 mb-1">
<p className="text-xs text-zinc-200 truncate">{d.video_title || "Downloading…"}</p>
<span className="text-[10px] text-zinc-400 tabular-nums shrink-0">{(d.progress_percent ?? 0).toFixed(0)}%</span>
</div>
<div className="h-0.5 bg-zinc-800 rounded-full overflow-hidden">
<div className="h-full bg-accent rounded-full transition-all duration-300" style={{ width: `${d.progress_percent ?? 0}%` }} />
</div>
</div>
))}
{tasks.map((task) => {
const pct = task.total > 0 ? (task.done / task.total) * 100 : 0;
return (
<div key={task.id}>
<div className="flex items-center justify-between gap-2 mb-1">
<p className="text-xs text-zinc-200 truncate">{task.label}</p>
{task.total > 0 && (
<span className="text-[10px] text-zinc-400 tabular-nums shrink-0">{task.done}/{task.total}</span>
)}
</div>
<p className="text-[10px] text-zinc-500 mb-1">{task.phase || "Running…"}</p>
<div className="h-0.5 bg-zinc-800 rounded-full overflow-hidden">
<div className="h-full bg-accent rounded-full transition-all duration-300" style={{ width: `${pct}%` }} />
</div>
</div>
);
})}
</div>
</div>
</div>
); );
} }