PetOffice

The CodeBlock Component and CopyToClipboard

Next.jsTypeScript

I found a few examples online for creating a code block with syntax highlighting and a copy-to-clipboard button, but I couldn’t get them to work straight away. So I thought, “It can’t be that hard to make this myself.” After some trial and error, I came up with these two components: CodeBlock and CopyToClipboard. Here’s how they work together and what they do:

Purpose

The CodeBlock component is responsible for:

  • Rendering a block of code.
  • Applying syntax highlighting using Highlight.js.
  • Adding a CopyToClipboard button for easy copying of the code.

Key Features

  1. Props:
    • code: The code snippet to display.
    • language: The programming language of the code snippet for syntax highlighting.
  2. Highlight.js Integration:
    • The hljs.highlightElement method is called on the code element to apply syntax highlighting.
    • Highlight.js styles are imported (felipec.css in this case) to style the highlighted code.
  3. HTML Parsing:
    • The html-react-parser library is used to safely parse HTML content in the code string, ensuring proper rendering of any HTML tags.
  4. Ref for Highlight.js:
    • The useRef hook is used to get a reference to the <code> element.
    • The useEffect hook ensures the highlighting runs whenever the language prop changes.
  5. Styling:
    • The <div> containing the code block is styled with padding, margins, and a rounded-lg border for aesthetics.

Code Breakdown

const CodeBlock = ({ code, language }: Props) => {
  const codeRef = useRef<HTMLElement>(null); // Ref to access the <code> element

  useEffect(() => {
    if (codeRef.current) {
      try {
        hljs.highlightElement(codeRef.current); // Apply syntax highlighting
      } catch (error) {
        console.error(`Error highlighting code with language "${language}":`, error);
      }
    }
  }, [language]); // Re-run highlighting if the language changes

  return (
    <div className='rounded-lg mr-11 ml-6 mt-6 mb-6 relative'>
      {/* Copy-to-clipboard button */}
      <CopyToClipboard content={parse(code) as string}></CopyToClipboard>
      
      <pre>
        <code ref={codeRef} className={`language-${language} rounded-2xl min-h-28`}>
          {parse(code)} {/* Render the parsed code */}
        </code>
      </pre>
    </div>
  );
};

2. The CopyToClipboard Component

Purpose

The CopyToClipboard component handles the logic for:

  • Copying text content to the clipboard.
  • Providing visual feedback (like a “copied!” message) when the copy operation is successful.

Key Features

  1. Props:
    • content: The text content to copy to the clipboard.
  2. Clipboard API:
    • The navigator.clipboard.writeText method is used to copy the content to the user’s clipboard.
    • Errors during the copy operation are caught and logged to the console.
  3. Visual Feedback:
    • The isCopied state tracks whether the content has been successfully copied.
    • A setTimeout resets isCopied to false after 1 second, so the “copied!” message disappears.
  4. SVG Button Design:
    • A button styled with a small, modern SVG clipboard icon provides the copy functionality.

Code Breakdown

const CopyToClipboard = ({ content }: CopyProp) => {
  const [isCopied, setIsCopied] = useState(false); // Tracks if the content is copied

  const copy = async (content: string) => {
    try {
      await navigator.clipboard.writeText(content); // Copy content to clipboard
      setIsCopied(true); // Show "copied!" feedback
      setTimeout(() => {
        setIsCopied(false); // Reset feedback after 1 second
      }, 1000);
    } catch (err) {
      console.error("Failed to copy text: ", err); // Handle copy errors
    }
  };

  return (
    <button
      className={`w-6 h-6 absolute right-6 top-5 text-xs ${isCopied ? "text-zinc-400" : "text-zinc-500"}`}
      onClick={() => copy(content)} // Call copy function on click
    >
      {/* SVG icon for clipboard */}
      <svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="..." stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
      </svg>
      {isCopied ? "copied!" : "copy"} {/* Display feedback or default "copy" */}
    </button>
  );
};

How They Work Together

  1. The CodeBlock component renders a code snippet with syntax highlighting using Highlight.js.
  2. The CopyToClipboard component is added to the CodeBlock, positioned as a floating button on the top-right corner.
  3. Clicking the copy button copies the content of the code snippet (passed via content) to the clipboard and briefly shows a “copied!” message.