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

88.88% Statements 40/45
63.63% Branches 21/33
100% Functions 10/10
100% Lines 38/38

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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 832x                       13x 13x         10x     10x   10x 10x   10x   10x   10x 10x       7x 6x 13x 13x   9x       6x 5x     6x   6x 7x 7x 7x 7x       6x         6x 1x 1x 1x 1x 1x 1x 1x   6x   6x 6x 6x       7x    
'use client';
 
import { useEffect } from 'react';
 
/**
 * Keeps every Storybook iframe on the page (`.component-demo` and
 * `.storybook-embed`) in sync with the current site theme. Watches
 * `data-theme` on `<html>` and re-points each iframe's `src` at the
 * theme-appropriate Storybook URL. Individual embeds can still
 * be toggled locally via a `.storybook-theme-toggle` button.
 */
function currentTheme(): 'light' | 'dark' {
  const attr = document.documentElement.getAttribute('data-theme');
  return attr === 'dark' ? 'dark' : 'light';
}
 
function applyTheme(embed: HTMLElement, theme: 'light' | 'dark') {
  const iframe =
    embed.tagName === 'IFRAME'
      ? (embed as HTMLIFrameElement)
      : (embed.querySelector('iframe') as HTMLIFrameElement | null);
  Iif (!iframe) return;
 
  const baseUrl = iframe.dataset.storybookUrl || iframe.getAttribute('data-src') || '';
  Iif (!baseUrl) return;
 
  const sep = baseUrl.includes('?') ? '&' : '?';
  const target =
    theme === 'dark' ? `${baseUrl}${sep}globals=theme:dark;backgrounds.value:!hex(333333)` : baseUrl;
 
  if (iframe.src !== target) iframe.src = target;
  embed.setAttribute('data-sb-theme', theme);
}
 
export function StorybookThemeToggle() {
  useEffect(() => {
    const syncAll = () => {
      const theme = currentTheme();
      document
        .querySelectorAll<HTMLElement>('.component-demo, .storybook-embed')
        .forEach((el) => applyTheme(el, theme));
    };
 
    // Preserve `src` in `data-storybook-url` so we can rewrite it later.
    document.querySelectorAll<HTMLIFrameElement>('iframe.storybook-embed').forEach((iframe) => {
      Eif (!iframe.dataset.storybookUrl) iframe.dataset.storybookUrl = iframe.src;
    });
 
    syncAll();
 
    const observer = new MutationObserver((mutations) => {
      for (const m of mutations) {
        Eif (m.type === 'attributes' && m.attributeName === 'data-theme') {
          syncAll();
          break;
        }
      }
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['data-theme'],
    });
 
    const onClick = (e: Event) => {
      Iif (!(e.target instanceof Element)) return;
      const btn = e.target.closest<HTMLElement>('.storybook-theme-toggle');
      Iif (!btn) return;
      const embed = btn.closest<HTMLElement>('.component-demo, .storybook-embed');
      Iif (!embed) return;
      const current = embed.getAttribute('data-sb-theme') === 'dark' ? 'dark' : 'light';
      applyTheme(embed, current === 'light' ? 'dark' : 'light');
    };
    document.addEventListener('click', onClick);
 
    return () => {
      observer.disconnect();
      document.removeEventListener('click', onClick);
    };
  }, []);
 
  return null;
}