Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new react-docgen propTypes to info addon #1562

Merged
merged 12 commits into from
Aug 18, 2017
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