|
9 | 9 |
|
10 | 10 | 'use strict'; |
11 | 11 |
|
12 | | -import React from 'react'; |
| 12 | +import React, {Component} from 'react'; |
13 | 13 | import {colors, media} from 'theme'; |
14 | 14 | import MetaTitle from '../MetaTitle'; |
15 | 15 | import ChevronSvg from '../ChevronSvg'; |
16 | 16 |
|
17 | | -// TODO Update isActive link as document scrolls past anchor tags |
18 | | -// Maybe used 'hashchange' along with 'scroll' to set/update active links |
19 | | - |
20 | | -const Section = ({ |
21 | | - createLink, |
22 | | - isActive, |
23 | | - location, |
24 | | - onLinkClick, |
25 | | - onSectionTitleClick, |
26 | | - section, |
27 | | -}) => ( |
28 | | - <div> |
29 | | - <MetaTitle |
30 | | - onClick={onSectionTitleClick} |
31 | | - cssProps={{ |
32 | | - marginTop: 10, |
33 | | - |
34 | | - [media.greaterThan('small')]: { |
35 | | - color: isActive ? colors.text : colors.subtle, |
36 | | - |
37 | | - ':hover': { |
38 | | - color: colors.text, |
39 | | - }, |
40 | | - }, |
41 | | - }}> |
42 | | - {section.title} |
43 | | - <ChevronSvg |
44 | | - cssProps={{ |
45 | | - marginLeft: 7, |
46 | | - transform: isActive ? 'rotateX(180deg)' : 'rotateX(0deg)', |
47 | | - transition: 'transform 0.2s ease', |
48 | | - |
49 | | - [media.lessThan('small')]: { |
50 | | - display: 'none', |
51 | | - }, |
52 | | - }} |
53 | | - /> |
54 | | - </MetaTitle> |
55 | | - <ul |
56 | | - css={{ |
57 | | - marginBottom: 10, |
58 | | - |
59 | | - [media.greaterThan('small')]: { |
60 | | - display: isActive ? 'block' : 'none', |
61 | | - }, |
62 | | - }}> |
63 | | - {section.items.map(item => ( |
64 | | - <li |
65 | | - key={item.id} |
| 17 | +class Section extends Component { |
| 18 | + constructor(props, context) { |
| 19 | + super(props, context); |
| 20 | + |
| 21 | + this.state = { |
| 22 | + activeItemId: null, |
| 23 | + itemTopOffsets: [], |
| 24 | + }; |
| 25 | + |
| 26 | + this.handleScroll = this.handleScroll.bind(this); |
| 27 | + } |
| 28 | + |
| 29 | + componentDidMount() { |
| 30 | + const {section} = this.props; |
| 31 | + |
| 32 | + const itemIds = _getItemIds(section.items); |
| 33 | + this.setState({ |
| 34 | + itemTopOffsets: _getElementTopOffsetsById(itemIds), |
| 35 | + }); |
| 36 | + |
| 37 | + window.addEventListener('scroll', this.handleScroll); |
| 38 | + } |
| 39 | + |
| 40 | + componentWillUnmount() { |
| 41 | + window.removeEventListener('scroll', this.handleScroll); |
| 42 | + } |
| 43 | + |
| 44 | + handleScroll() { |
| 45 | + const {itemTopOffsets} = this.state; |
| 46 | + const item = itemTopOffsets.find((itemTopOffset, i) => { |
| 47 | + const nextItemTopOffset = itemTopOffsets[i + 1]; |
| 48 | + if (nextItemTopOffset) { |
| 49 | + return ( |
| 50 | + window.scrollY >= itemTopOffset.offsetTop && |
| 51 | + window.scrollY < nextItemTopOffset.offsetTop |
| 52 | + ); |
| 53 | + } |
| 54 | + return window.scrollY >= itemTopOffset.offsetTop; |
| 55 | + }); |
| 56 | + this.setState({ |
| 57 | + activeItemId: item ? item.id : null, |
| 58 | + }); |
| 59 | + } |
| 60 | + |
| 61 | + render() { |
| 62 | + const { |
| 63 | + createLink, |
| 64 | + isActive, |
| 65 | + location, |
| 66 | + onLinkClick, |
| 67 | + onSectionTitleClick, |
| 68 | + section, |
| 69 | + } = this.props; |
| 70 | + const {activeItemId} = this.state; |
| 71 | + return ( |
| 72 | + <div> |
| 73 | + <MetaTitle |
| 74 | + onClick={onSectionTitleClick} |
| 75 | + cssProps={{ |
| 76 | + marginTop: 10, |
| 77 | + |
| 78 | + [media.greaterThan('small')]: { |
| 79 | + color: isActive ? colors.text : colors.subtle, |
| 80 | + |
| 81 | + ':hover': { |
| 82 | + color: colors.text, |
| 83 | + }, |
| 84 | + }, |
| 85 | + }}> |
| 86 | + {section.title} |
| 87 | + <ChevronSvg |
| 88 | + cssProps={{ |
| 89 | + marginLeft: 7, |
| 90 | + transform: isActive ? 'rotateX(180deg)' : 'rotateX(0deg)', |
| 91 | + transition: 'transform 0.2s ease', |
| 92 | + |
| 93 | + [media.lessThan('small')]: { |
| 94 | + display: 'none', |
| 95 | + }, |
| 96 | + }} |
| 97 | + /> |
| 98 | + </MetaTitle> |
| 99 | + <ul |
66 | 100 | css={{ |
67 | | - marginTop: 5, |
| 101 | + marginBottom: 10, |
| 102 | + |
| 103 | + [media.greaterThan('small')]: { |
| 104 | + display: isActive ? 'block' : 'none', |
| 105 | + }, |
68 | 106 | }}> |
69 | | - {createLink({ |
70 | | - item, |
71 | | - location, |
72 | | - onLinkClick, |
73 | | - section, |
74 | | - })} |
75 | | - |
76 | | - {item.subitems && ( |
77 | | - <ul css={{marginLeft: 20}}> |
78 | | - {item.subitems.map(subitem => ( |
79 | | - <li key={subitem.id}> |
80 | | - {createLink({ |
81 | | - item: subitem, |
82 | | - location, |
83 | | - onLinkClick, |
84 | | - section, |
85 | | - })} |
86 | | - </li> |
87 | | - ))} |
88 | | - </ul> |
89 | | - )} |
90 | | - </li> |
91 | | - ))} |
92 | | - </ul> |
93 | | - </div> |
94 | | -); |
| 107 | + {section.items.map(item => ( |
| 108 | + <li |
| 109 | + key={item.id} |
| 110 | + css={{ |
| 111 | + marginTop: 5, |
| 112 | + }}> |
| 113 | + {createLink({ |
| 114 | + item, |
| 115 | + location, |
| 116 | + onLinkClick, |
| 117 | + section, |
| 118 | + isActive: activeItemId === item.id, |
| 119 | + })} |
| 120 | + |
| 121 | + {item.subitems && ( |
| 122 | + <ul css={{marginLeft: 20}}> |
| 123 | + {item.subitems.map(subitem => ( |
| 124 | + <li key={subitem.id}> |
| 125 | + {createLink({ |
| 126 | + item: subitem, |
| 127 | + location, |
| 128 | + onLinkClick, |
| 129 | + section, |
| 130 | + isActive: activeItemId === subitem.id, |
| 131 | + })} |
| 132 | + </li> |
| 133 | + ))} |
| 134 | + </ul> |
| 135 | + )} |
| 136 | + </li> |
| 137 | + ))} |
| 138 | + </ul> |
| 139 | + </div> |
| 140 | + ); |
| 141 | + } |
| 142 | +} |
| 143 | + |
| 144 | +const _getItemIds = items => |
| 145 | + items |
| 146 | + .map(item => { |
| 147 | + let subItemIds = []; |
| 148 | + if (item.subitems) { |
| 149 | + subItemIds = item.subitems.map(subitem => subitem.id); |
| 150 | + } |
| 151 | + return [item.id, ...subItemIds]; |
| 152 | + }) |
| 153 | + .reduce((prev, current) => prev.concat(current)); |
| 154 | + |
| 155 | +const _getElementTopOffsetsById = ids => |
| 156 | + ids |
| 157 | + .map(id => { |
| 158 | + const element = document.getElementById(id); |
| 159 | + if (!element) { |
| 160 | + return null; |
| 161 | + } |
| 162 | + return { |
| 163 | + id, |
| 164 | + offsetTop: element.offsetTop, |
| 165 | + }; |
| 166 | + }) |
| 167 | + .filter(item => item); |
95 | 168 |
|
96 | 169 | export default Section; |
0 commit comments