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
JSX
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;