UNPKG

terra-props-table

Version:

React component to render a table view for the props metadata of another react component.

187 lines (168 loc) 5.47 kB
import React from 'react'; import PropTypes from 'prop-types'; import { parse, resolver } from 'react-docgen'; import Markdown from 'terra-markdown'; import classNames from 'classnames/bind'; import styles from './PropsTable.module.scss'; const cx = classNames.bind(styles); const propTypes = { /** * Title of component */ componentName: PropTypes.string, /** * Markdown source file */ src: PropTypes.string.isRequired, /** * Type of react-docgen resolver to use for prop-types resolution. Supported values are `default` or `findAllComponentDefinitions` */ propsResolution: PropTypes.oneOf(['default', 'findAllComponentDefinitions']), }; const defaultProps = { propsResolution: 'default', }; function formatShape(shape) { return JSON.stringify(shape, null, 1); } function determineType(type) { let typeName = type.name; if (typeName === 'enum') { typeName = 'enum'; } else if (typeName === 'arrayOf') { if (type.value.name === 'shape') { typeName = ( <span> {' '} array of objects structured like: <pre className={cx('props-table-pre')}> {' '} {formatShape(type.value.value)} {' '} </pre> </span> ); } else { typeName = `array of ${type.value.name}s`; } } else if (typeName === 'union') { const options = type.value.map((option) => { const name = option.name === 'shape' ? (( <span key={option.value}> {' '} an object structured like: <pre className={cx('props-table-pre')}> {' '} {formatShape(option.value)} {' '} </pre> </span> )) : ( <span key={option.name}> {' '} {option.name} </span> ); return name; }); typeName = options.reduce((curr, next) => [curr, <span key={`${curr.value}-${next.value}`}> or </span>, next]); } else if (typeName === 'shape') { typeName = ( <span> {' '} an object structured like: <pre className={cx('props-table-pre')}> {' '} {formatShape(type.value)} {' '} </pre> </span> ); } return typeName; } /** * Renders a table view for the props metadata of a react component generated by react-docgen */ const PropsTable = ({ componentName, propsResolution, src, ...customProps }) => { /** * Runs component source code through react-docgen. Passing second argument to parse * function to account for multiple export. * @type {Object} */ let componentMetaData; /** * Alias for props object from componentMetaData * @type {Object} */ let componentProps; // Resolve using react-docgen's default resolver if (propsResolution === 'default') { componentMetaData = parse(src); componentProps = componentMetaData.props; } // Resolve using react-docgen's findAllComponentDefinitions resolver if (propsResolution === 'findAllComponentDefinitions') { componentMetaData = parse(src, resolver.findAllComponentDefinitions); componentProps = componentMetaData[0].props; } const tableRowClass = cx('prop-table-row'); const tableClassNames = cx([ 'props-table', customProps.className, ]); return ( <div dir="ltr" className={cx('props-table-markdown')}> <h2> {componentName} {' '} Props </h2> <table {...customProps} className={tableClassNames}> <thead> <tr> <th className={cx('prop-table-name')}>Prop Name</th> <th className={cx('prop-table-type')}>Type</th> <th className={cx('prop-table-required')}>Is Required</th> <th className={cx('prop-table-default')}>Default Value</th> <th className={cx('prop-table-description')}>Description</th> </tr> </thead> <tbody> {Object.keys(componentProps).map((key) => { const prop = componentProps[key]; if (prop.description && prop.description.match(/@private/)) { return null; } const type = determineType(prop.type); /** * Check if the react-docgen parser returned a custom proptype. * If so, parse the raw value to see if the custom proptype has been marked as required. */ const customRequired = (prop.type.name === 'custom' && prop.type.raw.includes('isRequired')); /* eslint-disable react/forbid-dom-props */ return ( <tr className={tableRowClass} key={key} style={{ fontSize: '90%' }}> <td style={{ fontWeight: 'bold' }}>{key}</td> <td>{(prop.type ? type : '')}</td> {(customRequired || prop.required ? <td style={{ color: '#d53700' }}>required</td> : <td style={{ color: '#444' }}>optional</td>)} {(prop.defaultValue ? <td style={{ fontWeight: 'bold' }}>{prop.defaultValue.value}</td> : <td style={{ color: '#444' }}>none</td>)} <td><Markdown src={prop.description} /></td> </tr> ); /* eslint-enable react/forbid-dom-props */ })} </tbody> </table> </div> ); }; PropsTable.propTypes = propTypes; PropsTable.defaultProps = defaultProps; export default PropsTable;