115 lines
5.2 KiB
TypeScript
115 lines
5.2 KiB
TypeScript
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { ChevronLeftIcon, ScrollTextIcon } from "lucide-react";
|
|
|
|
import {
|
|
collectGuideSections,
|
|
PublicBlockRenderer,
|
|
} from "@/components/public/block-renderer";
|
|
import { SectionDrawer } from "@/components/public/section-drawer";
|
|
import { ScrollableNav } from "@/components/public/scrollable-nav";
|
|
import { SiteHeader } from "@/components/public/site-header";
|
|
import { LiveRefresh } from "@/components/live-refresh";
|
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import type { PageSnapshot } from "@/lib/content/types";
|
|
import { cn } from "@/lib/utils";
|
|
import { navigationIconPath } from "@/lib/navigation";
|
|
|
|
export function GuideShell({ snapshot }: { snapshot: PageSnapshot }) {
|
|
const sections = collectGuideSections(snapshot.blocks);
|
|
|
|
return (
|
|
<div className="min-h-svh bg-background">
|
|
{process.env.BUZZ_DEMO_MODE === "true" ? null : (
|
|
<LiveRefresh endpoint={`/api/events/page/${snapshot.page.id}`} />
|
|
)}
|
|
<SiteHeader />
|
|
<div className="sticky top-0 z-30 bg-background/95 supports-backdrop-filter:backdrop-blur">
|
|
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6">
|
|
<div className="flex min-h-14 items-center gap-3 py-2">
|
|
<Link
|
|
href="/"
|
|
className="grid size-10 shrink-0 place-items-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
aria-label="กลับไปหน้ารวมตัวละคร"
|
|
>
|
|
<ChevronLeftIcon aria-hidden="true" />
|
|
</Link>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate font-heading font-semibold">{snapshot.character.nameTh}</p>
|
|
{snapshot.character.nameEn ? (
|
|
<p className="truncate text-xs text-muted-foreground">{snapshot.character.nameEn}</p>
|
|
) : null}
|
|
</div>
|
|
{snapshot.character.element ? <Badge>{snapshot.character.element}</Badge> : null}
|
|
{snapshot.character.role ? (
|
|
<Badge variant="outline" className="hidden sm:inline-flex">
|
|
{snapshot.character.role}
|
|
</Badge>
|
|
) : null}
|
|
</div>
|
|
<ScrollableNav
|
|
ariaLabel={`หน้าคู่มือ ${snapshot.character.nameTh}`}
|
|
className="min-h-11 rounded-xl border bg-card p-2"
|
|
>
|
|
{snapshot.pages.map((page) => (
|
|
<Link
|
|
key={page.id}
|
|
href={`/${snapshot.character.slug}/${page.slug}`}
|
|
aria-current={page.id === snapshot.page.id ? "page" : undefined}
|
|
className={cn(
|
|
"relative flex min-h-11 shrink-0 items-center gap-2 rounded-md px-3 text-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring",
|
|
page.id === snapshot.page.id && "font-medium text-foreground",
|
|
)}
|
|
>
|
|
{navigationIconPath(page.slug) ? <Image src={navigationIconPath(page.slug)!} alt="" width={20} height={20} className="size-5 object-contain" /> : null}
|
|
{page.title}
|
|
</Link>
|
|
))}
|
|
</ScrollableNav>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mx-auto grid w-full max-w-7xl gap-8 px-4 py-6 sm:px-6 lg:grid-cols-[13rem_minmax(0,1fr)] lg:py-10">
|
|
<aside className="hidden lg:block">
|
|
<div className="sticky top-32 space-y-3">
|
|
<p className="flex items-center gap-2 text-sm font-medium">
|
|
<ScrollTextIcon aria-hidden="true" className="size-4 text-primary" />
|
|
สารบัญหน้านี้
|
|
</p>
|
|
<Separator />
|
|
{sections.length > 0 ? (
|
|
<nav aria-label="สารบัญหน้านี้" className="grid gap-1">
|
|
{sections.map((section) => (
|
|
<a
|
|
key={section.id}
|
|
href={`#${section.id}`}
|
|
className="rounded-md px-2 py-2 text-sm leading-6 text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
>
|
|
{section.label}
|
|
</a>
|
|
))}
|
|
</nav>
|
|
) : (
|
|
<p className="text-sm leading-6 text-muted-foreground">หน้านี้ยังไม่มีหัวข้อย่อย</p>
|
|
)}
|
|
</div>
|
|
</aside>
|
|
|
|
<main className="min-w-0 space-y-6">
|
|
{sections.length > 0 ? <SectionDrawer sections={sections} /> : null}
|
|
{snapshot.page.publicNote ? (
|
|
<Alert>
|
|
<ScrollTextIcon aria-hidden="true" />
|
|
<AlertTitle>หมายเหตุจากผู้เขียน</AlertTitle>
|
|
<AlertDescription>{snapshot.page.publicNote}</AlertDescription>
|
|
</Alert>
|
|
) : null}
|
|
<PublicBlockRenderer snapshot={snapshot} />
|
|
</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|