コードブロックにコピーボタンを追加する next-mdx-remote
コードブロックにコピーボタンを追加する方法
next-mdx-remoteではコードブロックのコピーボタンが利用できません。コピーボタンが有った方がクールでいいですよね。本記事では下のようなコピーボタンを実装する方法について紹介します。
Copied!!1. 右上にマウスを合わせるとコピーボタンがでます。 1. コピーボタンを押すと、ボタンのスタイルが変化します。
方法の概要
- カスタムコンポーネントを使う
- コピーボタンを実装する
本記事では次のコードをベースにします。
components/codeblock.jsCopied!!import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { atomDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; const CodeBlock = ({ className, children = "" }) => { // コードブロックの各要素を設定 const match = /language-(\w+)(:?.*)/.exec(className || ""); const language = match && match[1] ? match[1] : ""; const code = String(children).replace(/\n$/, ""); return ( <> <SyntaxHighlighter language={language} style={atomDark} className="code-block" > {code} </SyntaxHighlighter> <style jsx global>{` .code-block { border-radius: 0.3rem !important; padding: 1.5rem !important; } `}</style> </> ); }; export default CodeBlock;
方法
-
モジュールのインポート
Copied!!$ npm install --save react-copy-to-clipboard $ npm install react-icons --save
-
ボタンを実装する。変更量が多いのでdiff表示しています。
Copied!!import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { atomDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; +import { useState } from "react"; +import CopyToClipboard from "react-copy-to-clipboard"; +import { BiCheck, BiCopy } from "react-icons/bi"; const CodeBlock = ({ className, children = "" }) => { + // コピーボタンの処理 + const [isButtonActive, setIsButtonActive] = useState(false); + const normalStyle = { + opacity: 0, + transition: "0.1s", + }; + const activeStyle = { + opacity: 1, + transition: "0.1s", + }; + const copyBtnStyle = isButtonActive ? activeStyle : normalStyle; + + // コピー完了メッセージの処理 + const [isCopied, setCopied] = useState(false); + const handleClick = () => { + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 3000); + }; + const copiedStyle = isCopied ? activeStyle : normalStyle; + // コードブロックの各要素を設定 const match = /language-(\w+)(:?.*)/.exec(className || ""); const language = match && match[1] ? match[1] : ""; const code = String(children).replace(/\n$/, ""); return ( <> - <SyntaxHighlighter - language={language} - style={atomDark} - className="code-block" + <div + className="code-block-wrapper" + onMouseEnter={() => setIsButtonActive(true)} + onMouseLeave={() => setIsButtonActive(false)} > - {code} - </SyntaxHighlighter> + <div className="copied-tooltip" style={copiedStyle}> + Copied!! + </div> + <div className="copy-button" style={copyBtnStyle}> + <CopyToClipboard text={code} onCopy={() => handleClick()}> + {isCopied ? ( + <BiCheck color="grey" size={20} /> + ) : ( + <BiCopy color="grey" size={20} /> + )} + </CopyToClipboard> + </div> + <SyntaxHighlighter + language={language} + style={atomDark} + className="code-block" + > + {code} + </SyntaxHighlighter> + </div> + <style jsx>{` + .code-block-wrapper { + font-size: 0.9rem; + margin-bottom: 2rem; + position: relative; + } + .copy-button { + display: inline-block; + position: absolute; + top: 0.8rem; + right: 0.8rem; + background-color: rgba(50, 50, 50, 0.1); + border: 1px solid grey; + border-radius: 0.3rem; + padding: 0.2rem; + } + .copy-button:hover { + background-color: rgba(50, 50, 50, 0.9); + cursor: pointer; + } + .copied-tooltip { + position: absolute; + top: 0.8rem; + right: 3.2rem; + color: white; + background-color: rgba(50, 50, 50, 0.5); + border-radius: 0.2rem; + padding: 0.3rem; + } + `}</style> <style jsx global>{` .code-block { border-radius: 0.3rem !important;
-
MDXRemoteのコンポーネントに追加する
pages/hoge.jsCopied!!import CodeBlock from '../../components/codeblock'; const components = { code: (props) => ( <CodeBlock {...props} /> ), }; export default function HogePage({ source }){ return ( <MDXRemote {...source} components={components}> ) }
終わりに
本記事ではコードブロックにコピーボタンを実装する方法について紹介しました。カスタマイザビリティが高くていいですね。オリジナルのものを作りましょう!最後に完成形をのせておきます。
components/codeblock.jsCopied!!import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { atomDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; import { useState } from "react"; import CopyToClipboard from "react-copy-to-clipboard"; import { BiCheck, BiCopy } from "react-icons/bi"; const CodeBlock = ({ className, children = "" }) => { // コピーボタンの処理 const [isButtonActive, setIsButtonActive] = useState(false); const normalStyle = { opacity: 0, transition: "0.1s", }; const activeStyle = { opacity: 1, transition: "0.1s", }; const copyBtnStyle = isButtonActive ? activeStyle : normalStyle; // コピー完了メッセージの処理 const [isCopied, setCopied] = useState(false); const handleClick = () => { setCopied(true); setTimeout(() => { setCopied(false); }, 3000); }; const copiedStyle = isCopied ? activeStyle : normalStyle; // コードブロックの各要素を設定 const match = /language-(\w+)(:?.*)/.exec(className || ""); const language = match && match[1] ? match[1] : ""; const code = String(children).replace(/\n$/, ""); return ( <> <div className="code-block-wrapper" onMouseEnter={() => setIsButtonActive(true)} onMouseLeave={() => setIsButtonActive(false)} > <div className="copied-tooltip" style={copiedStyle}> Copied!! </div> <div className="copy-button" style={copyBtnStyle}> <CopyToClipboard text={code} onCopy={() => handleClick()}> {isCopied ? ( <BiCheck color="grey" size={20} /> ) : ( <BiCopy color="grey" size={20} /> )} </CopyToClipboard> </div> <SyntaxHighlighter language={language} style={atomDark} className="code-block" > {code} </SyntaxHighlighter> </div> <style jsx>{` .code-block-wrapper { font-size: 0.9rem; margin-bottom: 2rem; position: relative; } .copy-button { display: inline-block; position: absolute; top: 0.8rem; right: 0.8rem; background-color: rgba(50, 50, 50, 0.1); border: 1px solid grey; border-radius: 0.3rem; padding: 0.2rem; } .copy-button:hover { background-color: rgba(50, 50, 50, 0.9); cursor: pointer; } .copied-tooltip { position: absolute; top: 0.8rem; right: 3.2rem; color: white; background-color: rgba(50, 50, 50, 0.5); border-radius: 0.2rem; padding: 0.3rem; } `}</style> <style jsx global>{` .code-block { border-radius: 0.3rem !important; padding: 1.5rem !important; } `}</style> </> ); }; export default CodeBlock;