Skip to content

Commit 9427056

Browse files
committed
Tweak sidebar to update navigation highlight on scroll
1 parent 0047452 commit 9427056

3 files changed

Lines changed: 162 additions & 82 deletions

File tree

content/tutorial/nav.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
- title: Tutorial
22
items:
3-
- id: tutorial
3+
- id: before-we-start
44
title: Before We Start
55
href: /tutorial/tutorial.html#before-we-start
66
forceInternal: true

src/templates/components/Sidebar/Section.js

Lines changed: 150 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -9,88 +9,161 @@
99

1010
'use strict';
1111

12-
import React from 'react';
12+
import React, {Component} from 'react';
1313
import {colors, media} from 'theme';
1414
import MetaTitle from '../MetaTitle';
1515
import ChevronSvg from '../ChevronSvg';
1616

17-
// TODO Update isActive link as document scrolls past anchor tags
18-
// Maybe used 'hashchange' along with 'scroll' to set/update active links
19-
20-
const Section = ({
21-
createLink,
22-
isActive,
23-
location,
24-
onLinkClick,
25-
onSectionTitleClick,
26-
section,
27-
}) => (
28-
<div>
29-
<MetaTitle
30-
onClick={onSectionTitleClick}
31-
cssProps={{
32-
marginTop: 10,
33-
34-
[media.greaterThan('small')]: {
35-
color: isActive ? colors.text : colors.subtle,
36-
37-
':hover': {
38-
color: colors.text,
39-
},
40-
},
41-
}}>
42-
{section.title}
43-
<ChevronSvg
44-
cssProps={{
45-
marginLeft: 7,
46-
transform: isActive ? 'rotateX(180deg)' : 'rotateX(0deg)',
47-
transition: 'transform 0.2s ease',
48-
49-
[media.lessThan('small')]: {
50-
display: 'none',
51-
},
52-
}}
53-
/>
54-
</MetaTitle>
55-
<ul
56-
css={{
57-
marginBottom: 10,
58-
59-
[media.greaterThan('small')]: {
60-
display: isActive ? 'block' : 'none',
61-
},
62-
}}>
63-
{section.items.map(item => (
64-
<li
65-
key={item.id}
17+
class Section extends Component {
18+
constructor(props, context) {
19+
super(props, context);
20+
21+
this.state = {
22+
activeItemId: null,
23+
itemTopOffsets: [],
24+
};
25+
26+
this.handleScroll = this.handleScroll.bind(this);
27+
}
28+
29+
componentDidMount() {
30+
const {section} = this.props;
31+
32+
const itemIds = _getItemIds(section.items);
33+
this.setState({
34+
itemTopOffsets: _getElementTopOffsetsById(itemIds),
35+
});
36+
37+
window.addEventListener('scroll', this.handleScroll);
38+
}
39+
40+
componentWillUnmount() {
41+
window.removeEventListener('scroll', this.handleScroll);
42+
}
43+
44+
handleScroll() {
45+
const {itemTopOffsets} = this.state;
46+
const item = itemTopOffsets.find((itemTopOffset, i) => {
47+
const nextItemTopOffset = itemTopOffsets[i + 1];
48+
if (nextItemTopOffset) {
49+
return (
50+
window.scrollY >= itemTopOffset.offsetTop &&
51+
window.scrollY < nextItemTopOffset.offsetTop
52+
);
53+
}
54+
return window.scrollY >= itemTopOffset.offsetTop;
55+
});
56+
this.setState({
57+
activeItemId: item ? item.id : null,
58+
});
59+
}
60+
61+
render() {
62+
const {
63+
createLink,
64+
isActive,
65+
location,
66+
onLinkClick,
67+
onSectionTitleClick,
68+
section,
69+
} = this.props;
70+
const {activeItemId} = this.state;
71+
return (
72+
<div>
73+
<MetaTitle
74+
onClick={onSectionTitleClick}
75+
cssProps={{
76+
marginTop: 10,
77+
78+
[media.greaterThan('small')]: {
79+
color: isActive ? colors.text : colors.subtle,
80+
81+
':hover': {
82+
color: colors.text,
83+
},
84+
},
85+
}}>
86+
{section.title}
87+
<ChevronSvg
88+
cssProps={{
89+
marginLeft: 7,
90+
transform: isActive ? 'rotateX(180deg)' : 'rotateX(0deg)',
91+
transition: 'transform 0.2s ease',
92+
93+
[media.lessThan('small')]: {
94+
display: 'none',
95+
},
96+
}}
97+
/>
98+
</MetaTitle>
99+
<ul
66100
css={{
67-
marginTop: 5,
101+
marginBottom: 10,
102+
103+
[media.greaterThan('small')]: {
104+
display: isActive ? 'block' : 'none',
105+
},
68106
}}>
69-
{createLink({
70-
item,
71-
location,
72-
onLinkClick,
73-
section,
74-
})}
75-
76-
{item.subitems && (
77-
<ul css={{marginLeft: 20}}>
78-
{item.subitems.map(subitem => (
79-
<li key={subitem.id}>
80-
{createLink({
81-
item: subitem,
82-
location,
83-
onLinkClick,
84-
section,
85-
})}
86-
</li>
87-
))}
88-
</ul>
89-
)}
90-
</li>
91-
))}
92-
</ul>
93-
</div>
94-
);
107+
{section.items.map(item => (
108+
<li
109+
key={item.id}
110+
css={{
111+
marginTop: 5,
112+
}}>
113+
{createLink({
114+
item,
115+
location,
116+
onLinkClick,
117+
section,
118+
isActive: activeItemId === item.id,
119+
})}
120+
121+
{item.subitems && (
122+
<ul css={{marginLeft: 20}}>
123+
{item.subitems.map(subitem => (
124+
<li key={subitem.id}>
125+
{createLink({
126+
item: subitem,
127+
location,
128+
onLinkClick,
129+
section,
130+
isActive: activeItemId === subitem.id,
131+
})}
132+
</li>
133+
))}
134+
</ul>
135+
)}
136+
</li>
137+
))}
138+
</ul>
139+
</div>
140+
);
141+
}
142+
}
143+
144+
const _getItemIds = items =>
145+
items
146+
.map(item => {
147+
let subItemIds = [];
148+
if (item.subitems) {
149+
subItemIds = item.subitems.map(subitem => subitem.id);
150+
}
151+
return [item.id, ...subItemIds];
152+
})
153+
.reduce((prev, current) => prev.concat(current));
154+
155+
const _getElementTopOffsetsById = ids =>
156+
ids
157+
.map(id => {
158+
const element = document.getElementById(id);
159+
if (!element) {
160+
return null;
161+
}
162+
return {
163+
id,
164+
offsetTop: element.offsetTop,
165+
};
166+
})
167+
.filter(item => item);
95168

96169
export default Section;

src/utils/createLink.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,22 @@ const createLinkDocs = ({item, location, section}) => {
6565
);
6666
};
6767

68-
const createLinkTutorial = ({item, location, onLinkClick, section}) => {
69-
const isActive = isItemActive(location, item);
68+
const createLinkTutorial = ({
69+
item,
70+
location,
71+
onLinkClick,
72+
section,
73+
isActive,
74+
}) => {
75+
const active =
76+
typeof isActive === 'boolean' ? isActive : isItemActive(location, item);
7077

7178
return (
7279
<Link
73-
css={[linkCss, isActive && activeLinkCss]}
80+
css={[linkCss, active && activeLinkCss]}
7481
onClick={onLinkClick}
7582
to={item.href}>
76-
{isActive && <span css={activeLinkBefore} />}
83+
{active && <span css={activeLinkBefore} />}
7784
{item.title}
7885
</Link>
7986
);

0 commit comments

Comments
 (0)