/** The album detail sheet: cover, metadata, and a numbered track list. */ import { useEffect, useRef } from "react"; import type { Album } from "../api/types"; import { isBook, unitLabel } from "../lib/covers"; import { clock } from "../lib/format"; import { ANIMATE_VIEW_TRANSITIONS } from "../lib/theme"; import { Cover } from "./Cover"; interface Props { album: Album; currentAlbumId: string | null; currentTrackIndex: number; /** The track Ctrl+j/k has highlighted, so the keyboard has something visible to move. */ selectedIndex: number; onClose: () => void; onPlay: (trackIndex: number) => void; } export function AlbumModal({ album, currentAlbumId, currentTrackIndex, selectedIndex, onClose, onPlay, }: Props) { const book = isBook(album); const sheet = useRef(null); // Keep the Ctrl+j/k highlight on screen as it moves past the fold, the same way the // browse grid keeps its own keyboard selection visible. useEffect(() => { const box = sheet.current; const element = box?.querySelector(`[data-nav-index="${selectedIndex}"]`); if (!box || !element) return; const top = element.offsetTop - box.offsetTop; const bottom = top + element.offsetHeight; const pad = 12; if (top - pad < box.scrollTop) box.scrollTop = Math.max(0, top - pad); else if (bottom + pad > box.scrollTop + box.clientHeight) { box.scrollTop = bottom + pad - box.clientHeight; } }, [selectedIndex]); return (
event.stopPropagation()} style={{ background: "oklch(96% 0.012 210)", borderRadius: 26, padding: 26, width: 640, maxWidth: "100%", maxHeight: "100%", overflow: "auto", boxShadow: "0 24px 60px oklch(10% 0.04 210 / .55)", }} >
{album.title}
{album.artist}
{book ? "📖 Hörbuch" : "🎵 Musik"} · {unitLabel(album, album.tracks.length)} ·{" "} {clock(album.duration)} {album.figure && ` · 🧸 ${album.figure}`}
{album.tracks.map((track, index) => { const current = currentAlbumId === album.id && currentTrackIndex === index; return ( ); })}
); }