1 | # Storybook Addon Contexts
|
2 |
|
3 | **Storybook Addon Contexts** is an addon for driving your components under dynamic contexts in
|
4 | [Storybook](https://storybook.js.org/).
|
5 |
|
6 | ## 💡 Why you need this?
|
7 |
|
8 | Real world users expects your application being customizable, that is why often your components are **polymorphic**:
|
9 | they need to adapt themselves under different contextual environments. Imagine your components can speak
|
10 | Chinese, English, or even French, and they change their skin tone under dark or light theme. Yeah, you want to make
|
11 | sure a component looks great in all scenarios.
|
12 |
|
13 | A good practice to write maintainable components is separate the presentation and its business logic. Storybook is
|
14 | a great place for exercising the visualization and interaction of your components, which may depend on some contexts.
|
15 | Often enough, you will find it become very tedious to wrap each component deeply with its contextual environments
|
16 | before you can really write the main story. You even start to write extra components or factory functions just to
|
17 | make your life easier. How about changing the context of your story dynamically?! There was simply no good way so
|
18 | you ended up writing stories like an accountant.
|
19 |
|
20 | That is why you need this. An elegant way to wrap your component stories and change their contextual environment
|
21 | directly and dynamically in Storybook UI! Kind of like a dependency injection, eh! The best bit is **you define it
|
22 | once then apply it everywhere**.
|
23 |
|
24 | ## ✅ Features
|
25 |
|
26 | 1. Define a single global file for managing contextual environments (a.k.a. containers) for all of your stories
|
27 | declaratively. No more repetitive setups or noisy wrapping, making your stories more focused and readable.
|
28 | 2. Support dynamic contextual props switching from Storybook toolbar at runtime. You can slice into
|
29 | different environments (e.g. languages or themes ) to understand how your component is going to respond.
|
30 | 3. Library agnostic: no presumption on what kind of components you want to wrap around your stories. You can even
|
31 | use it to bridge with your favorite routing, state-management solutions, or even your own
|
32 | [React Context](https://reactjs.org/docs/context.html) provider.
|
33 | 4. Offer chainable and granular configurations. It is even possible to fine-tune at per story level.
|
34 | 5. Visual regression friendly. You can use this addon to drive the same story under different contexts to smoke
|
35 | test important visual states.
|
36 |
|
37 | ## 🧰 Requirements
|
38 |
|
39 | Make sure the version of your Storybook is above v5. For the full list of the current supported frameworks, see
|
40 | [Addon / Framework Support Table](../../ADDONS_SUPPORT.md).
|
41 |
|
42 | ## 🎬 Getting started
|
43 |
|
44 | To get it started, add this package into your project:
|
45 |
|
46 | ```bash
|
47 | yarn add -D @storybook/addon-contexts
|
48 | ```
|
49 |
|
50 | within `.storybook/main.js`:
|
51 |
|
52 | ```js
|
53 | module.exports = {
|
54 | addons: ['@storybook/addon-contexts/register']
|
55 | }
|
56 | ```
|
57 |
|
58 | To load your contextual setups for your stories globally, add the following lines into `preview.js` file (you should
|
59 | see it near your `addon.js` file):
|
60 |
|
61 | ```js
|
62 | import { addDecorator } from '@storybook/[framework]';
|
63 | import { withContexts } from '@storybook/addon-contexts/[framework]';
|
64 | import { contexts } from './configs/contexts'; // we will define the contextual setups later in API section
|
65 |
|
66 | addDecorator(withContexts(contexts));
|
67 | ```
|
68 |
|
69 | Alternatively, like other addons, you can use this addon only for a given set of stories:
|
70 |
|
71 | ```js
|
72 | import { withContexts } from '@storybook/addon-contexts/[framework]';
|
73 | import { contexts } from './configs/contexts';
|
74 |
|
75 | export default {
|
76 | title: 'Component With Contexts',
|
77 | decorators: [withContexts(contexts)],
|
78 | };
|
79 | ```
|
80 |
|
81 | Finally, you may want to create new contextual environments or disable default setups at the story level. To create a new contextual environment at the story level:
|
82 |
|
83 | ```js
|
84 | export const defaultView = () => <div />; // sample story in CSF format
|
85 | defaultView.story = {
|
86 | parameters: {
|
87 | contexts: [{ /* contextual environment defined using the API below */ }]
|
88 | }
|
89 | };
|
90 | ```
|
91 |
|
92 | To disable a default setup at the story level:
|
93 |
|
94 | ```js
|
95 | export const defaultView = () => <div />;
|
96 | defaultView.story = {
|
97 | parameters: {
|
98 | contexts: [
|
99 | {
|
100 | title: '[title of contextual environment defined in contexts.js]'
|
101 | options: { disable: true }
|
102 | }
|
103 | ]
|
104 | }
|
105 | };
|
106 | ```
|
107 |
|
108 | To override the default option for a default setup at the story level, see [this suggestion](https://discordapp.com/channels/486522875931656193/501692020226654208/687359410577604732).
|
109 |
|
110 |
|
111 | ## ⚙️ Setups
|
112 |
|
113 | ### Overview
|
114 |
|
115 | It is recommended to have a separate file for managing your contextual environment setups. Let's add a file named
|
116 | `contexts.js` first. Before diving into API details, here is an overview on the landscape. For example (in React),
|
117 | to inject component theming contexts to both `styled-components` and `material-ui` theme providers in stories:
|
118 |
|
119 | ```js
|
120 | export const contexts = [
|
121 | {
|
122 | icon: 'box', // a icon displayed in the Storybook toolbar to control contextual props
|
123 | title: 'Themes', // an unique name of a contextual environment
|
124 | components: [
|
125 | // an array of components that is going to be injected to wrap stories
|
126 | /* Styled-components ThemeProvider, */
|
127 | /* Material-ui ThemeProvider, */
|
128 | ],
|
129 | params: [
|
130 | // an array of params contains a set of predefined `props` for `components`
|
131 | { name: 'Light Theme', props: { theme /* : your light theme */ } },
|
132 | { name: 'Dark Theme', props: { theme /* : your dark theme */ }, default: true },
|
133 | ],
|
134 | options: {
|
135 | deep: true, // pass the `props` deeply into all wrapping components
|
136 | disable: false, // disable this contextual environment completely
|
137 | cancelable: false, // allow this contextual environment to be opt-out optionally in toolbar
|
138 | },
|
139 | },
|
140 | /* ... */ // multiple contexts setups are supported
|
141 | ];
|
142 | ```
|
143 |
|
144 | ---
|
145 |
|
146 | ### APIs
|
147 |
|
148 | #### `withContexts(contexts) : function`
|
149 |
|
150 | A decorating function for wrapping your stories under your predefined `contexts`. This means multiple contextual
|
151 | environments are supported. They are going to be loaded layer by layer and wrapped in a descending oder (top -> down
|
152 | -> story). The `contexts` is an array of objects that should have the following properties:
|
153 |
|
154 | ---
|
155 |
|
156 | #### `icon : string?`
|
157 |
|
158 | (default `undefined`)
|
159 |
|
160 | An icon displayed in the Storybook toolbar to control contextual props. This addon allows you to define an icon for
|
161 | each contextual environment individually. Take a look at the currently supported
|
162 | [icon lists](https://storybooks-official.netlify.com/?path=/story/basics-icon--labels) from the official Storybook
|
163 | story. You must define an icon first if you want to take advantage of switching props dynamically in your Storybook
|
164 | toolbar.
|
165 |
|
166 | ---
|
167 |
|
168 | #### `title : string`
|
169 |
|
170 | (required)
|
171 |
|
172 | A unique name of a contextual environment; if duplicate names are provided, the latter is going to be ignored.
|
173 |
|
174 | ---
|
175 |
|
176 | #### `components : (Component|string)[]`
|
177 |
|
178 | (required)
|
179 |
|
180 | An array of components that is going to be injected to wrap stories. This means this addon allows multiple wrapping
|
181 | components to coexist. The wrapping sequence is from the left to right (parent -> children -> story). This nested
|
182 | wrapping behaviour can be useful in some cases; for instance, in the above example, we are wrapping stories under
|
183 | `styled-components` and `material-ui` theme providers. Also, you can use this addon to wrap any valid HTML tags.
|
184 |
|
185 | ---
|
186 |
|
187 | #### `params : object[] | undefined`
|
188 |
|
189 | (default: `undefined`)
|
190 |
|
191 | An array of params contains a set of predefined `props` for `components`. This object has the following properties:
|
192 |
|
193 | #### `params.name : string`
|
194 |
|
195 | (required)
|
196 |
|
197 | A unique name for representing the props.
|
198 |
|
199 | #### `params.props : object | null:`
|
200 |
|
201 | (required)
|
202 |
|
203 | The `props` that are accepted by the wrapping component(s).
|
204 |
|
205 | #### `params.default : true?`
|
206 |
|
207 | (default: `undefined`)
|
208 |
|
209 | Set to `true` if you want to use this param initially. Only the first one marked as default is identified.
|
210 |
|
211 | ---
|
212 |
|
213 | #### `options`
|
214 |
|
215 | A set of options offers more granular control over the defined contextual environment. These properties can be
|
216 | overridden at the story level:
|
217 |
|
218 | #### `options.deep : boolean?`
|
219 |
|
220 | (default: `false`)
|
221 |
|
222 | Pass the `props` deeply into all wrapping components. Useful when you want them all to be passed with the same props.
|
223 |
|
224 | #### `options.disable : boolean?`
|
225 |
|
226 | (default: `false`)
|
227 |
|
228 | Disable this contextual environment completely. Useful when you want to opt-out this context from a given story.
|
229 |
|
230 | #### `options.cancelable : boolean?`
|
231 |
|
232 | (default: `false`)
|
233 |
|
234 | Allow this contextual environment to be opt-out optionally in toolbar. When set to `true`, an **Off** option will
|
235 | be shown at first in the toolbar menu in your Storybook.
|
236 |
|
237 | ## 📔 Notes
|
238 |
|
239 | 1. You can use this addon to inject any valid components, that is why `icon` and `params` can be optional.
|
240 | 2. As mentioned, extra contextual environment setups can be added at the story level. Please make sure they are
|
241 | passed via the second argument as `{ contexts: [{ /* extra contexts */ }}`.
|
242 | 3. Additional `params` can be "appended" into an existing setup at the story level too (make sure it goes with the
|
243 | correct `title`); however, they are never be able to overridden the default setups. So it is important to have
|
244 | non-colliding names.
|
245 | 4. The addon will persist the selected params (the addon state) between stories at run-time (similar to other
|
246 | addons). If the active params were gone after story switching, it falls back to the default then the first. As a
|
247 | rule of thumb, whenever collisions are possible, the first always wins.
|
248 | 5. Query parameters are supported for pre-selecting contexts param, which comes in handy for visual regression testing.
|
249 | You can do this by appending `&contexts=[name of contexts]=[name of param]` in the URL under iframe mode. Use `,`
|
250 | to separate multiple contexts (e.g. `&contexts=Theme=Forests,Language=Fr`).
|
251 |
|
252 | ## 📖 License
|
253 |
|
254 | MIT
|