fix(client): scroll top and focus an article on guide navigation
This commit is contained in:
@ -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;
|
||||||
|
@ -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';
|
||||||
|
@ -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'
|
||||||
>
|
>
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user