Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(examples): direct links, maximize, better props #1448

Merged
merged 7 commits into from
Mar 13, 2017
Merged
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
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