From 7ce56739361176d8327837ded564ca071f34d8ac Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 23 Aug 2017 15:32:03 -0700 Subject: [PATCH] addon info: make propTypes table better * more details for arrayOf, objectOf, shape, oneOf, oneOfType * refactored into cleaner code + many smaller components --- addons/info/example/Button.js | 15 ++ addons/info/src/components/PropTable.js | 148 +++--------------- addons/info/src/components/styles.js | 17 ++ addons/info/src/components/types/ArrayOf.js | 19 +++ .../src/components/types/HighlightButton.js | 56 +++++++ addons/info/src/components/types/ObjectOf.js | 28 ++++ .../info/src/components/types/ObjectType.js | 79 ++++++++++ addons/info/src/components/types/OneOfType.js | 22 +++ .../src/components/types/PrettyPropType.js | 73 +++++++++ .../src/components/types/PropertyLabel.js | 26 +++ addons/info/src/components/types/proptypes.js | 6 + .../src/components/DocgenButton.js | 40 +++++ 12 files changed, 401 insertions(+), 128 deletions(-) create mode 100644 addons/info/src/components/styles.js create mode 100644 addons/info/src/components/types/ArrayOf.js create mode 100644 addons/info/src/components/types/HighlightButton.js create mode 100644 addons/info/src/components/types/ObjectOf.js create mode 100644 addons/info/src/components/types/ObjectType.js create mode 100644 addons/info/src/components/types/OneOfType.js create mode 100644 addons/info/src/components/types/PrettyPropType.js create mode 100644 addons/info/src/components/types/PropertyLabel.js create mode 100644 addons/info/src/components/types/proptypes.js diff --git a/addons/info/example/Button.js b/addons/info/example/Button.js index df7c34c01288..69e07321300f 100644 --- a/addons/info/example/Button.js +++ b/addons/info/example/Button.js @@ -14,6 +14,21 @@ Object.assign(Button, { style: PropTypes.object, disabled: PropTypes.bool, onClick: PropTypes.func, + array: PropTypes.array, + arrayOf: PropTypes.arrayOf(PropTypes.string), + oneOf: PropTypes.oneOf(['foo', 'bar']), + shape: PropTypes.shape({ + foo: PropTypes.string, + bar: PropTypes.number, + }), + nestedArrayOf: PropTypes.arrayOf(PropTypes.shape({ + foo: PropTypes.shape({ + baz: PropTypes.string, + bar: PropTypes.arrayOf({ + PropTypes.string + }), + }), + })), }, }); diff --git a/addons/info/src/components/PropTable.js b/addons/info/src/components/PropTable.js index 173bce5b82ef..86bb8659b96f 100644 --- a/addons/info/src/components/PropTable.js +++ b/addons/info/src/components/PropTable.js @@ -2,7 +2,10 @@ import PropTypes from 'prop-types'; import React from 'react'; + +import styles from './styles'; import PropVal from './PropVal'; +import PrettyPropType from './types/PrettyPropType'; const PropTypesMap = new Map(); @@ -13,117 +16,10 @@ Object.keys(PropTypes).forEach(typeName => { PropTypesMap.set(type.isRequired, typeName); }); -const stylesheet = { - hasProperty: { - marginLeft: 10, - }, - code: { - fontFamily: 'Monaco, Consolas, "Courier New", monospace', - }, - block: { - display: 'block', - }, - propTable: { - marginTop: 10, - borderCollapse: 'collapse', - }, - propTableCell: { - border: '1px solid #ccc', - padding: '2px 6px', - }, -}; - const isNotEmpty = obj => obj && obj.props && Object.keys(obj.props).length > 0; -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 formatType = ({ propType, type, property, required }) => { - let result; - - if (type) { - const PropertyLabel = - property && - - {property}:{' '} - ; - - if (propType === 'other') { - return (result = type.name); - } else if (type && propType === 'arrayOf') { - return ( - - {PropertyLabel} - [ - - {formatType({ - parentType: propType, - type: type.value, - propType: type.value.name, - })} - - ] - - ); - } else if (propType === 'enum') { - return ( -
- {type.value.map(({ value }) => value).join(' | ')} -
- ); - } else if (propType === 'shape') { - const values = Object.keys(type.value).map(property => - formatType({ - property, - parentType: propType, - type: type.value[property], - propType: type.value[property].name, - required: type.value[property].required, - }) - ); - - return ( - - {PropertyLabel} - - {'{'} - - {values.map(value => - - {value} - - )} - - {'}'} - - - ); - } - } -}; - const hasDocgen = type => isNotEmpty(type.__docgenInfo); -const boolToString = value => (value ? 'yes' : 'no'); - const propsFromDocgen = type => { const props = {}; const docgenInfoProps = type.__docgenInfo.props; @@ -135,8 +31,8 @@ const propsFromDocgen = type => { props[property] = { property, - propType: renderDocgenPropType(propType), - required: boolToString(docgenInfoProp.required), + propType, + required: docgenInfoProp.required, description: docgenInfoProp.description, defaultValue: defaultValueDesc.value, }; @@ -151,7 +47,7 @@ const propsFromPropTypes = type => { if (type.propTypes) { Object.keys(type.propTypes).forEach(property => { const typeInfo = type.propTypes[property]; - const required = typeInfo.isRequired === undefined ? 'yes' : 'no'; + const required = typeInfo.isRequired === undefined; const docgenInfo = type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property]; const description = docgenInfo ? docgenInfo.description : null; @@ -159,13 +55,9 @@ const propsFromPropTypes = type => { if (propType === 'other') { if (docgenInfo && docgenInfo.type) { - propType = type.__docgenInfo.props[property].type.name; + propType = docgenInfo.type.name; } } - // const propType = formatType({ - // propType: docgenInfo && docgenInfo.type && docgenInfo.type.name, - // type: (docgenInfo && docgenInfo.type) || {} - // }); props[property] = { property, propType, required, description }; }); @@ -211,34 +103,34 @@ export default function PropTable(props) { }; return ( - +
- - - - - + + + + + {array.map(row => - - - - - diff --git a/addons/info/src/components/styles.js b/addons/info/src/components/styles.js new file mode 100644 index 000000000000..a4fcfedf6381 --- /dev/null +++ b/addons/info/src/components/styles.js @@ -0,0 +1,17 @@ +export default { + hasProperty: { + whiteSpace: 'nowrap', + }, + code: { + whiteSpace: 'nowrap', + fontFamily: 'Monaco, Consolas, "Courier New", monospace', + }, + propTable: { + marginTop: 10, + borderCollapse: 'collapse', + }, + propTableCell: { + border: '1px solid #ccc', + padding: '2px 6px', + }, +}; diff --git a/addons/info/src/components/types/ArrayOf.js b/addons/info/src/components/types/ArrayOf.js new file mode 100644 index 000000000000..5ec615798de1 --- /dev/null +++ b/addons/info/src/components/types/ArrayOf.js @@ -0,0 +1,19 @@ +import React from 'react'; + +import PrettyPropType from './PrettyPropType'; +import { TypeInfo } from './proptypes'; + +const ArrayOf = ({ propType }) => + + [ + + + + ] + ; + +ArrayOf.propTypes = { + propType: TypeInfo.isRequired, +}; + +export default ArrayOf; diff --git a/addons/info/src/components/types/HighlightButton.js b/addons/info/src/components/types/HighlightButton.js new file mode 100644 index 000000000000..a9325716db09 --- /dev/null +++ b/addons/info/src/components/types/HighlightButton.js @@ -0,0 +1,56 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +export default class HighlightButton extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + highlight: PropTypes.bool, + }; + + static defaultProps = { + highlight: false, + }; + + constructor(props) { + super(props); + this.state = { + hover: false, + }; + } + + handleMouseEnter = () => { + this.setState({ hover: true }); + }; + + handleMouseLeave = () => { + this.setState({ hover: false }); + }; + + render() { + const { children, highlight, ...otherProps } = this.props; + const style = + highlight || this.state.hover + ? { + backgroundColor: 'rgba(0, 0, 0, 0.05)', + border: '1px solid #ccc', + } + : {}; + return ( + + {children} + + ); + } +} diff --git a/addons/info/src/components/types/ObjectOf.js b/addons/info/src/components/types/ObjectOf.js new file mode 100644 index 000000000000..d709e45a71ed --- /dev/null +++ b/addons/info/src/components/types/ObjectOf.js @@ -0,0 +1,28 @@ +import React from 'react'; + +import PrettyPropType from './PrettyPropType'; +import { TypeInfo } from './proptypes'; + +const ObjectOf = ({ propType }) => + + + {'{'} + + + + {'[]:'} + + + + + + + {'}'} + + ; + +ObjectOf.propTypes = { + propType: TypeInfo.isRequired, +}; + +export default ObjectOf; diff --git a/addons/info/src/components/types/ObjectType.js b/addons/info/src/components/types/ObjectType.js new file mode 100644 index 000000000000..618bb8983a26 --- /dev/null +++ b/addons/info/src/components/types/ObjectType.js @@ -0,0 +1,79 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +import PrettyPropType from './PrettyPropType'; +import PropertyLabel from './PropertyLabel'; +import HighlightButton from './HighlightButton'; + +import { TypeInfo } from './proptypes'; + +const MARGIN_SIZE = 15; + +export default class ObjectType extends React.Component { + static propTypes = { + propType: TypeInfo, + depth: PropTypes.number.isRequired, + }; + + static defaultProps = { + propType: null, + }; + + constructor(props) { + super(props); + this.state = { + minimized: false, + }; + } + + handleToggle = () => { + this.setState({ + minimized: !this.state.minimized, + }); + }; + + handleMouseEnter = () => { + this.setState({ hover: true }); + }; + + handleMouseLeave = () => { + this.setState({ hover: false }); + }; + + render() { + const { propType, depth } = this.props; + return ( + + + {'{'} + + ... + {!this.state.minimized && + Object.keys(propType.value).map(childProperty => +
+ + + , +
+ )} + + + {'}'} + +
+ ); + } +} diff --git a/addons/info/src/components/types/OneOfType.js b/addons/info/src/components/types/OneOfType.js new file mode 100644 index 000000000000..62cd00a675bd --- /dev/null +++ b/addons/info/src/components/types/OneOfType.js @@ -0,0 +1,22 @@ +import React from 'react'; + +import PrettyPropType from './PrettyPropType'; +import { TypeInfo } from './proptypes'; + +const OneOfType = ({ propType }) => { + const length = propType.value.length; + return ( + + {propType.value + .map((value, i) => [ + , + i < length - 1 ? | : null, + ]) + .reduce((acc, tuple) => acc.concat(tuple), [])} + + ); +}; +OneOfType.propTypes = { + propType: TypeInfo.isRequired, +}; +export default OneOfType; diff --git a/addons/info/src/components/types/PrettyPropType.js b/addons/info/src/components/types/PrettyPropType.js new file mode 100644 index 000000000000..70929261cb27 --- /dev/null +++ b/addons/info/src/components/types/PrettyPropType.js @@ -0,0 +1,73 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +import ObjectType from './ObjectType'; +import OneOfType from './OneOfType'; +import ArrayOf from './ArrayOf'; +import ObjectOf from './ObjectOf'; + +import { TypeInfo } from './proptypes'; + +const PrettyPropType = ({ propType, depth }) => { + if (!propType) { + return unknown; + } + + const { name } = propType || {}; + + if (name === 'shape') { + return ; + } + + if (name === 'union') { + return ; + } + + if (name === 'arrayOf') { + return ; + } + + if (name === 'objectOf') { + return ; + } + + // Rest are just simple strings + let display; + + switch (name) { + case 'object': + display = '{}'; + break; + case 'enum': + display = propType.value.map(({ value }) => value).join(' | '); + break; + case 'instanceOf': + display = propType.value; + break; + case 'signature': + display = propType.raw; + break; + default: + display = name; + } + + return ( + + {display} + + ); +}; + +PrettyPropType.displayName = 'PrettyPropType'; + +PrettyPropType.defaultProps = { + propType: null, + depth: 1, +}; + +PrettyPropType.propTypes = { + propType: TypeInfo, + depth: PropTypes.number.isRequired, +}; + +export default PrettyPropType; diff --git a/addons/info/src/components/types/PropertyLabel.js b/addons/info/src/components/types/PropertyLabel.js new file mode 100644 index 000000000000..1b2616b26ab8 --- /dev/null +++ b/addons/info/src/components/types/PropertyLabel.js @@ -0,0 +1,26 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import styles from '../styles'; + +const PropertyLabel = ({ property, required }) => { + if (!property) return null; + + return ( + + {property} + {required ? '' : '?'}:{' '} + + ); +}; + +PropertyLabel.propTypes = { + property: PropTypes.string, + required: PropTypes.bool, +}; + +PropertyLabel.defaultProps = { + property: '', + required: false, +}; + +export default PropertyLabel; diff --git a/addons/info/src/components/types/proptypes.js b/addons/info/src/components/types/proptypes.js new file mode 100644 index 000000000000..916bc3404acb --- /dev/null +++ b/addons/info/src/components/types/proptypes.js @@ -0,0 +1,6 @@ +import PropTypes from 'prop-types'; + +export const TypeInfo = PropTypes.shape({ + name: PropTypes.string, + value: PropTypes.any, +}); diff --git a/examples/cra-kitchen-sink/src/components/DocgenButton.js b/examples/cra-kitchen-sink/src/components/DocgenButton.js index d6835f1ff470..20e997d58eb1 100644 --- a/examples/cra-kitchen-sink/src/components/DocgenButton.js +++ b/examples/cra-kitchen-sink/src/components/DocgenButton.js @@ -54,6 +54,46 @@ DocgenButton.propTypes = { ), }) ), + + /** + * Plain object propType (use shape!!) + */ + obj: PropTypes.object, // eslint-disable-line react/forbid-prop-types + + /** + * propType for shape with nested arraytOf + */ + shape: 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, + }) + ), + + shape: PropTypes.shape({ + shape: PropTypes.shape({ + foo: PropTypes.string, + }), + }), + }), + + arrayOf: PropTypes.arrayOf(PropTypes.number), + /** * `instanceOf` is also supported and the custom type will be shown instead of `instanceOf` */
propertypropTyperequireddefaultdescriptionpropertypropTyperequireddefaultdescription
+ {row.property} - {row.propType} + + - {row.required} + + {row.required ? 'yes' : '-'} + {row.defaultValue === undefined ? '-' : } + {row.description}