feat : create share function for comment
This commit is contained in:
@@ -1,18 +1,11 @@
|
|||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { connection } from "next/server";
|
import { connection } from "next/server";
|
||||||
import Image from "@/components/ui/resilient-image";
|
import { GuidePageShell } from "@/components/public/guide-page-shell";
|
||||||
import { SiteHeader } from "@/components/public/site-header";
|
|
||||||
import { SiteFooter } from "@/components/public/site-footer";
|
|
||||||
import { ScrollableNav } from "@/components/public/scrollable-nav";
|
|
||||||
import { CommentBoardContent } from "@/components/comments/comment-board-content";
|
import { CommentBoardContent } from "@/components/comments/comment-board-content";
|
||||||
import { CommentBoardSkeleton } from "@/components/comments/comment-skeleton";
|
import { CommentBoardSkeleton } from "@/components/comments/comment-skeleton";
|
||||||
import { getCachedPublicGuideSummary } from "@/lib/cache/public";
|
import { getCachedPublicGuideSummary } from "@/lib/cache/public";
|
||||||
import { commentNavigationSection } from "@/lib/comments/navigation";
|
|
||||||
import { navigationIconPath } from "@/lib/navigation";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
export const instant = false;
|
export const instant = false;
|
||||||
type Props = { params: Promise<{ character: string }> };
|
type Props = { params: Promise<{ character: string }> };
|
||||||
@@ -25,22 +18,11 @@ export default async function GuideCommentPage({ params }: Props) {
|
|||||||
const guide = await getCachedPublicGuideSummary((await params).character);
|
const guide = await getCachedPublicGuideSummary((await params).character);
|
||||||
if (!guide) notFound();
|
if (!guide) notFound();
|
||||||
const target = `guide:${guide.id}`;
|
const target = `guide:${guide.id}`;
|
||||||
const sections = [{ id: "overview", slug: "", title: "Overview" }, ...guide.sections, commentNavigationSection(guide.id)];
|
return (
|
||||||
return <div className="flex min-h-svh flex-col bg-background">
|
<GuidePageShell guide={guide} sections={guide.sections} section="comment">
|
||||||
<SiteHeader editHref={`/admin/${guide.characterKey}`} guideId={guide.id} />
|
<Suspense fallback={<CommentBoardSkeleton />}>
|
||||||
<main className="mx-auto flex w-full max-w-7xl flex-1 flex-col gap-6 p-4 sm:p-6 lg:p-10">
|
<CommentBoardContent target={target} />
|
||||||
<header className="flex items-center gap-3"><Image src={guide.coverUrl} alt="" width={64} height={64} className="size-16 rounded-lg object-cover" />
|
</Suspense>
|
||||||
<div><p className="text-sm text-muted-foreground">{guide.name}</p><h1 className="font-heading text-3xl font-semibold">ความคิดเห็น</h1></div>
|
</GuidePageShell>
|
||||||
</header>
|
);
|
||||||
<ScrollableNav ariaLabel={`เมนูคู่มือ ${guide.name}`} activeKey="comment" className="min-h-11 w-full rounded-xl border bg-card p-1 sm:p-2">
|
|
||||||
{sections.map((section) => <Link key={section.id} href={`/${guide.slug}${section.slug ? `/${section.slug}` : ""}`} prefetch={false}
|
|
||||||
aria-current={section.slug === "comment" ? "page" : undefined}
|
|
||||||
className={cn("flex min-h-11 shrink-0 items-center gap-2 rounded-md px-3 text-sm", section.slug === "comment" ? "bg-primary/15 font-semibold text-primary" : "text-muted-foreground hover:bg-muted")}>
|
|
||||||
{navigationIconPath(section.slug || "overview") && <Image src={navigationIconPath(section.slug || "overview")!} alt="" width={20} height={20} className="size-5 object-contain" />}
|
|
||||||
{section.title}
|
|
||||||
</Link>)}
|
|
||||||
</ScrollableNav>
|
|
||||||
<Suspense fallback={<CommentBoardSkeleton />}><CommentBoardContent target={target} /></Suspense>
|
|
||||||
</main><SiteFooter />
|
|
||||||
</div>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { renderToStaticMarkup } from "react-dom/server";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { GuidePageShell } from "./guide-page-shell";
|
||||||
|
|
||||||
|
vi.mock("@/components/public/site-header", () => ({ SiteHeader: () => null }));
|
||||||
|
vi.mock("@/components/live-refresh", () => ({ LiveRefresh: () => null }));
|
||||||
|
|
||||||
|
const guide = {
|
||||||
|
id: "test-guide",
|
||||||
|
slug: "raiden",
|
||||||
|
name: "Raiden",
|
||||||
|
characterKey: "raiden-key",
|
||||||
|
coverUrl: "/cover.png",
|
||||||
|
};
|
||||||
|
const sections = [
|
||||||
|
{ id: "weapon", slug: "weapon", title: "Weapon" },
|
||||||
|
{ id: "extra", slug: "rotation", title: "Rotation" },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("shared guide navigation", () => {
|
||||||
|
it("gives comments the home button and guide tabs around their content", () => {
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<GuidePageShell guide={guide} sections={sections} section="comment">
|
||||||
|
<section>Comment board</section>
|
||||||
|
</GuidePageShell>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toMatch(/<a\b[^>]*aria-label="กลับหน้ารวม"[^>]*href="\/"/);
|
||||||
|
expect(html).toMatch(/<a\b[^>]*aria-current="page"[^>]*href="\/raiden\/comment"/);
|
||||||
|
expect(html).toContain('href="/raiden/weapon"');
|
||||||
|
expect(html).toContain('href="/raiden/rotation"');
|
||||||
|
expect(html).toContain("Weapons");
|
||||||
|
expect(html).toContain("Rotation");
|
||||||
|
expect(html).toContain("<section>Comment board</section>");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps overview active and includes a single comments link", () => {
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<GuidePageShell guide={guide} sections={sections}>Overview content</GuidePageShell>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toMatch(/<a\b[^>]*aria-current="page"[^>]*href="\/raiden"/);
|
||||||
|
expect(html.match(/href="\/raiden\/comment"/g)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps preview links under admin and omits comments", () => {
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<GuidePageShell guide={guide} sections={sections} section="weapon" preview>
|
||||||
|
Preview content
|
||||||
|
</GuidePageShell>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toMatch(/<a\b[^>]*aria-label="กลับหน้าแก้ไข"[^>]*href="\/admin\/raiden-key"/);
|
||||||
|
expect(html).toMatch(/<a\b[^>]*aria-current="page"[^>]*href="\/admin\/raiden-key\/preview\/weapon"/);
|
||||||
|
expect(html).toContain('href="/admin/raiden-key/preview/rotation"');
|
||||||
|
expect(html).not.toContain("/comment");
|
||||||
|
expect(html).toContain("หน้านี้แสดงข้อมูลที่บันทึกล่าสุด");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { ArrowLeftIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import Image from "@/components/ui/resilient-image";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import { SiteHeader } from "@/components/public/site-header";
|
||||||
|
import { SiteFooter } from "@/components/public/site-footer";
|
||||||
|
import { ScrollableNav } from "@/components/public/scrollable-nav";
|
||||||
|
import { LiveRefresh } from "@/components/live-refresh";
|
||||||
|
import type { PublicGuideSummary } from "@/lib/guides/queries";
|
||||||
|
import { commentNavigationSection } from "@/lib/comments/navigation";
|
||||||
|
import { navigationIconPath } from "@/lib/navigation";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export function publicSectionTitle(slug: string, fallback: string): string {
|
||||||
|
if (slug === "weapon") return "Weapons";
|
||||||
|
if (slug === "artifact") return "Artifacts";
|
||||||
|
if (slug === "constellations") return "Constellations";
|
||||||
|
if (slug === "team") return "Team calc";
|
||||||
|
if (slug === "comment") return "Comment";
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GuidePageShell({
|
||||||
|
guide,
|
||||||
|
sections: guideSections,
|
||||||
|
section,
|
||||||
|
preview = false,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
guide: Pick<PublicGuideSummary, "id" | "slug" | "name" | "characterKey" | "coverUrl">;
|
||||||
|
sections: Pick<PublicGuideSummary["sections"][number], "id" | "slug" | "title">[];
|
||||||
|
section?: string;
|
||||||
|
preview?: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
}) {
|
||||||
|
const sections = preview ? guideSections : [...guideSections, commentNavigationSection(guide.id)];
|
||||||
|
const baseHref = preview ? `/admin/${guide.characterKey}/preview` : `/${guide.slug}`;
|
||||||
|
const currentPageTitle = section
|
||||||
|
? publicSectionTitle(section, sections.find((item) => item.slug === section)?.title ?? section)
|
||||||
|
: "Overview";
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-svh flex-col bg-background">
|
||||||
|
<LiveRefresh endpoint={`/api/events/page/${guide.id}`} />
|
||||||
|
<SiteHeader editHref={`/admin/${guide.characterKey}`} guideId={guide.id} />
|
||||||
|
{preview ? (
|
||||||
|
<div className="mx-auto w-full max-w-7xl px-4 pt-4 sm:px-6 lg:px-8">
|
||||||
|
<Alert>
|
||||||
|
<AlertTitle>Preview</AlertTitle>
|
||||||
|
<AlertDescription>หน้านี้แสดงข้อมูลที่บันทึกล่าสุดและเปิดดูได้เฉพาะผู้ดูแล</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="sticky top-0 z-40 bg-background/95 supports-backdrop-filter:backdrop-blur">
|
||||||
|
<div className="mx-auto flex min-h-12 w-full max-w-7xl items-center gap-3 px-4 sm:min-h-16 sm:px-6 lg:px-8">
|
||||||
|
<Button variant="ghost" size="icon" className="max-sm:size-11" render={<Link href={preview ? `/admin/${guide.characterKey}` : "/"} />} nativeButton={false} aria-label={preview ? "กลับหน้าแก้ไข" : "กลับหน้ารวม"}>
|
||||||
|
<ArrowLeftIcon />
|
||||||
|
</Button>
|
||||||
|
<div className="relative size-10 shrink-0 overflow-hidden rounded-lg border sm:size-16">
|
||||||
|
<Image
|
||||||
|
src={guide.coverUrl}
|
||||||
|
alt={guide.name}
|
||||||
|
fill
|
||||||
|
sizes="(min-width: 640px) 64px, 40px"
|
||||||
|
className="object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex min-w-0 flex-1 flex-col items-start gap-1">
|
||||||
|
<p className="truncate text-sm text-muted-foreground">
|
||||||
|
{guide.name}
|
||||||
|
</p>
|
||||||
|
<p className="max-w-full truncate font-heading text-lg font-semibold sm:text-2xl">
|
||||||
|
{currentPageTitle}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<ScrollableNav
|
||||||
|
ariaLabel={`เมนูคู่มือ ${guide.name}`}
|
||||||
|
activeKey={section ?? "overview"}
|
||||||
|
className="min-h-11 w-full rounded-xl border bg-card p-1 sm:p-2"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={baseHref}
|
||||||
|
aria-current={!section ? "page" : undefined}
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-11 shrink-0 items-center gap-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
|
!section
|
||||||
|
? "bg-primary/15 font-semibold text-primary"
|
||||||
|
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||||
|
)}
|
||||||
|
prefetch={false}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={navigationIconPath("overview")!}
|
||||||
|
alt=""
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className="size-5 object-contain"
|
||||||
|
/>
|
||||||
|
Overview
|
||||||
|
</Link>
|
||||||
|
{sections.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.id}
|
||||||
|
href={`${baseHref}/${item.slug}`}
|
||||||
|
aria-current={section === item.slug ? "page" : undefined}
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-11 shrink-0 items-center gap-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
|
section === item.slug
|
||||||
|
? "bg-primary/15 font-semibold text-primary"
|
||||||
|
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||||
|
)}
|
||||||
|
prefetch={false}
|
||||||
|
>
|
||||||
|
{navigationIconPath(item.slug) ? (
|
||||||
|
<Image
|
||||||
|
src={navigationIconPath(item.slug)!}
|
||||||
|
alt=""
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className="size-5 object-contain"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{publicSectionTitle(item.slug, item.title)}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</ScrollableNav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<main className="mx-auto w-full max-w-7xl p-4 sm:p-6 lg:p-10">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<SiteFooter />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import Image from "@/components/ui/resilient-image";
|
import Image from "@/components/ui/resilient-image";
|
||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react";
|
import { ArrowUpRightIcon } from "lucide-react";
|
||||||
import { Fragment, type ReactNode } from "react";
|
import { Fragment, type ReactNode } from "react";
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
||||||
import { ComparisonContext } from "@/components/public/comparison-context";
|
import { ComparisonContext } from "@/components/public/comparison-context";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardAction,
|
CardAction,
|
||||||
@@ -25,9 +23,7 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { SiteFooter } from "@/components/public/site-footer";
|
import { GuidePageShell, publicSectionTitle } from "@/components/public/guide-page-shell";
|
||||||
import { SiteHeader } from "@/components/public/site-header";
|
|
||||||
import { ScrollableNav } from "@/components/public/scrollable-nav";
|
|
||||||
import { TeamArtifactIcon, TeamSearch } from "@/components/public/team-search";
|
import { TeamArtifactIcon, TeamSearch } from "@/components/public/team-search";
|
||||||
import { GuideImageExport } from "@/components/public/guide-image-export";
|
import { GuideImageExport } from "@/components/public/guide-image-export";
|
||||||
import { ExportText } from "@/components/public/export-language";
|
import { ExportText } from "@/components/public/export-language";
|
||||||
@@ -40,7 +36,6 @@ import { LunarisText } from "@/components/public/lunaris-text";
|
|||||||
import { ConditionSwitcher } from "@/components/public/condition-switcher";
|
import { ConditionSwitcher } from "@/components/public/condition-switcher";
|
||||||
import { MaterialPoster } from "@/components/public/material-poster";
|
import { MaterialPoster } from "@/components/public/material-poster";
|
||||||
import { RichTextContent } from "@/components/rich-text-content";
|
import { RichTextContent } from "@/components/rich-text-content";
|
||||||
import { LiveRefresh } from "@/components/live-refresh";
|
|
||||||
import { rarityGradientClass, rarityTextClass } from "@/lib/catalog/rarity";
|
import { rarityGradientClass, rarityTextClass } from "@/lib/catalog/rarity";
|
||||||
import { elementIconFilter } from "@/lib/catalog/element";
|
import { elementIconFilter } from "@/lib/catalog/element";
|
||||||
import type { PublicGuide } from "@/lib/guides/queries";
|
import type { PublicGuide } from "@/lib/guides/queries";
|
||||||
@@ -76,15 +71,6 @@ export function publicSections(guide: PublicGuide) {
|
|||||||
return publicGuideSections(guide);
|
return publicGuideSections(guide);
|
||||||
}
|
}
|
||||||
|
|
||||||
function publicSectionTitle(slug: string, fallback: string): string {
|
|
||||||
if (slug === "weapon") return "Weapons";
|
|
||||||
if (slug === "artifact") return "Artifacts";
|
|
||||||
if (slug === "constellations") return "Constellations";
|
|
||||||
if (slug === "team") return "Team calc";
|
|
||||||
if (slug === "comment") return "Comment";
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function StructuredGuideShell({
|
export function StructuredGuideShell({
|
||||||
guide,
|
guide,
|
||||||
section,
|
section,
|
||||||
@@ -94,109 +80,17 @@ export function StructuredGuideShell({
|
|||||||
section?: string;
|
section?: string;
|
||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const sections = preview ? publicSections(guide) : [...publicSections(guide), commentNavigationSection(guide.id)];
|
const guideSections = publicSections(guide);
|
||||||
|
const sections = preview ? guideSections : [...guideSections, commentNavigationSection(guide.id)];
|
||||||
const baseHref = preview ? `/admin/${guide.characterKey}/preview` : `/${guide.slug}`;
|
const baseHref = preview ? `/admin/${guide.characterKey}/preview` : `/${guide.slug}`;
|
||||||
const currentPageTitle = section
|
|
||||||
? publicSectionTitle(section, sections.find((item) => item.slug === section)?.title ?? section)
|
|
||||||
: "Overview";
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-svh flex-col bg-background">
|
<GuidePageShell guide={guide} sections={guideSections} section={section} preview={preview}>
|
||||||
<LiveRefresh endpoint={`/api/events/page/${guide.id}`} />
|
{section ? (
|
||||||
<SiteHeader editHref={`/admin/${guide.characterKey}`} guideId={guide.id} />
|
<GuideSection guide={guide} section={section} />
|
||||||
{preview ? (
|
) : (
|
||||||
<div className="mx-auto w-full max-w-7xl px-4 pt-4 sm:px-6 lg:px-8">
|
<GuideOverview guide={guide} sections={sections} baseHref={baseHref} />
|
||||||
<Alert>
|
)}
|
||||||
<AlertTitle>Preview</AlertTitle>
|
</GuidePageShell>
|
||||||
<AlertDescription>หน้านี้แสดงข้อมูลที่บันทึกล่าสุดและเปิดดูได้เฉพาะผู้ดูแล</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="sticky top-0 z-40 bg-background/95 supports-backdrop-filter:backdrop-blur">
|
|
||||||
<div className="mx-auto flex min-h-12 w-full max-w-7xl items-center gap-3 px-4 sm:min-h-16 sm:px-6 lg:px-8">
|
|
||||||
<Button variant="ghost" size="icon" className="max-sm:size-11" render={<Link href={preview ? `/admin/${guide.characterKey}` : "/"} />} nativeButton={false} aria-label={preview ? "กลับหน้าแก้ไข" : "กลับหน้ารวม"}>
|
|
||||||
<ArrowLeftIcon />
|
|
||||||
</Button>
|
|
||||||
<div className="relative size-10 shrink-0 overflow-hidden rounded-lg border sm:size-16">
|
|
||||||
<Image
|
|
||||||
src={guide.coverUrl}
|
|
||||||
alt={guide.name}
|
|
||||||
fill
|
|
||||||
sizes="(min-width: 640px) 64px, 40px"
|
|
||||||
className="object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex min-w-0 flex-1 flex-col items-start gap-1">
|
|
||||||
<p className="truncate text-sm text-muted-foreground">
|
|
||||||
{guide.name}
|
|
||||||
</p>
|
|
||||||
<p className="max-w-full truncate font-heading text-lg font-semibold sm:text-2xl">
|
|
||||||
{currentPageTitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
||||||
<ScrollableNav
|
|
||||||
ariaLabel={`เมนูคู่มือ ${guide.name}`}
|
|
||||||
activeKey={section ?? "overview"}
|
|
||||||
className="min-h-11 w-full rounded-xl border bg-card p-1 sm:p-2"
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
href={baseHref}
|
|
||||||
aria-current={!section ? "page" : undefined}
|
|
||||||
className={cn(
|
|
||||||
"flex min-h-11 shrink-0 items-center gap-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
||||||
!section
|
|
||||||
? "bg-primary/15 font-semibold text-primary"
|
|
||||||
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
||||||
)}
|
|
||||||
prefetch={false}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={navigationIconPath("overview")!}
|
|
||||||
alt=""
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
className="size-5 object-contain"
|
|
||||||
/>
|
|
||||||
Overview
|
|
||||||
</Link>
|
|
||||||
{sections.map((item) => (
|
|
||||||
<Link
|
|
||||||
key={item.id}
|
|
||||||
href={`${baseHref}/${item.slug}`}
|
|
||||||
aria-current={section === item.slug ? "page" : undefined}
|
|
||||||
className={cn(
|
|
||||||
"flex min-h-11 shrink-0 items-center gap-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
||||||
section === item.slug
|
|
||||||
? "bg-primary/15 font-semibold text-primary"
|
|
||||||
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
||||||
)}
|
|
||||||
prefetch={false}
|
|
||||||
>
|
|
||||||
{navigationIconPath(item.slug) ? (
|
|
||||||
<Image
|
|
||||||
src={navigationIconPath(item.slug)!}
|
|
||||||
alt=""
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
className="size-5 object-contain"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{publicSectionTitle(item.slug, item.title)}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</ScrollableNav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<main className="mx-auto w-full max-w-7xl p-4 sm:p-6 lg:p-10">
|
|
||||||
{section ? (
|
|
||||||
<GuideSection guide={guide} section={section} />
|
|
||||||
) : (
|
|
||||||
<GuideOverview guide={guide} sections={sections} baseHref={baseHref} />
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
<SiteFooter />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user