feat(audiobooks): add narrator card to detail page

Surfaces the narrator as a first-class card with a circular initials
avatar. Rendered only when data.narrator is present, so audiobooks
without narrator metadata don't introduce empty space.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
RXWatcher
2026-05-25 01:41:11 +02:00
co-authored by Claude Opus 4.7
parent 9013718b2d
commit 07da12f8f4
2 changed files with 30 additions and 0 deletions
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
import { Play } from "lucide-react";
import AudiobookPlayer from "./player/AudiobookPlayer";
import { ChaptersSection } from "./components/ChaptersSection";
import { NarratorCard } from "./components/NarratorCard";
import DetailHero from "@/pages/ItemDetail/DetailHero";
import MetadataBadges from "@/pages/ItemDetail/components/MetadataBadges";
import type { AudiobookChapter, AudiobookFile } from "@/lib/audiobooks/types";
@@ -229,6 +230,7 @@ export default function AudiobookDetail() {
currentPositionSeconds={playerOpen ? startSeconds : resumeSeconds || null}
onSelect={(s) => openPlayer(s)}
/>
{narrator && <NarratorCard narrator={narrator} />}
</div>
</div>
);
@@ -0,0 +1,28 @@
interface NarratorCardProps {
narrator: string;
}
function initials(name: string): string {
return name
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((w) => w[0]?.toUpperCase() ?? "")
.join("");
}
export function NarratorCard({ narrator }: NarratorCardProps) {
return (
<section className="mt-10">
<h2 className="mb-4 text-xl font-semibold tracking-tight">Narrator</h2>
<div className="bg-muted/30 flex items-center gap-4 rounded-xl border p-4">
<div className="bg-muted text-muted-foreground flex h-16 w-16 shrink-0 items-center justify-center rounded-full text-lg font-medium">
{initials(narrator) || "?"}
</div>
<div className="min-w-0">
<p className="truncate text-base font-medium">{narrator}</p>
</div>
</div>
</section>
);
}