Skip to content

Commit

Permalink
fix(docs): Fixing doc navigation link issues (#2760)
Browse files Browse the repository at this point in the history
* fix(docs): Fixing doc navigation link issues

* Fixed code review comments

* refactor(docs): split up util functions

* fix(ComponentControls): show menu when deep linking

* fix(docs): handle ComponentExample hash names

* fix(docs): export getNewHash in utils

* consolidate hash functions and usage
  • Loading branch information
msrikanth508 authored and levithomason committed May 19, 2018
1 parent 4f82da4 commit 14b7eca
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 61 deletions.
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

0 comments on commit 14b7eca

Please sign in to comment.