-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathpreview.ts
129 lines (121 loc) · 5.16 KB
/
preview.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { DOCS_RENDERED } from "@storybook/core-events";
import { addons } from "@storybook/preview-api";
import { type ThemeVars } from "@storybook/theming";
import { type Preview } from "@storybook/vue3";
import { deepmerge } from "deepmerge-ts";
import { DARK_MODE_EVENT_NAME } from "storybook-dark-mode";
import { ONYX_BREAKPOINTS, createTheme } from "./theme";
const themes = {
light: createTheme(),
dark: createTheme({ base: "dark" }),
} as const;
/**
* Creates a default Storybook preview configuration for 'Onyx' with the following features:
* - Improved controls (sorting and expanded controls so descriptions etc. are also shown in a single story)
* - Improved Vue-specific code highlighting (e.g. using `@` instead of `v-on:`)
* - Setup for dark mode (including docs page). Requires addon `storybook-dark-mode` to be enabled in .storybook/main.ts file
* - Custom Storybook theme using Onyx colors (light and dark mode)
* - Support for setting the light/dark mode when Storybook is embedded as an iframe (via query parameter, e.g. `?theme=dark`)
* - Configure viewports / breakpoints as defined by Onyx
*
* @param overrides Custom preview / overrides, will be deep merged with the default preview.
*
* @example
* ```ts
* // .storybook/preview.ts
* const preview = {
* // you need to destructure here because as of Storybook 7.6
* // it can not statically analyze that the `preview` variable is an object
* ...createPreview({
* // custom preview / overrides
* },
* }),
* };
*
* export default preview;
* ```
*/
export const createPreview = <T extends Preview = Preview>(overrides?: T) => {
const defaultPreview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
sort: "requiredFirst",
// needed to also show props/events descriptions etc. when opening a single story
expanded: true,
},
docs: {
// see: https://github.com/hipstersmoothie/storybook-dark-mode/issues/127#issuecomment-840701971
get theme(): ThemeVars {
// support setting the theme via query parameters, useful if docs are embedded via an iframe
const params = new URLSearchParams(window.location.search);
const themeParam = params.get("theme");
const isDark = themeParam
? themeParam === "dark"
: parent.document.body.classList.contains("dark");
return isDark ? themes.dark : themes.light;
},
source: {
language: "html",
/**
* Use a custom transformer for the story source code to better fit to our
* Vue.js code because storybook per default does not render it exactly how
* we want it to look.
* @see https://storybook.js.org/docs/react/api/doc-block-source
*/
transform: (sourceCode: string): string => {
const replacements = [
// replace event bindings with shortcut
{ searchValue: "v-on:", replaceValue: "@" },
// remove empty event handlers, e.g. @click="()=>({})" will be removed
{ searchValue: / @.*['"]\(\)=>\({}\)['"]/g, replaceValue: "" },
// remove empty v-binds, e.g. v-bind="{}" will be removed
{ searchValue: / v-bind=['"]{}['"]/g, replaceValue: "" },
// replace boolean shortcuts for true, e.g. disabled="true" will be changed to just disabled
{ searchValue: /:(.*)=['"]true['"]/g, replaceValue: "$1" },
];
return replacements.reduce((code, replacement) => {
return replaceAll(code, replacement.searchValue, replacement.replaceValue);
}, sourceCode);
},
},
},
darkMode: {
stylePreview: true,
light: themes.light,
dark: themes.dark,
},
backgrounds: {
// backgrounds are not needed because we have configured the darkMode addon/toggle switch
disable: true,
},
viewport: {
viewports: ONYX_BREAKPOINTS,
},
},
} satisfies Preview;
const channel = addons.getChannel();
// our "workaround" above for dynamically setting the docs theme needs a page-reload after
// the theme has changed to take effect:
channel.once(DOCS_RENDERED, () => {
// the DARK_MODE_EVENT_NAME is emitted once after the docs have been rendered for the initial theme.
// We only want to reload the page on theme changes, that's why we use .once() to add the event listener
// only after the initial dark mode change event has been fired. Otherwise we would get an infinite loop.
channel.once(DARK_MODE_EVENT_NAME, () => {
channel.on(DARK_MODE_EVENT_NAME, () => {
window.location.reload();
});
});
});
return deepmerge<[T, typeof defaultPreview]>(overrides ?? ({} as T), defaultPreview);
};
/**
* Custom String.replaceAll implementation using a RegExp
* because String.replaceAll() is not available in our specified EcmaScript target in tsconfig.json
*/
const replaceAll = (message: string, searchValue: string | RegExp, replaceValue: string) => {
return message.replace(new RegExp(searchValue, "gi"), replaceValue);
};