Port scroll improvements

This commit is contained in:
Tom MacWright
2016-03-03 11:49:57 -08:00
parent 8fcfc1152b
commit 50e96d261f
4 changed files with 38 additions and 37 deletions

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset='utf-8' /> <meta charset='utf-8' />
<meta http-equiv='X-UA-Compatible' content='IE=11' /> <meta http-equiv='X-UA-Compatible' content='IE=11' />
<title>Mapbox</title> <title>Docbox</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<link href='css/base.css' rel='stylesheet' /> <link href='css/base.css' rel='stylesheet' />
<link href='css/style.css' rel='stylesheet' /> <link href='css/style.css' rel='stylesheet' />

View File

@ -68,25 +68,40 @@ var App = React.createClass({
}, },
componentDidMount() { componentDidMount() {
this.mediaQueryChanged(); this.mediaQueryChanged();
this.onScroll = debounce(this._onScroll, 100);
document.addEventListener('scroll', this.onScroll);
this._onScroll();
},
_onScroll() {
var sections = document.querySelectorAll('div.section');
if (!sections.length) return;
for (var i = 0; i < sections.length; i++) {
var rect = sections[i].getBoundingClientRect();
if (rect.bottom > 0) {
this.setState({
activeSection: sections[i].title
});
return;
}
}
}, },
mediaQueryChanged() { mediaQueryChanged() {
var queries = { this.setState({
mobile: this.state.mqls.mobile.matches, queries: {
tablet: this.state.mqls.tablet.matches, mobile: this.state.mqls.mobile.matches,
desktop: this.state.mqls.desktop.matches tablet: this.state.mqls.tablet.matches,
}; desktop: this.state.mqls.desktop.matches
this.setState({ queries }); }
});
}, },
componentWillUnmount() { componentWillUnmount() {
Object.keys(this.state.mqls).forEach(key => Object.keys(this.state.mqls).forEach(key =>
this.state.mqls[key].removeListener(this.mediaQueryChanged)); this.state.mqls[key].removeListener(this.mediaQueryChanged));
document.body.removeEventListener('scroll', this.onScroll);
}, },
onChangeLanguage(language) { onChangeLanguage(language) {
this.setState({ language }); this.setState({ language });
}, },
onSpy(activeSection) {
this.setState({ activeSection });
},
componentDidUpdate(_, prevState) { componentDidUpdate(_, prevState) {
if (prevState.activeSection !== this.state.activeSection) { if (prevState.activeSection !== this.state.activeSection) {
// when the section changes, replace the hash // when the section changes, replace the hash
@ -127,7 +142,6 @@ var App = React.createClass({
<Content <Content
ast={ast} ast={ast}
queries={queries} queries={queries}
onSpy={this.onSpy}
language={this.state.language}/> language={this.state.language}/>
</div> </div>
@ -143,10 +157,10 @@ var App = React.createClass({
{/* Header */ } {/* Header */ }
<div className={`fill-gray fixed-top ${queries.tablet ? 'pad1y pad2x col6' : 'pad0 width16'}`}> <div className={`fill-gray fixed-top ${queries.tablet ? 'pad1y pad2x col6' : 'pad0 width16'}`}>
<a href='/' className='active space-top1 space-left1 pin-topleft icon round fill-red dark pad0'></a> <a href='/' className='active space-top1 space-left1 pin-topleft icon round fill-blue dark pad0 mapbox'></a>
<div className={`strong small pad0 ${queries.mobile && 'space-left3'} ${queries.tablet ? 'space-left2' : 'space-left4 line-height15' }`}> <div className={`strong small pad0 ${queries.mobile && 'space-left3'} ${queries.tablet ? 'space-left2' : 'space-left4 line-height15' }`}>
{queries.desktop ? 'Example API Documentation' : {queries.desktop ? 'API Documentation' :
queries.mobile ? 'API Docs' : 'Example API Docs'} queries.mobile ? 'API Docs' : 'API Docs'}
</div> </div>
{queries.tablet && <div> {queries.tablet && <div>
<button <button

View File

@ -1,6 +1,9 @@
import React from 'react'; import React from 'react';
import Section from './section'; import Section from './section';
import PureRenderMixin from 'react-pure-render/mixin'; import PureRenderMixin from 'react-pure-render/mixin';
import GithubSlugger from 'github-slugger';
let slugger = new GithubSlugger();
let slug = title => { slugger.reset(); return slugger.slug(title); };
function highlightTokens(str) { function highlightTokens(str) {
return str.replace(/{[\w_]+}/g, return str.replace(/{[\w_]+}/g,
@ -92,7 +95,7 @@ function chunkifyAST(ast, language) {
left.push(node); left.push(node);
} }
}); });
return { left, right, title, preview }; return { left, right, title, preview, slug: slug(title) };
}); });
} }
@ -100,13 +103,11 @@ var Content = React.createClass({
mixins: [PureRenderMixin], mixins: [PureRenderMixin],
propTypes: { propTypes: {
ast: React.PropTypes.object.isRequired, ast: React.PropTypes.object.isRequired,
language: React.PropTypes.string.isRequired, language: React.PropTypes.string.isRequired
onSpy: React.PropTypes.func.isRequired
}, },
render() { render() {
return (<div className='clearfix'> return (<div className='clearfix'>
{chunkifyAST(this.props.ast, this.props.language).map((chunk, i) => <Section {chunkifyAST(this.props.ast, this.props.language).map((chunk, i) => <Section
onSpy={this.props.onSpy}
chunk={chunk} chunk={chunk}
key={i} />)} key={i} />)}
</div>); </div>);

View File

@ -2,7 +2,6 @@ import React from 'react';
import remark from 'remark'; import remark from 'remark';
import remarkHTML from 'remark-html'; import remarkHTML from 'remark-html';
import remarkHighlight from 'remark-highlight.js'; import remarkHighlight from 'remark-highlight.js';
import Waypoint from 'react-waypoint';
import PureRenderMixin from 'react-pure-render/mixin'; import PureRenderMixin from 'react-pure-render/mixin';
function renderHighlighted(nodes) { function renderHighlighted(nodes) {
@ -22,33 +21,20 @@ function renderHighlighted(nodes) {
var Section = React.createClass({ var Section = React.createClass({
mixins: [PureRenderMixin], mixins: [PureRenderMixin],
propTypes: { propTypes: {
chunk: React.PropTypes.object.isRequired, chunk: React.PropTypes.object.isRequired
onSpy: React.PropTypes.func.isRequired
},
waypointEnterFromAbove(e, direction) {
if (direction === 'above') {
this.props.onSpy(this.props.chunk.title);
}
},
waypointEnterFromBelow(e, direction) {
if (direction === 'below') {
this.props.onSpy(this.props.chunk.title);
}
}, },
render() { render() {
let { chunk } = this.props; let { chunk } = this.props;
let { left, right, preview } = chunk; let { left, right, preview } = chunk;
return (<div className={`contain clearfix ${preview ? 'preview' : ''}`}> return (<div
<Waypoint onEnter={this.waypointEnterFromBelow} /> title={chunk.title}
className={`section pad2y contain clearfix ${preview ? 'preview' : ''}`}>
<div <div
className='col6 pad2x prose clip' className='col6 pad2x prose clip'
dangerouslySetInnerHTML={renderHighlighted(left)} /> dangerouslySetInnerHTML={renderHighlighted(left)} />
{(right.length > 0) && <div {right.length > 0 && <div
className='col6 pad2 prose dark space-top5 clip keyline-top fill-dark' className='col6 pad2 prose dark space-top5 clip keyline-top fill-dark'
dangerouslySetInnerHTML={renderHighlighted(right)} />} dangerouslySetInnerHTML={renderHighlighted(right)} />}
<div className='pin-bottom'>
<Waypoint onEnter={this.waypointEnterFromAbove} />
</div>
</div>); </div>);
} }
}); });