mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-04 16:01:06 +08:00
Merge branch 'master' into fix-vue-webpack
This commit is contained in:
commit
c2f1329468
@ -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
|
||||
|
||||
@ -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';
|
||||
@ -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);
|
||||
|
||||
|
@ -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>",
|
||||
|
@ -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
addons/centered/src/react.js
vendored
Normal file
27
addons/centered/src/react.js
vendored
Normal file
@ -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
addons/centered/src/vue.js
Normal file
29
addons/centered/src/vue.js
Normal file
@ -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',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -68,9 +68,7 @@ It is possible to add infos by default to all components by using a global or st
|
||||
|
||||
It is important to declare this decorator as **the first decorator**, otherwise it won't work well.
|
||||
|
||||
```
|
||||
addDecorator((story, context) => withInfo('common info')(story)(context));
|
||||
```
|
||||
addDecorator((story, context) => withInfo('common info')(story)(context));
|
||||
|
||||
## Global options
|
||||
|
||||
@ -124,6 +122,60 @@ storiesOf('Component')
|
||||
|
||||
> Have a look at [this example](example/story.js) stories to learn more about the `addWithInfo` API.
|
||||
|
||||
To customize your defaults:
|
||||
|
||||
```js
|
||||
// config.js
|
||||
import infoAddon, { setDefaults } from '@storybook/addon-info';
|
||||
|
||||
// addon-info
|
||||
setDefaults({
|
||||
inline: true,
|
||||
maxPropsIntoLine: 1,
|
||||
maxPropObjectKeys: 10,
|
||||
maxPropArrayLength: 10,
|
||||
maxPropStringLength: 100,
|
||||
});
|
||||
setAddon(infoAddon);
|
||||
```
|
||||
|
||||
### React Docgen Integration
|
||||
|
||||
React Docgen is included as part of the @storybook/react package through the use of `babel-plugin-react-docgen` during compile time.
|
||||
When rendering a story with a React component commented in this supported format, the Addon Info prop table will display the prop's comment in the description column.
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/** Button component description */
|
||||
const DocgenButton = ({ disabled, label, style, onClick }) =>
|
||||
<button disabled={disabled} style={style} onClick={onClick}>
|
||||
{label}
|
||||
</button>;
|
||||
|
||||
DocgenButton.defaultProps = {
|
||||
disabled: false,
|
||||
onClick: () => {},
|
||||
style: {},
|
||||
};
|
||||
|
||||
DocgenButton.propTypes = {
|
||||
/** Boolean indicating whether the button should render as disabled */
|
||||
disabled: PropTypes.bool,
|
||||
/** button label. */
|
||||
label: PropTypes.string.isRequired,
|
||||
/** onClick handler */
|
||||
onClick: PropTypes.func,
|
||||
/** component styles */
|
||||
style: PropTypes.shape,
|
||||
};
|
||||
|
||||
export default DocgenButton;
|
||||
```
|
||||
|
||||
Storybook Info Addon should now render all the correct types for your component.
|
||||
|
||||
## The FAQ
|
||||
|
||||
**Components lose their names on static build**
|
||||
|
@ -21,19 +21,61 @@ const stylesheet = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function PropTable(props) {
|
||||
const { type, maxPropObjectKeys, maxPropArrayLength, maxPropStringLength } = props;
|
||||
const isNotEmpty = obj => obj && obj.props && Object.keys(obj.props).length > 0;
|
||||
|
||||
if (!type) {
|
||||
return null;
|
||||
const renderDocgenPropType = propType => {
|
||||
if (!propType) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
const accumProps = {};
|
||||
const name = propType.name;
|
||||
|
||||
switch (name) {
|
||||
case 'arrayOf':
|
||||
return `${propType.value.name}[]`;
|
||||
case 'instanceOf':
|
||||
return propType.value;
|
||||
case 'union':
|
||||
return propType.raw;
|
||||
case 'signature':
|
||||
return propType.raw;
|
||||
default:
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
const hasDocgen = type => isNotEmpty(type.__docgenInfo);
|
||||
|
||||
const boolToString = value => (value ? 'yes' : 'no');
|
||||
|
||||
const propsFromDocgen = type => {
|
||||
const props = {};
|
||||
const docgenInfoProps = type.__docgenInfo.props;
|
||||
|
||||
Object.keys(docgenInfoProps).forEach(property => {
|
||||
const docgenInfoProp = docgenInfoProps[property];
|
||||
const defaultValueDesc = docgenInfoProp.defaultValue || {};
|
||||
const propType = docgenInfoProp.flowType || docgenInfoProp.type || 'other';
|
||||
|
||||
props[property] = {
|
||||
property,
|
||||
propType: renderDocgenPropType(propType),
|
||||
required: boolToString(docgenInfoProp.required),
|
||||
description: docgenInfoProp.description,
|
||||
defaultValue: defaultValueDesc.value,
|
||||
};
|
||||
});
|
||||
|
||||
return props;
|
||||
};
|
||||
|
||||
const propsFromPropTypes = type => {
|
||||
const props = {};
|
||||
|
||||
if (type.propTypes) {
|
||||
Object.keys(type.propTypes).forEach(property => {
|
||||
const typeInfo = type.propTypes[property];
|
||||
const required = typeInfo.isRequired === undefined ? 'yes' : 'no';
|
||||
const required = boolToString(typeInfo.isRequired === undefined);
|
||||
const description =
|
||||
type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property]
|
||||
? type.__docgenInfo.props[property].description
|
||||
@ -51,7 +93,7 @@ export default function PropTable(props) {
|
||||
}
|
||||
}
|
||||
|
||||
accumProps[property] = { property, propType, required, description };
|
||||
props[property] = { property, propType, required, description };
|
||||
});
|
||||
}
|
||||
|
||||
@ -63,14 +105,25 @@ export default function PropTable(props) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accumProps[property]) {
|
||||
accumProps[property] = { property };
|
||||
if (!props[property]) {
|
||||
props[property] = { property };
|
||||
}
|
||||
|
||||
accumProps[property].defaultValue = value;
|
||||
props[property].defaultValue = value;
|
||||
});
|
||||
}
|
||||
|
||||
return props;
|
||||
};
|
||||
|
||||
export default function PropTable(props) {
|
||||
const { type, maxPropObjectKeys, maxPropArrayLength, maxPropStringLength } = props;
|
||||
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const accumProps = hasDocgen(type) ? propsFromDocgen(type) : propsFromPropTypes(type);
|
||||
const array = Object.values(accumProps);
|
||||
|
||||
if (!array.length) {
|
||||
|
@ -1,7 +1,5 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015", "react"
|
||||
],
|
||||
"presets": ["env", "react"],
|
||||
"plugins": [
|
||||
"transform-runtime"
|
||||
]
|
||||
|
10
app/react-native/src/preview/index.js
vendored
10
app/react-native/src/preview/index.js
vendored
@ -55,7 +55,15 @@ export default class Preview {
|
||||
getStorybookUI(params = {}) {
|
||||
return () => {
|
||||
let webUrl = null;
|
||||
let channel = addons.getChannel();
|
||||
let channel = null;
|
||||
|
||||
try {
|
||||
channel = addons.getChannel();
|
||||
} catch (e) {
|
||||
// getChannel throws if the channel is not defined,
|
||||
// which is fine in this case (we will define it below)
|
||||
}
|
||||
|
||||
if (params.resetStorybook || !channel) {
|
||||
const host = params.host || parse(NativeModules.SourceCode.scriptURL).hostname;
|
||||
const port = params.port !== false ? `:${params.port || 7007}` : '';
|
||||
|
@ -1,5 +1,7 @@
|
||||
import url from 'url';
|
||||
|
||||
const getExtensionForFilename = filename => /.+\.(\w+)$/.exec(filename)[1];
|
||||
|
||||
// assets.preview will be:
|
||||
// - undefined
|
||||
// - string e.g. 'static/preview.9adbb5ef965106be1cc3.bundle.js'
|
||||
@ -21,7 +23,6 @@ export const urlsFromAssets = assets => {
|
||||
css: [],
|
||||
};
|
||||
|
||||
const re = /.+\.(\w+)$/;
|
||||
Object.keys(assets)
|
||||
// Don't load the manager script in the iframe
|
||||
.filter(key => key !== 'manager')
|
||||
@ -30,9 +31,16 @@ export const urlsFromAssets = assets => {
|
||||
if (!Array.isArray(assetList)) {
|
||||
assetList = [assetList];
|
||||
}
|
||||
assetList.filter(assetUrl => re.exec(assetUrl)[1] !== 'map').forEach(assetUrl => {
|
||||
urls[re.exec(assetUrl)[1]].push(assetUrl);
|
||||
});
|
||||
assetList
|
||||
.filter(assetUrl => {
|
||||
const extension = getExtensionForFilename(assetUrl);
|
||||
const isMap = extension === 'map';
|
||||
const isSupportedExtension = Boolean(urls[extension]);
|
||||
return isSupportedExtension && !isMap;
|
||||
})
|
||||
.forEach(assetUrl => {
|
||||
urls[getExtensionForFilename(assetUrl)].push(assetUrl);
|
||||
});
|
||||
});
|
||||
|
||||
return urls;
|
||||
|
@ -18,4 +18,14 @@ describe('server.urlsFromAssets', () => {
|
||||
css: ['static/preview.y.css'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return non-js or non-css assets', () => {
|
||||
const fixture = {
|
||||
'some-thing.svg': 'some-thing.svg',
|
||||
};
|
||||
expect(urlsFromAssets(fixture)).toEqual({
|
||||
js: [],
|
||||
css: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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"
|
||||
|
@ -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.`);
|
||||
@ -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
|
||||
);
|
||||
|
||||
|
@ -134,20 +134,20 @@ 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', () => {
|
||||
@ -155,41 +155,41 @@ describe('preview.client_api', () => {
|
||||
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>`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import url from 'url';
|
||||
|
||||
const getExtensionForFilename = filename => /.+\.(\w+)$/.exec(filename)[1];
|
||||
|
||||
// assets.preview will be:
|
||||
// - undefined
|
||||
// - string e.g. 'static/preview.9adbb5ef965106be1cc3.bundle.js'
|
||||
@ -21,17 +23,27 @@ const urlsFromAssets = assets => {
|
||||
css: [],
|
||||
};
|
||||
|
||||
const re = /.+\.(\w+)$/;
|
||||
Object.keys(assets)
|
||||
// Don't load the manager script in the iframe
|
||||
.filter(key => key !== 'manager')
|
||||
.forEach(key => {
|
||||
const asset = assets[key];
|
||||
let asset = assets[key];
|
||||
if (typeof asset === 'string') {
|
||||
urls[re.exec(asset)[1]].push(asset);
|
||||
urls[getExtensionForFilename(asset)].push(asset);
|
||||
} else {
|
||||
const assetUrl = asset.find(u => re.exec(u)[1] !== 'map');
|
||||
urls[re.exec(assetUrl)[1]].push(assetUrl);
|
||||
if (!Array.isArray(asset)) {
|
||||
asset = [asset];
|
||||
}
|
||||
asset
|
||||
.filter(assetUrl => {
|
||||
const extension = getExtensionForFilename(assetUrl);
|
||||
const isMap = extension === 'map';
|
||||
const isSupportedExtension = Boolean(urls[extension]);
|
||||
return isSupportedExtension && !isMap;
|
||||
})
|
||||
.forEach(assetUrl => {
|
||||
urls[getExtensionForFilename(assetUrl)].push(assetUrl);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
71
examples/cra-kitchen-sink/src/components/DocgenButton.js
Normal file
71
examples/cra-kitchen-sink/src/components/DocgenButton.js
Normal file
@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/** Button component description */
|
||||
const DocgenButton = ({ disabled, label, onClick }) =>
|
||||
<button disabled={disabled} onClick={onClick}>
|
||||
{label}
|
||||
</button>;
|
||||
|
||||
DocgenButton.defaultProps = {
|
||||
disabled: false,
|
||||
onClick: () => {},
|
||||
};
|
||||
|
||||
/* eslint-disable react/no-unused-prop-types,react/require-default-props */
|
||||
|
||||
const Message = {};
|
||||
|
||||
DocgenButton.propTypes = {
|
||||
/** Boolean indicating whether the button should render as disabled */
|
||||
disabled: PropTypes.bool,
|
||||
/** button label. */
|
||||
label: PropTypes.string.isRequired,
|
||||
/** onClick handler */
|
||||
onClick: PropTypes.func,
|
||||
/**
|
||||
* A simple `objectOf` propType.
|
||||
*/
|
||||
one: PropTypes.objectOf(PropTypes.number),
|
||||
/**
|
||||
* A very complex `objectOf` propType.
|
||||
*/
|
||||
two: PropTypes.objectOf(
|
||||
PropTypes.shape({
|
||||
/**
|
||||
* Just an internal propType for a shape.
|
||||
* It's also required, and as you can see it supports multi-line comments!
|
||||
*/
|
||||
id: PropTypes.number.isRequired,
|
||||
/**
|
||||
* A simple non-required function
|
||||
*/
|
||||
func: PropTypes.func,
|
||||
/**
|
||||
* An `arrayOf` shape
|
||||
*/
|
||||
arr: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
/**
|
||||
* 5-level deep propType definition and still works.
|
||||
*/
|
||||
index: PropTypes.number.isRequired,
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
/**
|
||||
* `instanceOf` is also supported and the custom type will be shown instead of `instanceOf`
|
||||
*/
|
||||
msg: PropTypes.instanceOf(Message),
|
||||
/**
|
||||
* `oneOf` is basically an Enum which is also supported but can be pretty big.
|
||||
*/
|
||||
enm: PropTypes.oneOf(['News', 'Photos']),
|
||||
/**
|
||||
* A multi-type prop is also valid and is displayed as `Union<String|Message>`
|
||||
*/
|
||||
union: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Message)]),
|
||||
};
|
||||
|
||||
export default DocgenButton;
|
@ -26,6 +26,7 @@ import { Button, Welcome } from '@storybook/react/demo';
|
||||
import App from '../App';
|
||||
import Logger from './Logger';
|
||||
import Container from './Container';
|
||||
import DocgenButton from '../components/DocgenButton';
|
||||
|
||||
const EVENTS = {
|
||||
TEST_EVENT_1: 'test-event-1',
|
||||
@ -162,6 +163,10 @@ storiesOf('Button', module)
|
||||
)
|
||||
);
|
||||
|
||||
storiesOf('AddonInfo.DocgenButton', module).addWithInfo('DocgenButton', 'Some Description', () =>
|
||||
<DocgenButton onClick={action('clicked')} label="Docgen Button" />
|
||||
);
|
||||
|
||||
storiesOf('App', module).add('full app', () => <App />);
|
||||
|
||||
storiesOf('Some really long story kind description', module)
|
||||
|
@ -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": {
|
||||
|
@ -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,
|
||||
@ -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';
|
||||
@ -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>',
|
||||
@ -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>',
|
||||
|
@ -2,15 +2,19 @@ export class AddonStore {
|
||||
constructor() {
|
||||
this.loaders = {};
|
||||
this.panels = {};
|
||||
// this.channel should get overwritten by setChannel if package versions are
|
||||
// correct and AddonStore is a proper singleton. If not, this will be null
|
||||
// (currently required by @storybook/react-native getStorybookUI)
|
||||
this.channel = null;
|
||||
this.preview = null;
|
||||
this.database = null;
|
||||
}
|
||||
|
||||
getChannel() {
|
||||
// this.channel should get overwritten by setChannel if package versions are
|
||||
// correct and AddonStore is a proper singleton. If not, throw.
|
||||
if (!this.channel) {
|
||||
throw new Error(
|
||||
'Accessing nonexistent addons channel, see https://storybook.js.org/basics/faq/#why-is-there-no-addons-channel'
|
||||
);
|
||||
}
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user