feat(docs): add clickable mdx heading anchors

This commit is contained in:
shadcn
2026-05-06 13:27:03 +04:00
parent 92b4927a80
commit bc2db187aa

View File

@@ -55,69 +55,156 @@ function ComponentsListWrapper() {
)
}
function getNodeText(node: React.ReactNode): string {
if (typeof node === "string" || typeof node === "number") {
return String(node)
}
if (Array.isArray(node)) {
return node.map((child) => getNodeText(child)).join("")
}
if (React.isValidElement<{ children?: React.ReactNode }>(node)) {
return getNodeText(node.props.children)
}
return ""
}
function getHeadingId(children: React.ReactNode) {
const id = getNodeText(children)
.trim()
.replace(/\s+/g, "-")
.replace(/'/g, "")
.replace(/\?/g, "")
.toLowerCase()
return id || undefined
}
function HeadingAnchor({
id,
children,
}: {
id?: string
children: React.ReactNode
}) {
if (!id) {
return children
}
return (
<a className="group no-underline" href={`#${id}`}>
<span className="underline-offset-4 group-hover:underline">
{children}
</span>
<span
aria-hidden="true"
className="ml-2 text-muted-foreground opacity-0 group-hover:opacity-100"
>
#
</span>
</a>
)
}
export const mdxComponents = {
h1: ({ className, ...props }: React.ComponentProps<"h1">) => (
<h1
className={cn(
"mt-2 scroll-m-28 font-heading text-3xl font-bold tracking-tight",
className
)}
{...props}
/>
),
h2: ({ className, ...props }: React.ComponentProps<"h2">) => {
h1: ({ className, children, id, ...props }: React.ComponentProps<"h1">) => {
const headingId = id ?? getHeadingId(children)
return (
<h1
id={headingId}
className={cn(
"mt-2 scroll-m-28 font-heading text-3xl font-bold tracking-tight",
className
)}
{...props}
>
<HeadingAnchor id={headingId}>{children}</HeadingAnchor>
</h1>
)
},
h2: ({ className, children, id, ...props }: React.ComponentProps<"h2">) => {
const headingId = id ?? getHeadingId(children)
return (
<h2
id={props.children
?.toString()
.replace(/ /g, "-")
.replace(/'/g, "")
.replace(/\?/g, "")
.toLowerCase()}
id={headingId}
className={cn(
"[&+]*:[code]:text-xl mt-10 scroll-m-28 font-heading text-xl font-medium tracking-tight first:mt-0 lg:mt-12 [&+.steps]:mt-0! [&+.steps>h3]:mt-4! [&+h3]:mt-6! [&+p]:mt-4!",
className
)}
{...props}
/>
>
<HeadingAnchor id={headingId}>{children}</HeadingAnchor>
</h2>
)
},
h3: ({ className, children, id, ...props }: React.ComponentProps<"h3">) => {
const headingId = id ?? getHeadingId(children)
return (
<h3
id={headingId}
className={cn(
"mt-12 scroll-m-28 font-heading text-lg font-medium tracking-tight [&+p]:mt-4! *:[code]:text-xl",
className
)}
{...props}
>
<HeadingAnchor id={headingId}>{children}</HeadingAnchor>
</h3>
)
},
h4: ({ className, children, id, ...props }: React.ComponentProps<"h4">) => {
const headingId = id ?? getHeadingId(children)
return (
<h4
id={headingId}
className={cn(
"mt-8 scroll-m-28 font-heading text-base font-medium tracking-tight",
className
)}
{...props}
>
<HeadingAnchor id={headingId}>{children}</HeadingAnchor>
</h4>
)
},
h5: ({ className, children, id, ...props }: React.ComponentProps<"h5">) => {
const headingId = id ?? getHeadingId(children)
return (
<h5
id={headingId}
className={cn(
"mt-8 scroll-m-28 text-base font-medium tracking-tight",
className
)}
{...props}
>
<HeadingAnchor id={headingId}>{children}</HeadingAnchor>
</h5>
)
},
h6: ({ className, children, id, ...props }: React.ComponentProps<"h6">) => {
const headingId = id ?? getHeadingId(children)
return (
<h6
id={headingId}
className={cn(
"mt-8 scroll-m-28 text-base font-medium tracking-tight",
className
)}
{...props}
>
<HeadingAnchor id={headingId}>{children}</HeadingAnchor>
</h6>
)
},
h3: ({ className, ...props }: React.ComponentProps<"h3">) => (
<h3
className={cn(
"mt-12 scroll-m-28 font-heading text-lg font-medium tracking-tight [&+p]:mt-4! *:[code]:text-xl",
className
)}
{...props}
/>
),
h4: ({ className, ...props }: React.ComponentProps<"h4">) => (
<h4
className={cn(
"mt-8 scroll-m-28 font-heading text-base font-medium tracking-tight",
className
)}
{...props}
/>
),
h5: ({ className, ...props }: React.ComponentProps<"h5">) => (
<h5
className={cn(
"mt-8 scroll-m-28 text-base font-medium tracking-tight",
className
)}
{...props}
/>
),
h6: ({ className, ...props }: React.ComponentProps<"h6">) => (
<h6
className={cn(
"mt-8 scroll-m-28 text-base font-medium tracking-tight",
className
)}
{...props}
/>
),
a: ({ className, ...props }: React.ComponentProps<"a">) => (
<a
className={cn("font-medium underline underline-offset-4", className)}