【react-markdown】目次機能をつくる【tailwind cssのデザイン例あり】
2022-04-26
2022-04-26
はじめに
キャラメルって美味しい、こふです。
ページの上部にある目次の作り方と、デザイン例を公開します。
これのことです。

環境
React
をメインに、CSS
は Tailwind CSS
をメインに使います。
terminal
❯ node -v v16.14.0
package.json
... "dependencies": { ... "react": "^17.0.2", "react-dom": "^17.0.2", "react-markdown": "^8.0.1", "react-syntax-highlighter": "^15.5.0", ... }, "devDependencies": { ... "autoprefixer": "^10.4.0", "eslint": "^8.13.0", "postcss": "^8.4.5", "tailwindcss": "^3.0.7", "typescript": "4.5.4" ... } ...
方針
markdown
の本文のうち、 h2,h3
タグを目次に含めたい場合が多いかと思います。本ブログでは、h2
を見るだけで何をしているか分かることが多いため、h2
のみ採用しています。
つまり、markdown
を parse
するときに h2
のみを受け取り、そこから値を作れば良いだけです。
ソースコード
- TOC.tsx
- H2.tsx
の 2 つを作ります。
TOC.tsx
目次の本体です。
src/components/function/TOC/TOC.tsximport React, { FC } from 'react' import { HiLink } from 'react-icons/hi' import ReactMarkdown from 'react-markdown' type Props = { markdown: string } const customH2 = ({ ...props }) => { return ( <li> <a href={`#${props.children}`} className="inline-flex items-center py-1 text-base text-stone-700 duration-300 hover:text-stone-500 dark:text-stone-100 dark:hover:text-stone-300 md:text-lg" > <HiLink className="mr-2" /> {props.children} </a> </li> ) } const components = { h2: customH2 } const TOC: FC<Props> = ({ markdown }) => { return ( <details open className="my-4 rounded-md bg-white p-2 shadow-md hover:cursor-pointer focus:outline-none dark:bg-stone-700 md:p-6" > <summary className="text-base font-semibold text-stone-800 focus:outline-none dark:text-stone-100 md:text-lg"> 目次(タップして移動) </summary> <ol className="p-2 md:p-4"> <ReactMarkdown className="md:prose-md dark:prose-invert" children={markdown} allowedElements={['h2']} components={components} /> </ol> </details> ) } export default TOC
H2.tsx
これは見出しに用います。
src/components/Heading/H2/H2.tsximport React, { FC } from 'react' import { HiLink } from 'react-icons/hi' type Props = { label: string classNameText?: string } const H2: FC<Props> = ({ label, classNameText }) => { return ( <a href={`#${classNameText}`} className="duration-300 hover:opacity-75"> <h2 id={classNameText} className="inline-flex items-center text-xl font-semibold text-stone-800 underline underline-offset-4 dark:text-stone-100 md:text-2xl lg:text-3xl" > <HiLink className="mr-2" /> {label} </h2> </a> ) } export default H2
ソースコード解説
TOC.tsx の解説
src/components/function/TOC/TOC.tsximport React, { FC } from 'react' import { HiLink } from 'react-icons/hi' import ReactMarkdown from 'react-markdown' type Props = { markdown: string }
ライブラリのインポートと TOC
コンポーネントに渡す Props
の型を定義します。
src/components/function/TOC/TOC.tsxconst customH2 = ({ ...props }) => { return ( <li> <a href={`#${props.children}`} className="inline-flex items-center py-1 text-base text-stone-700 duration-300 hover:text-stone-500 dark:text-stone-100 dark:hover:text-stone-300 md:text-lg" > <HiLink className="mr-2" /> {props.children} </a> </li> ) } const components = { h2: customH2 }
目次中のタップできる見出しです。href
を与えて、タップ時に遷移するようにします。遷移先は後の H2
で作ります。
src/components/function/TOC/TOC.tsxconst TOC: FC<Props> = ({ markdown }) => { return ( <details open className="my-4 rounded-md bg-white p-2 shadow-md hover:cursor-pointer focus:outline-none dark:bg-stone-700 md:p-6" > <summary className="text-base font-semibold text-stone-800 focus:outline-none dark:text-stone-100 md:text-lg"> 目次(タップして移動) </summary> <ol className="p-2 md:p-4"> <ReactMarkdown className="md:prose-md dark:prose-invert" children={markdown} allowedElements={['h2']} components={components} /> </ol> </details> ) } export default TOC
html
標準である、折りたたみができる details,summary
を使いました。
参考:https://developer.mozilla.org/ja/docs/Web/HTML/Element/details
目次を作るポイントとして、ReactMarkdown
に渡すことのできるコンポーネントの指定を h2
のみにします。
h3
も目次に含めたい場合はallowedElements={['h2','h3']}
とすれば OK です。
参考:https://github.com/remarkjs/react-markdown#props
それに合わせてconst components = { h2: customH2, h3: customH3 }
とし、デザインした h3
を作れば OK です。
H2.tsx の解説
src/components/Heading/H2/H2.tsximport React, { FC } from 'react' import { HiLink } from 'react-icons/hi' type Props = { label: string classNameText?: string } const H2: FC<Props> = ({ label, classNameText }) => { return ( <a href={`#${classNameText}`} className="duration-300 hover:opacity-75"> <h2 id={classNameText} className="inline-flex items-center text-xl font-semibold text-stone-800 underline underline-offset-4 dark:text-stone-100 md:text-2xl lg:text-3xl" > <HiLink className="mr-2" /> {label} </h2> </a> ) } export default H2
href
に渡すことで、タップしたリンクの共有を行うと、適切な位置にスクロールして共有できます。
id
に指定することで、https://example.com/path#hoge
というときに hoge
という id
に遷移できます。
さいごに
これで react-markdown
に目次を実装することが可能です。
ちなみに、他のライブラリとしてtocbot
を使うとリッチな目次も可能です。