mirror of
https://github.com/reactjs/react.dev.git
synced 2026-02-23 20:23:08 +00:00
Tweak sidebar to update navigation highlight on scroll
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
class Section extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const Section = ({
|
||||
createLink,
|
||||
isActive,
|
||||
location,
|
||||
onLinkClick,
|
||||
onSectionTitleClick,
|
||||
section,
|
||||
}) => (
|
||||
<div>
|
||||
<MetaTitle
|
||||
onClick={onSectionTitleClick}
|
||||
cssProps={{
|
||||
marginTop: 10,
|
||||
this.state = {
|
||||
activeItemId: null,
|
||||
itemTopOffsets: [],
|
||||
};
|
||||
|
||||
[media.greaterThan('small')]: {
|
||||
color: isActive ? colors.text : colors.subtle,
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
}
|
||||
|
||||
':hover': {
|
||||
color: colors.text,
|
||||
},
|
||||
},
|
||||
}}>
|
||||
{section.title}
|
||||
<ChevronSvg
|
||||
cssProps={{
|
||||
marginLeft: 7,
|
||||
transform: isActive ? 'rotateX(180deg)' : 'rotateX(0deg)',
|
||||
transition: 'transform 0.2s ease',
|
||||
componentDidMount() {
|
||||
const {section} = this.props;
|
||||
|
||||
[media.lessThan('small')]: {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</MetaTitle>
|
||||
<ul
|
||||
css={{
|
||||
marginBottom: 10,
|
||||
const itemIds = _getItemIds(section.items);
|
||||
this.setState({
|
||||
itemTopOffsets: _getElementTopOffsetsById(itemIds),
|
||||
});
|
||||
|
||||
[media.greaterThan('small')]: {
|
||||
display: isActive ? 'block' : 'none',
|
||||
},
|
||||
}}>
|
||||
{section.items.map(item => (
|
||||
<li
|
||||
key={item.id}
|
||||
css={{
|
||||
marginTop: 5,
|
||||
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,
|
||||
},
|
||||
},
|
||||
}}>
|
||||
{createLink({
|
||||
item,
|
||||
location,
|
||||
onLinkClick,
|
||||
section,
|
||||
})}
|
||||
{section.title}
|
||||
<ChevronSvg
|
||||
cssProps={{
|
||||
marginLeft: 7,
|
||||
transform: isActive ? 'rotateX(180deg)' : 'rotateX(0deg)',
|
||||
transition: 'transform 0.2s ease',
|
||||
|
||||
{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>
|
||||
);
|
||||
[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}
|
||||
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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user