fix(client): scroll top and focus an article on guide navigation

This commit is contained in:
Valeriy S
2019-01-23 09:39:35 +03:00
committed by Stuart Taylor
parent a44935a520
commit 1f4fa29860
6 changed files with 149 additions and 168 deletions

View File

@ -29,70 +29,86 @@ const propTypes = {
location: PropTypes.object location: PropTypes.object
}; };
const Layout = ({ children }) => ( class Layout extends React.Component {
<StaticQuery getContentRef = ref => (this.contentRef = ref);
query={graphql`
query LayoutQuery { handleNavigation = () => {
allNavigationNode { this.contentRef.scrollTop = 0;
edges { this.contentRef.focus();
node { };
dashedName
hasChildren render() {
isStubbed return (
parentPath <StaticQuery
path query={graphql`
title query LayoutQuery {
allNavigationNode {
edges {
node {
dashedName
hasChildren
isStubbed
parentPath
path
title
}
}
} }
} }
} `}
} render={data => {
`} const { edges } = data.allNavigationNode;
render={data => { const pages = edges.map(edge => edge.node);
const { edges } = data.allNavigationNode; return (
const pages = edges.map(edge => edge.node); <NavigationContext>
return ( {({
<NavigationContext> toggleDisplaySideNav,
{({ displaySideNav,
toggleDisplaySideNav, expandedState,
displaySideNav, toggleExpandedState
expandedState, }) => (
toggleExpandedState <Fragment>
}) => ( <Spacer size={2} />
<Fragment> <Grid>
<Spacer size={2} /> <Row>
<Grid> <Col
<Row> md={4}
<Col smHidden={!displaySideNav}
md={4} xsHidden={!displaySideNav}
smHidden={!displaySideNav} >
xsHidden={!displaySideNav} <SideNav
> expandedState={expandedState}
<SideNav onNavigate={this.handleNavigation}
expandedState={expandedState} pages={pages}
pages={pages} toggleDisplaySideNav={toggleDisplaySideNav}
toggleDisplaySideNav={toggleDisplaySideNav} toggleExpandedState={toggleExpandedState}
toggleExpandedState={toggleExpandedState} />
/> </Col>
</Col> <Col
<Col md={8}
className='content' smHidden={displaySideNav}
md={8} xsHidden={displaySideNav}
smHidden={displaySideNav} >
xsHidden={displaySideNav} <main
> className='content'
<main className='main' id='main' tabIndex='-1'> id='main'
{children} ref={this.getContentRef}
</main> tabIndex='-1'
</Col> >
</Row> {this.props.children}
</Grid> </main>
</Fragment> </Col>
)} </Row>
</NavigationContext> </Grid>
); </Fragment>
}} )}
/> </NavigationContext>
); );
}}
/>
);
}
}
Layout.displayName = 'Layout'; Layout.displayName = 'Layout';
Layout.propTypes = propTypes; Layout.propTypes = propTypes;

View File

@ -4,23 +4,30 @@ import Link from 'gatsby-link';
const propTypes = { const propTypes = {
isStubbed: PropTypes.bool, isStubbed: PropTypes.bool,
onNavigate: PropTypes.func.isRequired,
path: PropTypes.string, path: PropTypes.string,
router: PropTypes.object, router: PropTypes.object,
title: PropTypes.string, title: PropTypes.string,
toggleDisplaySideNav: PropTypes.func.isRequired toggleDisplaySideNav: PropTypes.func.isRequired
}; };
function NavItem(props) { class NavItem extends React.Component {
const { isStubbed, path, title } = props; handleClick = () => {
return ( this.props.toggleDisplaySideNav();
<li> this.props.onNavigate();
<Link data-navitem='true' onClick={props.toggleDisplaySideNav} to={path}> };
<span className={'navItemTitle' + (isStubbed ? ' stubbed' : '')}> render() {
{title} const { isStubbed, path, title } = this.props;
</span> return (
</Link> <li>
</li> <Link data-navitem='true' onClick={this.handleClick} to={path}>
); <span className={'navItemTitle' + (isStubbed ? ' stubbed' : '')}>
{title}
</span>
</Link>
</li>
);
}
} }
NavItem.displayName = 'NavItem'; NavItem.displayName = 'NavItem';

View File

@ -9,6 +9,7 @@ const propTypes = {
handleClick: PropTypes.func.isRequired, handleClick: PropTypes.func.isRequired,
hasChildren: PropTypes.bool, hasChildren: PropTypes.bool,
isExpanded: PropTypes.bool, isExpanded: PropTypes.bool,
onNavigate: PropTypes.func.isRequired,
path: PropTypes.string, path: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
toggleDisplaySideNav: PropTypes.func.isRequired toggleDisplaySideNav: PropTypes.func.isRequired
@ -52,9 +53,10 @@ class NavPanel extends Component {
} }
handleTitleClick() { handleTitleClick() {
const { path, toggleDisplaySideNav } = this.props; const { path, toggleDisplaySideNav, onNavigate } = this.props;
toggleDisplaySideNav(); toggleDisplaySideNav();
navigate(path); navigate(path);
onNavigate();
} }
renderHeader() { renderHeader() {
@ -94,8 +96,6 @@ class NavPanel extends Component {
return ( return (
<Panel <Panel
bsClass='panelStyle panel' bsClass='panelStyle panel'
collapsible={true}
expanded={isExpanded}
id={`${dashedName}-panel`} id={`${dashedName}-panel`}
role='listitem' role='listitem'
> >

View File

@ -7,6 +7,7 @@ import NavItem from './NavItem';
const propTypes = { const propTypes = {
expandedState: PropTypes.object, expandedState: PropTypes.object,
onNavigate: PropTypes.func.isRequired,
pages: PropTypes.arrayOf(PropTypes.object), pages: PropTypes.arrayOf(PropTypes.object),
parents: PropTypes.arrayOf(PropTypes.object), parents: PropTypes.arrayOf(PropTypes.object),
toggleDisplaySideNav: PropTypes.func.isRequired, toggleDisplaySideNav: PropTypes.func.isRequired,
@ -42,7 +43,9 @@ class SideNav extends Component {
const [category] = pages.filter(page => page.path === path); const [category] = pages.filter(page => page.path === path);
const { title, hasChildren, dashedName } = category; const { title, hasChildren, dashedName } = category;
const children = this.renderChildren(childrenForParent, pages); const children = isExpanded
? this.renderChildren(childrenForParent, pages)
: null;
return ( return (
<NavPanel <NavPanel
dashedName={dashedName} dashedName={dashedName}
@ -50,11 +53,12 @@ class SideNav extends Component {
hasChildren={hasChildren} hasChildren={hasChildren}
isExpanded={isExpanded} isExpanded={isExpanded}
key={parent.path} key={parent.path}
onNavigate={this.props.onNavigate}
path={parent.path} path={parent.path}
title={title} title={title}
toggleDisplaySideNav={this.props.toggleDisplaySideNav} toggleDisplaySideNav={this.props.toggleDisplaySideNav}
> >
{isExpanded ? children : null} {children}
</NavPanel> </NavPanel>
); );
} }
@ -68,6 +72,7 @@ class SideNav extends Component {
<NavItem <NavItem
isStubbed={child.isStubbed} isStubbed={child.isStubbed}
key={child.path} key={child.path}
onNavigate={this.props.onNavigate}
path={child.path} path={child.path}
title={child.title} title={child.title}
toggleDisplaySideNav={this.props.toggleDisplaySideNav} toggleDisplaySideNav={this.props.toggleDisplaySideNav}

View File

@ -84,36 +84,6 @@
height: 20px; height: 20px;
} }
/* 404 */
.flexWrapper {
align-items: center;
display: flex;
flex-direction: column;
height: 50vh;
justify-content: center;
}
.verticalAlign {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
vertical-align: middle;
}
.button {
color: #006400;
background: #fff;
border-color: #006400;
}
.button:hover {
color: #fff;
background: #006400;
border-color: #fff;
}
/* SideNav */ /* SideNav */
.caretStyle { .caretStyle {
@ -197,6 +167,6 @@
position: absolute; position: absolute;
} }
.article { .content {
outline: 0; outline: 0;
} }

View File

@ -1,4 +1,4 @@
import React, { Component, Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { graphql } from 'gatsby'; import { graphql } from 'gatsby';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
@ -13,65 +13,48 @@ const propTypes = {
}) })
}; };
class GuideArticle extends Component { const GuideArticle = props => {
constructor(props) { const {
super(props); location: { pathname },
data: {
this.article = null; markdownRemark: {
} html,
fields: { slug },
componentDidMount() { frontmatter: { title }
if (this.article && document.activeElement.hasAttribute('data-navitem')) { }
this.article.focus(); },
} pageContext: { meta }
} } = props;
return (
render() { <Fragment>
const { <Helmet>
location: { pathname }, <title>{`${title} | freeCodeCamp Guide`}</title>
data: { <link href={`https://www.freecodecamp.org${slug}`} rel='canonical' />
markdownRemark: { <meta
html, content={`https://www.freecodecamp.org${slug}`}
fields: { slug }, property='og:url'
frontmatter: { title }
}
},
pageContext: { meta }
} = this.props;
return (
<Fragment>
<Helmet>
<title>{`${title} | freeCodeCamp Guide`}</title>
<link href={`https://www.freecodecamp.org${slug}`} rel='canonical' />
<meta
content={`https://www.freecodecamp.org${slug}`}
property='og:url'
/>
<meta content={title} property='og:title' />
<meta
content={meta.description ? meta.description : ''}
property='og:description'
/>
<meta
content={meta.description ? meta.description : ''}
name='description'
/>
<meta content={meta.featureImage} property='og:image' />
</Helmet>
<Breadcrumbs path={pathname} />
<article
className='article'
dangerouslySetInnerHTML={{ __html: html }}
id='article'
ref={article => {
this.article = article;
}}
tabIndex='-1'
/> />
</Fragment> <meta content={title} property='og:title' />
); <meta
} content={meta.description ? meta.description : ''}
} property='og:description'
/>
<meta
content={meta.description ? meta.description : ''}
name='description'
/>
<meta content={meta.featureImage} property='og:image' />
</Helmet>
<Breadcrumbs path={pathname} />
<article
className='article'
dangerouslySetInnerHTML={{ __html: html }}
id='article'
tabIndex='-1'
/>
</Fragment>
);
};
GuideArticle.displayName = 'GuideArticle'; GuideArticle.displayName = 'GuideArticle';
GuideArticle.propTypes = propTypes; GuideArticle.propTypes = propTypes;