mirror of
https://github.com/reactjs/react.dev.git
synced 2026-02-25 23:05:23 +00:00
Merge pull request #48 from jxom/update-sidebar-highlight-on-scroll
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
|
||||
|
||||
@@ -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
Normal file
103
src/templates/components/Sidebar/ScrollSyncSection.js
Normal file
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -27,6 +27,7 @@ const Tutorial = ({data, location}) => {
|
||||
|
||||
return (
|
||||
<MarkdownPage
|
||||
enableScrollSync
|
||||
createLink={createLinkTutorial}
|
||||
location={location}
|
||||
markdownRemark={data.markdownRemark}
|
||||
|
||||
@@ -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]}
|
||||
|
||||
Reference in New Issue
Block a user