diff --git a/storybook/.storybook/preview.ts b/storybook/.storybook/preview.ts index ad0e19695..3b3b2386e 100644 --- a/storybook/.storybook/preview.ts +++ b/storybook/.storybook/preview.ts @@ -1,20 +1,12 @@ import type { Preview } from "@storybook/react"; import { themes } from "@storybook/theming"; +import { useEffect } from "react"; import "../src/app/globals.css"; -// Extend Window interface to include our custom property -declare global { - interface Window { - __themeListenerAdded?: boolean; - } -} - const preview: Preview = { parameters: { darkMode: { - // Override the default dark theme dark: { ...themes.dark, appBg: "black" }, - // Override the default light theme light: { ...themes.normal, appBg: "white" }, darkClass: "dark", lightClass: "light", @@ -33,60 +25,52 @@ const preview: Preview = { }, }, }, - // Add custom script to handle theme messages from parent window + decorators: [ (Story) => { - // Add the message listener script only once - if (typeof window !== "undefined" && !window.__themeListenerAdded) { - window.__themeListenerAdded = true; + useEffect(() => { + const handler = (event: MessageEvent) => { + // ✅ security fix: validate message + if (!event.data || event.data.type !== "THEME_CHANGE") return; + + const { isDark, styles } = event.data; + + const root = document.documentElement; + + root.classList.toggle("dark", isDark); + root.classList.toggle("light", !isDark); + + if (styles?.background) { + document.body.style.background = styles.background; + root.style.background = styles.background; + } + }; + + window.addEventListener("message", handler); + + // initial sync (safe) + try { + if (window.parent && window.parent !== window) { + const parentDark = + window.parent.document.documentElement.classList.contains("dark"); + + const root = document.documentElement; + + root.classList.toggle("dark", parentDark); + root.classList.toggle("light", !parentDark); - const script = document.createElement("script"); - script.textContent = ` - (function() { - // Listen for theme messages from parent window - window.addEventListener('message', function(event) { - if (event.data && event.data.type === 'THEME_CHANGE') { - const { isDark, styles } = event.data; + const bg = parentDark ? "#0b0d0f" : "transparent"; + document.body.style.background = bg; + root.style.background = bg; + } + } catch { + // cross-origin safe + } - // Apply dark/light class to html element - if (isDark) { - document.documentElement.classList.add('dark'); - document.documentElement.classList.remove('light'); - } else { - document.documentElement.classList.add('light'); - document.documentElement.classList.remove('dark'); - } - - // Apply background styles - if (styles && styles.background) { - // Apply the background (either #0b0d0f for dark or transparent for light) - document.body.style.background = styles.background; - document.documentElement.style.background = styles.background; - } - } - }); - - // Also check if parent window has dark mode on initial load - try { - if (window.parent && window.parent !== window) { - const parentHasDark = window.parent.document.documentElement.classList.contains('dark'); - if (parentHasDark) { - document.documentElement.classList.add('dark'); - document.body.style.background = '#0b0d0f'; - document.documentElement.style.background = '#0b0d0f'; - } else { - document.documentElement.classList.add('light'); - document.body.style.background = 'transparent'; - document.documentElement.style.background = 'transparent'; - } - } - } catch (e) { - console.error('Cannot access parent document (expected for cross-origin)'); - } - })(); - `; - document.head.appendChild(script); - } + return () => { + window.removeEventListener("message", handler); + }; + }, []); return Story(); },