Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 43 additions & 59 deletions storybook/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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();
},
Expand Down