Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions docs/components/ComponentProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import React from 'react'
import styled from 'styled-components'
import Link from '../../src/Link'
import {H1, H2, H3, H4, H5, H6} from '@primer/gatsby-theme-doctocat/src/components/heading'
import BorderBox from '../../src/BorderBox'
import Button from '../../src/Button'
import Text from '../../src/Text'
import Details from '../../src/Details'
import InlineCode from '@primer/gatsby-theme-doctocat/src/components/inline-code'
import Paragraph from '@primer/gatsby-theme-doctocat/src/components/paragraph'
import Table from './Table'

function getHeadingElement(headingLevel) {
switch (headingLevel) {
case 1:
return H1
case 2:
return H2
case 3:
return H3
case 4:
return H4
case 5:
return H5
case 6:
return H6
}
}

const InheritedBox = styled(BorderBox)`
> :first-child {
margin-top: 0px !important;
}
`

function collect(inherited, acc = {system: [], inherited: []}, seen = new Set()) {
for (const Comp of inherited) {
if (Comp.propTypes && Comp.propTypes.__doc_spec) {
const {system, inherited: nestedInherited} = Comp.propTypes.__doc_spec
for (const sys of system) {
if (!seen.has(sys)) {
acc.system.push(sys)
seen.add(sys)
}
}
for (const inh of nestedInherited) {
if (!seen.has(inh)) {
acc.inherited.push(inh)
seen.add(inh)
}
}
if (nestedInherited.length) {
collect(nestedInherited, acc, seen)
}
}
}

return acc
}

function ComponentProps({Component, name, headingLevel, showInherited, showSystem}) {
if (!Component.propTypes || !Component.propTypes.__doc_spec) {
return null
}

const Heading = getHeadingElement(headingLevel)

const {own} = Component.propTypes.__doc_spec
const {system, inherited} = collect([Component])

const output = []

if (own) {
output.push(
<OwnProps key="own" name={name} props={own} defaults={Component.defaultProps || {}} headingLevel={headingLevel} />
)
}

const inheritedWithDocs = inherited.filter(Comp => Comp.propTypes && Comp.propTypes.__doc_spec)
if (inheritedWithDocs.length && showInherited) {
output.push()
output.push(
<React.Fragment key="inherited">
<Heading>Inherited props</Heading>
<Paragraph>
<InlineCode>{name}</InlineCode> inherits from the following components and thus receives their props:
</Paragraph>
</React.Fragment>
)
for (const Comp of inherited) {
output.push(
<Details mb={2} key={`inh-${Comp.displayName}`}>
{({open}) => (
<>
<Button as="summary">
{Comp.displayName} {open ? '▼' : '►'}
</Button>
<InheritedBox p={2} mt={2}>
<ComponentProps
Component={Comp}
name={Comp.displayName}
headingLevel={headingLevel + 1}
showInherited={false}
showSystem
/>
</InheritedBox>
</>
)}
</Details>
)
}
}

if (showSystem && system && system.length) {
output.push(<SystemProps key="system" name={name} systemProps={system} headingLevel={headingLevel} />)
}

return <>{output}</>
}

ComponentProps.defaultProps = {
headingLevel: 3,
showInherited: true,
showSystem: true
}

function getDefault(defaults, prop) {
const value = defaults[prop]
return getDisplayValue(value)
}

function getDisplayValue(value, key) {
const type = typeof value

if (type === 'object') {
return '(object)'
}

if (type === 'string') {
return <InlineCode key={key}>"{value}"</InlineCode>
}

if (type === 'number' || type === 'boolean') {
return <InlineCode key={key}>{String(value)}</InlineCode>
}

if (type === 'function') {
return <InlineCode key={key}>(function)</InlineCode>
}

return value
}

const PropValueList = styled.ul`
margin-block-start: 0;
margin-block-end: 0;
margin-left: -20px;
`

function getType(doc) {
switch (doc.name) {
case 'any':
return 'any'
case 'array':
return 'array'
case 'bool':
return 'boolean'
case 'func':
return 'function'
case 'number':
return 'number'
case 'node':
return 'node'
case 'object':
return 'object'
case 'string':
return 'string'
case 'symbol':
return 'symbol'
case 'element':
return 'element'
case 'elementType':
return 'element type'

case 'instanceOf':
return `instance of ${doc.args.name}`
case 'arrayOf':
return `array of ${getType(doc.args)}s`
case 'oneOf': {
const items = doc.args.map((item, idx) => getDisplayValue(item, idx))
return (
<>
<Text>One of:</Text>
<PropValueList>
{items.map((item, idx) => (
// eslint-disable-next-line react/no-array-index-key
<li key={idx}>{item}</li>
))}
</PropValueList>
</>
)
}
case 'oneOfType': {
const items = doc.args.map(item => getType(item.doc))
return (
<>
<Text>One of type:</Text>
<PropValueList>
{items.map((item, idx) => (
// eslint-disable-next-line react/no-array-index-key
<li key={idx}>{item}</li>
))}
</PropValueList>
</>
)
}
case 'objectOf': {
return `object with values of type ${getType(doc.args.doc)}`
}
default:
return '(unknown type)'
}
}

function SystemProps({name, systemProps, headingLevel}) {
const Heading = getHeadingElement(headingLevel)
return (
<>
<Heading>System props</Heading>
<Paragraph>
<InlineCode>{name}</InlineCode> components receive the following categories of system props. See our{' '}
<Link href="/system-props">System Props page</Link> for more information.
</Paragraph>
<Table
columns={['Category', 'Included props']}
rows={systemProps.map(s => [
<InlineCode key="system-prop-name">{s.systemPropsName}</InlineCode>,
<div key="included-props">{Object.keys(s.propTypes).join(', ')}</div>
])}
/>
</>
)
}

function OwnProps({props, defaults, headingLevel}) {
const Heading = getHeadingElement(headingLevel)
const propsToShow = Object.keys(props).filter(key => !props[key].doc.hidden)
if (propsToShow.length === 0) {
return (
<>
<Heading>Component props</Heading>
<Text>This component gets no additional component specific props.</Text>
</>
)
}

return (
<>
<Heading>Component props</Heading>
<Table
columns={['Name', 'Type', 'Default value', 'Description']}
rows={propsToShow.map(prop => [
`${prop}${props[prop].doc.isRequired ? '*' : ''}`,
getType(props[prop].doc),
getDefault(defaults, prop),
props[prop].doc.desc
])}
/>
</>
)
}

export default ComponentProps
32 changes: 32 additions & 0 deletions docs/components/Table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable react/no-array-index-key */
import React from 'react'
import DoctocatTable from '@primer/gatsby-theme-doctocat/src/components/table'

function Table({columns, rows, ...rest}) {
return (
<DoctocatTable {...rest}>
<thead>
<tr>
{columns.map((col, idx) => (
<th align="left" key={idx}>
{col}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, idx) => (
<tr key={idx}>
{row.map((val, iidx) => (
<td align="left" key={iidx}>
{val}
</td>
))}
</tr>
))}
</tbody>
</DoctocatTable>
)
}

export default Table
11 changes: 9 additions & 2 deletions docs/content/BorderBox.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ BorderBox is a Box component with a border. When no `borderColor` is present, th
<BorderBox>This is a BorderBox</BorderBox>
```

## System props
## Props

import {BorderBox} from "@primer/components"
import ComponentProps from "../components/ComponentProps"

<ComponentProps Component={BorderBox} name="BorderBox" />

<!-- ## System props

BorderBox components get `COMMON`, `LAYOUT` and `BORDER` system props. Read our [System Props](/system-props) doc page for a full list of available props.

Expand All @@ -22,4 +29,4 @@ BorderBox components get `COMMON`, `LAYOUT` and `BORDER` system props. Read our
| border | String | 'borders.1' (from theme) | Sets the border, use theme values or provide your own. |
| borderColor | String | 'gray.2' (from theme) | Sets the border color, use theme values or provide your own. |
| borderRadius | String or Number| 'radii.1' (from theme)| Sets the border radius, use theme values or provide your own. |
| boxShadow | String | | Sets box shadow, use theme values or provide your own. |
| boxShadow | String | | Sets box shadow, use theme values or provide your own. | -->
11 changes: 4 additions & 7 deletions docs/content/Box.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ The Box component serves as a wrapper component for most layout related needs. U
</Box>
```

## System props
## Props

Box components get the `COMMON` and `LAYOUT` categories of system props. Read our [System Props](/system-props) doc page for a full list of available props.
import {Box} from "@primer/components"
import ComponentProps from "../components/ComponentProps"

## Component props

| Prop name | Type | Default | Description |
| :- | :- | :-: | :- |
| as | String | `div` | sets the HTML tag for the component|
<ComponentProps Component={Box} name="Box" />
14 changes: 4 additions & 10 deletions docs/content/Link.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,9 @@ In special cases where you'd like a `<button>` styled like a `Link`, use `<Link
</Link>
```

## System props
## Props

Link components get `COMMON` and `TYPOGRAPHY` system props. Read our [System Props](/system-props) doc page for a full list of available props.
import {Link} from "@primer/components"
import ComponentProps from "../components/ComponentProps"

## Component props

| Name | Type | Default | Description |
| :-------- | :------ | :-----: | :------------------------------------------------ |
| href | String | | URL to be used for the Link |
| muted | Boolean | false | Uses light gray for Link color, and blue on hover |
| underline | Boolean | false | Adds underline to the Link |
| as | String | 'a' | Can be 'a', 'button', 'input', or 'summary' |
<ComponentProps Component={Link} name="Link" />
26 changes: 6 additions & 20 deletions docs/content/SideNav.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,27 +131,13 @@ If using React Router, you can use the `as` prop to render the element as a `Nav
<SideNav.Link as={NavLink} to="...">...</SideNav.Link>
```

## System props
## SideNav props

`SideNav` components get `COMMON`, `BORDER`, and `LAYOUT` system props. `SideNav.Link` components get `COMMON` and `TYPOGRAPHY` system props. Read our [System Props](/system-props) doc page for a full list of available props.
import {SideNav} from "@primer/components"
import ComponentProps from "../components/ComponentProps"

## Component props
<ComponentProps Component={SideNav} name="SideNav" />

### SideNav
## SideNav.Link props

| Name | Type | Default | Description |
| :- | :- | :-: | :- |
| as | String | 'nav' | Sets the HTML tag for the component. |
| bordered | Boolean | false | Renders the component with a border. |
| variant | String | 'normal' | Set to `lightweight` to render [in a lightweight style](#lightweight-variant). |

### SideNav.Link

| Name | Type | Default | Description |
| :- | :- | :-: | :- |
| as | String | 'a' | Sets the HTML tag for the component. |
| href | String | | URL to be used for the Link |
| muted | Boolean | false | Uses light gray for Link color, and blue on hover |
| selected | Boolean | false | Sets the link as selected, giving it a different style and setting the `aria-current` attribute. |
| underline | Boolean | false | Adds underline to the Link |
| variant | String | 'normal' | Set to `full` to render [a full variant](#full-variant), suitable for including icons and labels. |
<ComponentProps Component={SideNav.Link} name="SideNav.Link" />
Loading