Skip to content

Commit

Permalink
docs(examples): direct links, maximize, better props (#1448)
Browse files Browse the repository at this point in the history
* docs(Portal): add @see for Portal powered components

* docs(Menu): update icon prop description

* docs(routes): use Switch for exclusive routes

* docs(ComponentProps): callback return types, nested params

* refactor(docs): abstract scrollToAnchor

* docs(ComponentExample): copy link, maximize, scroll to anchor

* max
  • Loading branch information
levithomason authored Mar 13, 2017
1 parent 9fb59f2 commit 1e06daa
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 121 deletions.
179 changes: 124 additions & 55 deletions docs/app/Components/ComponentDoc/ComponentExample.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as Babel from 'babel-standalone'
import _ from 'lodash'
import React, { Component, createElement, isValidElement, PropTypes } from 'react'
import { withRouter } from 'react-router'
import { renderToStaticMarkup } from 'react-dom/server'
import { html } from 'js-beautify'
import copyToClipboard from 'copy-to-clipboard'

import { exampleContext, repoURL } from 'docs/app/utils'
import { Divider, Grid, Icon, Header, Menu } from 'src'
import { Divider, Grid, Icon, Header, Menu, Popup } from 'src'
import Editor from 'docs/app/Components/Editor/Editor'
import { scrollToAnchor } from 'docs/app/utils'

const babelConfig = {
presets: ['es2015', 'react', 'stage-1'],
Expand Down Expand Up @@ -50,41 +52,91 @@ const errorStyle = {
background: '#fff2f2',
}

const ToolTip = ({ children, content }) => (
<Popup
inverted
mouseEnterDelay={500}
position='top center'
size='tiny'
style={{ width: 80, textAlign: 'center', padding: '0.5em' }}
trigger={children}
content={content}
/>
)
ToolTip.propTypes = {
children: PropTypes.node,
content: PropTypes.node,
}

/**
* Renders a `component` and the raw `code` that produced it.
* Allows toggling the the raw `code` code block.
*/
export default class ComponentExample extends Component {
class ComponentExample extends Component {
static propTypes = {
children: PropTypes.node,
description: PropTypes.node,
examplePath: PropTypes.string.isRequired,
history: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
title: PropTypes.node,
}

componentWillMount() {
const { title } = this.props
const { examplePath } = this.props
const sourceCode = this.getOriginalSourceCode()

this.anchorName = _.kebabCase(_.last(examplePath.split('/')))

// show code for direct links to examples
const active = title && _.kebabCase(title) === location.hash.replace('#', '')
const showCode = this.anchorName === location.hash.replace('#', '')
const exampleElement = this.renderOriginalExample()
const staticMarkup = renderToStaticMarkup(exampleElement)
const markup = renderToStaticMarkup(exampleElement)

this.setState({
exampleElement,
showCode: active,
showHTML: active,
showCode,
sourceCode,
staticMarkup,
markup,
})
}

setHashAndScroll = () => {
const { history } = this.props
history.replace(location.pathname + '#' + this.anchorName)
scrollToAnchor()
}

removeHash = () => {
const { history } = this.props
history.replace(location.pathname)
}

handleDirectLinkClick = (e) => {
e.preventDefault()
this.setHashAndScroll()

copyToClipboard(this.anchorName)
this.setState({ copiedDirectLink: true })

setTimeout(() => this.setState({ copiedDirectLink: false }), 1000)
}

handleShowCodeClick = (e) => {
e.preventDefault()

const { showCode } = this.state
this.setState({ showCode: !showCode })

if (!showCode) this.setHashAndScroll()
else this.removeHash()
}

copyJSX = () => {
const { sourceCode } = this.state
copyToClipboard(sourceCode)
this.setState({ copied: true })
setTimeout(() => this.setState({ copied: false }), 1000)
copyToClipboard(this.getOriginalSourceCode())
this.setState({ copiedCode: true })
setTimeout(() => this.setState({ copiedCode: false }), 1000)
}

resetJSX = () => {
Expand All @@ -98,14 +150,17 @@ export default class ComponentExample extends Component {

getOriginalSourceCode = () => {
const { examplePath } = this.props
return require(`!raw!docs/app/Examples/${examplePath}`)
}

getKebabExamplePath = () => _.kebabCase(this.props.examplePath)
if (!this.sourceCode) this.sourceCode = require(`!raw!docs/app/Examples/${examplePath}`)

return this.sourceCode
}

toggleShowCode = () => this.setState({ showCode: !this.state.showCode })
getKebabExamplePath = () => {
if (!this.kebabExamplePath) this.kebabExamplePath = _.kebabCase(this.props.examplePath)

toggleShowHTML = () => this.setState({ showHTML: !this.state.showHTML })
return this.kebabExamplePath
}

renderError = _.debounce((error) => {
this.setState({ error })
Expand Down Expand Up @@ -191,7 +246,7 @@ export default class ComponentExample extends Component {
this.setState({
error,
exampleElement,
staticMarkup: renderToStaticMarkup(exampleElement),
markup: renderToStaticMarkup(exampleElement),
})
}
} catch (err) {
Expand All @@ -204,22 +259,23 @@ export default class ComponentExample extends Component {
this.renderSourceCode()
}

renderJSXControls = () => {
setGitHubHrefs = () => {
const { examplePath } = this.props
const { copied, error } = this.state

if (this.ghEditHref && this.ghBugHref) return

// get component name from file path:
// elements/Button/Types/ButtonButtonExample
const pathParts = examplePath.split(__PATH_SEP__)
const componentName = pathParts[1]
const filename = pathParts[pathParts.length - 1]

const color = error ? 'red' : 'black'
const ghEditHref = [
this.ghEditHref = [
`${repoURL}/edit/master/docs/app/Examples/${examplePath}.js`,
`?message=docs(${filename}): your description`,
].join('')
const ghBugHref = [

this.ghBugHref = [
`${repoURL}/issues/new?`,
_.map({
title: `fix(${componentName}): your description`,
Expand All @@ -240,16 +296,23 @@ export default class ComponentExample extends Component {
].join('\n'),
}, (val, key) => `${key}=${encodeURIComponent(val)}`).join('&'),
].join('')
}

renderJSXControls = () => {
const { copiedCode, error } = this.state

this.setGitHubHrefs()

const color = error ? 'red' : 'black'
return (
<Divider horizontal>
<Menu text>
<Menu.Item
active={copied || !!error} // to show the color
color={copied ? 'green' : color}
active={copiedCode || !!error} // to show the color
color={copiedCode ? 'green' : color}
onClick={this.copyJSX}
icon={!copied && 'copy'}
content={copied ? 'Copied!' : 'Copy'}
icon={!copiedCode && 'copy'}
content={copiedCode ? 'Copied!' : 'Copy'}
/>
<Menu.Item
active={!!error} // to show the color
Expand All @@ -263,15 +326,15 @@ export default class ComponentExample extends Component {
color={color}
icon='github'
content='Edit'
href={ghEditHref}
href={this.ghEditHref}
target='_blank'
/>
<Menu.Item
active={!!error} // to show the color
color={color}
icon='bug'
content='Issue'
href={ghBugHref}
href={this.ghBugHref}
target='_blank'
/>
</Menu>
Expand Down Expand Up @@ -304,12 +367,12 @@ export default class ComponentExample extends Component {
}

renderHTML = () => {
const { showHTML, staticMarkup } = this.state
if (!showHTML) return
const { showCode, markup } = this.state
if (!showCode) return

// add new lines between almost all adjacent elements
// moves inline elements to their own line
const preFormattedHTML = staticMarkup
const preFormattedHTML = markup
.replace(/><(?!\/i|\/label|\/span|option)/g, '>\n<')

const beautifiedHTML = html(preFormattedHTML, {
Expand All @@ -330,36 +393,40 @@ export default class ComponentExample extends Component {

render() {
const { children, description, title } = this.props
const { exampleElement, showCode, showHTML } = this.state
const active = showCode || showHTML
const { copiedDirectLink, exampleElement, showCode } = this.state
const exampleStyle = { marginBottom: '2em', paddingBottom: '1em', transition: 'box-shadow 300ms' }

const style = { marginBottom: '4em', transition: 'box-shadow 0 ease-out' }
if (active) {
style.transitionDuration = '0.2s'
style.boxShadow = '0 0 30px #ccc'
if (showCode || location.hash === `#${this.anchorName}`) {
exampleStyle.boxShadow = '0 0 30px #ccc'
}

return (
<Grid style={style} divided={active} columns='1'>
<Grid style={exampleStyle} divided={showCode} columns='1' id={this.anchorName}>
<Grid.Column style={headerColumnStyle}>
{title && <Header as='h3' style={titleStyle}>{title}</Header>}
{title && <Header as='h3' className='no-anchor' style={titleStyle}>{title}</Header>}
{description && <p>{description}</p>}
<div style={showCodeStyle}>
<Icon
link
bordered
name='code'
color={showCode ? 'green' : 'grey'}
onClick={this.toggleShowCode}
style={codeIconStyle}
/>
<Icon
link
bordered
name='html5'
color={showHTML ? 'green' : 'grey'}
onClick={this.toggleShowHTML}
/>
<ToolTip content={copiedDirectLink ? 'Copied!' : 'Copy link'}>
<a href={`#${this.anchorName}`} onClick={this.handleDirectLinkClick}>
<Icon bordered link color='grey' name='linkify' />
</a>
</ToolTip>
<ToolTip content='Maximize'>
<a href={`/maximize/${this.anchorName}`} target='_blank'>
<Icon bordered link color='grey' name='window maximize' />
</a>
</ToolTip>
<ToolTip content='Edit Code'>
<Icon
bordered
link
name='code'
inverted={showCode}
color={showCode ? 'green' : 'grey'}
onClick={this.handleShowCodeClick}
style={codeIconStyle}
/>
</ToolTip>
</div>
</Grid.Column>
{children && (
Expand All @@ -379,3 +446,5 @@ export default class ComponentExample extends Component {
)
}
}

export default withRouter(ComponentExample)
Loading

0 comments on commit 1e06daa

Please sign in to comment.