|
|
@ -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
|
|
|
|
|
|
|
|
const Section = ({ |
|
|
|
createLink, |
|
|
|
isActive, |
|
|
|
location, |
|
|
|
onLinkClick, |
|
|
|
onSectionTitleClick, |
|
|
|
section, |
|
|
|
}) => ( |
|
|
|
<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={{ |
|
|
|
marginBottom: 10, |
|
|
|
|
|
|
|
[media.greaterThan('small')]: { |
|
|
|
display: isActive ? 'block' : 'none', |
|
|
|
}, |
|
|
|
}}> |
|
|
|
{section.items.map(item => ( |
|
|
|
<li |
|
|
|
key={item.id} |
|
|
|
class Section extends Component { |
|
|
|
constructor(props, context) { |
|
|
|
super(props, context); |
|
|
|
|
|
|
|
this.state = { |
|
|
|
activeItemId: null, |
|
|
|
itemTopOffsets: [], |
|
|
|
}; |
|
|
|
|
|
|
|
this.handleScroll = this.handleScroll.bind(this); |
|
|
|
} |
|
|
|
|
|
|
|
componentDidMount() { |
|
|
|
const {section} = this.props; |
|
|
|
|
|
|
|
const itemIds = _getItemIds(section.items); |
|
|
|
this.setState({ |
|
|
|
itemTopOffsets: _getElementTopOffsetsById(itemIds), |
|
|
|
}); |
|
|
|
|
|
|
|
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, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}}> |
|
|
|
{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={{ |
|
|
|
marginTop: 5, |
|
|
|
marginBottom: 10, |
|
|
|
|
|
|
|
[media.greaterThan('small')]: { |
|
|
|
display: isActive ? 'block' : 'none', |
|
|
|
}, |
|
|
|
}}> |
|
|
|
{createLink({ |
|
|
|
item, |
|
|
|
location, |
|
|
|
onLinkClick, |
|
|
|
section, |
|
|
|
})} |
|
|
|
|
|
|
|
{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> |
|
|
|
); |
|
|
|
{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; |
|
|
|