@vueless/storybook-dark-mode 9.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/LICENSE +19 -0
- package/README.md +258 -0
- package/dist/cjs/Tool.js +256 -0
- package/dist/cjs/constants.js +10 -0
- package/dist/cjs/index.js +46 -0
- package/dist/cjs/preset/manager.js +35 -0
- package/dist/esm/Tool.js +242 -0
- package/dist/esm/constants.js +2 -0
- package/dist/esm/index.js +28 -0
- package/dist/esm/preset/manager.js +31 -0
- package/dist/ts/Tool.d.ts +35 -0
- package/dist/ts/constants.d.ts +2 -0
- package/dist/ts/index.d.ts +5 -0
- package/dist/ts/preset/manager.d.ts +1 -0
- package/package.json +87 -0
- package/preset.js +7 -0
- package/src/Tool.tsx +266 -0
- package/src/constants.ts +2 -0
- package/src/index.tsx +20 -0
- package/src/preset/manager.tsx +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2019 Andrew Lisowski <lisowski54@gmail.com>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
14
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
15
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
16
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
17
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
18
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
19
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Storybook Dark Mode
|
|
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).
|
|
4
|
+
|
|
5
|
+
The project is supported and maintained by the [Vueless UI](https://github.com/vuelessjs/vueless) core team.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Install the following npm module:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm i -D @vueless/storybook-dark-mode
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then, add following content to `.storybook/main.js`
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
export default {
|
|
21
|
+
addons: ['@vueless/storybook-dark-mode']
|
|
22
|
+
};
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
Configure the dark and light mode by adding the following to your `.storybook/preview.js` file:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { themes } from '@storybook/theming';
|
|
31
|
+
|
|
32
|
+
export const parameters = {
|
|
33
|
+
darkMode: {
|
|
34
|
+
// Override the default dark theme
|
|
35
|
+
dark: { ...themes.dark, appBg: 'black' },
|
|
36
|
+
// Override the default light theme
|
|
37
|
+
light: { ...themes.normal, appBg: 'red' }
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Default Theme
|
|
43
|
+
|
|
44
|
+
Order of precedence for the initial color scheme:
|
|
45
|
+
|
|
46
|
+
1. If the user has previously set a color theme it's used
|
|
47
|
+
2. The value you have configured for `current` parameter in your storybook
|
|
48
|
+
3. The OS color scheme preference
|
|
49
|
+
|
|
50
|
+
Once the initial color scheme has been set, subsequent reloads will use this value.
|
|
51
|
+
To clear the cached color scheme you have to `localStorage.clear()` in the chrome console.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
export const parameters = {
|
|
55
|
+
darkMode: {
|
|
56
|
+
// Set the initial theme
|
|
57
|
+
current: 'light'
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Dark/Light Class
|
|
63
|
+
|
|
64
|
+
This plugin will apply a dark and light class name to the manager.
|
|
65
|
+
This allows you to easily write dark mode aware theme overrides for the storybook UI.
|
|
66
|
+
|
|
67
|
+
You can override the classNames applied when switching between light and dark mode using the `darkClass` and `lightClass` parameters.
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
export const parameters = {
|
|
71
|
+
darkMode: {
|
|
72
|
+
darkClass: 'lights-out',
|
|
73
|
+
lightClass: 'lights-on'
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
You can also pass an array to apply multiple classes.
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
export const parameters = {
|
|
82
|
+
darkMode: {
|
|
83
|
+
darkClass: ['lights-out', 'foo'],
|
|
84
|
+
lightClass: ['lights-on', 'bar']
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Preview class target
|
|
90
|
+
|
|
91
|
+
This plugin will apply the dark/light class to the `<body>` element of the preview iframe. This can be configured with the `classTarget` parameter.
|
|
92
|
+
The value will be passed to a `querySelector()` inside the iframe.
|
|
93
|
+
|
|
94
|
+
This is useful if the `<body>` is styled according to a parent's class, in that case it can be set to `html`.
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
export const parameters = {
|
|
98
|
+
darkMode: {
|
|
99
|
+
classTarget: 'html'
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Story integration
|
|
105
|
+
|
|
106
|
+
### Preview ClassName
|
|
107
|
+
|
|
108
|
+
This plugin will apply the `darkClass` and `lightClass` classes to the preview iframe if you turn on the `stylePreview` option.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
export const parameters = {
|
|
112
|
+
darkMode: {
|
|
113
|
+
stylePreview: true
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### React
|
|
119
|
+
|
|
120
|
+
If your components use a custom Theme provider, you can integrate it by using the provided hook.
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import { useDarkMode } from '@vueless/storybook-dark-mode';
|
|
124
|
+
import { addDecorator } from '@storybook/react';
|
|
125
|
+
|
|
126
|
+
// your theme provider
|
|
127
|
+
import ThemeContext from './theme';
|
|
128
|
+
|
|
129
|
+
// create a component that uses the dark mode hook
|
|
130
|
+
function ThemeWrapper(props) {
|
|
131
|
+
// render your custom theme provider
|
|
132
|
+
return (
|
|
133
|
+
<ThemeContext.Provider value={useDarkMode() ? darkTheme : defaultTheme}>
|
|
134
|
+
{props.children}
|
|
135
|
+
</ThemeContext.Provider>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const decorators = [renderStory => <ThemeWrapper>{renderStory()}</ThemeWrapper>)];
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Theme Knobs
|
|
143
|
+
|
|
144
|
+
If you want to have you UI's dark mode separate from you components' dark mode, implement this global decorator:
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
import { themes } from '@storybook/theming';
|
|
148
|
+
|
|
149
|
+
// Add a global decorator that will render a dark background when the
|
|
150
|
+
// "Color Scheme" knob is set to dark
|
|
151
|
+
const knobDecorator = storyFn => {
|
|
152
|
+
// A knob for color scheme added to every story
|
|
153
|
+
const colorScheme = select('Color Scheme', ['light', 'dark'], 'light');
|
|
154
|
+
|
|
155
|
+
// Hook your theme provider with some knobs
|
|
156
|
+
return React.createElement(ThemeProvider, {
|
|
157
|
+
// A knob for theme added to every story
|
|
158
|
+
theme: select('Theme', Object.keys(themes), 'default'),
|
|
159
|
+
colorScheme,
|
|
160
|
+
children: [
|
|
161
|
+
React.createElement('style', {
|
|
162
|
+
dangerouslySetInnerHTML: {
|
|
163
|
+
__html: `html { ${
|
|
164
|
+
colorScheme === 'dark' ? 'background-color: rgb(35,35,35);' : ''
|
|
165
|
+
} }`
|
|
166
|
+
}
|
|
167
|
+
}),
|
|
168
|
+
storyFn()
|
|
169
|
+
]
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const decorators = [knobDecorator];
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Events
|
|
177
|
+
|
|
178
|
+
You can also listen for the `DARK_MODE` event via the addons channel.
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
import { addons } from '@storybook/preview-api';
|
|
182
|
+
import { addDecorator } from '@storybook/react';
|
|
183
|
+
import { DARK_MODE_EVENT_NAME } from '@vueless/storybook-dark-mode';
|
|
184
|
+
|
|
185
|
+
// your theme provider
|
|
186
|
+
import ThemeContext from './theme';
|
|
187
|
+
|
|
188
|
+
// get channel to listen to event emitter
|
|
189
|
+
const channel = addons.getChannel();
|
|
190
|
+
|
|
191
|
+
// create a component that listens for the DARK_MODE event
|
|
192
|
+
function ThemeWrapper(props) {
|
|
193
|
+
// this example uses hook but you can also use class component as well
|
|
194
|
+
const [isDark, setDark] = useState(false);
|
|
195
|
+
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
// listen to DARK_MODE event
|
|
198
|
+
channel.on(DARK_MODE_EVENT_NAME, setDark);
|
|
199
|
+
return () => channel.off(DARK_MODE_EVENT_NAME, setDark);
|
|
200
|
+
}, [channel, setDark]);
|
|
201
|
+
|
|
202
|
+
// render your custom theme provider
|
|
203
|
+
return (
|
|
204
|
+
<ThemeContext.Provider value={isDark ? darkTheme : defaultTheme}>
|
|
205
|
+
{props.children}
|
|
206
|
+
</ThemeContext.Provider>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const decorators = [renderStory => <ThemeWrapper>{renderStory()}</ThemeWrapper>)];
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Since in docs mode, Storybook will not display its toolbar,
|
|
214
|
+
You can also trigger the `UPDATE_DARK_MODE` event via the addons channel if you want to control that option in docs mode,
|
|
215
|
+
By editing your `.storybook/preview.js`.
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
import React from 'react';
|
|
219
|
+
import { addons } from '@storybook/preview-api';
|
|
220
|
+
import { DocsContainer } from '@storybook/addon-docs';
|
|
221
|
+
import { themes } from '@storybook/theming';
|
|
222
|
+
|
|
223
|
+
import {
|
|
224
|
+
DARK_MODE_EVENT_NAME,
|
|
225
|
+
UPDATE_DARK_MODE_EVENT_NAME
|
|
226
|
+
} from '@vueless/storybook-dark-mode';
|
|
227
|
+
|
|
228
|
+
const channel = addons.getChannel();
|
|
229
|
+
|
|
230
|
+
export const parameters = {
|
|
231
|
+
darkMode: {
|
|
232
|
+
current: 'light',
|
|
233
|
+
dark: { ...themes.dark },
|
|
234
|
+
light: { ...themes.light }
|
|
235
|
+
},
|
|
236
|
+
docs: {
|
|
237
|
+
container: props => {
|
|
238
|
+
const [isDark, setDark] = React.useState();
|
|
239
|
+
|
|
240
|
+
const onChangeHandler = () => {
|
|
241
|
+
channel.emit(UPDATE_DARK_MODE_EVENT_NAME);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
React.useEffect(() => {
|
|
245
|
+
channel.on(DARK_MODE_EVENT_NAME, setDark);
|
|
246
|
+
return () => channel.removeListener(DARK_MODE_EVENT_NAME, setDark);
|
|
247
|
+
}, [channel, setDark]);
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div>
|
|
251
|
+
<input type="checkbox" onChange={onChangeHandler} />
|
|
252
|
+
<DocsContainer {...props} />
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
```
|
package/dist/cjs/Tool.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.DarkMode = DarkMode;
|
|
8
|
+
exports.updateStore = exports.store = exports.prefersDark = exports["default"] = void 0;
|
|
9
|
+
var React = _interopRequireWildcard(require("react"));
|
|
10
|
+
var _global = require("@storybook/global");
|
|
11
|
+
var _theming = require("storybook/theming");
|
|
12
|
+
var _components = require("storybook/internal/components");
|
|
13
|
+
var _icons = require("@storybook/icons");
|
|
14
|
+
var _coreEvents = require("storybook/internal/core-events");
|
|
15
|
+
var _managerApi = require("storybook/manager-api");
|
|
16
|
+
var _fastDeepEqual = _interopRequireDefault(require("fast-deep-equal"));
|
|
17
|
+
var _constants = require("./constants");
|
|
18
|
+
var _excluded = ["current", "stylePreview"];
|
|
19
|
+
var _window$matchMedia;
|
|
20
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
21
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
22
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
23
|
+
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
|
|
24
|
+
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
|
25
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
26
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
27
|
+
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0) { ; } } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i["return"] && (_r = _i["return"](), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
|
|
28
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
29
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
30
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
31
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
32
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
|
33
|
+
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
34
|
+
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
|
|
35
|
+
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
36
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
37
|
+
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
|
|
38
|
+
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
|
39
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
40
|
+
var _ref = _global.global,
|
|
41
|
+
document = _ref.document,
|
|
42
|
+
window = _ref.window;
|
|
43
|
+
var modes = ['light', 'dark'];
|
|
44
|
+
var STORAGE_KEY = 'sb-addon-themes-3';
|
|
45
|
+
var prefersDark = (_window$matchMedia = window.matchMedia) === null || _window$matchMedia === void 0 ? void 0 : _window$matchMedia.call(window, '(prefers-color-scheme: dark)');
|
|
46
|
+
exports.prefersDark = prefersDark;
|
|
47
|
+
var defaultParams = {
|
|
48
|
+
classTarget: 'body',
|
|
49
|
+
dark: _theming.themes.dark,
|
|
50
|
+
darkClass: ['dark'],
|
|
51
|
+
light: _theming.themes.light,
|
|
52
|
+
lightClass: ['light'],
|
|
53
|
+
stylePreview: false,
|
|
54
|
+
userHasExplicitlySetTheTheme: false
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Persist the dark mode settings in localStorage */
|
|
58
|
+
var updateStore = function updateStore(newStore) {
|
|
59
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newStore));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Add the light/dark class to an element */
|
|
63
|
+
exports.updateStore = updateStore;
|
|
64
|
+
var toggleDarkClass = function toggleDarkClass(el, _ref2) {
|
|
65
|
+
var current = _ref2.current,
|
|
66
|
+
_ref2$darkClass = _ref2.darkClass,
|
|
67
|
+
darkClass = _ref2$darkClass === void 0 ? defaultParams.darkClass : _ref2$darkClass,
|
|
68
|
+
_ref2$lightClass = _ref2.lightClass,
|
|
69
|
+
lightClass = _ref2$lightClass === void 0 ? defaultParams.lightClass : _ref2$lightClass;
|
|
70
|
+
if (current === 'dark') {
|
|
71
|
+
var _el$classList, _el$classList2;
|
|
72
|
+
(_el$classList = el.classList).remove.apply(_el$classList, _toConsumableArray(arrayify(lightClass)));
|
|
73
|
+
(_el$classList2 = el.classList).add.apply(_el$classList2, _toConsumableArray(arrayify(darkClass)));
|
|
74
|
+
} else {
|
|
75
|
+
var _el$classList3, _el$classList4;
|
|
76
|
+
(_el$classList3 = el.classList).remove.apply(_el$classList3, _toConsumableArray(arrayify(darkClass)));
|
|
77
|
+
(_el$classList4 = el.classList).add.apply(_el$classList4, _toConsumableArray(arrayify(lightClass)));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Coerce a string to a single item array, or return an array as-is */
|
|
82
|
+
var arrayify = function arrayify(classes) {
|
|
83
|
+
var arr = [];
|
|
84
|
+
return arr.concat(classes).map(function (item) {
|
|
85
|
+
return item;
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Update the preview iframe class */
|
|
90
|
+
var updatePreview = function updatePreview(store) {
|
|
91
|
+
var _iframe$contentWindow;
|
|
92
|
+
var iframe = document.getElementById('storybook-preview-iframe');
|
|
93
|
+
if (!iframe) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
var iframeDocument = iframe.contentDocument || ((_iframe$contentWindow = iframe.contentWindow) === null || _iframe$contentWindow === void 0 ? void 0 : _iframe$contentWindow.document);
|
|
97
|
+
var target = iframeDocument === null || iframeDocument === void 0 ? void 0 : iframeDocument.querySelector(store.classTarget);
|
|
98
|
+
if (!target) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
toggleDarkClass(target, store);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** Update the manager iframe class */
|
|
105
|
+
var updateManager = function updateManager(store) {
|
|
106
|
+
var manager = document.querySelector(store.classTarget);
|
|
107
|
+
if (!manager) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
toggleDarkClass(manager, store);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** Update changed dark mode settings and persist to localStorage */
|
|
114
|
+
var store = function store() {
|
|
115
|
+
var userTheme = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
116
|
+
var storedItem = window.localStorage.getItem(STORAGE_KEY);
|
|
117
|
+
if (typeof storedItem === 'string') {
|
|
118
|
+
var stored = JSON.parse(storedItem);
|
|
119
|
+
if (userTheme) {
|
|
120
|
+
if (userTheme.dark && !(0, _fastDeepEqual["default"])(stored.dark, userTheme.dark)) {
|
|
121
|
+
stored.dark = userTheme.dark;
|
|
122
|
+
updateStore(stored);
|
|
123
|
+
}
|
|
124
|
+
if (userTheme.light && !(0, _fastDeepEqual["default"])(stored.light, userTheme.light)) {
|
|
125
|
+
stored.light = userTheme.light;
|
|
126
|
+
updateStore(stored);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return stored;
|
|
130
|
+
}
|
|
131
|
+
return _objectSpread(_objectSpread({}, defaultParams), userTheme);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// On initial load, set the dark mode class on the manager
|
|
135
|
+
// This is needed if you're using mostly CSS overrides to styles the storybook
|
|
136
|
+
// Otherwise the default theme is set in src/preset/manager.tsx
|
|
137
|
+
exports.store = store;
|
|
138
|
+
updateManager(store());
|
|
139
|
+
/** A toolbar icon to toggle between dark and light themes in storybook */
|
|
140
|
+
function DarkMode(_ref3) {
|
|
141
|
+
var api = _ref3.api;
|
|
142
|
+
var _React$useState = React.useState(prefersDark.matches),
|
|
143
|
+
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
144
|
+
isDark = _React$useState2[0],
|
|
145
|
+
setDark = _React$useState2[1];
|
|
146
|
+
var darkModeParams = (0, _managerApi.useParameter)('darkMode', {});
|
|
147
|
+
var defaultMode = darkModeParams.current,
|
|
148
|
+
stylePreview = darkModeParams.stylePreview,
|
|
149
|
+
params = _objectWithoutProperties(darkModeParams, _excluded);
|
|
150
|
+
var channel = api.getChannel();
|
|
151
|
+
// Save custom themes on init
|
|
152
|
+
var userHasExplicitlySetTheTheme = React.useMemo(function () {
|
|
153
|
+
return store(params).userHasExplicitlySetTheTheme;
|
|
154
|
+
}, [params]);
|
|
155
|
+
/** Set the theme in storybook, update the local state, and emit an event */
|
|
156
|
+
var setMode = React.useCallback(function (mode) {
|
|
157
|
+
var currentStore = store();
|
|
158
|
+
api.setOptions({
|
|
159
|
+
theme: currentStore[mode]
|
|
160
|
+
});
|
|
161
|
+
setDark(mode === 'dark');
|
|
162
|
+
api.getChannel().emit(_constants.DARK_MODE_EVENT_NAME, mode === 'dark');
|
|
163
|
+
updateManager(currentStore);
|
|
164
|
+
if (stylePreview) {
|
|
165
|
+
updatePreview(currentStore);
|
|
166
|
+
}
|
|
167
|
+
}, [api, stylePreview]);
|
|
168
|
+
|
|
169
|
+
/** Update the theme settings in localStorage, react, and storybook */
|
|
170
|
+
var updateMode = React.useCallback(function (mode) {
|
|
171
|
+
var currentStore = store();
|
|
172
|
+
var current = mode || (currentStore.current === 'dark' ? 'light' : 'dark');
|
|
173
|
+
updateStore(_objectSpread(_objectSpread({}, currentStore), {}, {
|
|
174
|
+
current: current
|
|
175
|
+
}));
|
|
176
|
+
setMode(current);
|
|
177
|
+
}, [setMode]);
|
|
178
|
+
|
|
179
|
+
/** Update the theme based on the color preference */
|
|
180
|
+
function prefersDarkUpdate(event) {
|
|
181
|
+
if (userHasExplicitlySetTheTheme || defaultMode) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
updateMode(event.matches ? 'dark' : 'light');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Render the current theme */
|
|
188
|
+
var renderTheme = React.useCallback(function () {
|
|
189
|
+
var _store = store(),
|
|
190
|
+
_store$current = _store.current,
|
|
191
|
+
current = _store$current === void 0 ? 'light' : _store$current;
|
|
192
|
+
setMode(current);
|
|
193
|
+
}, [setMode]);
|
|
194
|
+
|
|
195
|
+
/** Handle the user event and side effects */
|
|
196
|
+
var handleIconClick = function handleIconClick() {
|
|
197
|
+
updateMode();
|
|
198
|
+
var currentStore = store();
|
|
199
|
+
updateStore(_objectSpread(_objectSpread({}, currentStore), {}, {
|
|
200
|
+
userHasExplicitlySetTheTheme: true
|
|
201
|
+
}));
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** When storybook params change update the stored themes */
|
|
205
|
+
React.useEffect(function () {
|
|
206
|
+
var currentStore = store();
|
|
207
|
+
// Ensure we use the stores `current` value first to persist
|
|
208
|
+
// themeing between page loads and story changes.
|
|
209
|
+
updateStore(_objectSpread(_objectSpread(_objectSpread({}, currentStore), darkModeParams), {}, {
|
|
210
|
+
current: currentStore.current || darkModeParams.current
|
|
211
|
+
}));
|
|
212
|
+
renderTheme();
|
|
213
|
+
}, [darkModeParams, renderTheme]);
|
|
214
|
+
React.useEffect(function () {
|
|
215
|
+
channel.on(_coreEvents.STORY_CHANGED, renderTheme);
|
|
216
|
+
channel.on(_coreEvents.SET_STORIES, renderTheme);
|
|
217
|
+
channel.on(_coreEvents.DOCS_RENDERED, renderTheme);
|
|
218
|
+
prefersDark.addListener(prefersDarkUpdate);
|
|
219
|
+
return function () {
|
|
220
|
+
channel.removeListener(_coreEvents.STORY_CHANGED, renderTheme);
|
|
221
|
+
channel.removeListener(_coreEvents.SET_STORIES, renderTheme);
|
|
222
|
+
channel.removeListener(_coreEvents.DOCS_RENDERED, renderTheme);
|
|
223
|
+
prefersDark.removeListener(prefersDarkUpdate);
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
React.useEffect(function () {
|
|
227
|
+
channel.on(_constants.UPDATE_DARK_MODE_EVENT_NAME, updateMode);
|
|
228
|
+
return function () {
|
|
229
|
+
channel.removeListener(_constants.UPDATE_DARK_MODE_EVENT_NAME, updateMode);
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
// Storybook's first render doesn't have the global user params loaded so we
|
|
233
|
+
// need the effect to run whenever defaultMode is updated
|
|
234
|
+
React.useEffect(function () {
|
|
235
|
+
// If a users has set the mode this is respected
|
|
236
|
+
if (userHasExplicitlySetTheTheme) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (defaultMode) {
|
|
240
|
+
updateMode(defaultMode);
|
|
241
|
+
} else if (prefersDark.matches) {
|
|
242
|
+
updateMode('dark');
|
|
243
|
+
}
|
|
244
|
+
}, [defaultMode, updateMode, userHasExplicitlySetTheTheme]);
|
|
245
|
+
return /*#__PURE__*/React.createElement(_components.IconButton, {
|
|
246
|
+
key: "dark-mode",
|
|
247
|
+
title: isDark ? 'Change theme to light mode' : 'Change theme to dark mode',
|
|
248
|
+
onClick: handleIconClick
|
|
249
|
+
}, isDark ? /*#__PURE__*/React.createElement(_icons.SunIcon, {
|
|
250
|
+
"aria-hidden": "true"
|
|
251
|
+
}) : /*#__PURE__*/React.createElement(_icons.MoonIcon, {
|
|
252
|
+
"aria-hidden": "true"
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
var _default = DarkMode;
|
|
256
|
+
exports["default"] = _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.UPDATE_DARK_MODE_EVENT_NAME = exports.DARK_MODE_EVENT_NAME = void 0;
|
|
7
|
+
var DARK_MODE_EVENT_NAME = 'DARK_MODE';
|
|
8
|
+
exports.DARK_MODE_EVENT_NAME = DARK_MODE_EVENT_NAME;
|
|
9
|
+
var UPDATE_DARK_MODE_EVENT_NAME = 'UPDATE_DARK_MODE';
|
|
10
|
+
exports.UPDATE_DARK_MODE_EVENT_NAME = UPDATE_DARK_MODE_EVENT_NAME;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
useDarkMode: true
|
|
8
|
+
};
|
|
9
|
+
exports.useDarkMode = useDarkMode;
|
|
10
|
+
var _previewApi = require("storybook/preview-api");
|
|
11
|
+
var _constants = require("./constants");
|
|
12
|
+
Object.keys(_constants).forEach(function (key) {
|
|
13
|
+
if (key === "default" || key === "__esModule") return;
|
|
14
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
15
|
+
if (key in exports && exports[key] === _constants[key]) return;
|
|
16
|
+
Object.defineProperty(exports, key, {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function get() {
|
|
19
|
+
return _constants[key];
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
var _Tool = require("./Tool");
|
|
24
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
25
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
26
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
27
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
28
|
+
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0) { ; } } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i["return"] && (_r = _i["return"](), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
|
|
29
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
30
|
+
/**
|
|
31
|
+
* Returns the current state of storybook's dark-mode
|
|
32
|
+
*/
|
|
33
|
+
function useDarkMode() {
|
|
34
|
+
var _useState = (0, _previewApi.useState)((0, _Tool.store)().current === 'dark'),
|
|
35
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
36
|
+
isDark = _useState2[0],
|
|
37
|
+
setIsDark = _useState2[1];
|
|
38
|
+
(0, _previewApi.useEffect)(function () {
|
|
39
|
+
var chan = _previewApi.addons.getChannel();
|
|
40
|
+
chan.on(_constants.DARK_MODE_EVENT_NAME, setIsDark);
|
|
41
|
+
return function () {
|
|
42
|
+
return chan.off(_constants.DARK_MODE_EVENT_NAME, setIsDark);
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
return isDark;
|
|
46
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _managerApi = require("storybook/manager-api");
|
|
4
|
+
var _types = require("storybook/internal/types");
|
|
5
|
+
var _theming = require("storybook/theming");
|
|
6
|
+
var React = _interopRequireWildcard(require("react"));
|
|
7
|
+
var _Tool = _interopRequireWildcard(require("../Tool"));
|
|
8
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
9
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
10
|
+
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
|
|
11
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
12
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
13
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
14
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
|
15
|
+
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
16
|
+
var currentStore = (0, _Tool.store)();
|
|
17
|
+
var currentTheme = currentStore.current || _Tool.prefersDark.matches && 'dark' || 'light';
|
|
18
|
+
_managerApi.addons.setConfig({
|
|
19
|
+
theme: _objectSpread(_objectSpread({}, _theming.themes[currentTheme]), currentStore[currentTheme])
|
|
20
|
+
});
|
|
21
|
+
_managerApi.addons.register('storybook/dark-mode', function (api) {
|
|
22
|
+
_managerApi.addons.add('storybook/dark-mode', {
|
|
23
|
+
title: 'dark mode',
|
|
24
|
+
type: _types.Addon_TypesEnum.TOOL,
|
|
25
|
+
match: function match(_ref) {
|
|
26
|
+
var viewMode = _ref.viewMode;
|
|
27
|
+
return viewMode === 'story' || viewMode === 'docs';
|
|
28
|
+
},
|
|
29
|
+
render: function render() {
|
|
30
|
+
return /*#__PURE__*/React.createElement(_Tool["default"], {
|
|
31
|
+
api: api
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|