Skip to content

Commit

Permalink
Merge pull request #1595 from kazupon/improve/decorator-for-vue
Browse files Browse the repository at this point in the history
Improve the decorator for vue
  • Loading branch information
ndelangen authored Aug 18, 2017
2 parents f6ac4a8 + f1bf341 commit afeebe0
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 57 deletions.
46 changes: 42 additions & 4 deletions addons/centered/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
Storybook Centered Decorator can be used to center components inside the preview in [Storybook](https://storybook.js.org).

This addon works with Storybook for:
[React](https://github.com/storybooks/storybook/tree/master/app/react).

- [React](https://github.com/storybooks/storybook/tree/master/app/react)
- [Vue](https://github.com/storybooks/storybook/tree/master/app/vue)

### Usage

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

#### As a decorator

You can set the decorator locally:
You can set the decorator locally.

exampwle for React:

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

Or you can also add this decorator globally:
example for Vue:

```js
import { storiesOf } from '@storybook/vue';
import centered from '@storybook/addon-centered';

import MyComponent from '../Component.vue';
storiesOf('MyComponent', module)
.addDecorator(centered)
.add('without props', () => ({
components: { MyComponent },
template: '<my-component />'
})
.add('with some props', () => ({
components: { MyComponent },
template: '<my-component text="The Comp"/>'
});
```
Also, you can also add this decorator globally
example for React:
```js
import { configure, addDecorator } from '@storybook/react';
import centered from '@storybook/react-storybook-decorator-centered';
import centered from '@storybook/addon-centered';

addDecorator(centered);

configure(function () {
//...
}, module);
```
example for Vue:
```js
import { configure, addDecorator } from '@storybook/vue';
import centered from '@storybook/addon-centered';

addDecorator(centered);

Expand Down
2 changes: 1 addition & 1 deletion addons/centered/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-centered",
"version": "3.2.0",
"version": "3.2.1",
"description": "Storybook decorator to center components",
"license": "MIT",
"author": "Muhammed Thanish <mnmtanish@gmail.com>",
Expand Down
30 changes: 5 additions & 25 deletions addons/centered/src/index.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,7 @@
import React from 'react';
import { window } from 'global';
import ReactCentered from './react';
import VueCentered from './vue';

const style = {
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'auto',
};
const Centered = window.STORYBOOK_ENV === 'vue' ? VueCentered : ReactCentered;

const innerStyle = {
margin: 'auto',
};

export default function(storyFn) {
return (
<div style={style}>
<div style={innerStyle}>
{storyFn()}
</div>
</div>
);
}
export default Centered;
27 changes: 27 additions & 0 deletions addons/centered/src/react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

const style = {
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'auto',
};

const innerStyle = {
margin: 'auto',
};

export default function(storyFn) {
return (
<div style={style}>
<div style={innerStyle}>
{storyFn()}
</div>
</div>
);
}
29 changes: 29 additions & 0 deletions addons/centered/src/vue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export default function () {
return {
template: `
<div :style="style">
<div :style="innerStyle">
<story/>
</div>
</div>
`,
data() {
return {
style: {
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'auto',
},
innerStyle: {
margin: 'auto',
}
}
}
}
}
4 changes: 2 additions & 2 deletions app/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@
"url-loader": "^0.5.8",
"util-deprecate": "^1.0.2",
"uuid": "^3.1.0",
"vue": "^2.4.1",
"vue": "^2.4.2",
"vue-hot-reload-api": "^2.1.0",
"vue-loader": "^12.2.1",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.4.1",
"vue-template-compiler": "^2.4.2",
"webpack": "^2.5.1 || ^3.0.0",
"webpack-dev-middleware": "^1.10.2",
"webpack-hot-middleware": "^2.18.0"
Expand Down
15 changes: 14 additions & 1 deletion app/vue/src/client/preview/client_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ export default class ClientApi {
};
});

const createWrapperComponent = Target => ({
functional: true,
render (h, c) {
return h(Target, c.data, c.children);
}
});

api.add = (storyName, getStory) => {
if (typeof storyName !== 'string') {
throw new Error(`Invalid or missing storyName provided for a "${kind}" story.`);
Expand All @@ -69,7 +76,13 @@ export default class ClientApi {
const decorators = [...localDecorators, ...this._globalDecorators];

const getDecoratedStory = decorators.reduce(
(decorated, decorator) => context => decorator(() => decorated(context), context),
(decorated, decorator) => context => {
const story = () => decorated(context);
const decoratedStory = decorator(story, context);
decoratedStory.components = decoratedStory.components || {};
decoratedStory.components.story = createWrapperComponent(story());
return decoratedStory
},
getStory
);

Expand Down
32 changes: 16 additions & 16 deletions app/vue/src/client/preview/client_api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,62 +134,62 @@ describe('preview.client_api', () => {
const storyStore = new StoryStore();
const api = new ClientAPI({ storyStore });
const localApi = api.storiesOf('none');
localApi.addDecorator(fn => `aa-${fn()}`);
localApi.addDecorator(fn => ({ template: `<div>aa${fn().template}</div>` }));

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

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

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

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

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

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

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

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

const kind = 'dfdfd';
const story = 'ef349ff';

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

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

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

const kind = 'dfdfd';
const story = 'ef349ff';

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

Expand Down
10 changes: 5 additions & 5 deletions examples/vue-kitchen-sink/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
"@storybook/addons": "^3.2.0",
"@storybook/addon-notes": "^3.2.0",
"@storybook/addon-knobs": "^3.2.0",
"vue-hot-reload-api": "^2.1.0",
"vue-style-loader": "^3.0.1",
"vue-loader": "^12.2.1",
"babel-core": "^6.25.0",
"babel-loader": "^7.0.0",
"babel-preset-env": "^1.6.0",
"cross-env": "^3.0.0",
"css-loader": "^0.28.1",
"file-loader": "^0.11.1",
"vue-template-compiler": "^2.4.1",
"vue-hot-reload-api": "^2.1.0",
"vue-loader": "^12.2.1",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.4.2",
"webpack": "^2.5.1 || ^3.0.0",
"webpack-dev-server": "^2.4.5"
},
"dependencies": {
"vue": "^2.4.1",
"vue": "^2.4.2",
"vuex": "^2.3.1"
},
"scripts": {
Expand Down
35 changes: 32 additions & 3 deletions examples/vue-kitchen-sink/src/stories/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import Vuex from 'vuex';
import { storiesOf } from '@storybook/vue';

import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';

import { withNotes } from '@storybook/addon-notes';

import {
withKnobs,
text,
Expand All @@ -16,6 +13,7 @@ import {
color,
date,
} from '@storybook/addon-knobs';
import Centered from '@storybook/addon-centered';

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

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

storiesOf('Decorator for Vue', module)
.addDecorator(story => {
// Decorated with story function
const WrapButton = story();
return {
components: { WrapButton },
template: '<div :style="{ border: borderStyle }"><wrap-button/></div>',
data() {
return { borderStyle: 'medium solid red' };
},
};
})
.addDecorator(() => ({
// Decorated with `story` component
template: '<div :style="{ border: borderStyle }"><story/></div>',
data() {
return {
borderStyle: 'medium solid blue',
};
},
}))
.add('template', () => ({
template: '<my-button>MyButton with template</my-button>',
}))
.add('render', () => ({
render(h) {
return h(MyButton, { props: { color: 'pink' } }, ['renders component: MyButton']);
},
}));

storiesOf('Addon Actions', module)
.add('Action only', () => ({
template: '<my-button :handle-click="log">Click me to log the action</my-button>',
Expand Down

0 comments on commit afeebe0

Please sign in to comment.