diff --git a/examples/advanced-blog/src/app/(web)/posts/[slug]/page.tsx b/examples/advanced-blog/src/app/(web)/posts/[slug]/page.tsx index f5d777ed..b98b9f92 100644 --- a/examples/advanced-blog/src/app/(web)/posts/[slug]/page.tsx +++ b/examples/advanced-blog/src/app/(web)/posts/[slug]/page.tsx @@ -57,7 +57,7 @@ export default async function Post(params: Params) {
- +
diff --git a/examples/advanced-blog/src/components/mdx/mdx-component.tsx b/examples/advanced-blog/src/components/mdx/mdx-component.tsx index 73ec9377..b547ed6d 100644 --- a/examples/advanced-blog/src/components/mdx/mdx-component.tsx +++ b/examples/advanced-blog/src/components/mdx/mdx-component.tsx @@ -1,9 +1,10 @@ -"use client"; -import { getMDXComponent } from "mdx-bundler/client"; -import Image from "next/image"; -import { ImgHTMLAttributes, useMemo } from "react"; -import { CustomCode, Pre } from "./custom-code"; -import CustomLink from "./custom-link"; +'use client' +import { getMDXComponent } from 'mdx-bundler/client' +import Image from 'next/image' +import { ImgHTMLAttributes, useMemo, useRef, useEffect, useState } from 'react' +import { CustomCode, Pre } from './custom-code' +import CustomLink from './custom-link' +import TOC from './toc' const MDXComponentsMap = { a: CustomLink, @@ -12,30 +13,51 @@ const MDXComponentsMap = { ), pre: Pre, - code: CustomCode, -}; + code: CustomCode +} type MDXComponentProps = { - content: string; - components?: Record; -}; + content: string + components?: Record + showTOC?: boolean +} export const MDXComponent = ({ content, components = {}, + showTOC = false }: MDXComponentProps) => { - const Component = useMemo(() => getMDXComponent(content), [content]); + const [renderedContent, setRenderedContent] = useState('') + const contentRef = useRef(null) + + const Component = useMemo(() => getMDXComponent(content), [content]) + + useEffect(() => { + if (contentRef.current) { + setRenderedContent(contentRef.current.innerHTML) + } + }, [content]) + + const shouldShowTOC = useMemo(() => { + if (showTOC === true) return true + if (showTOC === false) return false + }, [showTOC]) return ( - - ); -}; - -export default MDXComponent; + <> + {shouldShowTOC && } +
+ +
+ + ) +} + +export default MDXComponent diff --git a/examples/advanced-blog/src/components/mdx/toc.tsx b/examples/advanced-blog/src/components/mdx/toc.tsx new file mode 100644 index 00000000..52c264a3 --- /dev/null +++ b/examples/advanced-blog/src/components/mdx/toc.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useState, useMemo } from 'react' +import { twMerge } from 'tailwind-merge' + +interface TOCItem { + depth: number + value: string + url: string +} + +interface TOCProps { + content: string + maxDepth?: number + className?: string + headingClassName?: string + title?: string +} + +const TOC: React.FC = ({ + content, + maxDepth = 3, + className = 'toc', + headingClassName = 'toc-heading', + title = 'Table of Contents' +}) => { + const [headings, setHeadings] = useState([]) + + const extractHeadings = useMemo( + () => () => { + try { + const tempDiv = document.createElement('div') + tempDiv.innerHTML = content + + const headingElements = tempDiv.querySelectorAll( + 'h1, h2, h3, h4, h5, h6' + ) + return Array.from(headingElements) + .filter((el) => parseInt(el.tagName[1]) <= maxDepth) + .map((el) => ({ + depth: parseInt(el.tagName[1]), + value: el.textContent?.trim() || '', + url: `#${el.id}` + })) + } catch (error) { + console.error('Error extracting headings:', error) + return [] + } + }, + [content, maxDepth] + ) + + useEffect(() => { + setHeadings(extractHeadings()) + }, [extractHeadings]) + + if (headings.length === 0) { + return null + } + + return ( + + ) +} + +export default TOC