docs: Changelog, Release-Notes und DEVLOG für 0.3.0
This commit is contained in:
@@ -1,137 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
/**
|
||||
* Staffel- und Episodenliste.
|
||||
*
|
||||
* Staffeln sind eingeklappt, damit eine Serie mit zwanzig Staffeln die Seite
|
||||
* nicht sprengt; die erste reguläre Staffel steht offen. In Etappe 3 kommen
|
||||
* hier die Häkchen für den Fortschritt dazu.
|
||||
*/
|
||||
|
||||
export type EpisodeData = {
|
||||
id: string;
|
||||
episodeNumber: number;
|
||||
name: string;
|
||||
overview: string | null;
|
||||
airDate: string | null;
|
||||
runtime: number | null;
|
||||
};
|
||||
|
||||
export type SeasonData = {
|
||||
id: string;
|
||||
seasonNumber: number;
|
||||
name: string;
|
||||
overview: string | null;
|
||||
airDate: string | null;
|
||||
episodes: EpisodeData[];
|
||||
};
|
||||
|
||||
function formatDate(value: string | null): string | null {
|
||||
if (!value) return null;
|
||||
return new Date(value).toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
export function SeasonList({ seasons }: { seasons: SeasonData[] }) {
|
||||
const firstRegular = seasons.find((season) => season.seasonNumber > 0) ?? seasons[0];
|
||||
const [open, setOpen] = useState<string[]>(firstRegular ? [firstRegular.id] : []);
|
||||
|
||||
function toggle(id: string) {
|
||||
setOpen((current) =>
|
||||
current.includes(id) ? current.filter((entry) => entry !== id) : [...current, id],
|
||||
);
|
||||
}
|
||||
|
||||
if (seasons.length === 0) {
|
||||
return <p className="text-sm text-[var(--fl-faint)]">Zu dieser Serie liegen keine Staffeln vor.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{seasons.map((season) => {
|
||||
const isOpen = open.includes(season.id);
|
||||
const air = formatDate(season.airDate);
|
||||
|
||||
return (
|
||||
<section key={season.id} className="fl-card overflow-hidden">
|
||||
<h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggle(season.id)}
|
||||
aria-expanded={isOpen}
|
||||
className="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-[var(--fl-surface-2)]"
|
||||
>
|
||||
<Chevron open={isOpen} />
|
||||
<span className="flex-1">
|
||||
<span className="font-medium">{season.name}</span>
|
||||
<span className="ml-2 text-sm text-[var(--fl-faint)]">
|
||||
{season.episodes.length}{" "}
|
||||
{season.episodes.length === 1 ? "Episode" : "Episoden"}
|
||||
{air ? ` · ab ${air}` : ""}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
{isOpen && (
|
||||
<ol className="border-t border-[var(--fl-border)]">
|
||||
{season.episodes.map((episode) => (
|
||||
<li
|
||||
key={episode.id}
|
||||
className="flex gap-3 border-b border-[var(--fl-border)] px-4 py-2.5 last:border-b-0"
|
||||
>
|
||||
<span className="w-14 shrink-0 pt-0.5 font-mono text-xs text-[var(--fl-faint)]">
|
||||
S{String(season.seasonNumber).padStart(2, "0")}E
|
||||
{String(episode.episodeNumber).padStart(2, "0")}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-sm font-medium">{episode.name}</span>
|
||||
{episode.overview && (
|
||||
<span className="mt-0.5 line-clamp-2 block text-xs text-[var(--fl-faint)]">
|
||||
{episode.overview}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="shrink-0 pt-0.5 text-right text-xs text-[var(--fl-faint)]">
|
||||
{formatDate(episode.airDate) ?? "—"}
|
||||
{episode.runtime ? <span className="block">{episode.runtime} min</span> : null}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
{season.episodes.length === 0 && (
|
||||
<li className="px-4 py-3 text-sm text-[var(--fl-faint)]">
|
||||
Für diese Staffel liegen noch keine Episoden vor.
|
||||
</li>
|
||||
)}
|
||||
</ol>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Chevron({ open }: { open: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
className={clsx(
|
||||
"h-4 w-4 shrink-0 text-[var(--fl-muted)] transition-transform duration-200",
|
||||
open && "rotate-90",
|
||||
)}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
aria-hidden
|
||||
>
|
||||
<path d="m9 6 6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user