Skip to content

Commit

Permalink
Add TableComponent option for addon-info
Browse files Browse the repository at this point in the history
  • Loading branch information
terrencewwong committed Dec 4, 2017
1 parent 0988d0c commit 7b8f765
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 84 deletions.
99 changes: 98 additions & 1 deletion addons/info/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ setDefaults({
maxPropsIntoLine: 1, // Max props to display per line in source code
maxPropObjectKeys: 10, // Displays the first 10 characters of the prop name
maxPropArrayLength: 10, // Displays the first 10 items in the default prop array
maxPropStringLength: 100, // Displays the first 100 characters in the default prop string
maxPropStringLength: 100, // Displays the first 100 characters in the default prop string,
TableComponent: props => {}, // Override the component used to render the props table
}
```

Expand Down Expand Up @@ -160,6 +161,102 @@ setDefaults({
setAddon(infoAddon);
```

### Rendering a Custom Table

The `TableComponent` option allows you to define how the prop table should be rendered. Your component will be rendered with the following props.

```js
{
propDefinitions: Array<{
property: string, // The name of the prop
propType: Object | string, // The prop type. TODO: info about what this object is...
required: boolean, // True if the prop is required
description: string, // The description of the prop
defaultValue: any // The default value of the prop
}>
}
```

Example:

```js
// button.js
// @flow
import React from 'react'

const paddingStyles = {
small: '4px 8px',
medium: '8px 16px'
}

const Button = ({
size,
...rest
}: {
/** The size of the button */
size: 'small' | 'medium'
}) => {
const style = {
padding: paddingStyles[size] || ''
}
return <button style={style} {...rest} />
}
Button.defaultProps = {
size: 'medium'
}

export default Button
```
```js
// stories.js
import React from "react";

import { storiesOf } from "@storybook/react";
import { withInfo } from "@storybook/addon-info";
import Button from "./button";

const Red = props => <span style={{ color: "red" }} {...props} />;

const TableComponent = ({ propDefinitions }) => {
const props = propDefinitions.map(
({ property, propType, required, description, defaultValue }) => {
return (
<tr key={property}>
<td>
{property}
{required ? <Red>*</Red> : null}
</td>
<td>{propType.name}</td>
<td>{defaultValue}</td>
<td>{description}</td>
</tr>
);
}
);

return (
<table>
<thead>
<tr>
<th>name</th>
<th>type</th>
<th>default</th>
<th>description</th>
</tr>
</thead>
<tbody>{props}</tbody>
</table>
);
};

storiesOf("Button", module).add(
"with text",
withInfo({
TableComponent
})(() => <Button>Hello Button</Button>)
);
```

### React Docgen Integration

React Docgen is included as part of the @storybook/react package through the use of `babel-plugin-react-docgen` during babel compile time.
Expand Down
100 changes: 19 additions & 81 deletions addons/info/src/components/PropTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,81 +7,6 @@ import { Table, Td, Th } from '@storybook/components';
import PropVal from './PropVal';
import PrettyPropType from './types/PrettyPropType';

const PropTypesMap = new Map();

Object.keys(PropTypes).forEach(typeName => {
const type = PropTypes[typeName];

PropTypesMap.set(type, typeName);
PropTypesMap.set(type.isRequired, typeName);
});

const isNotEmpty = obj => obj && obj.props && Object.keys(obj.props).length > 0;

const hasDocgen = type => isNotEmpty(type.__docgenInfo);

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,
required: 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;
const docgenInfo =
type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property];
const description = docgenInfo ? docgenInfo.description : null;
let propType = PropTypesMap.get(typeInfo) || 'other';

if (propType === 'other') {
if (docgenInfo && docgenInfo.type) {
propType = docgenInfo.type.name;
}
}

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

if (type.defaultProps) {
Object.keys(type.defaultProps).forEach(property => {
const value = type.defaultProps[property];

if (value === undefined) {
return;
}

if (!props[property]) {
props[property] = { property };
}

props[property].defaultValue = value;
});
}

return props;
};

export const multiLineText = input => {
if (!input) return input;
const text = String(input);
Expand All @@ -100,16 +25,19 @@ export const multiLineText = input => {
};

export default function PropTable(props) {
const { type, maxPropObjectKeys, maxPropArrayLength, maxPropStringLength } = props;
const {
type,
maxPropObjectKeys,
maxPropArrayLength,
maxPropStringLength,
propDefinitions,
} = props;

if (!type) {
return null;
}

const accumProps = hasDocgen(type) ? propsFromDocgen(type) : propsFromPropTypes(type);
const array = Object.values(accumProps);

if (!array.length) {
if (!propDefinitions.length) {
return <small>No propTypes defined!</small>;
}

Expand All @@ -131,7 +59,7 @@ export default function PropTable(props) {
</tr>
</thead>
<tbody>
{array.map(row => (
{propDefinitions.map(row => (
<tr key={row.property}>
<Td bordered code>
{row.property}
Expand All @@ -158,10 +86,20 @@ export default function PropTable(props) {
PropTable.displayName = 'PropTable';
PropTable.defaultProps = {
type: null,
propDefinitions: [],
};
PropTable.propTypes = {
type: PropTypes.func,
maxPropObjectKeys: PropTypes.number.isRequired,
maxPropArrayLength: PropTypes.number.isRequired,
maxPropStringLength: PropTypes.number.isRequired,
propDefinitions: PropTypes.arrayOf(
PropTypes.shape({
property: PropTypes.string.isRequired,
propType: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
required: PropTypes.bool.isRequired,
description: PropTypes.string,
defaultValue: PropTypes.any,
})
),
};
3 changes: 1 addition & 2 deletions addons/info/src/components/Story.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { baseFonts } from '@storybook/components';

import marksy from 'marksy';

import PropTable from './PropTable';
import Node from './Node';
import { Pre } from './markdown';

Expand Down Expand Up @@ -339,7 +338,7 @@ export default class Story extends React.Component {
// eslint-disable-next-line react/no-array-index-key
<div key={`${getName(type)}_${i}`}>
<h2 style={this.state.stylesheet.propTableHead}>"{getName(type)}" Component</h2>
<PropTable
<this.props.PropTable
type={type}
maxPropObjectKeys={maxPropObjectKeys}
maxPropArrayLength={maxPropArrayLength}
Expand Down
94 changes: 94 additions & 0 deletions addons/info/src/components/makeTableComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* eslint-disable no-underscore-dangle */

import PropTypes from 'prop-types';
import React from 'react';

const PropTypesMap = new Map();

Object.keys(PropTypes).forEach(typeName => {
const type = PropTypes[typeName];

PropTypesMap.set(type, typeName);
PropTypesMap.set(type.isRequired, typeName);
});

const isNotEmpty = obj => obj && obj.props && Object.keys(obj.props).length > 0;

const hasDocgen = type => isNotEmpty(type.__docgenInfo);

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,
required: 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;
const docgenInfo =
type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property];
const description = docgenInfo ? docgenInfo.description : null;
let propType = PropTypesMap.get(typeInfo) || 'other';

if (propType === 'other') {
if (docgenInfo && docgenInfo.type) {
propType = docgenInfo.type.name;
}
}

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

if (type.defaultProps) {
Object.keys(type.defaultProps).forEach(property => {
const value = type.defaultProps[property];

if (value === undefined) {
return;
}

if (!props[property]) {
props[property] = { property };
}

props[property].defaultValue = value;
});
}

return props;
};

export default function makeTableComponent(Component) {
return props => {
if (!props.type) { // eslint-disable-line
return null;
}

const propDefinitionsMap = hasDocgen(props.type)
? propsFromDocgen(props.type)
: propsFromPropTypes(props.type);
const propDefinitions = Object.values(propDefinitionsMap);

return <Component propDefinitions={propDefinitions} {...props} />;
};
}
4 changes: 4 additions & 0 deletions addons/info/src/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from 'react';
import deprecate from 'util-deprecate';
import Story from './components/Story';
import PropTable from './components/PropTable';
import makeTableComponent from './components/makeTableComponent';
import { H1, H2, H3, H4, H5, H6, Code, P, UL, A, LI } from './components/markdown';

const defaultOptions = {
inline: false,
header: true,
source: true,
propTables: [],
TableComponent: PropTable,
maxPropsIntoLine: 3,
maxPropObjectKeys: 3,
maxPropArrayLength: 3,
Expand Down Expand Up @@ -53,6 +56,7 @@ function addInfo(storyFn, context, infoOptions) {
showSource: Boolean(options.source),
propTables: options.propTables,
propTablesExclude: options.propTablesExclude,
PropTable: makeTableComponent(options.TableComponent),
styles: typeof options.styles === 'function' ? options.styles : s => s,
marksyConf,
maxPropObjectKeys: options.maxPropObjectKeys,
Expand Down
Loading

0 comments on commit 7b8f765

Please sign in to comment.