136 lines
4.4 KiBLFS
TypeScript
136 lines
4.4 KiBLFS
TypeScript
import { source } from "@/lib/source";
|
|
import { getMDXComponents } from "@/mdx-components";
|
|
import { notFound } from "next/navigation";
|
|
import Link from "next/link";
|
|
import { ChevronRight } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { DocTOC, MobileTOC } from "./doc-toc";
|
|
|
|
const PAGE_ORDER = ["getting-started", "contributing"];
|
|
|
|
function getDocPages() {
|
|
return source
|
|
.getPages()
|
|
.filter((p) => p.slugs.length > 0)
|
|
.sort((a, b) => {
|
|
const ai = PAGE_ORDER.indexOf(a.slugs[0]);
|
|
const bi = PAGE_ORDER.indexOf(b.slugs[0]);
|
|
return (ai === -1 ? Infinity : ai) - (bi === -1 ? Infinity : bi);
|
|
});
|
|
}
|
|
|
|
export default async function DocPage(props: {
|
|
params: Promise<{ slug: string }>;
|
|
}) {
|
|
const { slug } = await props.params;
|
|
const page = source.getPage([slug]);
|
|
if (!page) notFound();
|
|
|
|
const data = page.data as any;
|
|
const MDX = data.body;
|
|
const allPages = getDocPages();
|
|
|
|
return (
|
|
<div className="flex-1 pt-20 md:pt-24">
|
|
<div className="max-w-[1400px] mx-auto flex">
|
|
{/* Left sidebar - doc tree */}
|
|
<aside className="hidden md:block w-56 shrink-0">
|
|
<div className="sticky top-24 p-6 pr-4 overflow-y-auto max-h-[calc(100vh-6rem)]">
|
|
<h4 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-4">
|
|
Documentation
|
|
</h4>
|
|
<nav className="flex flex-col gap-0.5">
|
|
{allPages.map((p) => {
|
|
const pData = p.data as any;
|
|
const isActive = p.slugs[0] === slug;
|
|
return (
|
|
<Link
|
|
key={p.url}
|
|
href={p.url}
|
|
className={cn(
|
|
"text-sm px-3 py-1.5 rounded-md transition-colors",
|
|
isActive
|
|
? "bg-primary/10 text-primary font-medium"
|
|
: "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
)}
|
|
>
|
|
{pData.title}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Center content */}
|
|
<main className="flex-1 min-w-0 px-6 md:px-10 py-8">
|
|
{/* Breadcrumbs */}
|
|
<nav className="flex items-center gap-1.5 text-sm text-muted-foreground mb-6">
|
|
<Link href="/" className="hover:text-foreground transition-colors">
|
|
Home
|
|
</Link>
|
|
<ChevronRight className="w-3.5 h-3.5" />
|
|
<Link
|
|
href="/docs"
|
|
className="hover:text-foreground transition-colors"
|
|
>
|
|
Docs
|
|
</Link>
|
|
<ChevronRight className="w-3.5 h-3.5" />
|
|
<span className="text-foreground font-medium">{data.title}</span>
|
|
</nav>
|
|
|
|
<h1 className="text-3xl font-bold tracking-tight mb-3">
|
|
{data.title}
|
|
</h1>
|
|
{data.description && (
|
|
<p className="text-muted-foreground text-lg mb-8 max-w-2xl">
|
|
{data.description}
|
|
</p>
|
|
)}
|
|
|
|
{/* Mobile/tablet TOC */}
|
|
{data.toc && data.toc.length > 0 && (
|
|
<div className="lg:hidden mb-8">
|
|
<MobileTOC items={data.toc} />
|
|
</div>
|
|
)}
|
|
|
|
<article className="prose prose-neutral dark:prose-invert max-w-none prose-headings:scroll-mt-24 prose-headings:font-semibold prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg prose-a:text-primary prose-a:no-underline prose-a:hover:underline prose-img:rounded-xl">
|
|
<MDX components={getMDXComponents()} />
|
|
</article>
|
|
</main>
|
|
|
|
{/* Right sidebar - TOC (desktop) */}
|
|
{data.toc && data.toc.length > 0 && (
|
|
<aside className="hidden lg:block w-56 shrink-0">
|
|
<div className="sticky top-24 py-8 pr-6 overflow-y-auto max-h-[calc(100vh-6rem)]">
|
|
<DocTOC items={data.toc} />
|
|
</div>
|
|
</aside>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export async function generateStaticParams() {
|
|
return getDocPages().map((page) => ({
|
|
slug: page.slugs[0],
|
|
}));
|
|
}
|
|
|
|
export async function generateMetadata(props: {
|
|
params: Promise<{ slug: string }>;
|
|
}) {
|
|
const { slug } = await props.params;
|
|
const page = source.getPage([slug]);
|
|
if (!page) notFound();
|
|
|
|
const data = page.data as any;
|
|
return {
|
|
title: data.title,
|
|
description: data.description,
|
|
};
|
|
}
|