diff --git a/addons/info/src/components/PropTable.js b/addons/info/src/components/PropTable.js
index f353644784b8..301f7e341cbd 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 { Table, Td, Th } from '@storybook/components';
import PropVal from './PropVal';
+import PrettyPropType from './types/PrettyPropType';
const PropTypesMap = new Map();
@@ -13,41 +16,10 @@ Object.keys(PropTypes).forEach(typeName => {
PropTypesMap.set(type.isRequired, typeName);
});
-const stylesheet = {
- propTable: {
- marginLeft: -10,
- borderSpacing: '10px 5px',
- borderCollapse: 'separate',
- },
-};
-
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 hasDocgen = type => isNotEmpty(type.__docgenInfo);
-const boolToString = value => (value ? 'yes' : 'no');
-
const propsFromDocgen = type => {
const props = {};
const docgenInfoProps = type.__docgenInfo.props;
@@ -59,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,
};
@@ -75,21 +47,15 @@ const propsFromPropTypes = type => {
if (type.propTypes) {
Object.keys(type.propTypes).forEach(property => {
const typeInfo = type.propTypes[property];
- const required = boolToString(typeInfo.isRequired === undefined);
- const description =
- type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property]
- ? type.__docgenInfo.props[property].description
- : null;
+ 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 (
- type.__docgenInfo &&
- type.__docgenInfo.props &&
- type.__docgenInfo.props[property] &&
- type.__docgenInfo.props[property].type
- ) {
- propType = type.__docgenInfo.props[property].type.name;
+ if (docgenInfo && docgenInfo.type) {
+ propType = docgenInfo.type.name;
}
}
@@ -137,40 +103,40 @@ export default function PropTable(props) {
};
return (
-
+
- property |
- propType |
- required |
- default |
- description |
+ property |
+ propType |
+ required |
+ default |
+ description |
{array.map(row =>
-
+ |
{row.property}
- |
-
- {row.propType}
- |
-
- {row.required}
- |
-
+ |
+
+
+ |
+
+ {row.required ? 'yes' : '-'}
+ |
+
{row.defaultValue === undefined
? '-'
: }
- |
-
+ |
+
{row.description}
- |
+
)}
-
+
);
}
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/Enum.js b/addons/info/src/components/types/Enum.js
new file mode 100644
index 000000000000..de574d88598d
--- /dev/null
+++ b/addons/info/src/components/types/Enum.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import { TypeInfo } from './proptypes';
+
+const Enum = ({ propType }) =>
+
+ {propType.value.map(({ value }) => value).join(' | ')}
+ ;
+
+Enum.propTypes = {
+ propType: TypeInfo.isRequired,
+};
diff --git a/addons/info/src/components/types/InstanceOf.js b/addons/info/src/components/types/InstanceOf.js
new file mode 100644
index 000000000000..27cc8ca8e318
--- /dev/null
+++ b/addons/info/src/components/types/InstanceOf.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { TypeInfo } from './proptypes';
+
+const InstanceOf = ({ propType }) =>
+
+ {propType.value}
+ ;
+
+InstanceOf.propTypes = {
+ propType: TypeInfo.isRequired,
+};
+
+export default InstanceOf;
diff --git a/addons/info/src/components/types/Object.js b/addons/info/src/components/types/Object.js
new file mode 100644
index 000000000000..b9fa4cfd0662
--- /dev/null
+++ b/addons/info/src/components/types/Object.js
@@ -0,0 +1,8 @@
+import React from 'react';
+
+const ObjectType = () =>
+
+ {'{}'}
+ ;
+
+export default ObjectType;
diff --git a/addons/info/src/components/types/ObjectOf.js b/addons/info/src/components/types/ObjectOf.js
new file mode 100644
index 000000000000..c4d7d61a146e
--- /dev/null
+++ b/addons/info/src/components/types/ObjectOf.js
@@ -0,0 +1,17 @@
+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..b9fa4cfd0662
--- /dev/null
+++ b/addons/info/src/components/types/ObjectType.js
@@ -0,0 +1,8 @@
+import React from 'react';
+
+const ObjectType = () =>
+
+ {'{}'}
+ ;
+
+export default ObjectType;
diff --git a/addons/info/src/components/types/OneOf.js b/addons/info/src/components/types/OneOf.js
new file mode 100644
index 000000000000..65177f77e165
--- /dev/null
+++ b/addons/info/src/components/types/OneOf.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { TypeInfo } from './proptypes';
+
+const OneOf = ({ propType }) =>
+
+ {propType.value.map(({ value }) => value).join(' | ')}
+ ;
+
+OneOf.propTypes = {
+ propType: TypeInfo.isRequired,
+};
+
+export default OneOf;
diff --git a/addons/info/src/components/types/OneOfType.js b/addons/info/src/components/types/OneOfType.js
new file mode 100644
index 000000000000..901cd5621065
--- /dev/null
+++ b/addons/info/src/components/types/OneOfType.js
@@ -0,0 +1,25 @@
+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..fe50881408f7
--- /dev/null
+++ b/addons/info/src/components/types/PrettyPropType.js
@@ -0,0 +1,61 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import ObjectType from './ObjectType';
+import Shape from './Shape';
+import OneOfType from './OneOfType';
+import ArrayOf from './ArrayOf';
+import ObjectOf from './ObjectOf';
+import OneOf from './OneOf';
+import InstanceOf from './InstanceOf';
+import Signature from './Signature';
+
+import { TypeInfo } from './proptypes';
+
+// propType -> Component map - these are a bit more complex prop types to display
+const propTypeComponentMap = new Map([
+ ['shape', Shape],
+ ['union', OneOfType],
+ ['arrayOf', ArrayOf],
+ ['objectOf', ObjectOf],
+ // Might be overkill to have below proptypes as separate components *shrug*
+ ['object', ObjectType],
+ ['enum', OneOf],
+ ['instanceOf', InstanceOf],
+ ['signature', Signature],
+]);
+
+const PrettyPropType = props => {
+ const { propType, depth } = props;
+ if (!propType) {
+ return unknown;
+ }
+
+ const { name } = propType || {};
+
+ if (propTypeComponentMap.has(name)) {
+ const Component = propTypeComponentMap.get(name);
+ return ;
+ }
+
+ // Otherwise, propType does not have a dedicated component, display proptype name by default
+ return (
+
+ {name}
+
+ );
+};
+
+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..8eca3d423256
--- /dev/null
+++ b/addons/info/src/components/types/PropertyLabel.js
@@ -0,0 +1,31 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+
+const styles = {
+ hasProperty: {
+ whiteSpace: 'nowrap',
+ },
+};
+
+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/Shape.js b/addons/info/src/components/types/Shape.js
new file mode 100644
index 000000000000..7a2f114ef178
--- /dev/null
+++ b/addons/info/src/components/types/Shape.js
@@ -0,0 +1,81 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import { HighlightButton } from '@storybook/components';
+import PrettyPropType from './PrettyPropType';
+import PropertyLabel from './PropertyLabel';
+
+import { TypeInfo } from './proptypes';
+
+const MARGIN_SIZE = 15;
+
+class Shape extends React.Component {
+ 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 =>
+
+ )}
+
+
+ {'}'}
+
+
+ );
+ }
+}
+
+Shape.propTypes = {
+ propType: TypeInfo,
+ depth: PropTypes.number.isRequired,
+};
+
+Shape.defaultProps = {
+ propType: null,
+};
+
+export default Shape;
diff --git a/addons/info/src/components/types/Signature.js b/addons/info/src/components/types/Signature.js
new file mode 100644
index 000000000000..5f85770b45e4
--- /dev/null
+++ b/addons/info/src/components/types/Signature.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { TypeInfo } from './proptypes';
+
+const Signature = ({ propType }) =>
+
+ {propType.raw}
+ ;
+
+Signature.propTypes = {
+ propType: TypeInfo.isRequired,
+};
+
+export default Signature;
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`
*/
diff --git a/examples/cra-kitchen-sink/src/stories/Container.js b/examples/cra-kitchen-sink/src/stories/Container.js
index aebe66d28126..efd894da18a1 100644
--- a/examples/cra-kitchen-sink/src/stories/Container.js
+++ b/examples/cra-kitchen-sink/src/stories/Container.js
@@ -1,26 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
-const Container = ({ children, title, age, isAmazing }) =>
+const Container = ({ children, title, age, isAmazing }) => (
{children}
{isAmazing ? '!!!' : ''}
- {age
- ?
- age = {age}
-
- : null}
-
;
+ {age.isOld ? age = {age.value}
: null}
+
+);
Container.propTypes = {
+ /** The nodes to be rendered in the button */
children: PropTypes.node.isRequired,
+ /** Show exclamation marks */
isAmazing: PropTypes.bool,
- age: PropTypes.number,
+ /** Show age prop */
+ age: PropTypes.shape({
+ isOld: PropTypes.bool,
+ value: PropTypes.number,
+ }),
+ /** Main title */
title: PropTypes.string,
};
Container.defaultProps = {
isAmazing: false,
- age: 0,
+ age: { isOld: false, value: 0 },
title: 'the best container ever',
};
diff --git a/lib/components/src/highlight_button.js b/lib/components/src/highlight_button.js
new file mode 100644
index 000000000000..609bdc5aa419
--- /dev/null
+++ b/lib/components/src/highlight_button.js
@@ -0,0 +1,27 @@
+import glamorous from 'glamorous';
+
+export default glamorous.button(
+ {
+ border: '1px solid rgba(0, 0, 0, 0)',
+ font: 'inherit',
+ background: 'none',
+ 'box-shadow': 'none',
+ padding: 0,
+ ':hover': {
+ backgroundColor: 'rgba(0, 0, 0, 0.05)',
+ border: '1px solid #ccc',
+ },
+ },
+ props => {
+ const styles = [];
+
+ if (props.highlight) {
+ styles.push({
+ backgroundColor: 'rgba(0, 0, 0, 0.05)',
+ border: '1px solid #ccc',
+ });
+ }
+
+ return styles;
+ }
+);
diff --git a/lib/components/src/index.js b/lib/components/src/index.js
index 446888e9dc92..7e73841424ad 100644
--- a/lib/components/src/index.js
+++ b/lib/components/src/index.js
@@ -2,3 +2,6 @@ export { baseFonts } from './theme';
export { default as RoutedLink } from './navigation/routed_link';
export { default as MenuLink } from './navigation/menu_link';
+export { default as HighlightButton } from './highlight_button';
+export { default as Table } from './table/table';
+export { td as Td, th as Th } from './table/cell';
diff --git a/lib/components/src/table/cell.js b/lib/components/src/table/cell.js
new file mode 100644
index 000000000000..fa23e60876c6
--- /dev/null
+++ b/lib/components/src/table/cell.js
@@ -0,0 +1,27 @@
+import glamorous from 'glamorous';
+
+const dynamicStyles = props => {
+ const styles = [];
+
+ if (props.bordered) {
+ styles.push({
+ border: '1px solid #ccc',
+ });
+ }
+
+ if (props.code) {
+ styles.push({
+ whiteSpace: 'nowrap',
+ fontFamily: 'Monaco, Consolas, "Courier New", monospace',
+ });
+ }
+
+ return styles;
+};
+
+const styles = {
+ padding: '2px 6px',
+};
+
+export const td = glamorous.td(styles, dynamicStyles);
+export const th = glamorous.th(styles, dynamicStyles);
diff --git a/lib/components/src/table/table.js b/lib/components/src/table/table.js
new file mode 100644
index 000000000000..2871c6ca9154
--- /dev/null
+++ b/lib/components/src/table/table.js
@@ -0,0 +1,5 @@
+import glamorous from 'glamorous';
+
+export default glamorous.table({
+ borderCollapse: 'collapse',
+});