feat: add component props parse feature

This commit is contained in:
Pedro Nauck
2018-05-11 19:23:27 -03:00
parent 7bb53a0cf6
commit 987627de8f
19 changed files with 330 additions and 59 deletions

View File

@@ -2,6 +2,8 @@ import React, { Fragment } from 'react'
import styled from 'react-emotion'
import t from 'prop-types'
import Button from './Button'
const kinds = {
info: '#5352ED',
positive: '#2ED573',
@@ -9,7 +11,7 @@ const kinds = {
warning: '#FFA502',
}
export const Alert = styled('div')`
const Alert = styled('div')`
padding: 15px 20px;
background: white;
border-radius: 3px;
@@ -20,3 +22,5 @@ export const Alert = styled('div')`
Alert.propTypes = {
color: t.oneOf(['info', 'positive', 'negative', 'warning']),
}
export default Alert

View File

@@ -1,5 +1,5 @@
import { doc, Playground } from 'docz'
import { Alert } from './Alert'
import Alert from './Alert'
export const meta = doc('Alert')
.category('Components')

View File

@@ -1,3 +1,14 @@
import React from 'react'
import t from 'prop-types'
export const Button = ({ children }) => <button>{children}</button>
const Button = ({ children }) => <button>{children}</button>
Button.propTypes = {
/**
Button element children
*/
children: t.any,
color: t.string,
}
export default Button

View File

@@ -1,11 +1,22 @@
import { doc, Playground } from 'docz'
import { Button } from './Button'
import { doc, Playground, PropsTable } from 'docz'
import Button from './Button'
export const meta = doc('Button')
.category('Components')
# Button
Buttons make common actions more obvious and help users more easily perform them. Buttons use labels and sometimes icons to communicate the action that will occur when the user touches them.
### Best practices
- Group buttons logically into sets based on usage and importance.
- Ensure that button actions are clear and consistent.
- The main action of a group set can be a primary button.
- Select a single button variation and do not mix them.
<PropsTable of={Button} />
## Basic usage
<Playground>

View File

@@ -28,6 +28,7 @@
"@types/shelljs": "^0.7.9",
"art-template": "^4.12.2",
"babel-loader": "^8.0.0-beta.1",
"babel-plugin-react-docgen": "^1.9.0",
"babel-polyfill": "^7.0.0-beta.3",
"babel-preset-react-app": "^4.0.0-next.b2fd8db8",
"chokidar": "^2.0.3",

View File

@@ -100,7 +100,7 @@ export const createConfig = (args: ConfigObj) => (): Configuration => {
cacheDirectory: true,
highlightCode: true,
presets: [require.resolve('babel-preset-react-app')],
plugins: [require.resolve('react-hot-loader/babel')],
plugins: [],
})
config.module
@@ -116,7 +116,13 @@ export const createConfig = (args: ConfigObj) => (): Configuration => {
.end()
.use('babel-loader')
.loader(require.resolve('babel-loader'))
.options(babelrc)
.options({
...babelrc,
plugins: babelrc.plugins.concat([
require.resolve('react-hot-loader/babel'),
require.resolve('babel-plugin-react-docgen'),
]),
})
config.module
.rule('mdx')

View File

@@ -4,14 +4,19 @@ import nodeToString from 'hast-util-to-string'
import { format } from '../utils/format'
const hasOpenTag = (node: any) => /^\<Playground/.test(node.value)
const componentName = (value: any) => {
const match = value.match(/^\<\\?(\w+)/)
return match && match[1]
}
export const plugin = () => (tree: any, file: any) => {
visit(tree, 'jsx', visitor)
const isPlayground = (name: string) => name === 'Playground'
const isPropsTable = (name: string) => name === 'PropsTable'
function visitor(node: any, idx: any, parent: any): void {
if (!hasOpenTag(node)) return
const addCodeProp = (node: any) => {
const name = componentName(node.value)
if (isPlayground(name)) {
const tagOpen = new RegExp(`^\\<${name}`)
const code = format(nodeToString(node)).slice(1, Infinity)
const html = prism.highlight(code, prism.languages.jsx)
@@ -21,8 +26,27 @@ export const plugin = () => (tree: any, file: any) => {
</pre>
)`
node.value = node.value
.replace(/^\<Playground/, `<Playground __code={${codeComponent}}`)
.replace(/^\<Playground/, '<Playground components={components}')
node.value = node.value.replace(
tagOpen,
`<${name} __code={${codeComponent}}`
)
}
}
const addComponentsProp = (node: any) => {
const name = componentName(node.value)
if (isPlayground(name) || isPropsTable(name)) {
const tagOpen = new RegExp(`^\\<${name}`)
node.value = node.value.replace(tagOpen, `<${name} components={components}`)
}
}
export const plugin = () => (tree: any, file: any) => {
visit(tree, 'jsx', visitor)
function visitor(node: any, idx: any, parent: any): void {
addComponentsProp(node)
addCodeProp(node)
}
}

View File

@@ -10,7 +10,7 @@ const componentName = (value: any) => {
}
// iterate in a reverse way to merge values then delete the unused node
const valuesFromNodes = (tree: any) => (first: any, last: any) => {
const valuesFromNodes = (tree: any) => (first: number, last: number) => {
const values = []
if (first !== last) {
@@ -57,11 +57,13 @@ const mergeNodeWithoutCloseTag = (tree: any, node: any, idx: any) => {
return hasJustCloseTag(value)
})
// merge all values from node open tag until node with the close tag
const mergeUntilCloseTag = valuesFromNodes(tree)
const values = mergeUntilCloseTag(idx, tagCloseIdx)
if (tagCloseIdx > -1 && tagCloseIdx !== idx) {
// merge all values from node open tag until node with the close tag
const mergeUntilCloseTag = valuesFromNodes(tree)
const values = mergeUntilCloseTag(idx, tagCloseIdx)
node.value = values.reverse().join('\n')
node.value = values.reverse().join('\n')
}
}
// turns `html` nodes into `jsx` nodes

View File

@@ -5,6 +5,7 @@ import styled from 'react-emotion'
import * as colors from '../styles/colors'
import { Render } from './Render'
import { Table } from './Table'
const Container = styled('div')`
width: ${rem(960)};
@@ -17,7 +18,7 @@ const Title = styled('h1')`
position: relative;
font-size: ${rem(48)};
font-weight: 200;
margin: ${rem(20)} 0 ${rem(30)};
margin: ${rem(20)} 0 ${rem(40)};
&:before {
position: absolute;
@@ -26,7 +27,7 @@ const Title = styled('h1')`
left: 0;
width: 10%;
height: 3px;
background: ${colors.PURPLE};
background: ${colors.purple};
}
`
@@ -36,8 +37,21 @@ const Subtitle = styled('h2')`
font-weight: 200;
`
const H3 = styled('h3')`
margin: ${rem(30)} 0 ${rem(20)};
font-weight: 600;
`
export const Doc: SFC<DocObj> = ({ id, component: Component }) => (
<Container key={id}>
<Component components={{ h1: Title, h2: Subtitle, Render }} />
<Component
components={{
h1: Title,
h2: Subtitle,
h3: H3,
table: Table,
Render,
}}
/>
</Container>
)

View File

@@ -18,11 +18,11 @@ const LinkStyled = styled(BaseLink)`
&,
&:visited {
color: ${colors.GRAY_DARK};
color: ${colors.silver};
}
&.active {
background: ${colors.GRAY};
background: ${colors.darkSnow};
}
&:before {
@@ -32,7 +32,7 @@ const LinkStyled = styled(BaseLink)`
left: 0;
width: 4px;
height: 100%;
background: ${colors.PURPLE};
background: ${colors.purple};
transform: scaleX(0);
transform-origin: 0 50%;
transition: transform 0.3s;

View File

@@ -10,8 +10,8 @@ const Sidebar = styled('div')`
padding: 15px 0;
width: 200px;
height: 100vh;
border-right: 1px solid ${colors.BORDER};
background: ${colors.GRAY_LIGHT};
border-right: 1px solid ${colors.border};
background: ${colors.snow};
`
const List = styled('ul')`
@@ -30,7 +30,7 @@ const Category = styled('li')`
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
color: ${colors.GRAY_MEDIUM};
color: ${colors.steel};
`
interface LinksProps {

View File

@@ -9,12 +9,12 @@ const ComponentWrapper = styled('div')`
position: relative;
padding: 2rem;
background: white;
border: 1px solid ${colors.GRAY};
border: 1px solid ${colors.border};
border-radius: 3px 3px 0 0;
`
const CodeWrapper = styled('div')`
border: 1px solid ${colors.GRAY};
border: 1px solid ${colors.border};
border-top: 0;
border-radius: 0 0 3px 3px;

View File

@@ -0,0 +1,39 @@
import styled from 'react-emotion'
import { rem } from 'polished'
import * as colors from '../styles/colors'
export const Table = styled('table')`
width: 100%;
padding: 0;
margin-bottom: ${rem(50)};
table-layout: fixed;
box-shadow: 0 0 0 1px ${colors.border};
background-color: transparent;
border-radius: 3px;
border-spacing: 0;
border-collapse: collapse;
border-style: hidden;
font-size: ${rem(14)};
& thead {
background: ${colors.darkSnow};
}
& thead th {
text-align: left;
font-weight: 400;
padding: ${rem(20)} ${rem(20)};
}
& tbody td {
padding: ${rem(12)} ${rem(20)};
line-height: 2;
font-weight: 200;
}
& tbody > tr {
display: table-row;
border-top: 1px solid ${colors.border};
}
`

View File

@@ -1,9 +1,19 @@
export const BORDER = '#ced6e0'
export const PURPLE = '#6554C0'
export const BLUE = '#0052CC'
export const OCEAN_BLUE = '#00B8D9'
export const ORANGE = '#FF5630'
export const GRAY = '#EAECEF'
export const GRAY_LIGHT = '#F4F5F7'
export const GRAY_MEDIUM = '#C1C7D0'
export const GRAY_DARK = '#172B4D'
export const snow = '#F9FAFC'
export const darkSnow = '#EFF2F7'
export const extraDarkSnow = '#E5E9F2'
export const smoke = '#E0E6ED'
export const darkSmoke = '#D3DCE6'
export const extraDarkSmoke = '#C0CCDA'
export const silver = '#8492A6'
export const slate = '#3C4858'
export const steel = '#273444'
export const black = '#1F2D3D'
export const purple = '#6554C0'
export const blue = '#1FB6FF'
export const textColor = slate
export const linkColor = purple
export const background = 'white'
export const border = darkSmoke

View File

@@ -1,18 +1,16 @@
import { css, injectGlobal } from 'emotion'
const BACKGROUND = 'white'
const TEXT_COLOR = '#2f3542'
const LINK_COLOR = '#5352ed'
import * as colors from './colors'
const selection = css`
background: ${LINK_COLOR};
background: ${colors.linkColor};
color: white;
`
// tslint:disable
injectGlobal`
@import url('https://fonts.googleapis.com/css?family=Fira+Mono');
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700');
@import url('https://fonts.googleapis.com/css?family=Open+Sans:100,300,400,600,700');
@import url('https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css');
*, *:before, *:after {
@@ -32,7 +30,7 @@ injectGlobal`
font-family: 'Open Sans', sans-serif;
font-size: 16px;
line-height: 1.5;
background: ${BACKGROUND};
background: ${colors.background};
overflow: hidden;
}
@@ -41,7 +39,7 @@ injectGlobal`
}
body > *, #root {
color: ${TEXT_COLOR};
color: ${colors.textColor};
}
html, body, #root {
@@ -51,11 +49,11 @@ injectGlobal`
a, a:visited, a:active {
text-decoration: none;
color: ${LINK_COLOR};
color: ${colors.linkColor};
}
a:hover {
color: ${LINK_COLOR};
color: ${colors.linkColor};
}
input:-webkit-autofill,
@@ -76,7 +74,7 @@ injectGlobal`
}
select {
color: ${TEXT_COLOR};
color: ${colors.textColor};
}
code, pre {

View File

@@ -11,9 +11,7 @@ export type RenderComponent = ComponentType<{
export interface PlaygroundProps {
__code: string
children: any
components: {
[key: string]: ComponentType<any>
}
components: any
}
const DefaultRender: RenderComponent = ({ component, code }) => (

View File

@@ -0,0 +1,80 @@
import * as React from 'react'
import { Fragment, SFC, ComponentType } from 'react'
export interface Prop {
type: {
name: string
}
required: boolean
description?: string
}
export type ComponentWithDocGenInfo = ComponentType & {
__docgenInfo: {
description?: string
props?: Record<string, Prop>
}
}
export interface PropsTable {
of: ComponentWithDocGenInfo
components: {
[key: string]: ComponentType<any>
}
}
export const PropsTable: SFC<PropsTable> = ({ of: component, components }) => {
const info = component.__docgenInfo
if (info && info.props && Object.keys(info.props).length === 0) {
return null
}
const { props } = info
const H2 = components.h2 || 'h2'
const Table = components.table || 'table'
const Thead = components.thead || 'thead'
const Tr = components.tr || 'tr'
const Th = components.th || 'th'
const Tbody = components.tbody || 'tbody'
const Td = components.td || 'td'
return (
<Fragment>
<H2>Properties</H2>
<Table class="PropsTable">
<Thead>
<Tr>
<Th width="15%" class="PropsTable--property">
Property
</Th>
<Th width="15%" class="PropsTable--type">
Type
</Th>
<Th width="15%" class="PropsTable--required">
Required
</Th>
<Th width="55%" class="PropsTable--description">
Description
</Th>
</Tr>
</Thead>
<Tbody>
{props &&
Object.keys(props).map((name: string) => {
const prop = props[name]
return (
<Tr key={name}>
<Td>{name}</Td>
<Td>{prop.type.name}</Td>
<Td>{String(prop.required)}</Td>
<Td>{prop.description}</Td>
</Tr>
)
})}
</Tbody>
</Table>
</Fragment>
)
}

View File

@@ -3,3 +3,4 @@ export { doc } from './Doc'
export { theme } from './theme'
export { Docs } from './components/Docs'
export { Playground, RenderComponent } from './components/Playground'
export { PropsTable } from './components/PropsTable'

View File

@@ -1780,6 +1780,10 @@ assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
ast-types@0.10.1:
version "0.10.1"
resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd"
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@@ -1792,7 +1796,7 @@ async@^1.4.0, async@^1.5.0:
version "1.5.2"
resolved "https://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.3.0:
async@^2.1.4, async@^2.3.0:
version "2.6.0"
resolved "https://registry.npmjs.org/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
dependencies:
@@ -1858,6 +1862,14 @@ babel-plugin-macros@^2.0.0:
dependencies:
cosmiconfig "^4.0.0"
babel-plugin-react-docgen@^1.9.0:
version "1.9.0"
resolved "https://registry.npmjs.org/babel-plugin-react-docgen/-/babel-plugin-react-docgen-1.9.0.tgz#2e79aeed2f93b53a172398f93324fdcf9f02e01f"
dependencies:
babel-types "^6.24.1"
lodash "^4.17.0"
react-docgen "^3.0.0-beta11"
babel-plugin-syntax-jsx@^6.18.0:
version "6.18.0"
resolved "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
@@ -1900,6 +1912,26 @@ babel-preset-react-app@^4.0.0-next.b2fd8db8:
babel-plugin-transform-dynamic-import "2.0.0"
babel-plugin-transform-react-remove-prop-types "0.4.12"
babel-runtime@^6.26.0, babel-runtime@^6.9.2:
version "6.26.0"
resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
babel-types@^6.24.1:
version "6.26.0"
resolved "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
dependencies:
babel-runtime "^6.26.0"
esutils "^2.0.2"
lodash "^4.17.4"
to-fast-properties "^1.0.3"
babylon@7.0.0-beta.31:
version "7.0.0-beta.31"
resolved "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.31.tgz#7ec10f81e0e456fd0f855ad60fa30c2ac454283f"
babylon@7.0.0-beta.42:
version "7.0.0-beta.42"
resolved "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.42.tgz#67cfabcd4f3ec82999d29031ccdea89d0ba99657"
@@ -2467,7 +2499,7 @@ command-join@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/command-join/-/command-join-2.0.0.tgz#52e8b984f4872d952ff1bdc8b98397d27c7144cf"
commander@2.15.x, commander@^2.12.1, commander@^2.8.1, commander@~2.15.0:
commander@2.15.x, commander@^2.12.1, commander@^2.8.1, commander@^2.9.0, commander@~2.15.0:
version "2.15.1"
resolved "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
@@ -2755,6 +2787,10 @@ core-js@^2.4.0, core-js@^2.5.3:
version "2.5.5"
resolved "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b"
core-js@^2.4.1:
version "2.5.6"
resolved "https://registry.npmjs.org/core-js/-/core-js-2.5.6.tgz#0fe6d45bf3cac3ac364a9d72de7576f4eb221b9d"
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -3138,7 +3174,7 @@ dir-glob@^2.0.0:
arrify "^1.0.1"
path-type "^3.0.0"
doctrine@^2.1.0:
doctrine@^2.0.0, doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
dependencies:
@@ -3447,7 +3483,7 @@ esprima@^3.1.3:
version "3.1.3"
resolved "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
esprima@^4.0.0:
esprima@^4.0.0, esprima@~4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
@@ -5245,7 +5281,7 @@ lodash.toarray@^4.4.0:
version "4.4.0"
resolved "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
lodash@^4.14.0, lodash@^4.17.3, lodash@^4.17.5:
lodash@^4.14.0, lodash@^4.17.0, lodash@^4.17.3, lodash@^4.17.5:
version "4.17.10"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
@@ -5750,6 +5786,12 @@ no-case@^2.2.0:
dependencies:
lower-case "^1.1.1"
node-dir@^0.1.10:
version "0.1.17"
resolved "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"
dependencies:
minimatch "^3.0.2"
node-emoji@^1.8.1:
version "1.8.1"
resolved "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826"
@@ -6311,6 +6353,10 @@ pluralize@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
polished@^1.9.2:
version "1.9.2"
resolved "https://registry.npmjs.org/polished/-/polished-1.9.2.tgz#d705cac66f3a3ed1bd38aad863e2c1e269baf6b6"
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@@ -6358,7 +6404,7 @@ prismjs@~1.6.0:
optionalDependencies:
clipboard "^1.5.5"
private@^0.1.6:
private@^0.1.6, private@~0.1.5:
version "0.1.8"
resolved "https://registry.npmjs.org/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -6520,6 +6566,18 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-docgen@^3.0.0-beta11:
version "3.0.0-beta9"
resolved "https://registry.npmjs.org/react-docgen/-/react-docgen-3.0.0-beta9.tgz#6be987e640786ecb10ce2dd22157a022c8285e95"
dependencies:
async "^2.1.4"
babel-runtime "^6.9.2"
babylon "7.0.0-beta.31"
commander "^2.9.0"
doctrine "^2.0.0"
node-dir "^0.1.10"
recast "^0.12.6"
react-dom@^16.3.1, react-dom@^16.3.2:
version "16.3.2"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df"
@@ -6691,6 +6749,16 @@ readdirp@^2.0.0:
readable-stream "^2.0.2"
set-immediate-shim "^1.0.1"
recast@^0.12.6:
version "0.12.9"
resolved "https://registry.npmjs.org/recast/-/recast-0.12.9.tgz#e8e52bdb9691af462ccbd7c15d5a5113647a15f1"
dependencies:
ast-types "0.10.1"
core-js "^2.4.1"
esprima "~4.0.0"
private "~0.1.5"
source-map "~0.6.1"
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
@@ -7783,6 +7851,10 @@ to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
to-fast-properties@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"