Files
buzz-sheet/components/public/guide-shell.tsx
T
2026-08-30 23:48:03 +07:00

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>
);
}