Skip to content

Commit 25a15ba

Browse files
committed
🤖 fix: improve storybook theme injection
1 parent 5c1acee commit 25a15ba

File tree

1 file changed

+67
-9
lines changed

1 file changed

+67
-9
lines changed

.storybook/preview.tsx

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,82 @@
11
import React from "react";
22
import type { Preview } from "@storybook/react-vite";
3-
import {
4-
ThemeProvider,
5-
type ThemeMode,
6-
} from "../src/browser/contexts/ThemeContext";
3+
import { ThemeProvider, type ThemeMode } from "../src/browser/contexts/ThemeContext";
4+
import { getStorageChangeEvent } from "@/common/constants/events";
75
import { UI_THEME_KEY } from "@/common/constants/storage";
86
import "../src/browser/styles/globals.css";
97

10-
const syncStorybookTheme = (mode?: ThemeMode) => {
11-
if (typeof window === "undefined" || !mode) {
8+
const applyStorybookTheme = (mode: ThemeMode) => {
9+
if (typeof window === "undefined") {
1210
return;
1311
}
1412

1513
try {
16-
window.localStorage.setItem(UI_THEME_KEY, JSON.stringify(mode));
14+
const serialized = JSON.stringify(mode);
15+
window.localStorage.setItem(UI_THEME_KEY, serialized);
1716
const root = document.documentElement;
1817
root.dataset.theme = mode;
1918
root.style.colorScheme = mode;
19+
20+
const event = new CustomEvent(getStorageChangeEvent(UI_THEME_KEY), {
21+
detail: { key: UI_THEME_KEY, newValue: mode, origin: "storybook" },
22+
});
23+
window.dispatchEvent(event);
2024
} catch (error) {
21-
console.warn("Failed to sync Storybook theme:", error);
25+
console.warn("Failed to write Storybook theme:", error);
26+
}
27+
};
28+
29+
const syncStorybookTheme = (mode?: ThemeMode): ThemeMode => {
30+
if (typeof window === "undefined") {
31+
return mode ?? "dark";
32+
}
33+
34+
const stored = window.localStorage.getItem(UI_THEME_KEY);
35+
const persisted = stored ? (JSON.parse(stored) as ThemeMode) : undefined;
36+
const resolved = mode ?? persisted ?? "dark";
37+
applyStorybookTheme(resolved);
38+
return resolved;
39+
};
40+
41+
const StorybookThemeToggle: React.FC<{ initialTheme: ThemeMode }> = ({ initialTheme }) => {
42+
const [theme, setTheme] = React.useState(initialTheme);
43+
44+
React.useEffect(() => {
45+
setTheme(initialTheme);
46+
}, [initialTheme]);
47+
48+
const handleToggle = () => {
49+
const next = theme === "dark" ? "light" : "dark";
50+
applyStorybookTheme(next);
51+
setTheme(next);
52+
};
53+
54+
if (typeof window === "undefined") {
55+
return null;
2256
}
57+
58+
return (
59+
<button
60+
onClick={handleToggle}
61+
style={{
62+
position: "fixed",
63+
bottom: "1rem",
64+
right: "1rem",
65+
zIndex: 9999,
66+
padding: "0.5rem 1rem",
67+
background: theme === "dark" ? "#f5f6f8" : "#1e1e1e",
68+
color: theme === "dark" ? "#1e1e1e" : "#f5f6f8",
69+
border: "1px solid #777",
70+
borderRadius: "4px",
71+
cursor: "pointer",
72+
fontFamily: "system-ui, sans-serif",
73+
fontSize: "12px",
74+
boxShadow: "0 2px 5px rgba(0,0,0,0.2)",
75+
}}
76+
>
77+
{theme === "dark" ? "☀️ Light" : "🌙 Dark"}
78+
</button>
79+
);
2380
};
2481

2582
const preview: Preview = {
@@ -40,10 +97,11 @@ const preview: Preview = {
4097
decorators: [
4198
(Story, context) => {
4299
const mode = context.globals.theme as ThemeMode | undefined;
43-
syncStorybookTheme(mode);
100+
const resolved = syncStorybookTheme(mode);
44101
return (
45102
<ThemeProvider>
46103
<Story />
104+
{!mode && <StorybookThemeToggle initialTheme={resolved} />}
47105
</ThemeProvider>
48106
);
49107
},

0 commit comments

Comments
 (0)