@vueless/storybook-dark-mode 10.0.1-beta.1 → 10.0.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/README.md +5 -1
- package/dist/index.js +4 -8
- package/dist/manager.js +9 -9
- package/dist/preset.js +0 -7
- package/package.json +5 -5
- package/preset.js +7 -1
- package/src/Tool.tsx +10 -130
- package/src/index.tsx +1 -1
- package/src/preset/manager.tsx +4 -3
- package/src/preset.ts +1 -5
- package/src/store.ts +115 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Storybook Dark Mode
|
|
2
2
|
|
|
3
|
-
A Storybook v10
|
|
3
|
+
A Storybook v9 and v10 optimized addon that enables users to toggle between dark and light modes. For support with earlier Storybook versions, see the [original addon](https://github.com/hipstersmoothie/storybook-dark-mode).
|
|
4
4
|
|
|
5
5
|
The project is supported and maintained by the [Vueless UI](https://github.com/vuelessjs/vueless) core team. | [See the demo](https://ui.vueless.com) 🌗
|
|
6
6
|
|
|
@@ -11,7 +11,11 @@ The project is supported and maintained by the [Vueless UI](https://github.com/v
|
|
|
11
11
|
Install the following npm module:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
+
# for Storybook v10
|
|
14
15
|
npm i -D @vueless/storybook-dark-mode
|
|
16
|
+
|
|
17
|
+
# for Storybook v9
|
|
18
|
+
npm i -D @vueless/storybook-dark-mode@^9
|
|
15
19
|
```
|
|
16
20
|
|
|
17
21
|
Then, add following content to `.storybook/main.js`
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,6 @@ import { useState, useEffect } from 'react';
|
|
|
2
2
|
import { addons } from 'storybook/preview-api';
|
|
3
3
|
import { global } from '@storybook/global';
|
|
4
4
|
import { themes } from 'storybook/theming';
|
|
5
|
-
import 'storybook/internal/components';
|
|
6
|
-
import '@storybook/icons';
|
|
7
|
-
import 'storybook/internal/core-events';
|
|
8
|
-
import 'storybook/manager-api';
|
|
9
5
|
import equal from 'fast-deep-equal';
|
|
10
6
|
|
|
11
7
|
// src/index.tsx
|
|
@@ -28,6 +24,10 @@ var defaultParams = {
|
|
|
28
24
|
var updateStore = (newStore) => {
|
|
29
25
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newStore));
|
|
30
26
|
};
|
|
27
|
+
var arrayify = (classes) => {
|
|
28
|
+
const arr = [];
|
|
29
|
+
return arr.concat(classes).map((item) => item);
|
|
30
|
+
};
|
|
31
31
|
var toggleDarkClass = (el, {
|
|
32
32
|
current,
|
|
33
33
|
darkClass = defaultParams.darkClass,
|
|
@@ -41,10 +41,6 @@ var toggleDarkClass = (el, {
|
|
|
41
41
|
el.classList.add(...arrayify(lightClass));
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
-
var arrayify = (classes) => {
|
|
45
|
-
const arr = [];
|
|
46
|
-
return arr.concat(classes).map((item) => item);
|
|
47
|
-
};
|
|
48
44
|
var updateManager = (store2) => {
|
|
49
45
|
const manager = document.querySelector(store2.classTarget);
|
|
50
46
|
if (!manager) {
|
package/dist/manager.js
CHANGED
|
@@ -2,10 +2,10 @@ import { addons, useParameter } from 'storybook/manager-api';
|
|
|
2
2
|
import { Addon_TypesEnum } from 'storybook/internal/types';
|
|
3
3
|
import { themes } from 'storybook/theming';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import { global } from '@storybook/global';
|
|
6
5
|
import { IconButton } from 'storybook/internal/components';
|
|
7
6
|
import { SunIcon, MoonIcon } from '@storybook/icons';
|
|
8
7
|
import { STORY_CHANGED, SET_STORIES, DOCS_RENDERED } from 'storybook/internal/core-events';
|
|
8
|
+
import { global } from '@storybook/global';
|
|
9
9
|
import equal from 'fast-deep-equal';
|
|
10
10
|
|
|
11
11
|
// src/preset/manager.tsx
|
|
@@ -13,8 +13,6 @@ import equal from 'fast-deep-equal';
|
|
|
13
13
|
// src/constants.ts
|
|
14
14
|
var DARK_MODE_EVENT_NAME = "DARK_MODE";
|
|
15
15
|
var UPDATE_DARK_MODE_EVENT_NAME = "UPDATE_DARK_MODE";
|
|
16
|
-
|
|
17
|
-
// src/Tool.tsx
|
|
18
16
|
var { document, window } = global;
|
|
19
17
|
var STORAGE_KEY = "sb-addon-themes-3";
|
|
20
18
|
var prefersDark = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
@@ -30,6 +28,10 @@ var defaultParams = {
|
|
|
30
28
|
var updateStore = (newStore) => {
|
|
31
29
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newStore));
|
|
32
30
|
};
|
|
31
|
+
var arrayify = (classes) => {
|
|
32
|
+
const arr = [];
|
|
33
|
+
return arr.concat(classes).map((item) => item);
|
|
34
|
+
};
|
|
33
35
|
var toggleDarkClass = (el, {
|
|
34
36
|
current,
|
|
35
37
|
darkClass = defaultParams.darkClass,
|
|
@@ -43,10 +45,6 @@ var toggleDarkClass = (el, {
|
|
|
43
45
|
el.classList.add(...arrayify(lightClass));
|
|
44
46
|
}
|
|
45
47
|
};
|
|
46
|
-
var arrayify = (classes) => {
|
|
47
|
-
const arr = [];
|
|
48
|
-
return arr.concat(classes).map((item) => item);
|
|
49
|
-
};
|
|
50
48
|
var updatePreview = (store2) => {
|
|
51
49
|
const iframe = document.getElementById("storybook-preview-iframe");
|
|
52
50
|
if (!iframe) {
|
|
@@ -85,6 +83,8 @@ var store = (userTheme = {}) => {
|
|
|
85
83
|
return { ...defaultParams, ...userTheme };
|
|
86
84
|
};
|
|
87
85
|
updateManager(store());
|
|
86
|
+
|
|
87
|
+
// src/Tool.tsx
|
|
88
88
|
function DarkMode({ api }) {
|
|
89
89
|
const [isDark, setDark] = React.useState(prefersDark.matches);
|
|
90
90
|
const darkModeParams = useParameter("darkMode", {});
|
|
@@ -189,8 +189,8 @@ addons.setConfig({
|
|
|
189
189
|
...currentStore[currentTheme]
|
|
190
190
|
}
|
|
191
191
|
});
|
|
192
|
-
addons.register("storybook
|
|
193
|
-
addons.add("storybook
|
|
192
|
+
addons.register("@vueless/storybook-dark-mode", (api) => {
|
|
193
|
+
addons.add("@vueless/storybook-dark-mode", {
|
|
194
194
|
title: "dark mode",
|
|
195
195
|
type: Addon_TypesEnum.TOOL,
|
|
196
196
|
match: ({ viewMode }) => viewMode === "story" || viewMode === "docs",
|
package/dist/preset.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vueless/storybook-dark-mode",
|
|
3
|
-
"version": "10.0.1
|
|
3
|
+
"version": "10.0.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": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"vite": "^7.1.11"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"storybook": "^
|
|
61
|
+
"storybook": "^10.0.0"
|
|
62
62
|
},
|
|
63
63
|
"type": "module",
|
|
64
64
|
"main": "dist/index.js",
|
|
@@ -83,13 +83,13 @@
|
|
|
83
83
|
],
|
|
84
84
|
"bundler": {
|
|
85
85
|
"managerEntries": [
|
|
86
|
-
"src/preset/manager.tsx"
|
|
86
|
+
"./src/preset/manager.tsx"
|
|
87
87
|
],
|
|
88
88
|
"previewEntries": [
|
|
89
|
-
"src/index.tsx"
|
|
89
|
+
"./src/index.tsx"
|
|
90
90
|
],
|
|
91
91
|
"nodeEntries": [
|
|
92
|
-
"src/preset.ts"
|
|
92
|
+
"./src/preset.ts"
|
|
93
93
|
]
|
|
94
94
|
},
|
|
95
95
|
"prettier": {
|
package/preset.js
CHANGED
package/src/Tool.tsx
CHANGED
|
@@ -1,138 +1,18 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { global } from "@storybook/global";
|
|
3
|
-
import { themes, ThemeVars } from "storybook/theming";
|
|
4
2
|
import { IconButton } from "storybook/internal/components";
|
|
5
3
|
import { MoonIcon, SunIcon } from "@storybook/icons";
|
|
6
|
-
import { STORY_CHANGED, SET_STORIES, DOCS_RENDERED } from "storybook/internal/core-events";
|
|
7
4
|
import { API, useParameter } from "storybook/manager-api";
|
|
8
|
-
import
|
|
5
|
+
import { SET_STORIES, DOCS_RENDERED, STORY_CHANGED } from "storybook/internal/core-events";
|
|
9
6
|
import { DARK_MODE_EVENT_NAME, UPDATE_DARK_MODE_EVENT_NAME } from "./constants";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
current: Mode;
|
|
20
|
-
/** The dark theme for storybook */
|
|
21
|
-
dark: ThemeVars;
|
|
22
|
-
/** The dark class name for the preview iframe */
|
|
23
|
-
darkClass: string | string[];
|
|
24
|
-
/** The light theme for storybook */
|
|
25
|
-
light: ThemeVars;
|
|
26
|
-
/** The light class name for the preview iframe */
|
|
27
|
-
lightClass: string | string[];
|
|
28
|
-
/** Apply mode to iframe */
|
|
29
|
-
stylePreview: boolean;
|
|
30
|
-
/** Persist if the user has set the theme */
|
|
31
|
-
userHasExplicitlySetTheTheme: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const STORAGE_KEY = "sb-addon-themes-3";
|
|
35
|
-
|
|
36
|
-
export const prefersDark = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
37
|
-
|
|
38
|
-
const defaultParams: Required<Omit<DarkModeStore, "current">> = {
|
|
39
|
-
classTarget: "body",
|
|
40
|
-
dark: themes.dark,
|
|
41
|
-
darkClass: ["dark"],
|
|
42
|
-
light: themes.light,
|
|
43
|
-
lightClass: ["light"],
|
|
44
|
-
stylePreview: false,
|
|
45
|
-
userHasExplicitlySetTheTheme: false,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/** Persist the dark mode settings in localStorage */
|
|
49
|
-
export const updateStore = (newStore: DarkModeStore) => {
|
|
50
|
-
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newStore));
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/** Add the light/dark class to an element */
|
|
54
|
-
const toggleDarkClass = (
|
|
55
|
-
el: Element,
|
|
56
|
-
{
|
|
57
|
-
current,
|
|
58
|
-
darkClass = defaultParams.darkClass,
|
|
59
|
-
lightClass = defaultParams.lightClass,
|
|
60
|
-
}: DarkModeStore,
|
|
61
|
-
) => {
|
|
62
|
-
if (current === "dark") {
|
|
63
|
-
el.classList.remove(...arrayify(lightClass));
|
|
64
|
-
el.classList.add(...arrayify(darkClass));
|
|
65
|
-
} else {
|
|
66
|
-
el.classList.remove(...arrayify(darkClass));
|
|
67
|
-
el.classList.add(...arrayify(lightClass));
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/** Coerce a string to a single item array, or return an array as-is */
|
|
72
|
-
const arrayify = (classes: string | string[]): string[] => {
|
|
73
|
-
const arr: string[] = [];
|
|
74
|
-
|
|
75
|
-
return arr.concat(classes).map((item) => item);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/** Update the preview iframe class */
|
|
79
|
-
const updatePreview = (store: DarkModeStore) => {
|
|
80
|
-
const iframe = document.getElementById("storybook-preview-iframe") as HTMLIFrameElement;
|
|
81
|
-
|
|
82
|
-
if (!iframe) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
|
|
87
|
-
const target = iframeDocument?.querySelector<HTMLElement>(store.classTarget);
|
|
88
|
-
|
|
89
|
-
if (!target) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
toggleDarkClass(target, store);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/** Update the manager iframe class */
|
|
97
|
-
const updateManager = (store: DarkModeStore) => {
|
|
98
|
-
const manager = document.querySelector(store.classTarget);
|
|
99
|
-
|
|
100
|
-
if (!manager) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
toggleDarkClass(manager, store);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/** Update changed dark mode settings and persist to localStorage */
|
|
108
|
-
export const store = (userTheme: Partial<DarkModeStore> = {}): DarkModeStore => {
|
|
109
|
-
const storedItem = window.localStorage.getItem(STORAGE_KEY);
|
|
110
|
-
|
|
111
|
-
if (typeof storedItem === "string") {
|
|
112
|
-
const stored = JSON.parse(storedItem) as DarkModeStore;
|
|
113
|
-
|
|
114
|
-
if (userTheme) {
|
|
115
|
-
if (userTheme.dark && !equal(stored.dark, userTheme.dark)) {
|
|
116
|
-
stored.dark = userTheme.dark;
|
|
117
|
-
updateStore(stored);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (userTheme.light && !equal(stored.light, userTheme.light)) {
|
|
121
|
-
stored.light = userTheme.light;
|
|
122
|
-
updateStore(stored);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return stored;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { ...defaultParams, ...userTheme } as DarkModeStore;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// On initial load, set the dark mode class on the manager
|
|
133
|
-
// This is needed if you're using mostly CSS overrides to styles the storybook
|
|
134
|
-
// Otherwise the default theme is set in src/preset/manager.tsx
|
|
135
|
-
updateManager(store());
|
|
7
|
+
import {
|
|
8
|
+
store,
|
|
9
|
+
prefersDark,
|
|
10
|
+
updateStore,
|
|
11
|
+
updateManager,
|
|
12
|
+
updatePreview,
|
|
13
|
+
type DarkModeStore,
|
|
14
|
+
type Mode,
|
|
15
|
+
} from "./store";
|
|
136
16
|
|
|
137
17
|
interface DarkModeProps {
|
|
138
18
|
/** The storybook API */
|
package/src/index.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
import { addons } from "storybook/preview-api";
|
|
3
3
|
import { DARK_MODE_EVENT_NAME } from "./constants";
|
|
4
|
-
import { store } from "./
|
|
4
|
+
import { store } from "./store";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Returns the current state of storybook's dark-mode
|
package/src/preset/manager.tsx
CHANGED
|
@@ -3,7 +3,8 @@ import { Addon_TypesEnum } from "storybook/internal/types";
|
|
|
3
3
|
import { themes } from "storybook/theming";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
|
-
import Tool
|
|
6
|
+
import Tool from "../Tool";
|
|
7
|
+
import { prefersDark, store } from "../store";
|
|
7
8
|
|
|
8
9
|
const currentStore = store();
|
|
9
10
|
const currentTheme = currentStore.current || (prefersDark.matches && "dark") || "light";
|
|
@@ -15,8 +16,8 @@ addons.setConfig({
|
|
|
15
16
|
},
|
|
16
17
|
});
|
|
17
18
|
|
|
18
|
-
addons.register("storybook
|
|
19
|
-
addons.add("storybook
|
|
19
|
+
addons.register("@vueless/storybook-dark-mode", (api) => {
|
|
20
|
+
addons.add("@vueless/storybook-dark-mode", {
|
|
20
21
|
title: "dark mode",
|
|
21
22
|
type: Addon_TypesEnum.TOOL,
|
|
22
23
|
match: ({ viewMode }) => viewMode === "story" || viewMode === "docs",
|
package/src/preset.ts
CHANGED
package/src/store.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { global } from "@storybook/global";
|
|
2
|
+
import { themes, ThemeVars } from "storybook/theming";
|
|
3
|
+
import equal from "fast-deep-equal";
|
|
4
|
+
|
|
5
|
+
const { document, window } = global as { document: Document; window: Window };
|
|
6
|
+
|
|
7
|
+
type Mode = "light" | "dark";
|
|
8
|
+
|
|
9
|
+
interface DarkModeStore {
|
|
10
|
+
classTarget: string;
|
|
11
|
+
current: Mode;
|
|
12
|
+
dark: ThemeVars;
|
|
13
|
+
darkClass: string | string[];
|
|
14
|
+
light: ThemeVars;
|
|
15
|
+
lightClass: string | string[];
|
|
16
|
+
stylePreview: boolean;
|
|
17
|
+
userHasExplicitlySetTheTheme: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const STORAGE_KEY = "sb-addon-themes-3";
|
|
21
|
+
|
|
22
|
+
export const prefersDark = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
23
|
+
|
|
24
|
+
const defaultParams: Required<Omit<DarkModeStore, "current">> = {
|
|
25
|
+
classTarget: "body",
|
|
26
|
+
dark: themes.dark,
|
|
27
|
+
darkClass: ["dark"],
|
|
28
|
+
light: themes.light,
|
|
29
|
+
lightClass: ["light"],
|
|
30
|
+
stylePreview: false,
|
|
31
|
+
userHasExplicitlySetTheTheme: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const updateStore = (newStore: DarkModeStore) => {
|
|
35
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newStore));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const arrayify = (classes: string | string[]): string[] => {
|
|
39
|
+
const arr: string[] = [];
|
|
40
|
+
|
|
41
|
+
return arr.concat(classes).map((item) => item);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const toggleDarkClass = (
|
|
45
|
+
el: Element,
|
|
46
|
+
{
|
|
47
|
+
current,
|
|
48
|
+
darkClass = defaultParams.darkClass,
|
|
49
|
+
lightClass = defaultParams.lightClass,
|
|
50
|
+
}: DarkModeStore,
|
|
51
|
+
) => {
|
|
52
|
+
if (current === "dark") {
|
|
53
|
+
el.classList.remove(...arrayify(lightClass));
|
|
54
|
+
el.classList.add(...arrayify(darkClass));
|
|
55
|
+
} else {
|
|
56
|
+
el.classList.remove(...arrayify(darkClass));
|
|
57
|
+
el.classList.add(...arrayify(lightClass));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const updatePreview = (store: DarkModeStore) => {
|
|
62
|
+
const iframe = document.getElementById("storybook-preview-iframe") as HTMLIFrameElement;
|
|
63
|
+
|
|
64
|
+
if (!iframe) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
|
|
69
|
+
const target = iframeDocument?.querySelector<HTMLElement>(store.classTarget);
|
|
70
|
+
|
|
71
|
+
if (!target) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
toggleDarkClass(target, store);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const updateManager = (store: DarkModeStore) => {
|
|
79
|
+
const manager = document.querySelector(store.classTarget);
|
|
80
|
+
|
|
81
|
+
if (!manager) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
toggleDarkClass(manager, store);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const store = (userTheme: Partial<DarkModeStore> = {}): DarkModeStore => {
|
|
89
|
+
const storedItem = window.localStorage.getItem(STORAGE_KEY);
|
|
90
|
+
|
|
91
|
+
if (typeof storedItem === "string") {
|
|
92
|
+
const stored = JSON.parse(storedItem) as DarkModeStore;
|
|
93
|
+
|
|
94
|
+
if (userTheme) {
|
|
95
|
+
if (userTheme.dark && !equal(stored.dark, userTheme.dark)) {
|
|
96
|
+
stored.dark = userTheme.dark;
|
|
97
|
+
updateStore(stored);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (userTheme.light && !equal(stored.light, userTheme.light)) {
|
|
101
|
+
stored.light = userTheme.light;
|
|
102
|
+
updateStore(stored);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return stored;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { ...defaultParams, ...userTheme } as DarkModeStore;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
updateManager(store());
|
|
113
|
+
|
|
114
|
+
export { updatePreview };
|
|
115
|
+
export type { DarkModeStore, Mode };
|