All files / packages/design-system-2-site/src/components CodeBlockHydrator.tsx

100% Statements 43/43
87.5% Branches 7/8
100% Functions 8/8
100% Lines 40/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 672x                     7x 6x 15x 15x 5x 5x 4x   4x 4x   5x 5x   5x 5x   5x 5x 5x 5x   5x 5x 5x 5x 5x 2x 2x 1x 1x 1x 1x 1x     1x     5x   5x 5x 5x       6x 6x 6x 6x     7x    
'use client';
 
import { useEffect } from 'react';
 
/**
 * Post-hydration hydrator: finds every <pre><code> in the rendered markdown
 * and augments it with a copy button + optional filename/language header.
 * Runs on mount and re-runs when the document body mutates so newly injected
 * blocks (e.g. from client-side navigation) are still wired up.
 */
export function CodeBlockHydrator() {
  useEffect(() => {
    const decorate = () => {
      const pres = document.querySelectorAll<HTMLPreElement>('pre:not([data-cb-hydrated])');
      pres.forEach((pre) => {
        const code = pre.querySelector('code');
        if (!code) return;
        pre.dataset.cbHydrated = 'true';
 
        const languageMatch = Array.from(code.classList).find((c) => c.startsWith('language-'));
        const language = languageMatch ? languageMatch.replace('language-', '') : '';
 
        const wrapper = document.createElement('div');
        wrapper.className = 'code-block';
 
        const header = document.createElement('div');
        header.className = 'code-block__header';
 
        const meta = document.createElement('span');
        meta.className = 'code-block__lang';
        meta.textContent = language || 'text';
        header.appendChild(meta);
 
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'code-block__copy';
        btn.textContent = 'Copy';
        btn.addEventListener('click', async () => {
          try {
            await navigator.clipboard.writeText(code.textContent ?? '');
            btn.textContent = 'Copied';
            btn.setAttribute('data-copied', 'true');
            setTimeout(() => {
              btn.textContent = 'Copy';
              btn.removeAttribute('data-copied');
            }, 1500);
          } catch {
            btn.textContent = 'Error';
          }
        });
        header.appendChild(btn);
 
        pre.parentNode?.insertBefore(wrapper, pre);
        wrapper.appendChild(header);
        wrapper.appendChild(pre);
      });
    };
 
    decorate();
    const observer = new MutationObserver(decorate);
    observer.observe(document.body, { childList: true, subtree: true });
    return () => observer.disconnect();
  }, []);
 
  return null;
}