Browse Source

PR Changes

main
jxom 8 years ago
parent
commit
e9d0884a43
  1. 7
      src/components/MarkdownPage/MarkdownPage.js
  2. 108
      src/templates/components/Sidebar/ScrollSyncSection.js
  3. 228
      src/templates/components/Sidebar/Section.js
  4. 9
      src/templates/components/Sidebar/Sidebar.js
  5. 1
      src/templates/tutorial.js
  6. 37
      src/utils/createLink.js
  7. 2
      src/utils/isItemActive.js

7
src/components/MarkdownPage/MarkdownPage.js

@ -25,7 +25,8 @@ import createOgUrl from 'utils/createOgUrl';
const MarkdownPage = ({ const MarkdownPage = ({
authors, authors,
createLink, createLink,
date, date,
enableScrollSync,
ogDescription, ogDescription,
location, location,
markdownRemark, markdownRemark,
@ -98,6 +99,7 @@ const MarkdownPage = ({
<div css={sharedStyles.articleLayout.sidebar}> <div css={sharedStyles.articleLayout.sidebar}>
<StickyResponsiveSidebar <StickyResponsiveSidebar
enableScrollSync={enableScrollSync}
createLink={createLink} createLink={createLink}
defaultActiveSection={findSectionForPath( defaultActiveSection={findSectionForPath(
location.pathname, location.pathname,
@ -131,7 +133,8 @@ MarkdownPage.defaultProps = {
MarkdownPage.propTypes = { MarkdownPage.propTypes = {
authors: PropTypes.array.isRequired, authors: PropTypes.array.isRequired,
createLink: PropTypes.func.isRequired, createLink: PropTypes.func.isRequired,
date: PropTypes.string, date: PropTypes.string,
enableScrollSync: PropTypes.bool,
location: PropTypes.object.isRequired, location: PropTypes.object.isRequired,
markdownRemark: PropTypes.object.isRequired, markdownRemark: PropTypes.object.isRequired,
sectionList: PropTypes.array.isRequired, sectionList: PropTypes.array.isRequired,

108
src/templates/components/Sidebar/ScrollSyncSection.js

@ -0,0 +1,108 @@
/**
* 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;

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

@ -9,161 +9,89 @@
'use strict'; 'use strict';
import React, {Component} from 'react';
import {colors, media} from 'theme'; import {colors, media} from 'theme';
import isItemActive from 'utils/isItemActive';
import MetaTitle from '../MetaTitle'; import MetaTitle from '../MetaTitle';
import ChevronSvg from '../ChevronSvg'; import ChevronSvg from '../ChevronSvg';
class Section extends Component { const Section = ({
constructor(props, context) { activeItemId,
super(props, context); createLink,
isActive,
this.state = { isScrollSync,
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={{
marginBottom: 10, marginTop: 5,
[media.greaterThan('small')]: {
display: isActive ? 'block' : 'none',
},
}}> }}>
{section.items.map(item => ( {createLink({
<li isActive: isScrollSync ? activeItemId === item.id : isItemActive(location, item),
key={item.id} item,
css={{ location,
marginTop: 5, onLinkClick,
}}> section,
{createLink({ })}
item,
location, {item.subitems && (
onLinkClick, <ul css={{marginLeft: 20}}>
section, {item.subitems.map(subitem => (
isActive: activeItemId === item.id, <li key={subitem.id}>
})} {createLink({
isActive: isScrollSync ? activeItemId === subitem.id : isItemActive(location, subitem),
{item.subitems && ( item: subitem,
<ul css={{marginLeft: 20}}> location,
{item.subitems.map(subitem => ( onLinkClick,
<li key={subitem.id}> section,
{createLink({ })}
item: subitem, </li>
location, ))}
onLinkClick, </ul>
section, )}
isActive: activeItemId === subitem.id, </li>
})} ))}
</li> </ul>
))} </div>
</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;

9
src/templates/components/Sidebar/Sidebar.js

@ -12,6 +12,7 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import Flex from 'components/Flex'; import Flex from 'components/Flex';
import Section from './Section'; import Section from './Section';
import ScrollSyncSection from './ScrollSyncSection';
import {media} from 'theme'; import {media} from 'theme';
class Sidebar extends Component { class Sidebar extends Component {
@ -24,8 +25,10 @@ class Sidebar extends Component {
} }
render() { render() {
const {closeParentMenu, createLink, location, sectionList} = this.props; const {closeParentMenu, createLink, enableScrollSync, location, sectionList} = this.props;
const {activeSection} = this.state; const {activeSection} = this.state;
const SectionComponent = enableScrollSync ? ScrollSyncSection : Section;
return ( return (
<Flex <Flex
@ -46,7 +49,7 @@ class Sidebar extends Component {
}, },
}}> }}>
{sectionList.map((section, index) => ( {sectionList.map((section, index) => (
<Section <SectionComponent
createLink={createLink} createLink={createLink}
isActive={activeSection === section || sectionList.length === 1} isActive={activeSection === section || sectionList.length === 1}
key={index} key={index}

1
src/templates/tutorial.js

@ -27,6 +27,7 @@ const Tutorial = ({data, location}) => {
return ( return (
<MarkdownPage <MarkdownPage
enableScrollSync
createLink={createLinkTutorial} createLink={createLinkTutorial}
location={location} location={location}
markdownRemark={data.markdownRemark} markdownRemark={data.markdownRemark}

37
src/utils/createLink.js

@ -18,19 +18,16 @@ import isItemActive from 'utils/isItemActive';
import slugify from 'utils/slugify'; import slugify from 'utils/slugify';
import {colors, media} from 'theme'; import {colors, media} from 'theme';
const createLinkBlog = ({item, location, section, isActive}) => { const createLinkBlog = ({isActive, item, section}) => {
const active =
typeof isActive === 'boolean' ? isActive : isItemActive(location, item);
return ( return (
<Link css={[linkCss, active && activeLinkCss]} to={item.id}> <Link css={[linkCss, isActive && activeLinkCss]} to={item.id}>
{active && <span css={activeLinkBefore} />} {isActive && <span css={activeLinkBefore} />}
{item.title} {item.title}
</Link> </Link>
); );
}; };
const createLinkCommunity = ({item, location, section}) => { const createLinkCommunity = ({isActive, item, section}) => {
if (item.href) { if (item.href) {
return ( return (
<a css={[linkCss]} href={item.href} target="_blank" rel="noopener"> <a css={[linkCss]} href={item.href} target="_blank" rel="noopener">
@ -47,42 +44,30 @@ const createLinkCommunity = ({item, location, section}) => {
); );
} }
return createLinkDocs({ return createLinkDocs({
isActive,
item, item,
location,
section, section,
}); });
}; };
const createLinkDocs = ({item, location, section, isActive}) => { const createLinkDocs = ({isActive, item, section}) => {
const active =
typeof isActive === 'boolean' ? isActive : isItemActive(location, item);
return ( return (
<Link <Link
css={[linkCss, active && activeLinkCss]} css={[linkCss, isActive && activeLinkCss]}
to={slugify(item.id, section.directory)}> to={slugify(item.id, section.directory)}>
{active && <span css={activeLinkBefore} />} {isActive && <span css={activeLinkBefore} />}
{item.title} {item.title}
</Link> </Link>
); );
}; };
const createLinkTutorial = ({ const createLinkTutorial = ({isActive, item, onLinkClick, section}) => {
item,
location,
onLinkClick,
section,
isActive,
}) => {
const active =
typeof isActive === 'boolean' ? isActive : isItemActive(location, item);
return ( return (
<Link <Link
css={[linkCss, active && activeLinkCss]} css={[linkCss, isActive && activeLinkCss]}
onClick={onLinkClick} onClick={onLinkClick}
to={item.href}> to={item.href}>
{active && <span css={activeLinkBefore} />} {isActive && <span css={activeLinkBefore} />}
{item.title} {item.title}
</Link> </Link>
); );

2
src/utils/isItemActive.js

@ -20,7 +20,7 @@ const toAnchor = (href = '') => {
// This comment should not be true anymore since we're using 300 redirects // This comment should not be true anymore since we're using 300 redirects
const isItemActive = (location, item) => { const isItemActive = (location, item) => {
if (location.hash) { if (location.hash) {
if (item.href) { if (item.href) {
return location.hash === toAnchor(item.href); return location.hash === toAnchor(item.href);
} }

Loading…
Cancel
Save