Files
SkillCompiler/data/skills-bench/website/src/app/docs/[slug]/page.tsx
T
2026-09-04 14:58:42 +08:00

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