mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
* feat: add rhea * fix: blocks * feat: build chat example * fix * fix: sidebar * fix * feat: update home * fix * fix * fix * feat: optimizine fonts * feat * fix * fix * fix * fix * fix * fix * fix: font in preview * fix
199 lines
6.6 KiB
TypeScript
199 lines
6.6 KiB
TypeScript
import Link from "next/link"
|
|
import { notFound } from "next/navigation"
|
|
import { mdxComponents } from "@/mdx-components"
|
|
import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react"
|
|
import { findNeighbour } from "fumadocs-core/page-tree"
|
|
|
|
import { replaceComponentsList } from "@/lib/llm"
|
|
import { source } from "@/lib/source"
|
|
import { absoluteUrl } from "@/lib/utils"
|
|
import { DocsBaseSwitcher } from "@/components/docs-base-switcher"
|
|
import { DocsCopyPage } from "@/components/docs-copy-page"
|
|
import { DocsTableOfContents } from "@/components/docs-toc"
|
|
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
|
|
export const revalidate = false
|
|
export const dynamic = "force-static"
|
|
export const dynamicParams = false
|
|
|
|
export function generateStaticParams() {
|
|
return source.generateParams()
|
|
}
|
|
|
|
export async function generateMetadata(props: {
|
|
params: Promise<{ slug: string[] }>
|
|
}) {
|
|
const params = await props.params
|
|
const page = source.getPage(params.slug)
|
|
|
|
if (!page) {
|
|
notFound()
|
|
}
|
|
|
|
const doc = page.data
|
|
|
|
if (!doc.title || !doc.description) {
|
|
notFound()
|
|
}
|
|
|
|
return {
|
|
title: doc.title,
|
|
description: doc.description,
|
|
openGraph: {
|
|
title: doc.title,
|
|
description: doc.description,
|
|
type: "article",
|
|
url: absoluteUrl(page.url),
|
|
images: [
|
|
{
|
|
url: `/og?title=${encodeURIComponent(
|
|
doc.title
|
|
)}&description=${encodeURIComponent(doc.description)}`,
|
|
},
|
|
],
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: doc.title,
|
|
description: doc.description,
|
|
images: [
|
|
{
|
|
url: `/og?title=${encodeURIComponent(
|
|
doc.title
|
|
)}&description=${encodeURIComponent(doc.description)}`,
|
|
},
|
|
],
|
|
creator: "@shadcn",
|
|
},
|
|
}
|
|
}
|
|
|
|
export default async function Page(props: {
|
|
params: Promise<{ slug: string[] }>
|
|
}) {
|
|
const params = await props.params
|
|
const page = source.getPage(params.slug)
|
|
if (!page) {
|
|
notFound()
|
|
}
|
|
|
|
const doc = page.data
|
|
const MDX = doc.body
|
|
const isChangelog = params.slug?.[0] === "changelog"
|
|
const neighbours = isChangelog
|
|
? { previous: null, next: null }
|
|
: findNeighbour(source.pageTree, page.url)
|
|
const raw = replaceComponentsList(await page.data.getText("raw"))
|
|
|
|
return (
|
|
<div
|
|
data-slot="docs"
|
|
className="flex scroll-mt-24 items-stretch pb-8 text-[1.05rem] sm:text-[15px] xl:w-full"
|
|
>
|
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
<div className="h-(--top-spacing) shrink-0" />
|
|
<div className="mx-auto flex w-full max-w-160 min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-foreground md:px-0 lg:py-8 dark:text-foreground">
|
|
<div className="flex flex-col gap-2">
|
|
<div className="flex flex-col gap-2">
|
|
<div className="flex items-center justify-between md:items-start">
|
|
<h1 className="scroll-m-24 text-3xl font-semibold tracking-tight sm:text-3xl">
|
|
{doc.title}
|
|
</h1>
|
|
<div className="docs-nav flex items-center gap-2">
|
|
<div className="hidden sm:block">
|
|
<DocsCopyPage page={raw} url={absoluteUrl(page.url)} />
|
|
</div>
|
|
<div className="ml-auto flex gap-2">
|
|
{neighbours.previous && (
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
className="extend-touch-target size-8 shadow-none md:size-7"
|
|
asChild
|
|
>
|
|
<Link href={neighbours.previous.url}>
|
|
<IconArrowLeft />
|
|
<span className="sr-only">Previous</span>
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
{neighbours.next && (
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
className="extend-touch-target size-8 shadow-none md:size-7"
|
|
asChild
|
|
>
|
|
<Link href={neighbours.next.url}>
|
|
<span className="sr-only">Next</span>
|
|
<IconArrowRight />
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{doc.description && (
|
|
<p className="text-[1.05rem] text-muted-foreground sm:text-base sm:text-balance md:max-w-[80%]">
|
|
{doc.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="w-full flex-1 pb-16 *:data-[slot=alert]:first:mt-0 sm:pb-0">
|
|
{params.slug &&
|
|
params.slug[0] === "components" &&
|
|
params.slug[1] &&
|
|
params.slug[2] && (
|
|
<DocsBaseSwitcher
|
|
base={params.slug[1]}
|
|
component={params.slug[2]}
|
|
className="mb-4"
|
|
/>
|
|
)}
|
|
<MDX components={mdxComponents} />
|
|
</div>
|
|
<div className="hidden h-16 w-full items-center gap-2 px-4 sm:flex sm:px-0">
|
|
{neighbours.previous && (
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
asChild
|
|
className="shadow-none"
|
|
>
|
|
<Link href={neighbours.previous.url}>
|
|
<IconArrowLeft /> {neighbours.previous.name}
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
{neighbours.next && (
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
className="ml-auto shadow-none"
|
|
asChild
|
|
>
|
|
<Link href={neighbours.next.url}>
|
|
{neighbours.next.name} <IconArrowRight />
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-(--sidebar-width) flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
|
<div className="h-(--top-spacing) shrink-0"></div>
|
|
{doc.toc?.length ? (
|
|
<div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8">
|
|
<DocsTableOfContents toc={doc.toc} />
|
|
</div>
|
|
) : null}
|
|
<div className="hidden flex-1 flex-col gap-6 px-6 xl:flex">
|
|
<OpenInV0Cta />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|