Skip to content

Commit afeebe0

Browse files
authored
Merge pull request #1595 from kazupon/improve/decorator-for-vue
Improve the decorator for vue
2 parents f6ac4a8 + f1bf341 commit afeebe0

10 files changed

Lines changed: 173 additions & 57 deletions

File tree

addons/centered/README.md

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
Storybook Centered Decorator can be used to center components inside the preview in [Storybook](https://storybook.js.org).
1111

1212
This addon works with Storybook for:
13-
[React](https://github.com/storybooks/storybook/tree/master/app/react).
13+
14+
- [React](https://github.com/storybooks/storybook/tree/master/app/react)
15+
- [Vue](https://github.com/storybooks/storybook/tree/master/app/vue)
1416

1517
### Usage
1618

@@ -20,7 +22,9 @@ npm install @storybook/addon-centered --save-dev
2022

2123
#### As a decorator
2224

23-
You can set the decorator locally:
25+
You can set the decorator locally.
26+
27+
exampwle for React:
2428

2529
```js
2630
import { storiesOf } from '@storybook/react';
@@ -34,11 +38,45 @@ storiesOf('MyComponent', module)
3438
.add('with some props', () => (<MyComponent text="The Comp"/>));
3539
```
3640

37-
Or you can also add this decorator globally:
41+
example for Vue:
42+
43+
```js
44+
import { storiesOf } from '@storybook/vue';
45+
import centered from '@storybook/addon-centered';
46+
47+
import MyComponent from '../Component.vue';
48+
storiesOf('MyComponent', module)
49+
.addDecorator(centered)
50+
.add('without props', () => ({
51+
components: { MyComponent },
52+
template: '<my-component />'
53+
})
54+
.add('with some props', () => ({
55+
components: { MyComponent },
56+
template: '<my-component text="The Comp"/>'
57+
});
58+
```
59+
60+
Also, you can also add this decorator globally
61+
62+
example for React:
3863
3964
```js
4065
import { configure, addDecorator } from '@storybook/react';
41-
import centered from '@storybook/react-storybook-decorator-centered';
66+
import centered from '@storybook/addon-centered';
67+
68+
addDecorator(centered);
69+
70+
configure(function () {
71+
//...
72+
}, module);
73+
```
74+
75+
example for Vue:
76+
77+
```js
78+
import { configure, addDecorator } from '@storybook/vue';
79+
import centered from '@storybook/addon-centered';
4280

4381
addDecorator(centered);
4482

addons/centered/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@storybook/addon-centered",
3-
"version": "3.2.0",
3+
"version": "3.2.1",
44
"description": "Storybook decorator to center components",
55
"license": "MIT",
66
"author": "Muhammed Thanish <mnmtanish@gmail.com>",

addons/centered/src/index.js

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,7 @@
1-
import React from 'react';
1+
import { window } from 'global';
2+
import ReactCentered from './react';
3+
import VueCentered from './vue';
24

3-
const style = {
4-
position: 'fixed',
5-
top: 0,
6-
left: 0,
7-
bottom: 0,
8-
right: 0,
9-
display: 'flex',
10-
alignItems: 'center',
11-
justifyContent: 'center',
12-
overflow: 'auto',
13-
};
5+
const Centered = window.STORYBOOK_ENV === 'vue' ? VueCentered : ReactCentered;
146

15-
const innerStyle = {
16-
margin: 'auto',
17-
};
18-
19-
export default function(storyFn) {
20-
return (
21-
<div style={style}>
22-
<div style={innerStyle}>
23-
{storyFn()}
24-
</div>
25-
</div>
26-
);
27-
}
7+
export default Centered;

addons/centered/src/react.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
3+
const style = {
4+
position: 'fixed',
5+
top: 0,
6+
left: 0,
7+
bottom: 0,
8+
right: 0,
9+
display: 'flex',
10+
alignItems: 'center',
11+
justifyContent: 'center',
12+
overflow: 'auto',
13+
};
14+
15+
const innerStyle = {
16+
margin: 'auto',
17+
};
18+
19+
export default function(storyFn) {
20+
return (
21+
<div style={style}>
22+
<div style={innerStyle}>
23+
{storyFn()}
24+
</div>
25+
</div>
26+
);
27+
}

addons/centered/src/vue.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export default function () {
2+
return {
3+
template: `
4+
<div :style="style">
5+
<div :style="innerStyle">
6+
<story/>
7+
</div>
8+
</div>
9+
`,
10+
data() {
11+
return {
12+
style: {
13+
position: 'fixed',
14+
top: 0,
15+
left: 0,
16+
bottom: 0,
17+
right: 0,
18+
display: 'flex',
19+
alignItems: 'center',
20+
justifyContent: 'center',
21+
overflow: 'auto',
22+
},
23+
innerStyle: {
24+
margin: 'auto',
25+
}
26+
}
27+
}
28+
}
29+
}

app/vue/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@
6767
"url-loader": "^0.5.8",
6868
"util-deprecate": "^1.0.2",
6969
"uuid": "^3.1.0",
70-
"vue": "^2.4.1",
70+
"vue": "^2.4.2",
7171
"vue-hot-reload-api": "^2.1.0",
7272
"vue-loader": "^12.2.1",
7373
"vue-style-loader": "^3.0.1",
74-
"vue-template-compiler": "^2.4.1",
74+
"vue-template-compiler": "^2.4.2",
7575
"webpack": "^2.5.1 || ^3.0.0",
7676
"webpack-dev-middleware": "^1.10.2",
7777
"webpack-hot-middleware": "^2.18.0"

app/vue/src/client/preview/client_api.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ export default class ClientApi {
5454
};
5555
});
5656

57+
const createWrapperComponent = Target => ({
58+
functional: true,
59+
render (h, c) {
60+
return h(Target, c.data, c.children);
61+
}
62+
});
63+
5764
api.add = (storyName, getStory) => {
5865
if (typeof storyName !== 'string') {
5966
throw new Error(`Invalid or missing storyName provided for a "${kind}" story.`);
@@ -69,7 +76,13 @@ export default class ClientApi {
6976
const decorators = [...localDecorators, ...this._globalDecorators];
7077

7178
const getDecoratedStory = decorators.reduce(
72-
(decorated, decorator) => context => decorator(() => decorated(context), context),
79+
(decorated, decorator) => context => {
80+
const story = () => decorated(context);
81+
const decoratedStory = decorator(story, context);
82+
decoratedStory.components = decoratedStory.components || {};
83+
decoratedStory.components.story = createWrapperComponent(story());
84+
return decoratedStory
85+
},
7386
getStory
7487
);
7588

app/vue/src/client/preview/client_api.test.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,62 +134,62 @@ describe('preview.client_api', () => {
134134
const storyStore = new StoryStore();
135135
const api = new ClientAPI({ storyStore });
136136
const localApi = api.storiesOf('none');
137-
localApi.addDecorator(fn => `aa-${fn()}`);
137+
localApi.addDecorator(fn => ({ template: `<div>aa${fn().template}</div>` }));
138138

139-
localApi.add('storyName', () => 'Hello');
140-
expect(storyStore.stories[0].fn()).toBe('aa-Hello');
139+
localApi.add('storyName', () => ({ template: '<p>hello</p>' }));
140+
expect(storyStore.stories[0].fn().template).toBe('<div>aa<p>hello</p></div>');
141141
});
142142

143143
it('should add global decorators', () => {
144144
const storyStore = new StoryStore();
145145
const api = new ClientAPI({ storyStore });
146-
api.addDecorator(fn => `bb-${fn()}`);
146+
api.addDecorator(fn => ({ template: `<div>bb${fn().template}</div>` }));
147147
const localApi = api.storiesOf('none');
148148

149-
localApi.add('storyName', () => 'Hello');
150-
expect(storyStore.stories[0].fn()).toBe('bb-Hello');
149+
localApi.add('storyName', () => ({ template: '<p>hello</p>' }));
150+
expect(storyStore.stories[0].fn().template).toBe('<div>bb<p>hello</p></div>');
151151
});
152152

153153
it('should utilize both decorators at once', () => {
154154
const storyStore = new StoryStore();
155155
const api = new ClientAPI({ storyStore });
156156
const localApi = api.storiesOf('none');
157157

158-
api.addDecorator(fn => `aa-${fn()}`);
159-
localApi.addDecorator(fn => `bb-${fn()}`);
158+
api.addDecorator(fn => ({ template: `<div>aa${fn().template}</div>` }));
159+
localApi.addDecorator(fn => ({ template: `<div>bb${fn().template}</div>` }));
160160

161-
localApi.add('storyName', () => 'Hello');
162-
expect(storyStore.stories[0].fn()).toBe('aa-bb-Hello');
161+
localApi.add('storyName', () => ({ template: '<p>hello</p>' }));
162+
expect(storyStore.stories[0].fn().template).toBe('<div>aa<div>bb<p>hello</p></div></div>');
163163
});
164164

165165
it('should pass the context', () => {
166166
const storyStore = new StoryStore();
167167
const api = new ClientAPI({ storyStore });
168168
const localApi = api.storiesOf('none');
169-
localApi.addDecorator(fn => `aa-${fn()}`);
169+
localApi.addDecorator(fn => ({ template: `<div>aa${fn().template}</div>` }));
170170

171-
localApi.add('storyName', ({ kind, story }) => `${kind}-${story}`);
171+
localApi.add('storyName', ({ kind, story }) => ({ template: `<p>${kind}-${story}</p>` }));
172172

173173
const kind = 'dfdfd';
174174
const story = 'ef349ff';
175175

176176
const result = storyStore.stories[0].fn({ kind, story });
177-
expect(result).toBe(`aa-${kind}-${story}`);
177+
expect(result.template).toBe(`<div>aa<p>${kind}-${story}</p></div>`);
178178
});
179179

180180
it('should have access to the context', () => {
181181
const storyStore = new StoryStore();
182182
const api = new ClientAPI({ storyStore });
183183
const localApi = api.storiesOf('none');
184-
localApi.addDecorator((fn, { kind, story }) => `${kind}-${story}-${fn()}`);
184+
localApi.addDecorator((fn, { kind, story }) => ({ template: `<div>${kind}-${story}-${fn().template}</div>` }));
185185

186-
localApi.add('storyName', () => 'Hello');
186+
localApi.add('storyName', () => ({ template: '<p>hello</p>' }));
187187

188188
const kind = 'dfdfd';
189189
const story = 'ef349ff';
190190

191191
const result = storyStore.stories[0].fn({ kind, story });
192-
expect(result).toBe(`${kind}-${story}-Hello`);
192+
expect(result.template).toBe(`<div>${kind}-${story}-<p>hello</p></div>`);
193193
});
194194
});
195195

examples/vue-kitchen-sink/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@
99
"@storybook/addons": "^3.2.0",
1010
"@storybook/addon-notes": "^3.2.0",
1111
"@storybook/addon-knobs": "^3.2.0",
12-
"vue-hot-reload-api": "^2.1.0",
13-
"vue-style-loader": "^3.0.1",
14-
"vue-loader": "^12.2.1",
1512
"babel-core": "^6.25.0",
1613
"babel-loader": "^7.0.0",
1714
"babel-preset-env": "^1.6.0",
1815
"cross-env": "^3.0.0",
1916
"css-loader": "^0.28.1",
2017
"file-loader": "^0.11.1",
21-
"vue-template-compiler": "^2.4.1",
18+
"vue-hot-reload-api": "^2.1.0",
19+
"vue-loader": "^12.2.1",
20+
"vue-style-loader": "^3.0.1",
21+
"vue-template-compiler": "^2.4.2",
2222
"webpack": "^2.5.1 || ^3.0.0",
2323
"webpack-dev-server": "^2.4.5"
2424
},
2525
"dependencies": {
26-
"vue": "^2.4.1",
26+
"vue": "^2.4.2",
2727
"vuex": "^2.3.1"
2828
},
2929
"scripts": {

examples/vue-kitchen-sink/src/stories/index.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import Vuex from 'vuex';
22
import { storiesOf } from '@storybook/vue';
3-
43
import { action } from '@storybook/addon-actions';
54
import { linkTo } from '@storybook/addon-links';
6-
75
import { withNotes } from '@storybook/addon-notes';
8-
96
import {
107
withKnobs,
118
text,
@@ -16,6 +13,7 @@ import {
1613
color,
1714
date,
1815
} from '@storybook/addon-knobs';
16+
import Centered from '@storybook/addon-centered';
1917

2018
import MyButton from './Button.vue';
2119
import Welcome from './Welcome.vue';
@@ -30,6 +28,7 @@ storiesOf('App', module).add('App', () => ({
3028
}));
3129

3230
storiesOf('Button', module)
31+
.addDecorator(Centered)
3332
// Works if Vue.component is called in the config.js in .storybook
3433
.add('rounded', () => ({
3534
template: '<my-button :rounded="true">A Button with rounded edges</my-button>',
@@ -117,6 +116,36 @@ storiesOf('Method for rendering Vue', module)
117116
</p>`,
118117
}));
119118

119+
storiesOf('Decorator for Vue', module)
120+
.addDecorator(story => {
121+
// Decorated with story function
122+
const WrapButton = story();
123+
return {
124+
components: { WrapButton },
125+
template: '<div :style="{ border: borderStyle }"><wrap-button/></div>',
126+
data() {
127+
return { borderStyle: 'medium solid red' };
128+
},
129+
};
130+
})
131+
.addDecorator(() => ({
132+
// Decorated with `story` component
133+
template: '<div :style="{ border: borderStyle }"><story/></div>',
134+
data() {
135+
return {
136+
borderStyle: 'medium solid blue',
137+
};
138+
},
139+
}))
140+
.add('template', () => ({
141+
template: '<my-button>MyButton with template</my-button>',
142+
}))
143+
.add('render', () => ({
144+
render(h) {
145+
return h(MyButton, { props: { color: 'pink' } }, ['renders component: MyButton']);
146+
},
147+
}));
148+
120149
storiesOf('Addon Actions', module)
121150
.add('Action only', () => ({
122151
template: '<my-button :handle-click="log">Click me to log the action</my-button>',

0 commit comments

Comments
 (0)