@vueless/storybook-dark-mode 9.0.10 → 10.0.1-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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Storybook Dark Mode
2
2
 
3
- A Storybook v9-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).
3
+ A Storybook 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
 
@@ -0,0 +1,9 @@
1
+ declare const DARK_MODE_EVENT_NAME = "DARK_MODE";
2
+ declare const UPDATE_DARK_MODE_EVENT_NAME = "UPDATE_DARK_MODE";
3
+
4
+ /**
5
+ * Returns the current state of storybook's dark-mode
6
+ */
7
+ declare function useDarkMode(): boolean;
8
+
9
+ export { DARK_MODE_EVENT_NAME, UPDATE_DARK_MODE_EVENT_NAME, useDarkMode };
package/dist/index.js ADDED
@@ -0,0 +1,86 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { addons } from 'storybook/preview-api';
3
+ import { global } from '@storybook/global';
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
+ import equal from 'fast-deep-equal';
10
+
11
+ // src/index.tsx
12
+
13
+ // src/constants.ts
14
+ var DARK_MODE_EVENT_NAME = "DARK_MODE";
15
+ var UPDATE_DARK_MODE_EVENT_NAME = "UPDATE_DARK_MODE";
16
+ var { document, window } = global;
17
+ var STORAGE_KEY = "sb-addon-themes-3";
18
+ window.matchMedia?.("(prefers-color-scheme: dark)");
19
+ var defaultParams = {
20
+ classTarget: "body",
21
+ dark: themes.dark,
22
+ darkClass: ["dark"],
23
+ light: themes.light,
24
+ lightClass: ["light"],
25
+ stylePreview: false,
26
+ userHasExplicitlySetTheTheme: false
27
+ };
28
+ var updateStore = (newStore) => {
29
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newStore));
30
+ };
31
+ var toggleDarkClass = (el, {
32
+ current,
33
+ darkClass = defaultParams.darkClass,
34
+ lightClass = defaultParams.lightClass
35
+ }) => {
36
+ if (current === "dark") {
37
+ el.classList.remove(...arrayify(lightClass));
38
+ el.classList.add(...arrayify(darkClass));
39
+ } else {
40
+ el.classList.remove(...arrayify(darkClass));
41
+ el.classList.add(...arrayify(lightClass));
42
+ }
43
+ };
44
+ var arrayify = (classes) => {
45
+ const arr = [];
46
+ return arr.concat(classes).map((item) => item);
47
+ };
48
+ var updateManager = (store2) => {
49
+ const manager = document.querySelector(store2.classTarget);
50
+ if (!manager) {
51
+ return;
52
+ }
53
+ toggleDarkClass(manager, store2);
54
+ };
55
+ var store = (userTheme = {}) => {
56
+ const storedItem = window.localStorage.getItem(STORAGE_KEY);
57
+ if (typeof storedItem === "string") {
58
+ const stored = JSON.parse(storedItem);
59
+ if (userTheme) {
60
+ if (userTheme.dark && !equal(stored.dark, userTheme.dark)) {
61
+ stored.dark = userTheme.dark;
62
+ updateStore(stored);
63
+ }
64
+ if (userTheme.light && !equal(stored.light, userTheme.light)) {
65
+ stored.light = userTheme.light;
66
+ updateStore(stored);
67
+ }
68
+ }
69
+ return stored;
70
+ }
71
+ return { ...defaultParams, ...userTheme };
72
+ };
73
+ updateManager(store());
74
+
75
+ // src/index.tsx
76
+ function useDarkMode() {
77
+ const [isDark, setIsDark] = useState(() => store().current === "dark");
78
+ useEffect(() => {
79
+ const chan = addons.getChannel();
80
+ chan.on(DARK_MODE_EVENT_NAME, setIsDark);
81
+ return () => chan.off(DARK_MODE_EVENT_NAME, setIsDark);
82
+ }, []);
83
+ return isDark;
84
+ }
85
+
86
+ export { DARK_MODE_EVENT_NAME, UPDATE_DARK_MODE_EVENT_NAME, useDarkMode };
@@ -0,0 +1,199 @@
1
+ import { addons, useParameter } from 'storybook/manager-api';
2
+ import { Addon_TypesEnum } from 'storybook/internal/types';
3
+ import { themes } from 'storybook/theming';
4
+ import * as React from 'react';
5
+ import { global } from '@storybook/global';
6
+ import { IconButton } from 'storybook/internal/components';
7
+ import { SunIcon, MoonIcon } from '@storybook/icons';
8
+ import { STORY_CHANGED, SET_STORIES, DOCS_RENDERED } from 'storybook/internal/core-events';
9
+ import equal from 'fast-deep-equal';
10
+
11
+ // src/preset/manager.tsx
12
+
13
+ // src/constants.ts
14
+ var DARK_MODE_EVENT_NAME = "DARK_MODE";
15
+ var UPDATE_DARK_MODE_EVENT_NAME = "UPDATE_DARK_MODE";
16
+
17
+ // src/Tool.tsx
18
+ var { document, window } = global;
19
+ var STORAGE_KEY = "sb-addon-themes-3";
20
+ var prefersDark = window.matchMedia?.("(prefers-color-scheme: dark)");
21
+ var defaultParams = {
22
+ classTarget: "body",
23
+ dark: themes.dark,
24
+ darkClass: ["dark"],
25
+ light: themes.light,
26
+ lightClass: ["light"],
27
+ stylePreview: false,
28
+ userHasExplicitlySetTheTheme: false
29
+ };
30
+ var updateStore = (newStore) => {
31
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newStore));
32
+ };
33
+ var toggleDarkClass = (el, {
34
+ current,
35
+ darkClass = defaultParams.darkClass,
36
+ lightClass = defaultParams.lightClass
37
+ }) => {
38
+ if (current === "dark") {
39
+ el.classList.remove(...arrayify(lightClass));
40
+ el.classList.add(...arrayify(darkClass));
41
+ } else {
42
+ el.classList.remove(...arrayify(darkClass));
43
+ el.classList.add(...arrayify(lightClass));
44
+ }
45
+ };
46
+ var arrayify = (classes) => {
47
+ const arr = [];
48
+ return arr.concat(classes).map((item) => item);
49
+ };
50
+ var updatePreview = (store2) => {
51
+ const iframe = document.getElementById("storybook-preview-iframe");
52
+ if (!iframe) {
53
+ return;
54
+ }
55
+ const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
56
+ const target = iframeDocument?.querySelector(store2.classTarget);
57
+ if (!target) {
58
+ return;
59
+ }
60
+ toggleDarkClass(target, store2);
61
+ };
62
+ var updateManager = (store2) => {
63
+ const manager = document.querySelector(store2.classTarget);
64
+ if (!manager) {
65
+ return;
66
+ }
67
+ toggleDarkClass(manager, store2);
68
+ };
69
+ var store = (userTheme = {}) => {
70
+ const storedItem = window.localStorage.getItem(STORAGE_KEY);
71
+ if (typeof storedItem === "string") {
72
+ const stored = JSON.parse(storedItem);
73
+ if (userTheme) {
74
+ if (userTheme.dark && !equal(stored.dark, userTheme.dark)) {
75
+ stored.dark = userTheme.dark;
76
+ updateStore(stored);
77
+ }
78
+ if (userTheme.light && !equal(stored.light, userTheme.light)) {
79
+ stored.light = userTheme.light;
80
+ updateStore(stored);
81
+ }
82
+ }
83
+ return stored;
84
+ }
85
+ return { ...defaultParams, ...userTheme };
86
+ };
87
+ updateManager(store());
88
+ function DarkMode({ api }) {
89
+ const [isDark, setDark] = React.useState(prefersDark.matches);
90
+ const darkModeParams = useParameter("darkMode", {});
91
+ const { current: defaultMode, stylePreview, ...params } = darkModeParams;
92
+ const channel = api.getChannel();
93
+ const userHasExplicitlySetTheTheme = React.useMemo(
94
+ () => store(params).userHasExplicitlySetTheTheme,
95
+ [params]
96
+ );
97
+ const setMode = React.useCallback(
98
+ (mode) => {
99
+ const currentStore2 = store();
100
+ api.setOptions({ theme: currentStore2[mode] });
101
+ setDark(mode === "dark");
102
+ api.getChannel().emit(DARK_MODE_EVENT_NAME, mode === "dark");
103
+ updateManager(currentStore2);
104
+ if (stylePreview) {
105
+ updatePreview(currentStore2);
106
+ }
107
+ },
108
+ [api, stylePreview]
109
+ );
110
+ const updateMode = React.useCallback(
111
+ (mode) => {
112
+ const currentStore2 = store();
113
+ const current = mode || (currentStore2.current === "dark" ? "light" : "dark");
114
+ updateStore({ ...currentStore2, current });
115
+ setMode(current);
116
+ },
117
+ [setMode]
118
+ );
119
+ function prefersDarkUpdate(event) {
120
+ if (userHasExplicitlySetTheTheme || defaultMode) {
121
+ return;
122
+ }
123
+ updateMode(event.matches ? "dark" : "light");
124
+ }
125
+ const renderTheme = React.useCallback(() => {
126
+ const { current = "light" } = store();
127
+ setMode(current);
128
+ }, [setMode]);
129
+ const handleIconClick = () => {
130
+ updateMode();
131
+ const currentStore2 = store();
132
+ updateStore({ ...currentStore2, userHasExplicitlySetTheTheme: true });
133
+ };
134
+ React.useEffect(() => {
135
+ const currentStore2 = store();
136
+ updateStore({
137
+ ...currentStore2,
138
+ ...darkModeParams,
139
+ current: currentStore2.current || darkModeParams.current
140
+ });
141
+ renderTheme();
142
+ }, [darkModeParams, renderTheme]);
143
+ React.useEffect(() => {
144
+ channel.on(STORY_CHANGED, renderTheme);
145
+ channel.on(SET_STORIES, renderTheme);
146
+ channel.on(DOCS_RENDERED, renderTheme);
147
+ prefersDark.addListener(prefersDarkUpdate);
148
+ return () => {
149
+ channel.removeListener(STORY_CHANGED, renderTheme);
150
+ channel.removeListener(SET_STORIES, renderTheme);
151
+ channel.removeListener(DOCS_RENDERED, renderTheme);
152
+ prefersDark.removeListener(prefersDarkUpdate);
153
+ };
154
+ });
155
+ React.useEffect(() => {
156
+ channel.on(UPDATE_DARK_MODE_EVENT_NAME, updateMode);
157
+ return () => {
158
+ channel.removeListener(UPDATE_DARK_MODE_EVENT_NAME, updateMode);
159
+ };
160
+ });
161
+ React.useEffect(() => {
162
+ if (userHasExplicitlySetTheTheme) {
163
+ return;
164
+ }
165
+ if (defaultMode) {
166
+ updateMode(defaultMode);
167
+ } else if (prefersDark.matches) {
168
+ updateMode("dark");
169
+ }
170
+ }, [defaultMode, updateMode, userHasExplicitlySetTheTheme]);
171
+ return /* @__PURE__ */ React.createElement(
172
+ IconButton,
173
+ {
174
+ key: "dark-mode",
175
+ title: isDark ? "Change theme to light mode" : "Change theme to dark mode",
176
+ onClick: handleIconClick
177
+ },
178
+ isDark ? /* @__PURE__ */ React.createElement(SunIcon, { "aria-hidden": "true" }) : /* @__PURE__ */ React.createElement(MoonIcon, { "aria-hidden": "true" })
179
+ );
180
+ }
181
+ var Tool_default = DarkMode;
182
+
183
+ // src/preset/manager.tsx
184
+ var currentStore = store();
185
+ var currentTheme = currentStore.current || prefersDark.matches && "dark" || "light";
186
+ addons.setConfig({
187
+ theme: {
188
+ ...themes[currentTheme],
189
+ ...currentStore[currentTheme]
190
+ }
191
+ });
192
+ addons.register("storybook/dark-mode", (api) => {
193
+ addons.add("storybook/dark-mode", {
194
+ title: "dark mode",
195
+ type: Addon_TypesEnum.TOOL,
196
+ match: ({ viewMode }) => viewMode === "story" || viewMode === "docs",
197
+ render: () => /* @__PURE__ */ React.createElement(Tool_default, { api })
198
+ });
199
+ });
package/dist/preset.js ADDED
@@ -0,0 +1,8 @@
1
+ import { fileURLToPath } from 'url';
2
+
3
+ // src/preset.ts
4
+ function managerEntries(entry = []) {
5
+ return [...entry, fileURLToPath(import.meta.resolve("./manager"))];
6
+ }
7
+
8
+ export { managerEntries };
package/package.json CHANGED
@@ -1,15 +1,7 @@
1
1
  {
2
2
  "name": "@vueless/storybook-dark-mode",
3
- "version": "9.0.10",
3
+ "version": "10.0.1-beta.1",
4
4
  "description": "Toggle between light and dark mode in Storybook",
5
- "main": "dist/cjs/index.js",
6
- "module": "dist/esm/index.js",
7
- "types": "dist/ts/index.d.ts",
8
- "files": [
9
- "src",
10
- "dist",
11
- "preset.js"
12
- ],
13
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
14
6
  "repository": {
15
7
  "type": "git",
@@ -20,57 +12,85 @@
20
12
  },
21
13
  "scripts": {
22
14
  "clean": "rimraf ./dist",
23
- "buildBabel": "concurrently \"npm run buildBabel:cjs\" \"npm run buildBabel:esm\"",
24
- "buildBabel:cjs": "babel ./src -d ./dist/cjs --extensions \".js,.jsx,.ts,.tsx\"",
25
- "buildBabel:esm": "babel ./src -d ./dist/esm --env-name esm --extensions \".js,.jsx,.ts,.tsx\"",
26
- "buildTsc": "tsc --declaration --emitDeclarationOnly --outDir ./dist/ts -p tsconfig.build.json",
27
15
  "prebuild": "npm run clean",
28
- "build": "concurrently \"npm run buildBabel\" \"npm run buildTsc\"",
29
- "build:watch": "concurrently \"npm run buildBabel:esm -- --watch\" \"npm run buildTsc -- --watch\"",
30
- "lint": "eslint --ext .ts --ext .tsx src/**",
31
- "release": "auto shipit"
16
+ "build": "tsup",
17
+ "build:watch": "tsup --watch",
18
+ "lint": "eslint --no-fix .",
19
+ "lint:fix": "eslint --fix .",
20
+ "lint:ci": "eslint --no-fix src/ --max-warnings=0",
21
+ "release:beta": "release-it --increment=prerelease --preRelease=beta --ci --no-git.tag --no-github.release",
22
+ "release:patch": "release-it patch --ci",
23
+ "release:minor": "release-it minor --ci",
24
+ "release:major": "release-it major --ci"
32
25
  },
33
26
  "dependencies": {
34
27
  "@storybook/global": "^5.0.0",
35
- "@storybook/icons": "^1.4.0",
36
28
  "fast-deep-equal": "^3.1.3",
37
29
  "memoizerific": "^1.11.3"
38
30
  },
39
31
  "devDependencies": {
40
- "@babel/cli": "^7.28.3",
41
- "@babel/core": "^7.28.3",
42
- "@babel/preset-env": "^7.28.3",
43
- "@babel/preset-react": "^7.27.1",
44
- "@babel/preset-typescript": "^7.27.1",
45
- "@storybook/builder-vite": "^9.1.3",
46
- "@storybook/react": "^9.1.3",
47
- "@storybook/react-vite": "^9.1.3",
32
+ "@eslint/js": "^9.33.0",
33
+ "@release-it/bumper": "^7.0.5",
34
+ "@release-it/conventional-changelog": "^10.0.1",
35
+ "@storybook/builder-vite": "^10.0.0",
36
+ "@storybook/icons": "^1.4.0",
37
+ "@storybook/react": "^10.0.0",
38
+ "@storybook/react-vite": "^10.0.0",
39
+ "@stylistic/eslint-plugin": "^5.2.3",
48
40
  "@types/node": "^24.3.0",
49
41
  "@types/react": "^18.0.26",
50
- "@typescript-eslint/eslint-plugin": "5.45.1",
51
- "@typescript-eslint/parser": "5.45.1",
52
- "all-contributors-cli": "^6.26.1",
53
- "auto": "^11.3.0",
54
- "auto-config-hipstersmoothie": "^4.0.0",
55
- "babel-loader": "^10.0.0",
56
- "concurrently": "^7.6.0",
57
- "eslint": "8.29.0",
58
- "eslint-config-prettier": "8.5.0",
59
- "eslint-config-xo": "0.43.1",
60
- "eslint-config-xo-react": "0.27.0",
61
- "eslint-plugin-react": "7.31.11",
62
- "eslint-plugin-react-hooks": "4.6.0",
63
- "prettier": "^2.8.0",
42
+ "eslint": "^9.33.0",
43
+ "eslint-config-prettier": "^10.1.8",
44
+ "eslint-plugin-prettier": "^5.5.4",
45
+ "eslint-plugin-react": "^7.37.5",
46
+ "eslint-plugin-react-hooks": "^5.1.0",
47
+ "globals": "^15.14.0",
48
+ "prettier": "^3.6.2",
64
49
  "react": "^18.2.0",
65
50
  "react-dom": "^18.2.0",
51
+ "release-it": "^19.0.4",
66
52
  "rimraf": "^3.0.2",
67
- "storybook": "^9.1.3",
53
+ "storybook": "^10.0.0",
68
54
  "ts-node": "^10.9.2",
55
+ "tsup": "^8.0.0",
69
56
  "typescript": "^5.9.2",
57
+ "typescript-eslint": "^8.40.0",
70
58
  "vite": "^7.1.11"
71
59
  },
72
- "auto": {
73
- "extends": "hipstersmoothie"
60
+ "peerDependencies": {
61
+ "storybook": "^9.0.0 || ^10.0.0"
62
+ },
63
+ "type": "module",
64
+ "main": "dist/index.js",
65
+ "types": "dist/index.d.ts",
66
+ "exports": {
67
+ ".": {
68
+ "types": "./dist/index.d.ts",
69
+ "default": "./dist/index.js"
70
+ },
71
+ "./preview": {
72
+ "types": "./dist/index.d.ts",
73
+ "default": "./dist/index.js"
74
+ },
75
+ "./preset": "./dist/preset.js",
76
+ "./manager": "./dist/manager.js",
77
+ "./package.json": "./package.json"
78
+ },
79
+ "files": [
80
+ "src",
81
+ "dist",
82
+ "preset.js"
83
+ ],
84
+ "bundler": {
85
+ "managerEntries": [
86
+ "src/preset/manager.tsx"
87
+ ],
88
+ "previewEntries": [
89
+ "src/index.tsx"
90
+ ],
91
+ "nodeEntries": [
92
+ "src/preset.ts"
93
+ ]
74
94
  },
75
95
  "prettier": {
76
96
  "singleQuote": true
@@ -79,7 +99,11 @@
79
99
  "node": ">=20"
80
100
  },
81
101
  "keywords": [
82
- "storybook-addons",
83
- "appearance"
102
+ "storybook-addon",
103
+ "appearance",
104
+ "storybook",
105
+ "vueless",
106
+ "dark mode",
107
+ "color mode"
84
108
  ]
85
109
  }
package/preset.js CHANGED
@@ -1,7 +1 @@
1
- function managerEntries(entry = []) {
2
- return [...entry, require.resolve('./dist/esm/preset/manager')];
3
- }
4
-
5
- module.exports = {
6
- managerEntries
7
- };
1
+ export * from "./dist/preset.js";