Browse Source

Tweak sidebar to update navigation highlight on scroll

main
jxom 8 years ago
parent
commit
94270569a5
  1. 2
      content/tutorial/nav.yml
  2. 227
      src/templates/components/Sidebar/Section.js
  3. 15
      src/utils/createLink.js

2
content/tutorial/nav.yml

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

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

@ -9,88 +9,161 @@
'use strict'; 'use strict';
import React from 'react'; import React, {Component} from 'react';
import {colors, media} from 'theme'; import {colors, media} from 'theme';
import MetaTitle from '../MetaTitle'; import MetaTitle from '../MetaTitle';
import ChevronSvg from '../ChevronSvg'; import ChevronSvg from '../ChevronSvg';
// TODO Update isActive link as document scrolls past anchor tags class Section extends Component {
// Maybe used 'hashchange' along with 'scroll' to set/update active links constructor(props, context) {
super(props, context);
const Section = ({
createLink, this.state = {
isActive, activeItemId: null,
location, itemTopOffsets: [],
onLinkClick, };
onSectionTitleClick,
section, this.handleScroll = this.handleScroll.bind(this);
}) => ( }
<div>
<MetaTitle componentDidMount() {
onClick={onSectionTitleClick} const {section} = this.props;
cssProps={{
marginTop: 10, const itemIds = _getItemIds(section.items);
this.setState({
[media.greaterThan('small')]: { itemTopOffsets: _getElementTopOffsetsById(itemIds),
color: isActive ? colors.text : colors.subtle, });
':hover': { window.addEventListener('scroll', this.handleScroll);
color: colors.text, }
},
}, componentWillUnmount() {
}}> window.removeEventListener('scroll', this.handleScroll);
{section.title} }
<ChevronSvg
cssProps={{ handleScroll() {
marginLeft: 7, const {itemTopOffsets} = this.state;
transform: isActive ? 'rotateX(180deg)' : 'rotateX(0deg)', const item = itemTopOffsets.find((itemTopOffset, i) => {
transition: 'transform 0.2s ease', const nextItemTopOffset = itemTopOffsets[i + 1];
if (nextItemTopOffset) {
[media.lessThan('small')]: { return (
display: 'none', window.scrollY >= itemTopOffset.offsetTop &&
}, window.scrollY < nextItemTopOffset.offsetTop
}} );
/> }
</MetaTitle> return window.scrollY >= itemTopOffset.offsetTop;
<ul });
css={{ this.setState({
marginBottom: 10, activeItemId: item ? item.id : null,
});
[media.greaterThan('small')]: { }
display: isActive ? 'block' : 'none',
}, render() {
}}> const {
{section.items.map(item => ( createLink,
<li isActive,
key={item.id} location,
onLinkClick,
onSectionTitleClick,
section,
} = this.props;
const {activeItemId} = this.state;
return (
<div>
<MetaTitle
onClick={onSectionTitleClick}
cssProps={{
marginTop: 10,
[media.greaterThan('small')]: {
color: isActive ? colors.text : colors.subtle,
':hover': {
color: colors.text,
},
},
}}>
{section.title}
<ChevronSvg
cssProps={{
marginLeft: 7,
transform: isActive ? 'rotateX(180deg)' : 'rotateX(0deg)',
transition: 'transform 0.2s ease',
[media.lessThan('small')]: {
display: 'none',
},
}}
/>
</MetaTitle>
<ul
css={{ css={{
marginTop: 5, marginBottom: 10,
[media.greaterThan('small')]: {
display: isActive ? 'block' : 'none',
},
}}> }}>
{createLink({ {section.items.map(item => (
item, <li
location, key={item.id}
onLinkClick, css={{
section, marginTop: 5,
})} }}>
{createLink({
{item.subitems && ( item,
<ul css={{marginLeft: 20}}> location,
{item.subitems.map(subitem => ( onLinkClick,
<li key={subitem.id}> section,
{createLink({ isActive: activeItemId === item.id,
item: subitem, })}
location,
onLinkClick, {item.subitems && (
section, <ul css={{marginLeft: 20}}>
})} {item.subitems.map(subitem => (
</li> <li key={subitem.id}>
))} {createLink({
</ul> item: subitem,
)} location,
</li> onLinkClick,
))} section,
</ul> isActive: activeItemId === subitem.id,
</div> })}
); </li>
))}
</ul>
)}
</li>
))}
</ul>
</div>
);
}
}
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 Section; export default Section;

15
src/utils/createLink.js

@ -65,15 +65,22 @@ const createLinkDocs = ({item, location, section}) => {
); );
}; };
const createLinkTutorial = ({item, location, onLinkClick, section}) => { const createLinkTutorial = ({
const isActive = isItemActive(location, item); item,
location,
onLinkClick,
section,
isActive,
}) => {
const active =
typeof isActive === 'boolean' ? isActive : isItemActive(location, item);
return ( return (
<Link <Link
css={[linkCss, isActive && activeLinkCss]} css={[linkCss, active && activeLinkCss]}
onClick={onLinkClick} onClick={onLinkClick}
to={item.href}> to={item.href}>
{isActive && <span css={activeLinkBefore} />} {active && <span css={activeLinkBefore} />}
{item.title} {item.title}
</Link> </Link>
); );

Loading…
Cancel
Save