swingset
Version:
drop-in component library and documentation pages for next.js
113 lines (108 loc) • 3.47 kB
JSX
import s from './style.module.css'
import marked from 'marked'
import { Fragment } from 'react'
export default function PropsTable({ props }) {
return (
<table className={s.root}>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>{renderRows(props)}</tbody>
</table>
)
}
function renderRows(props, prefixes = []) {
const res = []
if (Array.isArray(props)) {
props.map((prop) => {
// render the row for the current property
res.push(renderRow('[x]', prop, prefixes, true))
// render rows for sub-properties if they exist
if (prop.properties)
res.push(renderRows(prop.properties, [...prefixes, '[x]']))
})
} else {
for (let key in props) {
const value = props[key]
// figure out whether the property of the current item is an array, and whether it has multiple valid types
const arrayPropertyOptions =
value.properties && value.properties.length
? value.properties.length > 1
? value.properties.length
: 'single'
: null
// render the row given the information
res.push(renderRow(key, value, prefixes, null, arrayPropertyOptions))
// render rows for sub-properties if relevant
if (value.properties)
res.push(renderRows(value.properties, [...prefixes, key]))
}
}
return res
}
function renderRow(key, value, prefixes, isArray, arrayOptions) {
// this bunch of business is to ensure that object syntax chain are separated by periods,
// but array syntax are not. like `foo.bar.baz` vs `foo[x].bar`
// if the current item is an array syntax, we slice off the trailing period below
const prefixSet = prefixes.reduce(
(m, p, i) => `${m}${prefixes[i + 1]?.charAt(0) === '[' ? p : p + '.'}`,
''
)
return (
<tr key={key}>
<td>
<code>
{prefixes.length ? (
<span className={s.prefix}>
{isArray ? prefixSet.slice(0, -1) : prefixSet}
</span>
) : (
''
)}
{key}
{value.required ? <span className={s.required}>*</span> : ''}
</code>
<div className={s.type}>{value.type}</div>
</td>
<td className={s.descriptionCol}>
<span
dangerouslySetInnerHTML={{
__html: marked.parseInline(value.description || ''),
}}
/>
{value.options && (
<div className={s.options}>
<strong>Options: </strong>
{value.options.map((opt, idx) => (
<Fragment key={opt}>
<code>{JSON.stringify(opt)}</code>
{idx < value.options.length - 1 && `, `}
</Fragment>
))}
</div>
)}
{value.itemType && (
<div className={s.options}>
<strong>Item Type: </strong>
<code>{value.itemType}</code>
</div>
)}
{value.properties && (
<div className={s.containsNested}>
{renderHelperText(arrayOptions)}
</div>
)}
</td>
</tr>
)
}
function renderHelperText(arrayOptions) {
if (typeof arrayOptions === 'number')
return `Array can contain any of the ${arrayOptions} types below:`
if (arrayOptions === 'single')
return 'Array members must be of the type below:'
return 'Object contains nested props, see below:'
}