Browse Source

Merge pull request #48 from jxom/update-sidebar-highlight-on-scroll

Tweak sidebar to update navigation highlight on scroll
main
Brian Vaughn 8 years ago
committed by GitHub
parent
commit
be1f9a60d8
  1. 2
      content/tutorial/nav.yml
  2. 3
      src/components/MarkdownPage/MarkdownPage.js
  3. 103
      src/templates/components/Sidebar/ScrollSyncSection.js
  4. 13
      src/templates/components/Sidebar/Section.js
  5. 13
      src/templates/components/Sidebar/Sidebar.js
  6. 1
      src/templates/tutorial.js
  7. 17
      src/utils/createLink.js

2
content/tutorial/nav.yml

@ -1,6 +1,6 @@
- title: Tutorial
items:
- id: tutorial
- id: before-we-start
title: Before We Start
href: /tutorial/tutorial.html#before-we-start
forceInternal: true

3
src/components/MarkdownPage/MarkdownPage.js

@ -26,6 +26,7 @@ const MarkdownPage = ({
authors,
createLink,
date,
enableScrollSync,
ogDescription,
location,
markdownRemark,
@ -98,6 +99,7 @@ const MarkdownPage = ({
<div css={sharedStyles.articleLayout.sidebar}>
<StickyResponsiveSidebar
enableScrollSync={enableScrollSync}
createLink={createLink}
defaultActiveSection={findSectionForPath(
location.pathname,
@ -132,6 +134,7 @@ MarkdownPage.propTypes = {
authors: PropTypes.array.isRequired,
createLink: PropTypes.func.isRequired,
date: PropTypes.string,
enableScrollSync: PropTypes.bool,
location: PropTypes.object.isRequired,
markdownRemark: PropTypes.object.isRequired,
sectionList: PropTypes.array.isRequired,

103
src/templates/components/Sidebar/ScrollSyncSection.js

@ -0,0 +1,103 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the CC-BY-4.0 license found
* in the LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
import React, {Component} from 'react';
import Section from './Section';
class ScrollSyncSection extends Component {
constructor(props, context) {
super(props, context);
this.state = {
activeItemId: '',
itemTopOffsets: [],
};
this.calculateItemTopOffsets = this.calculateItemTopOffsets.bind(this);
this.handleResize = this.handleResize.bind(this);
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount() {
this.calculateItemTopOffsets();
window.addEventListener('resize', this.handleResize);
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
window.removeEventListener('scroll', this.handleScroll);
}
calculateItemTopOffsets() {
const {section} = this.props;
const itemIds = _getItemIds(section.items);
this.setState({
itemTopOffsets: _getElementTopOffsetsById(itemIds),
});
}
handleResize() {
this.calculateItemTopOffsets();
this.handleScroll();
}
handleScroll() {
const {itemTopOffsets} = this.state;
const item = itemTopOffsets.find((itemTopOffset, i) => {
const nextItemTopOffset = itemTopOffsets[i + 1];
if (nextItemTopOffset) {
return (
window.scrollY >= itemTopOffset.offsetTop &&
window.scrollY < nextItemTopOffset.offsetTop
);
}
return window.scrollY >= itemTopOffset.offsetTop;
});
this.setState({
activeItemId: item ? item.id : '',
});
}
render() {
const {activeItemId} = this.state;
return <Section isScrollSync activeItemId={activeItemId} {...this.props} />;
}
}
const _getItemIds = items =>
items
.map(item => {
let subItemIds = [];
if (item.subitems) {
subItemIds = item.subitems.map(subitem => subitem.id);
}
return [item.id, ...subItemIds];
})
.reduce((prev, current) => prev.concat(current));
const _getElementTopOffsetsById = ids =>
ids
.map(id => {
const element = document.getElementById(id);
if (!element) {
return null;
}
return {
id,
offsetTop: element.offsetTop,
};
})
.filter(item => item);
export default ScrollSyncSection;

13
src/templates/components/Sidebar/Section.js

@ -9,17 +9,16 @@
'use strict';
import React from 'react';
import {colors, media} from 'theme';
import isItemActive from 'utils/isItemActive';
import MetaTitle from '../MetaTitle';
import ChevronSvg from '../ChevronSvg';
// TODO Update isActive link as document scrolls past anchor tags
// Maybe used 'hashchange' along with 'scroll' to set/update active links
const Section = ({
activeItemId,
createLink,
isActive,
isScrollSync,
location,
onLinkClick,
onSectionTitleClick,
@ -67,6 +66,9 @@ const Section = ({
marginTop: 5,
}}>
{createLink({
isActive: isScrollSync
? activeItemId === item.id
: isItemActive(location, item),
item,
location,
onLinkClick,
@ -78,6 +80,9 @@ const Section = ({
{item.subitems.map(subitem => (
<li key={subitem.id}>
{createLink({
isActive: isScrollSync
? activeItemId === subitem.id
: isItemActive(location, subitem),
item: subitem,
location,
onLinkClick,

13
src/templates/components/Sidebar/Sidebar.js

@ -12,6 +12,7 @@
import React, {Component} from 'react';
import Flex from 'components/Flex';
import Section from './Section';
import ScrollSyncSection from './ScrollSyncSection';
import {media} from 'theme';
class Sidebar extends Component {
@ -24,9 +25,17 @@ class Sidebar extends Component {
}
render() {
const {closeParentMenu, createLink, location, sectionList} = this.props;
const {
closeParentMenu,
createLink,
enableScrollSync,
location,
sectionList,
} = this.props;
const {activeSection} = this.state;
const SectionComponent = enableScrollSync ? ScrollSyncSection : Section;
return (
<Flex
type="nav"
@ -46,7 +55,7 @@ class Sidebar extends Component {
},
}}>
{sectionList.map((section, index) => (
<Section
<SectionComponent
createLink={createLink}
isActive={activeSection === section || sectionList.length === 1}
key={index}

1
src/templates/tutorial.js

@ -27,6 +27,7 @@ const Tutorial = ({data, location}) => {
return (
<MarkdownPage
enableScrollSync
createLink={createLinkTutorial}
location={location}
markdownRemark={data.markdownRemark}

17
src/utils/createLink.js

@ -12,13 +12,10 @@
import Link from 'gatsby-link';
import React from 'react';
import ExternalLinkSvg from 'templates/components/ExternalLinkSvg';
import isItemActive from 'utils/isItemActive';
import slugify from 'utils/slugify';
import {colors, media} from 'theme';
const createLinkBlog = ({item, location, section}) => {
const isActive = isItemActive(location, item);
const createLinkBlog = ({isActive, item, section}) => {
return (
<Link css={[linkCss, isActive && activeLinkCss]} to={item.id}>
{isActive && <span css={activeLinkBefore} />}
@ -27,7 +24,7 @@ const createLinkBlog = ({item, location, section}) => {
);
};
const createLinkCommunity = ({item, location, section}) => {
const createLinkCommunity = ({isActive, item, section}) => {
if (item.href) {
return (
<a css={[linkCss]} href={item.href} target="_blank" rel="noopener">
@ -44,15 +41,13 @@ const createLinkCommunity = ({item, location, section}) => {
);
}
return createLinkDocs({
isActive,
item,
location,
section,
});
};
const createLinkDocs = ({item, location, section}) => {
const isActive = isItemActive(location, item);
const createLinkDocs = ({isActive, item, section}) => {
return (
<Link
css={[linkCss, isActive && activeLinkCss]}
@ -63,9 +58,7 @@ const createLinkDocs = ({item, location, section}) => {
);
};
const createLinkTutorial = ({item, location, onLinkClick, section}) => {
const isActive = isItemActive(location, item);
const createLinkTutorial = ({isActive, item, onLinkClick, section}) => {
return (
<Link
css={[linkCss, isActive && activeLinkCss]}

Loading…
Cancel
Save