Skip to content

Commit 484886a

Browse files
authored
Implement a extension API (#258)
* Implement the new addon API. * Tested addon and decorator API with a real app. * Add context support to decorators. * Add kind to the api. * Add extension documentation. * Update extension docs. * Fix a small typo.
1 parent b95d2e2 commit 484886a

12 files changed

Lines changed: 391 additions & 72 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ There are many things you can do with React Storybook. You can explore them with
123123
* [Writing Stories](docs/writing_stories.md)
124124
* [Setting up for CSS](docs/setting_up_for_css.md)
125125
* [Configuration APIs](docs/configure_storybook.md)
126+
* [Extensions](docs/extensions.md)
126127
* [Power Tools](https://voice.kadira.io/power-tools-for-react-storybook-d404d7b29b82#.4yodlbqi8)
127128
* [How Storybook Works](docs/how_storybook_works.md)
128129
* [Known Issues](docs/known_issues.md)

dist/client/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Object.defineProperty(exports, "__esModule", {
44
value: true
55
});
6-
exports.configure = exports.linkTo = exports.action = exports.storiesOf = undefined;
6+
exports.configure = exports.addDecorator = exports.setAddon = exports.linkTo = exports.action = exports.storiesOf = undefined;
77

88
var _preview = require('./preview');
99

@@ -14,4 +14,6 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj;
1414
var storiesOf = exports.storiesOf = previewApi.storiesOf;
1515
var action = exports.action = previewApi.action;
1616
var linkTo = exports.linkTo = previewApi.linkTo;
17+
var setAddon = exports.setAddon = previewApi.setAddon;
18+
var addDecorator = exports.addDecorator = previewApi.addDecorator;
1719
var configure = exports.configure = previewApi.configure;

dist/client/preview/client_api.js

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ var _from = require('babel-runtime/core-js/array/from');
88

99
var _from2 = _interopRequireDefault(_from);
1010

11+
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
12+
13+
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
14+
15+
var _extends2 = require('babel-runtime/helpers/extends');
16+
17+
var _extends3 = _interopRequireDefault(_extends2);
18+
1119
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
1220

1321
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
@@ -30,9 +38,21 @@ var ClientApi = function () {
3038

3139
this._pageBus = pageBus;
3240
this._storyStore = storyStore;
41+
this._addons = {};
42+
this._globalDecorators = [];
3343
}
3444

3545
(0, _createClass3.default)(ClientApi, [{
46+
key: 'setAddon',
47+
value: function setAddon(addon) {
48+
this._addons = (0, _extends3.default)({}, this._addons, addon);
49+
}
50+
}, {
51+
key: 'addDecorator',
52+
value: function addDecorator(decorator) {
53+
this._globalDecorators.push(decorator);
54+
}
55+
}, {
3656
key: 'storiesOf',
3757
value: function storiesOf(kind, m) {
3858
var _this = this;
@@ -43,16 +63,39 @@ var ClientApi = function () {
4363
});
4464
}
4565

46-
var decorators = [];
47-
var api = {};
66+
var localDecorators = [];
67+
var api = {
68+
kind: kind
69+
};
70+
71+
// apply addons
72+
for (var name in this._addons) {
73+
if (this._addons.hasOwnProperty(name)) {
74+
(function () {
75+
var addon = _this._addons[name];
76+
api[name] = function () {
77+
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
78+
args[_key] = arguments[_key];
79+
}
80+
81+
addon.apply(api, args);
82+
return api;
83+
};
84+
})();
85+
}
86+
}
4887

4988
api.add = function (storyName, getStory) {
5089
// Wrap the getStory function with each decorator. The first
5190
// decorator will wrap the story function. The second will
5291
// wrap the first decorator and so on.
92+
var decorators = [].concat(localDecorators, (0, _toConsumableArray3.default)(_this._globalDecorators));
93+
5394
var fn = decorators.reduce(function (decorated, decorator) {
54-
return function () {
55-
return decorator(decorated);
95+
return function (context) {
96+
return decorator(function () {
97+
return decorated(context);
98+
}, context);
5699
};
57100
}, getStory);
58101

@@ -62,7 +105,7 @@ var ClientApi = function () {
62105
};
63106

64107
api.addDecorator = function (decorator) {
65-
decorators.push(decorator);
108+
localDecorators.push(decorator);
66109
return api;
67110
};
68111

@@ -74,8 +117,8 @@ var ClientApi = function () {
74117
var pageBus = this._pageBus;
75118

76119
return function () {
77-
for (var _len = arguments.length, _args = Array(_len), _key = 0; _key < _len; _key++) {
78-
_args[_key] = arguments[_key];
120+
for (var _len2 = arguments.length, _args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
121+
_args[_key2] = arguments[_key2];
79122
}
80123

81124
var args = (0, _from2.default)(_args);

dist/client/preview/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Object.defineProperty(exports, "__esModule", {
44
value: true
55
});
6-
exports.configure = exports.linkTo = exports.action = exports.storiesOf = undefined;
6+
exports.configure = exports.addDecorator = exports.setAddon = exports.linkTo = exports.action = exports.storiesOf = undefined;
77

88
require('es6-shim');
99

@@ -60,6 +60,8 @@ var configApi = new _config_api2.default(context);
6060
var storiesOf = exports.storiesOf = clientApi.storiesOf.bind(clientApi);
6161
var action = exports.action = clientApi.action.bind(clientApi);
6262
var linkTo = exports.linkTo = clientApi.linkTo.bind(clientApi);
63+
var setAddon = exports.setAddon = clientApi.setAddon.bind(clientApi);
64+
var addDecorator = exports.addDecorator = clientApi.addDecorator.bind(clientApi);
6365
var configure = exports.configure = configApi.configure.bind(configApi);
6466

6567
// initialize the UI

dist/manager.js

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/manager.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/extensions.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# React Storybook Extensions
2+
3+
React Storybook comes with an extensions API to customize the storybook experience. Let's have a look at them.
4+
5+
## TOC
6+
7+
* [API](#api)
8+
* [Decorators](#decorators)
9+
* [Addons](#addons)
10+
* [Available Extensions](#available-extensions)
11+
12+
## API
13+
14+
### Decorators
15+
16+
A decorator is a way to wrap an story with a common set of component(s). Let's say you want to center all your stories. Then this is how we can do it with a decorator:
17+
18+
```js
19+
import React from 'react';
20+
import { storiesOf } from '@kadira/storybook';
21+
import MyComponent from '../my_component';
22+
23+
storiesOf('MyComponent', module)
24+
.addDecorator((story) => (
25+
<div style={{textAlign: 'center'}}>
26+
{story()}
27+
</div>
28+
));
29+
.add('without props', () => (<MyComponent />))
30+
.add('with some props', () => (<MyComponent text="The Comp"/>));
31+
```
32+
33+
Here we only add the decorator for the current set of stories for a given story kind.
34+
35+
But, you can add a decorator **globally** and it'll be applied to all the stories you create. This is how to add a decorator like that.
36+
37+
```js
38+
import { configure, addDecorator } from '@kadira/storybook';
39+
40+
addDecorator((story) => (
41+
<div style={{textAlign: 'center'}}>
42+
{story()}
43+
</div>
44+
));
45+
46+
configure(function () {
47+
...
48+
}, module);
49+
```
50+
51+
### Addons
52+
53+
With an addon, you can introduce new methods to the story creation API. For an example, you can achieve the above centered component functionality with an addon like this:
54+
55+
```js
56+
import React from 'react';
57+
import { storiesOf } from '@kadira/storybook';
58+
import MyComponent from '../my_component';
59+
60+
storiesOf('MyComponent', module)
61+
.addWithCentered('without props', () => (<MyComponent />))
62+
.addWithCentered('with some props', () => (<MyComponent text="The Comp"/>));
63+
```
64+
Here we are using a new API called `addWithCentered`. That's introduce by an addon.
65+
66+
This is how we set that addon.
67+
68+
```js
69+
import { configure, setAddon } from '@kadira/storybook';
70+
71+
setAddon({
72+
addWithCentered(storyName, storyFn) {
73+
// You can access the .add and other API added by addons in here.
74+
this.add(storyName, (context) => (
75+
<div style={{textAlign: "center"}}>
76+
{storyFn(context)}
77+
</div>
78+
));
79+
}
80+
});
81+
82+
configure(function () {
83+
...
84+
}, module);
85+
```
86+
87+
## Available Extensions
88+
89+
Rather than creating extensions yourself, you can use extensions available below:
90+
91+
* [Centered Decorator](https://github.com/kadirahq/react-storybook-decorator-centered)
92+
* [Info addon for displaying propTypes, source and more info](https://github.com/kadirahq/react-storybook-addon-info)
93+
94+
> Feel free to include your extension to the above list and share it with other. <br/>
95+
> Just make it available on NPM (and GitHub) and send a PR to this page.

docs/writing_stories.md

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
* [Basic API](#basic-api)
66
* [Creating Actions](#creating-actions)
7-
* [Using Decorators](#using-decorators)
87
* [Linking Stories](#linking-stories)
8+
* [Use Extensions](#use-extensions)
99

1010
You need to write stories to show your components inside React Storybook. We've a set of APIs allows you to write stories and do more with them:
1111

@@ -76,51 +76,6 @@ Here we can see the name we've mentioned when creating the action. After that, w
7676

7777
> For simplicity, React Storybook does not show the actual object. Instead it will show `[SyntheticEvent]`.
7878
79-
## Using Decorators
80-
81-
In some apps, we need to wrap our components with a given context. Most of the time, you have to do this when you are using Material UI or Radium.
82-
83-
So, you need to write your stories like this:
84-
85-
```js
86-
import React from 'react';
87-
import { storiesOf } from '@kadira/storybook';
88-
import Theme from '../theme';
89-
import MyComponent from '../my_component';
90-
91-
storiesOf('MyComponent', modules)
92-
.add('without props', () => (
93-
<Theme>
94-
<MyComponent />
95-
</Theme>
96-
))
97-
.add('with some props', () => (
98-
<Theme>
99-
<MyComponent name="Arunoda"/>
100-
</Theme>
101-
));
102-
```
103-
104-
As you can see, you always need to wrap your components with the `Theme` component. But, there's a much better way. See following example with a decorator:
105-
106-
```js
107-
import React from 'react';
108-
import { storiesOf } from '@kadira/storybook';
109-
import Theme from '../theme';
110-
import MyComponent from '../my_component';
111-
112-
storiesOf('MyComponent', modules)
113-
.addDecorator((story) => (
114-
<Theme>
115-
{story()}
116-
</Theme>
117-
))
118-
.add('without props', () => (<MyComponent />))
119-
.add('with some props', () => (<MyComponent name="Arunoda"/>));
120-
```
121-
122-
You can add as many as decorators you want, but make sure to call `.addDecorator()` before you call `.add()`.
123-
12479
## Linking Stories
12580

12681
Sometimes, we may need to link stories. With that, we could use Storybook as a prototype builder. (like [InVision](https://www.invisionapp.com/), [Framer.js](http://framerjs.com/)). Here's how to do that.
@@ -153,3 +108,9 @@ With that, you can link an event prop to any story in the Storybook.
153108
> You can also pass a function instead for any of above parameter. That function accepts arguments emitted by the event and it should return a string.
154109
155110
Have a look at [PR86](https://github.com/kadirahq/react-storybook/pull/86) for more information.
111+
112+
## Use Extensions
113+
114+
You can use [React Storybook Extensions](extensions.md) to group common functionalities and reduce the amount of code you need to write. You can also [re-use extensions](extensions.md#available-extensions) created by others.
115+
116+
Have a look at [React Storybook Extensions](extensions.md) for more information.

src/client/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ import * as previewApi from './preview';
33
export const storiesOf = previewApi.storiesOf;
44
export const action = previewApi.action;
55
export const linkTo = previewApi.linkTo;
6+
export const setAddon = previewApi.setAddon;
7+
export const addDecorator = previewApi.addDecorator;
68
export const configure = previewApi.configure;

0 commit comments

Comments
 (0)