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

fix(docs): Fixing doc navigation link issues #2760

Merged
merged 8 commits into from
May 19, 2018
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,17 @@ const ComponentControls = (props) => {
const { anchorName, showHTML, showCode, onCopyLink, onShowHTML, onShowCode, visible } = props

return (
<Transition
duration={200}
transitionOnMount
visible={!!visible}
unmountOnHide
>
<Transition duration={200} visible={!!visible} unmountOnHide>
{/*
Heads up! Don't remove this `div`, visible Transition applies `display: block`,
while Menu should have `display: inline-flex`
*/}
<div>
<Menu
color='green'
compact
icon
size='small'
text
>
<ComponentControlsCopyLink
anchorName={anchorName}
onClick={onCopyLink}
/>
<Menu color='green' compact icon size='small' text>
<ComponentControlsCopyLink anchorName={anchorName} onClick={onCopyLink} />
<ComponentControlsMaximize anchorName={anchorName} />
<ComponentControlsShowHtml
active={showHTML}
onClick={onShowHTML}
/>
<ComponentControlsEditCode
active={showCode}
onClick={onShowCode}
/>
<ComponentControlsShowHtml active={showHTML} onClick={onShowHTML} />
<ComponentControlsEditCode active={showCode} onClick={onShowCode} />
</Menu>
</div>
</Transition>
Expand Down
36 changes: 30 additions & 6 deletions docs/app/Components/ComponentDoc/ComponentDoc.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import _ from 'lodash'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import DocumentTitle from 'react-document-title'
import { withRouter } from 'react-router'
import { Grid, Icon } from 'semantic-ui-react'

import withDocInfo from 'docs/app/HOC/withDocInfo'
import { scrollToAnchor } from 'docs/app/utils'
import { scrollToAnchor, examplePathToHash, getFormattedHash } from 'docs/app/utils'
import ComponentDocHeader from './ComponentDocHeader'
import ComponentDocLinks from './ComponentDocLinks'
import ComponentDocSee from './ComponentDocSee'
Expand Down Expand Up @@ -50,6 +49,16 @@ class ComponentDoc extends Component {

state = {}

componentWillMount() {
const { history } = this.props

if (location.hash) {
const activePath = getFormattedHash(location.hash)
history.replace(`${location.pathname}#${activePath}`)
this.setState({ activePath })
}
}

getChildContext() {
return {
onPassed: this.handleExamplePassed,
Expand All @@ -62,20 +71,35 @@ class ComponentDoc extends Component {
if (current !== next) this.setState({ activePath: undefined })
}

handleExamplePassed = (e, { examplePath }) => this.setState({ activePath: examplePath })
handleExamplePassed = (e, { examplePath }) =>
this.setState({ activePath: examplePathToHash(examplePath) })

handleExamplesRef = examplesRef => this.setState({ examplesRef })

handleSidebarItemClick = (e, { path }) => {
const { history } = this.props
const aPath = _.kebabCase(_.last(path.split('/')))
const aPath = examplePathToHash(path)

history.replace(`${location.pathname}#${aPath}`)
scrollToAnchor()
// set active hash path
this.setState(
{
activePath: aPath,
},
scrollToAnchor,
)
}

render() {
const { componentGroup, componentName, description, ghLink, path, seeItems, suiLink } = this.props
const {
componentGroup,
componentName,
description,
ghLink,
path,
seeItems,
suiLink,
} = this.props
const { activePath, examplesRef } = this.state

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import { renderToStaticMarkup } from 'react-dom/server'
import { html } from 'js-beautify'
import copyToClipboard from 'copy-to-clipboard'

import { exampleContext, repoURL, scrollToAnchor } from 'docs/app/utils'
import {
exampleContext,
repoURL,
scrollToAnchor,
examplePathToHash,
getFormattedHash,
} from 'docs/app/utils'
import { Divider, Grid, Menu, Visibility } from 'src'
import Editor from 'docs/app/Components/Editor/Editor'
import ComponentControls from '../ComponentControls'
Expand Down Expand Up @@ -70,7 +76,7 @@ class ComponentExample extends PureComponent {
const { examplePath } = this.props
const sourceCode = this.getOriginalSourceCode()

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

const exampleElement = this.renderOriginalExample()
const markup = renderToStaticMarkup(exampleElement)
Expand All @@ -91,7 +97,7 @@ class ComponentExample extends PureComponent {
return showCode || showHTML
}

isActiveHash = () => this.anchorName === this.props.location.hash.replace('#', '')
isActiveHash = () => this.anchorName === getFormattedHash(this.props.location.hash)

updateHash = () => {
if (this.isActiveState()) this.setHashAndScroll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,32 @@ import _ from 'lodash'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { Accordion, Icon, Menu } from 'semantic-ui-react'

import { examplePathToHash } from 'docs/app/utils'
import { pure } from 'docs/app/HOC'
import ComponentSidebarItem from './ComponentSidebarItem'

class ComponentSidebarSection extends Component {
static propTypes = {
activePath: PropTypes.string,
examples: PropTypes.arrayOf(PropTypes.shape({
title: PropTypes.string,
path: PropTypes.string,
})),
examples: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
path: PropTypes.string,
}),
),
name: PropTypes.string,
onItemClick: PropTypes.func,
onTitleClick: PropTypes.func,
}

state = {}
constructor(props) {
super(props)
this.state = {
isActiveByProps: this.isActiveAccordion(),
}
}

componentWillReceiveProps(nextProps) {
const { activePath, examples } = nextProps
const isActiveByProps = !!_.find(examples, { path: activePath })

const isActiveByProps = this.isActiveAccordion(nextProps)
const didCloseByProps = this.state.isActiveByProps && !isActiveByProps

// We allow the user to open accordions, but we close them when we scroll passed them
Expand All @@ -35,7 +39,12 @@ class ComponentSidebarSection extends Component {

handleItemClick = (e, itemProps) => _.invoke(this.props, 'onItemClick', e, itemProps)

handleTitleClick = () => this.setState(prevState => ({ isActiveByUser: !prevState.isActiveByUser }))
handleTitleClick = () =>
this.setState(prevState => ({ isActiveByUser: !prevState.isActiveByUser }))

isActiveAccordion = (props = this.props) =>
(props.examples || []).findIndex(item => examplePathToHash(item.path) === props.activePath) !==
-1

render() {
const { activePath, examples, name } = this.props
Expand All @@ -52,7 +61,7 @@ class ComponentSidebarSection extends Component {
<Accordion.Content as={Menu.Menu} active={active}>
{_.map(examples, ({ title, path }) => (
<ComponentSidebarItem
active={activePath === path}
active={activePath === examplePathToHash(path)}
key={path}
onClick={this.handleItemClick}
path={path}
Expand Down
6 changes: 6 additions & 0 deletions docs/app/utils/exampleContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Get the Webpack Context for all doc site examples.
*/
const exampleContext = require.context('docs/app/Examples/', true, /(\w+Example\w*|index)\.js$/)

export default exampleContext
26 changes: 26 additions & 0 deletions docs/app/utils/examplePathToHash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import _ from 'lodash/fp'

/**
* Creates a short hash path from an example filename.
*
* Typical Hash structure ${pathname}-${section}-${exampleName}
* shorten to new structure ${section} - -${exampleName without "component-example"}
* @param {string} examplePath
*/
const examplePathToHash = (examplePath) => {
const hashParts = examplePath.split('/').filter(part => part !== '.')

if (!hashParts.length) return examplePath

// eslint-disable-next-line no-unused-vars
const [type, componentName, section, exampleName] = hashParts

// ButtonExample => Button
// ButtonExampleButton => Button
// ButtonExampleActive => Active
const shortExampleName = exampleName.replace(`${componentName}Example`, '').replace('.js', '')

return _.kebabCase(`${section}-${shortExampleName || componentName}`)
}

export default examplePathToHash
42 changes: 42 additions & 0 deletions docs/app/utils/getFormattedHash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import _ from 'lodash/fp'

import exampleContext from './exampleContext'
import examplePathToHash from './examplePathToHash'

/**
* Check whether given hash is old or new, redirect to new hash in case of old one
* @param {string} hash
*/
const isOldHash = (hash) => {
if (!hash) return false

const [firstPart] = hash.split('-') || []

if (!firstPart) return false

return !_.includes(firstPart, ['types', 'states', 'variations'])
}

/**
* Retrieve hash string from location path
* @param {string} hash
*/
const getFormattedHash = (hash) => {
const hashString = (hash || '').replace('#', '')

if (isOldHash(hashString)) {
const filename = `${_.startCase(hashString).replace(/\s/g, '')}`
const completeFilename = `/${filename}.js`
const exampleKeys = exampleContext.keys()
const examplePath = _.find(key => _.endsWith(completeFilename, key), exampleKeys)

// found old to new hashString match
if (examplePath) {
return examplePathToHash(examplePath)
}
}

return hashString
}

export default getFormattedHash
24 changes: 24 additions & 0 deletions docs/app/utils/getNewHash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import _ from 'lodash/fp'

import exampleContext from './exampleContext'
import examplePathToHash from './examplePathToHash'
import isOldHash from './isOldHash'

/**
* get new hash using old
* @param {string} hash
*/
const getNewHash = (hash) => {
if (isOldHash(hash)) {
const fileName = _.startCase(hash).replace(/\s/g, '')
const str = exampleContext.keys().find(item => item.indexOf(fileName) !== -1)
// found old to new hash match
if (str) {
return examplePathToHash(str.replace(/((\.)(?:[\w]+))|(^\.\/)/g, ''))
}
}

return hash
}

export default getNewHash
19 changes: 4 additions & 15 deletions docs/app/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import _ from 'lodash/fp'

import * as semanticUIReact from 'src'
import { META } from 'src/lib'

export * from './constants'
export exampleContext from './exampleContext'
export getComponentGroup from './getComponentGroup'
export getFormattedHash from './getFormattedHash'
export getSeeItems from './getSeeItems'
export scrollToAnchor from './scrollToAnchor'

/**
* Get the Webpack Context for all doc site examples.
*/
export const exampleContext = require.context('docs/app/Examples/', true, /(\w+Example\w*|index)\.js$/)

export const parentComponents = _.flow(
_.filter(META.isParent),
_.sortBy('_meta.name'),
)(semanticUIReact)
export examplePathToHash from './examplePathToHash'
export parentComponents from './parentComponents'
11 changes: 11 additions & 0 deletions docs/app/utils/isOldHash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Check whether given hash is old or new, redirect to new hash in case of old one
* @param {string} hash
*/
const isOldHash = (hash) => {
const expectedTypes = ['types', 'states', 'variations']
const hashParts = hash.split('-') || []
return !(expectedTypes.findIndex(item => item === hashParts[0]) !== -1)
}

export default isOldHash
8 changes: 8 additions & 0 deletions docs/app/utils/parentComponents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import _ from 'lodash/fp'

import * as semanticUIReact from 'src'
import { META } from 'src/lib'

const parentComponents = _.flow(_.filter(META.isParent), _.sortBy('_meta.name'))(semanticUIReact)

export default parentComponents