Components
Loading preview...
In-page documentation table of contents with scroll-position spy, animated SVG tree indicator, and mobile sticky collapsible.
@reapollo
npx shadcn@latest add https://21st.dev/r/larsen66/table-of-contents"use client"
import * as React from "react"
import {
TableOfContents,
TableOfContentsMobile,
type TocItem,
} from "@/components/ui/table-of-contents"
const sections: Array<TocItem & { body: string }> = [
{
id: "quick-start",
title: "Quick Start",
depth: 2,
body: "Get up and running in minutes. Install the package, configure your environment, and add the handler to your application entry point.",
},
{
id: "usage",
title: "Usage",
depth: 2,
body: "Learn how to integrate the handler into your stack. The API is designed to stay out of your way while giving you full control over auth flows.",
},
{
id: "mount-the-handler",
title: "Mount the handler",
depth: 3,
body: "Place the handler at the root of your app so every route and server action can access the session. This section walks through the exact mount points for Next.js, Remix, and plain Node.",
},
{
id: "usage-tips",
title: "Usage tips",
depth: 3,
body: "Prefer lazy session reads on static pages, cache session lookups per request, and keep redirect URLs explicit so users land where they expect after sign-in.",
},
{
id: "protecting-resources",
title: "Protecting Resources",
depth: 2,
body: "Once the handler is mounted, you can guard pages, layouts, and server functions with a consistent API. Pick the pattern that matches your routing model.",
},
{
id: "protecting-routes",
title: "Protecting Routes",
depth: 3,
body: "Wrap individual routes with a guard that checks the session before rendering. Redirect unauthenticated users to your sign-in page.",
},
{
id: "protecting-multiple-routes-layout",
title: "Protecting Multiple Routes (Layout)",
depth: 3,
body: "Apply protection at the layout level when an entire section of your app requires authentication. Child routes inherit the guard automatically.",
},
{
id: "protecting-server-functions",
title: "Protecting Server Functions",
depth: 3,
body: "Validate the session inside server actions and route handlers before performing mutations or returning sensitive data.",
},
]
export default function TableOfContentsDemo() {
const [activeId, setActiveId] = React.useState(sections[2]?.id)
const handleItemClick = React.useCallback((id: string) => {
setActiveId(id)
}, [])
return (
<div className="flex min-h-screen w-full items-center justify-center overflow-hidden bg-[radial-gradient(circle_at_top,hsl(var(--muted))_0,transparent_34rem)] p-6 text-foreground">
<TableOfContentsMobile
items={sections}
activeId={activeId}
onItemClick={handleItemClick}
className="md:hidden"
/>
<div className="grid w-full max-w-4xl grid-cols-1 overflow-hidden rounded-[2rem] border bg-background/95 shadow-2xl shadow-foreground/10 md:grid-cols-[minmax(0,1fr)_260px]">
<article className="relative min-h-[560px] border-r bg-card/40 p-7">
<div className="mb-8 flex items-center gap-2">
<span className="size-2.5 rounded-full bg-red-400" />
<span className="size-2.5 rounded-full bg-yellow-400" />
<span className="size-2.5 rounded-full bg-green-400" />
<span className="ml-3 text-xs font-medium text-muted-foreground">
Quick Start Guide
</span>
</div>
<h1 className="text-4xl font-semibold tracking-tight">Quick Start</h1>
<p className="mt-3 max-w-lg text-sm leading-6 text-muted-foreground">
Click a table of contents item to move the active indicator and
highlight the matching documentation section.
</p>
<div className="mt-7 space-y-3">
{sections.map((section) => {
const active = section.id === activeId
return (
<section
key={section.id}
data-section-id={section.id}
className={
"rounded-2xl border p-4 transition-all duration-500 " +
(active
? "border-foreground/20 bg-foreground/[0.055] shadow-sm"
: "border-border/45 bg-background/50 opacity-55")
}
>
<h2 className="text-base font-semibold tracking-tight">
{section.title}
</h2>
<p className="mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground">
{section.body}
</p>
</section>
)
})}
</div>
</article>
<aside className="flex items-center justify-center bg-muted/25 p-6">
<div className="w-full max-w-[220px] rounded-2xl border bg-background p-4 shadow-sm">
<TableOfContents
items={sections}
activeId={activeId}
onItemClick={handleItemClick}
className="[&_a]:py-0.5 [&_a]:pr-0 [&_a]:text-[0.72rem] [&_a]:leading-4"
/>
</div>
</aside>
</div>
</div>
)
}
export { TableOfContentsDemo }