Web frontend
This commit is contained in:
189
web/src/components/AlbumModal.tsx
Normal file
189
web/src/components/AlbumModal.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
/** The album detail sheet: cover, metadata, and a numbered track list. */
|
||||
|
||||
import type { Album } from "../api/types";
|
||||
import { isBook, unitLabel } from "../lib/covers";
|
||||
import { clock } from "../lib/format";
|
||||
import { Cover } from "./Cover";
|
||||
|
||||
interface Props {
|
||||
album: Album;
|
||||
currentAlbumId: string | null;
|
||||
currentTrackIndex: number;
|
||||
onClose: () => void;
|
||||
onPlay: (trackIndex: number) => void;
|
||||
}
|
||||
|
||||
export function AlbumModal({
|
||||
album,
|
||||
currentAlbumId,
|
||||
currentTrackIndex,
|
||||
onClose,
|
||||
onPlay,
|
||||
}: Props) {
|
||||
const book = isBook(album);
|
||||
return (
|
||||
<div className="overlay" style={{ zIndex: 4, background: "oklch(15% 0.03 210 / .6)" }} onClick={onClose}>
|
||||
<div
|
||||
onClick={(event) => 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)",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", gap: 20, alignItems: "flex-start" }}>
|
||||
<div style={{ width: 150, flex: "none" }}>
|
||||
<Cover album={album} size={150} radius={book ? "10px 18px 18px 10px" : "16px"} label />
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 26,
|
||||
fontWeight: 900,
|
||||
color: "oklch(22% 0.03 210)",
|
||||
lineHeight: 1.15,
|
||||
}}
|
||||
>
|
||||
{album.title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 15,
|
||||
fontWeight: 700,
|
||||
color: "oklch(45% 0.03 210 / .85)",
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
{album.artist}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
color: "oklch(50% 0.03 210 / .7)",
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
{book ? "📖 Hörbuch" : "🎵 Musik"} · {unitLabel(album, album.tracks.length)} ·{" "}
|
||||
{clock(album.duration)}
|
||||
{album.figure && ` · 🧸 ${album.figure}`}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onPlay(0)}
|
||||
style={{
|
||||
marginTop: 14,
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
background: "var(--accent)",
|
||||
color: "#fff",
|
||||
fontSize: 16,
|
||||
fontWeight: 800,
|
||||
padding: "12px 22px",
|
||||
borderRadius: 999,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
boxShadow: "0 4px 14px oklch(70% 0.16 340 / .45)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: 14,
|
||||
height: 16,
|
||||
background: "#fff",
|
||||
clipPath: "polygon(6% 0%, 100% 50%, 6% 100%)",
|
||||
}}
|
||||
/>
|
||||
{book ? "Hörbuch abspielen" : "Alle Songs abspielen"}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
aria-label="Schließen"
|
||||
style={{
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
background: "oklch(88% 0.02 210)",
|
||||
color: "var(--ink)",
|
||||
fontSize: 18,
|
||||
fontWeight: 900,
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 999,
|
||||
flex: "none",
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 6, marginTop: 22 }}>
|
||||
{album.tracks.map((track, index) => {
|
||||
const current = currentAlbumId === album.id && currentTrackIndex === index;
|
||||
return (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onPlay(index)}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 14,
|
||||
padding: "10px 14px",
|
||||
borderRadius: 14,
|
||||
cursor: "pointer",
|
||||
border: "none",
|
||||
font: "inherit",
|
||||
textAlign: "left",
|
||||
background: current
|
||||
? "oklch(70% 0.16 340 / .16)"
|
||||
: "oklch(30% 0.03 210 / .05)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 999,
|
||||
flex: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
font: "800 13px ui-monospace, Menlo, monospace",
|
||||
background: current ? "var(--accent)" : "oklch(88% 0.02 210)",
|
||||
color: current ? "#fff" : "oklch(35% 0.03 210)",
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
fontSize: 16,
|
||||
fontWeight: 800,
|
||||
color: current ? "oklch(45% 0.16 340)" : "oklch(24% 0.03 210)",
|
||||
}}
|
||||
>
|
||||
{track.title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
font: "700 13px ui-monospace, Menlo, monospace",
|
||||
color: "oklch(45% 0.03 210 / .7)",
|
||||
}}
|
||||
>
|
||||
{clock(track.duration)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user