From bc2db187aabeb6707cc32f3de1a5553c96894b2d Mon Sep 17 00:00:00 2001 From: shadcn Date: Wed, 6 May 2026 13:27:03 +0400 Subject: [PATCH] feat(docs): add clickable mdx heading anchors --- apps/v4/mdx-components.tsx | 193 +++++++++++++++++++++++++++---------- 1 file changed, 140 insertions(+), 53 deletions(-) diff --git a/apps/v4/mdx-components.tsx b/apps/v4/mdx-components.tsx index 1e81720c9f..623cfd925e 100644 --- a/apps/v4/mdx-components.tsx +++ b/apps/v4/mdx-components.tsx @@ -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 ( + + + {children} + + + + ) +} + export const mdxComponents = { - h1: ({ className, ...props }: React.ComponentProps<"h1">) => ( -

- ), - h2: ({ className, ...props }: React.ComponentProps<"h2">) => { + h1: ({ className, children, id, ...props }: React.ComponentProps<"h1">) => { + const headingId = id ?? getHeadingId(children) + + return ( +

+ {children} +

+ ) + }, + h2: ({ className, children, id, ...props }: React.ComponentProps<"h2">) => { + const headingId = id ?? getHeadingId(children) + return (

h3]:mt-4! [&+h3]:mt-6! [&+p]:mt-4!", className )} {...props} - /> + > + {children} +

+ ) + }, + h3: ({ className, children, id, ...props }: React.ComponentProps<"h3">) => { + const headingId = id ?? getHeadingId(children) + + return ( +

+ {children} +

+ ) + }, + h4: ({ className, children, id, ...props }: React.ComponentProps<"h4">) => { + const headingId = id ?? getHeadingId(children) + + return ( +

+ {children} +

+ ) + }, + h5: ({ className, children, id, ...props }: React.ComponentProps<"h5">) => { + const headingId = id ?? getHeadingId(children) + + return ( +
+ {children} +
+ ) + }, + h6: ({ className, children, id, ...props }: React.ComponentProps<"h6">) => { + const headingId = id ?? getHeadingId(children) + + return ( +
+ {children} +
) }, - h3: ({ className, ...props }: React.ComponentProps<"h3">) => ( -

- ), - h4: ({ className, ...props }: React.ComponentProps<"h4">) => ( -

- ), - h5: ({ className, ...props }: React.ComponentProps<"h5">) => ( -

- ), - h6: ({ className, ...props }: React.ComponentProps<"h6">) => ( -
- ), a: ({ className, ...props }: React.ComponentProps<"a">) => (