Idea
Storybook decorators are very powerful, but with great power comes great responsibility.
In the beginning it is ok to define global decorators. But as the project grows, different paths might require different management or decorators.
Adding these decorators to the every story does not scale from a maintanance point of view.
So, what about defining decorators on a folder level?
😎 think about it as .eslintrc or .babelrc configuration you can have globally defined as well as "per folder"
Setup
Install this addon by adding the storybook-include
dependency:
yarn add -D storybook-include
within .storybook/main.js:
module.exports = {
addons: [
'storybook-include',
// or
require.resolve('storybook-include/preset'),
],
};
Usage
-
no changes has to be made to stories, everything can be configured externaly
-
create a
storybook.include.js
, or.ts
or.jsx/.tsx
in a folder. It can be any folder, including your home folder -
configure which decorators has to be included
-
it works
Configuration
Example file
// storybook.include.js
import { addStoryDecorators } from 'storybook-include';
// decorators are defined via default export
export default () => {
addStoryDecorators((story, { storyName, fileName }) => {
// you can mutate story
story.args = { decorated: true };
// you can add decorators
story.decorators.push(myDecorator);
// you can return an array of decorators to add
return storyName.includes('dark') ? [darkModeDecorator] : undefined;
});
// another set
addStoryDecorators((story, { fileName }) => {
return fileName.includes('page') ? [ReactRouterDecorator, StaticRouterDecorator] : undefined;
});
// another set
addStoryDecorators((story) => {
return [CSSInJS, Theme, AndSomethingElse];
});
};
Reusing configuration
In rare cases you might consider sharing the same configuration between two configuration files. While simple duplication is usually preferred, there is a simpler way to do it
// storybook.include
import another from '../../other/storybook.include';
// ^ it's from another "branch", all explit parents will be "included" in any case
export default () => {
// do you thing
another();
};
Using for not .stories.*
Sometimes a non-standard file names for stories can be used - .story.*
, example.*
and so on.
In this case default preset will not work, and a custom one will be required
// your custom preset
/* eslint-disable no-param-reassign */
export function babel(options) {
options.overrides = options.overrides || [];
options.overrides.push({
test: /\.stories\.tsx?/, // <-- pattern matching your stories
plugins: [require.resolve('storybook-include/babel')],
});
return options;
}
Note on caching
While storybook does not enforce build caching this plugin is built with it in mind. As a result every file " autoconfigures" all "include files" above it, and every other file can "reconfigure" that list.
Theoretically that affects performance for non cached systems.
⛔️ removing-renaming include file might require cache update.
See also
- storybook-csf-title - a plugin with close semantic - creates
title
for a storybook basing on the location of a story file.
License
MIT