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
items:
- id: tutorial
- id: before-we-start
title: Before We Start
href: /tutorial/tutorial.html#before-we-start
forceInternal: true

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

@ -9,88 +9,161 @@
'use strict';
import React from 'react';
import React, {Component} from 'react';
import {colors, media} from 'theme';
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 = ({
createLink,
isActive,
location,
onLinkClick,
onSectionTitleClick,
section,
}) => (
<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={{
marginBottom: 10,
[media.greaterThan('small')]: {
display: isActive ? 'block' : 'none',
},
}}>
{section.items.map(item => (
<li
key={item.id}
class Section extends Component {
constructor(props, context) {
super(props, context);
this.state = {
activeItemId: null,
itemTopOffsets: [],
};
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount() {
const {section} = this.props;
const itemIds = _getItemIds(section.items);
this.setState({
itemTopOffsets: _getElementTopOffsetsById(itemIds),
});
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', 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 : null,
});
}
render() {
const {
createLink,
isActive,
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={{
marginTop: 5,
marginBottom: 10,
[media.greaterThan('small')]: {
display: isActive ? 'block' : 'none',
},
}}>
{createLink({
item,
location,
onLinkClick,
section,
})}
{item.subitems && (
<ul css={{marginLeft: 20}}>
{item.subitems.map(subitem => (
<li key={subitem.id}>
{createLink({
item: subitem,
location,
onLinkClick,
section,
})}
</li>
))}
</ul>
)}
</li>
))}
</ul>
</div>
);
{section.items.map(item => (
<li
key={item.id}
css={{
marginTop: 5,
}}>
{createLink({
item,
location,
onLinkClick,
section,
isActive: activeItemId === item.id,
})}
{item.subitems && (
<ul css={{marginLeft: 20}}>
{item.subitems.map(subitem => (
<li key={subitem.id}>
{createLink({
item: subitem,
location,
onLinkClick,
section,
isActive: activeItemId === subitem.id,
})}
</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;

15
src/utils/createLink.js

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

Loading…
Cancel
Save