@vueless/storybook-dark-mode 10.0.7 → 10.0.8-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manager.js +59 -16
- package/package.json +25 -25
- package/src/Tool.tsx +76 -16
- package/src/preset/manager.tsx +2 -2
package/dist/manager.js
CHANGED
|
@@ -86,10 +86,16 @@ updateManager(store());
|
|
|
86
86
|
|
|
87
87
|
// src/Tool.tsx
|
|
88
88
|
function DarkMode({ api }) {
|
|
89
|
-
const [isDark, setDark] = React.useState(
|
|
89
|
+
const [isDark, setDark] = React.useState(() => {
|
|
90
|
+
const current = store().current;
|
|
91
|
+
return current === "dark";
|
|
92
|
+
});
|
|
90
93
|
const darkModeParams = useParameter("darkMode", {});
|
|
91
94
|
const { current: defaultMode, stylePreview, ...params } = darkModeParams;
|
|
92
95
|
const channel = api.getChannel();
|
|
96
|
+
const darkModeParamsRef = React.useRef(darkModeParams);
|
|
97
|
+
darkModeParamsRef.current = darkModeParams;
|
|
98
|
+
const deferredSysThemeRef = React.useRef(null);
|
|
93
99
|
const userHasExplicitlySetTheTheme = React.useMemo(
|
|
94
100
|
() => store(params).userHasExplicitlySetTheTheme,
|
|
95
101
|
[params]
|
|
@@ -116,12 +122,15 @@ function DarkMode({ api }) {
|
|
|
116
122
|
},
|
|
117
123
|
[setMode]
|
|
118
124
|
);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
const prefersDarkUpdate = React.useCallback(
|
|
126
|
+
(event) => {
|
|
127
|
+
if (userHasExplicitlySetTheTheme || defaultMode) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
updateMode(event.matches ? "dark" : "light");
|
|
131
|
+
},
|
|
132
|
+
[userHasExplicitlySetTheTheme, defaultMode, updateMode]
|
|
133
|
+
);
|
|
125
134
|
const renderTheme = React.useCallback(() => {
|
|
126
135
|
const { current = "light" } = store();
|
|
127
136
|
setMode(current);
|
|
@@ -132,13 +141,49 @@ function DarkMode({ api }) {
|
|
|
132
141
|
updateStore({ ...currentStore2, userHasExplicitlySetTheTheme: true });
|
|
133
142
|
};
|
|
134
143
|
React.useEffect(() => {
|
|
144
|
+
if (deferredSysThemeRef.current !== null) {
|
|
145
|
+
clearTimeout(deferredSysThemeRef.current);
|
|
146
|
+
deferredSysThemeRef.current = null;
|
|
147
|
+
}
|
|
135
148
|
const currentStore2 = store();
|
|
136
|
-
|
|
149
|
+
const previewCurrent = darkModeParams.current;
|
|
150
|
+
const persistedCurrent = currentStore2.current;
|
|
151
|
+
const base = {
|
|
137
152
|
...currentStore2,
|
|
138
|
-
...darkModeParams
|
|
139
|
-
|
|
140
|
-
|
|
153
|
+
...darkModeParams
|
|
154
|
+
};
|
|
155
|
+
if (previewCurrent !== void 0) {
|
|
156
|
+
updateStore({ ...base, current: previewCurrent });
|
|
157
|
+
renderTheme();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (persistedCurrent) {
|
|
161
|
+
updateStore({ ...base, current: persistedCurrent });
|
|
162
|
+
renderTheme();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
updateStore({ ...base, current: "light" });
|
|
141
166
|
renderTheme();
|
|
167
|
+
deferredSysThemeRef.current = setTimeout(() => {
|
|
168
|
+
deferredSysThemeRef.current = null;
|
|
169
|
+
const latestParams = darkModeParamsRef.current;
|
|
170
|
+
if (latestParams.current !== void 0) {
|
|
171
|
+
const cs2 = store();
|
|
172
|
+
updateStore({ ...cs2, ...latestParams, current: latestParams.current });
|
|
173
|
+
renderTheme();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const cs = store();
|
|
177
|
+
const nextCurrent = prefersDark.matches ? "dark" : "light";
|
|
178
|
+
updateStore({ ...cs, ...latestParams, current: nextCurrent });
|
|
179
|
+
renderTheme();
|
|
180
|
+
}, 0);
|
|
181
|
+
return () => {
|
|
182
|
+
if (deferredSysThemeRef.current !== null) {
|
|
183
|
+
clearTimeout(deferredSysThemeRef.current);
|
|
184
|
+
deferredSysThemeRef.current = null;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
142
187
|
}, [darkModeParams, renderTheme]);
|
|
143
188
|
React.useEffect(() => {
|
|
144
189
|
channel.on(STORY_CHANGED, renderTheme);
|
|
@@ -151,21 +196,19 @@ function DarkMode({ api }) {
|
|
|
151
196
|
channel.removeListener(DOCS_RENDERED, renderTheme);
|
|
152
197
|
prefersDark.removeListener(prefersDarkUpdate);
|
|
153
198
|
};
|
|
154
|
-
});
|
|
199
|
+
}, [channel, renderTheme, prefersDarkUpdate]);
|
|
155
200
|
React.useEffect(() => {
|
|
156
201
|
channel.on(UPDATE_DARK_MODE_EVENT_NAME, updateMode);
|
|
157
202
|
return () => {
|
|
158
203
|
channel.removeListener(UPDATE_DARK_MODE_EVENT_NAME, updateMode);
|
|
159
204
|
};
|
|
160
|
-
});
|
|
205
|
+
}, [channel, updateMode]);
|
|
161
206
|
React.useEffect(() => {
|
|
162
207
|
if (userHasExplicitlySetTheTheme) {
|
|
163
208
|
return;
|
|
164
209
|
}
|
|
165
210
|
if (defaultMode) {
|
|
166
211
|
updateMode(defaultMode);
|
|
167
|
-
} else if (prefersDark.matches) {
|
|
168
|
-
updateMode("dark");
|
|
169
212
|
}
|
|
170
213
|
}, [defaultMode, updateMode, userHasExplicitlySetTheTheme]);
|
|
171
214
|
return /* @__PURE__ */ React.createElement(
|
|
@@ -185,7 +228,7 @@ var Tool_default = DarkMode;
|
|
|
185
228
|
|
|
186
229
|
// src/preset/manager.tsx
|
|
187
230
|
var currentStore = store();
|
|
188
|
-
var currentTheme = currentStore.current
|
|
231
|
+
var currentTheme = currentStore.current ?? "light";
|
|
189
232
|
addons.setConfig({
|
|
190
233
|
theme: {
|
|
191
234
|
...themes[currentTheme],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vueless/storybook-dark-mode",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.8-beta.1",
|
|
4
4
|
"description": "Toggle between light and dark mode in Storybook",
|
|
5
5
|
"author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
|
|
6
6
|
"repository": {
|
|
@@ -25,37 +25,37 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@storybook/global": "^5.0.0",
|
|
28
|
-
"lodash-es": "^4.
|
|
28
|
+
"lodash-es": "^4.18.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@eslint/js": "^9.
|
|
31
|
+
"@eslint/js": "^9.39.4",
|
|
32
32
|
"@release-it/bumper": "^7.0.5",
|
|
33
|
-
"@release-it/conventional-changelog": "^
|
|
34
|
-
"@storybook/builder-vite": "^10.
|
|
35
|
-
"@storybook/icons": "^
|
|
36
|
-
"@storybook/react": "^10.
|
|
37
|
-
"@storybook/react-vite": "^10.
|
|
38
|
-
"@stylistic/eslint-plugin": "^5.
|
|
33
|
+
"@release-it/conventional-changelog": "^11.0.0",
|
|
34
|
+
"@storybook/builder-vite": "^10.3.5",
|
|
35
|
+
"@storybook/icons": "^2.0.1",
|
|
36
|
+
"@storybook/react": "^10.3.5",
|
|
37
|
+
"@storybook/react-vite": "^10.3.5",
|
|
38
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
39
39
|
"@types/lodash-es": "^4.17.12",
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"@types/react": "^
|
|
42
|
-
"eslint": "^9.
|
|
40
|
+
"@types/node": "^25.6.0",
|
|
41
|
+
"@types/react": "^19.2.14",
|
|
42
|
+
"eslint": "^9.39.4",
|
|
43
43
|
"eslint-config-prettier": "^10.1.8",
|
|
44
|
-
"eslint-plugin-prettier": "^5.5.
|
|
44
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
45
45
|
"eslint-plugin-react": "^7.37.5",
|
|
46
|
-
"eslint-plugin-react-hooks": "^
|
|
47
|
-
"globals": "^
|
|
48
|
-
"prettier": "^3.
|
|
49
|
-
"react": "^
|
|
50
|
-
"react-dom": "^
|
|
51
|
-
"release-it": "^
|
|
52
|
-
"rimraf": "^
|
|
53
|
-
"storybook": "^10.
|
|
46
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
47
|
+
"globals": "^17.5.0",
|
|
48
|
+
"prettier": "^3.8.3",
|
|
49
|
+
"react": "^19.2.5",
|
|
50
|
+
"react-dom": "^19.2.5",
|
|
51
|
+
"release-it": "^20.0.1",
|
|
52
|
+
"rimraf": "^6.1.3",
|
|
53
|
+
"storybook": "^10.3.5",
|
|
54
54
|
"ts-node": "^10.9.2",
|
|
55
|
-
"tsup": "^8.
|
|
56
|
-
"typescript": "^
|
|
57
|
-
"typescript-eslint": "^8.
|
|
58
|
-
"vite": "^
|
|
55
|
+
"tsup": "^8.5.1",
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"typescript-eslint": "^8.59.0",
|
|
58
|
+
"vite": "^8.0.10"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"storybook": "^10.0.0"
|
package/src/Tool.tsx
CHANGED
|
@@ -21,10 +21,18 @@ interface DarkModeProps {
|
|
|
21
21
|
|
|
22
22
|
/** A toolbar icon to toggle between dark and light themes in storybook */
|
|
23
23
|
export function DarkMode({ api }: DarkModeProps) {
|
|
24
|
-
const [isDark, setDark] = React.useState(
|
|
24
|
+
const [isDark, setDark] = React.useState(() => {
|
|
25
|
+
const current = store().current;
|
|
26
|
+
|
|
27
|
+
return current === "dark";
|
|
28
|
+
});
|
|
25
29
|
const darkModeParams = useParameter<Partial<DarkModeStore>>("darkMode", {});
|
|
26
30
|
const { current: defaultMode, stylePreview, ...params } = darkModeParams;
|
|
27
31
|
const channel = api.getChannel();
|
|
32
|
+
const darkModeParamsRef = React.useRef(darkModeParams);
|
|
33
|
+
|
|
34
|
+
darkModeParamsRef.current = darkModeParams;
|
|
35
|
+
const deferredSysThemeRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
28
36
|
// Save custom themes on init
|
|
29
37
|
const userHasExplicitlySetTheTheme = React.useMemo(
|
|
30
38
|
() => store(params).userHasExplicitlySetTheTheme,
|
|
@@ -60,13 +68,16 @@ export function DarkMode({ api }: DarkModeProps) {
|
|
|
60
68
|
);
|
|
61
69
|
|
|
62
70
|
/** Update the theme based on the color preference */
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
const prefersDarkUpdate = React.useCallback(
|
|
72
|
+
(event: MediaQueryListEvent) => {
|
|
73
|
+
if (userHasExplicitlySetTheTheme || defaultMode) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
updateMode(event.matches ? "dark" : "light");
|
|
78
|
+
},
|
|
79
|
+
[userHasExplicitlySetTheTheme, defaultMode, updateMode],
|
|
80
|
+
);
|
|
70
81
|
|
|
71
82
|
/** Render the current theme */
|
|
72
83
|
const renderTheme = React.useCallback(() => {
|
|
@@ -85,17 +96,67 @@ export function DarkMode({ api }: DarkModeProps) {
|
|
|
85
96
|
|
|
86
97
|
/** When storybook params change update the stored themes */
|
|
87
98
|
React.useEffect(() => {
|
|
99
|
+
if (deferredSysThemeRef.current !== null) {
|
|
100
|
+
clearTimeout(deferredSysThemeRef.current);
|
|
101
|
+
deferredSysThemeRef.current = null;
|
|
102
|
+
}
|
|
103
|
+
|
|
88
104
|
const currentStore = store();
|
|
105
|
+
const previewCurrent = darkModeParams.current;
|
|
106
|
+
const persistedCurrent = currentStore.current;
|
|
89
107
|
|
|
90
|
-
|
|
91
|
-
// themeing between page loads and story changes.
|
|
92
|
-
updateStore({
|
|
108
|
+
const base = {
|
|
93
109
|
...currentStore,
|
|
94
110
|
...darkModeParams,
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (previewCurrent !== undefined) {
|
|
114
|
+
updateStore({ ...base, current: previewCurrent });
|
|
115
|
+
renderTheme();
|
|
116
|
+
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (persistedCurrent) {
|
|
121
|
+
updateStore({ ...base, current: persistedCurrent });
|
|
122
|
+
renderTheme();
|
|
123
|
+
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Preview `darkMode.current` can hydrate after the first commit. Avoid applying
|
|
128
|
+
// prefers-color-scheme until then so `current: 'light'` is not overwritten.
|
|
129
|
+
updateStore({ ...base, current: "light" });
|
|
97
130
|
renderTheme();
|
|
131
|
+
|
|
132
|
+
deferredSysThemeRef.current = setTimeout(() => {
|
|
133
|
+
deferredSysThemeRef.current = null;
|
|
134
|
+
const latestParams = darkModeParamsRef.current;
|
|
135
|
+
|
|
136
|
+
if (latestParams.current !== undefined) {
|
|
137
|
+
const cs = store();
|
|
138
|
+
|
|
139
|
+
updateStore({ ...cs, ...latestParams, current: latestParams.current });
|
|
140
|
+
renderTheme();
|
|
141
|
+
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const cs = store();
|
|
146
|
+
const nextCurrent = prefersDark.matches ? "dark" : "light";
|
|
147
|
+
|
|
148
|
+
updateStore({ ...cs, ...latestParams, current: nextCurrent });
|
|
149
|
+
renderTheme();
|
|
150
|
+
}, 0);
|
|
151
|
+
|
|
152
|
+
return () => {
|
|
153
|
+
if (deferredSysThemeRef.current !== null) {
|
|
154
|
+
clearTimeout(deferredSysThemeRef.current);
|
|
155
|
+
deferredSysThemeRef.current = null;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
98
158
|
}, [darkModeParams, renderTheme]);
|
|
159
|
+
|
|
99
160
|
React.useEffect(() => {
|
|
100
161
|
channel.on(STORY_CHANGED, renderTheme);
|
|
101
162
|
channel.on(SET_STORIES, renderTheme);
|
|
@@ -108,14 +169,15 @@ export function DarkMode({ api }: DarkModeProps) {
|
|
|
108
169
|
channel.removeListener(DOCS_RENDERED, renderTheme);
|
|
109
170
|
prefersDark.removeListener(prefersDarkUpdate);
|
|
110
171
|
};
|
|
111
|
-
});
|
|
172
|
+
}, [channel, renderTheme, prefersDarkUpdate]);
|
|
173
|
+
|
|
112
174
|
React.useEffect(() => {
|
|
113
175
|
channel.on(UPDATE_DARK_MODE_EVENT_NAME, updateMode);
|
|
114
176
|
|
|
115
177
|
return () => {
|
|
116
178
|
channel.removeListener(UPDATE_DARK_MODE_EVENT_NAME, updateMode);
|
|
117
179
|
};
|
|
118
|
-
});
|
|
180
|
+
}, [channel, updateMode]);
|
|
119
181
|
// Storybook's first render doesn't have the global user params loaded so we
|
|
120
182
|
// need the effect to run whenever defaultMode is updated
|
|
121
183
|
React.useEffect(() => {
|
|
@@ -126,8 +188,6 @@ export function DarkMode({ api }: DarkModeProps) {
|
|
|
126
188
|
|
|
127
189
|
if (defaultMode) {
|
|
128
190
|
updateMode(defaultMode);
|
|
129
|
-
} else if (prefersDark.matches) {
|
|
130
|
-
updateMode("dark");
|
|
131
191
|
}
|
|
132
192
|
}, [defaultMode, updateMode, userHasExplicitlySetTheTheme]);
|
|
133
193
|
|
package/src/preset/manager.tsx
CHANGED
|
@@ -4,10 +4,10 @@ import { themes } from "storybook/theming";
|
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
6
|
import Tool from "../Tool";
|
|
7
|
-
import {
|
|
7
|
+
import { store } from "../store";
|
|
8
8
|
|
|
9
9
|
const currentStore = store();
|
|
10
|
-
const currentTheme = currentStore.current
|
|
10
|
+
const currentTheme = currentStore.current ?? "light";
|
|
11
11
|
|
|
12
12
|
addons.setConfig({
|
|
13
13
|
theme: {
|