From 87f08b7fbac45cecff63741f70c2db5b39906cd4 Mon Sep 17 00:00:00 2001 From: Danilo Britto Date: Fri, 30 Aug 2024 12:32:35 -0500 Subject: [PATCH] feat: Add support for copy button (#314) * Add support for copy button * Minor fixes * Clean up global styles * Update CopyButton components * Update rehypeCopyButton helper * Update CopyButton entrypoint * Minor changes * Code --------- Co-authored-by: Antoine BERNIER --- src/app/globals.css | 8 ---- src/components/mdx/Code/Code.tsx | 59 +++++++++++++++++++++++++++ src/components/mdx/Code/index.ts | 1 + src/components/mdx/Code/rehypeCode.ts | 19 +++++++++ src/components/mdx/index.tsx | 1 + src/utils/docs.tsx | 2 + 6 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/components/mdx/Code/Code.tsx create mode 100644 src/components/mdx/Code/index.ts create mode 100644 src/components/mdx/Code/rehypeCode.ts diff --git a/src/app/globals.css b/src/app/globals.css index 33a46d20..e2b124f9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -45,7 +45,6 @@ body { code[class*='language-'], pre[class*='language-'] { - background: none; text-align: left; white-space: pre; word-spacing: normal; @@ -70,13 +69,6 @@ pre[class*='language-'] { --falsy: #f087bd; --linenumber-border-width: theme('space.1'); --pad: theme('space.6'); - - @apply my-5 overflow-auto rounded-lg p-[--pad] font-mono text-sm; -} - -:not(pre) > code[class*='language-'], -pre[class*='language-'] { - @apply bg-inverse-surface-light; } /* Inline code */ diff --git a/src/components/mdx/Code/Code.tsx b/src/components/mdx/Code/Code.tsx new file mode 100644 index 00000000..89a10bc6 --- /dev/null +++ b/src/components/mdx/Code/Code.tsx @@ -0,0 +1,59 @@ +'use client' + +import cn from '@/lib/cn' +import { ComponentProps, ReactNode, useEffect, useState } from 'react' +import { TbClipboard, TbClipboardCheck } from 'react-icons/tb' + +export const Code = ({ children, className, ...props }: ComponentProps<'pre'>) => { + const [copied, setCopied] = useState(false) + + const handleClick = async () => { + const textToCopy = extractTextFromChildren(children) + + await navigator.clipboard.writeText(textToCopy) + setCopied(true) + } + + useEffect(() => { + if (!copied) return + const int = setTimeout(() => setCopied(false), 2000) + return () => clearTimeout(int) + }, [copied]) + + return ( +
+
+        {children}
+      
+ +
+ ) +} + +// Recursive function to extract text content from React nodes +const extractTextFromChildren = (children: ReactNode): string => { + if (typeof children === 'string') { + return children + } + + if (Array.isArray(children)) { + return children.map(extractTextFromChildren).join('') + } + + if (typeof children === 'object' && children !== null && 'props' in children) { + return extractTextFromChildren(children.props.children) + } + + return '' +} diff --git a/src/components/mdx/Code/index.ts b/src/components/mdx/Code/index.ts new file mode 100644 index 00000000..8fc05455 --- /dev/null +++ b/src/components/mdx/Code/index.ts @@ -0,0 +1 @@ +export * from './Code' diff --git a/src/components/mdx/Code/rehypeCode.ts b/src/components/mdx/Code/rehypeCode.ts new file mode 100644 index 00000000..8ceef060 --- /dev/null +++ b/src/components/mdx/Code/rehypeCode.ts @@ -0,0 +1,19 @@ +import type { Root } from 'hast' +import { visit } from 'unist-util-visit' + +export function rehypeCode() { + return () => (tree: Root) => { + visit(tree, null, function (node) { + // console.log('node', node) + + const isMDPre = + 'tagName' in node && + node.tagName === 'pre' && + node.properties?.className?.toString()?.includes('language-') + + if (isMDPre) { + node.tagName = 'Code' // map to React component + } + }) + } +} diff --git a/src/components/mdx/index.tsx b/src/components/mdx/index.tsx index aefd7275..5e75739f 100644 --- a/src/components/mdx/index.tsx +++ b/src/components/mdx/index.tsx @@ -1,3 +1,4 @@ +export * from './Code' export * from './Codesandbox' export * from './Details' export * from './Gha' diff --git a/src/utils/docs.tsx b/src/utils/docs.tsx index 6f60af76..e505160c 100644 --- a/src/utils/docs.tsx +++ b/src/utils/docs.tsx @@ -1,5 +1,6 @@ import type { Doc, DocToC } from '@/app/[...slug]/DocsContext' import * as components from '@/components/mdx' +import { rehypeCode } from '@/components/mdx/Code/rehypeCode' import { Codesandbox } from '@/components/mdx/Codesandbox' import { fetchCSB } from '@/components/mdx/Codesandbox/fetchCSB' import { rehypeCodesandbox } from '@/components/mdx/Codesandbox/rehypeCodesandbox' @@ -156,6 +157,7 @@ async function _getDocs( rehypeSummary, rehypeGha, rehypePrismPlus, + rehypeCode(), rehypeCodesandbox(boxes), // 1. put all Codesandbox[id] into `doc.boxes` rehypeToc(tableOfContents, url, title), // 2. will populate `doc.tableOfContents` ],