Skip to content

Commit

Permalink
Merge pull request #1562 from storybooks/info-docgen
Browse files Browse the repository at this point in the history
Add new react-docgen propTypes to info addon
  • Loading branch information
danielduan authored Aug 18, 2017
2 parents 7ea6c54 + a46d83e commit 2c289d7
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 13 deletions.
58 changes: 55 additions & 3 deletions addons/info/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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**
Expand Down
73 changes: 63 additions & 10 deletions addons/info/src/components/PropTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 accumProps = {};
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
Expand All @@ -51,7 +93,7 @@ export default function PropTable(props) {
}
}

accumProps[property] = { property, propType, required, description };
props[property] = { property, propType, required, description };
});
}

Expand All @@ -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) {
Expand Down
71 changes: 71 additions & 0 deletions examples/cra-kitchen-sink/src/components/DocgenButton.js
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 5 additions & 0 deletions examples/cra-kitchen-sink/src/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 2c289d7

Please sign in to comment.