@@ -36,25 +36,28 @@ function useScrollToHash({ stickyHeight }: { stickyHeight: number }) {
36
36
37
37
type IStickyMiniNavBar = {
38
38
title : string | JSX . Element ;
39
- linkUnderlineColor : string ;
40
39
stickyBackgroundColor ?: string ;
41
40
} ;
42
41
42
+ function getHeader ( ) {
43
+ return document . querySelector ( 'header' ) ;
44
+ }
45
+
43
46
export default function StickyMiniNavBar ( {
44
47
title,
45
- linkUnderlineColor,
46
48
stickyBackgroundColor,
47
49
} : IStickyMiniNavBar ) {
48
50
const [ headerHeight , setHeaderHeight ] = useState ( 0 ) ;
49
51
const [ isSticky , setIsSticky ] = useState ( false ) ;
52
+ const [ isScrollingUp , setIsScrollingUp ] = useState ( false ) ;
50
53
const [ sections , setSections ] = useState <
51
54
{ id : string ; label : string | null } [ ]
52
55
> ( [ ] ) ;
53
56
const [ passedElements , setPassedElements ] = useState <
54
57
Record < string , { isPassed : boolean ; isInView : boolean } >
55
58
> ( { } ) ;
56
59
const stickyDivRef = useRef < HTMLDivElement | null > ( null ) ;
57
- const hash = useScrollToHash ( {
60
+ useScrollToHash ( {
58
61
stickyHeight :
59
62
headerHeight +
60
63
( stickyDivRef . current ?. getBoundingClientRect ( ) . height ?? 0 ) ,
@@ -71,7 +74,7 @@ export default function StickyMiniNavBar({
71
74
} ) ;
72
75
setSections ( newSections ) ;
73
76
74
- const headerElement = document . querySelector ( 'header' ) ;
77
+ const headerElement = getHeader ( ) ;
75
78
76
79
const updateHeaderHeight = ( ) => {
77
80
if ( headerElement ) {
@@ -123,107 +126,128 @@ export default function StickyMiniNavBar({
123
126
} ;
124
127
} , [ sections ] ) ;
125
128
126
- const handleScroll = ( ) => {
127
- if ( stickyDivRef . current ) {
128
- const stickyOffset = stickyDivRef . current . getBoundingClientRect ( ) . top ;
129
- setIsSticky ( stickyOffset <= headerHeight ) ;
130
- }
131
- } ;
132
-
133
129
useEffect ( ( ) => {
130
+ return ( ) => {
131
+ const headerElement = getHeader ( ) ;
132
+ if ( headerElement ) {
133
+ headerElement . setAttribute ( 'style' , '' ) ;
134
+ }
135
+ } ;
136
+ } , [ ] ) ;
137
+ useEffect ( ( ) => {
138
+ let lastScrollY = 0 ;
139
+ const handleScroll = ( ) => {
140
+ if ( stickyDivRef . current ) {
141
+ const stickyOffset = stickyDivRef . current . getBoundingClientRect ( ) . top ;
142
+ setIsSticky ( stickyOffset <= headerHeight ) ;
143
+ }
144
+ const headerElement = getHeader ( ) ;
145
+ const currentScrollY = window . scrollY ;
146
+ const newIsScrollingUp = currentScrollY < lastScrollY ;
147
+ lastScrollY = currentScrollY ;
148
+ if ( headerElement ) {
149
+ if ( newIsScrollingUp ) {
150
+ headerElement . setAttribute ( 'style' , '' ) ;
151
+ } else {
152
+ headerElement . setAttribute ( 'style' , 'position: static;' ) ;
153
+ }
154
+ }
155
+ setIsScrollingUp ( newIsScrollingUp ) ;
156
+ } ;
134
157
window . addEventListener ( 'scroll' , handleScroll ) ;
135
158
136
159
return ( ) => {
137
160
window . removeEventListener ( 'scroll' , handleScroll ) ;
138
161
} ;
139
162
} , [ headerHeight ] ) ;
140
163
141
- const currentSectionId = [ ... sections ] . sort (
142
- ( { id : leftId } , { id : rightId } ) => {
143
- const leftPassedInfo = passedElements [ leftId ] ?? { } ;
144
- const rightPassedInfo = passedElements [ rightId ] ?? { } ;
145
- if ( `# ${ leftId } ` === hash && leftPassedInfo . isInView ) {
146
- return - 1 ;
147
- } else if ( `# ${ rightId } ` === hash && rightPassedInfo . isInView ) {
148
- return 1 ;
149
- } else if ( leftPassedInfo . isInView && ! rightPassedInfo . isInView ) {
150
- return - 1 ;
151
- } else if ( rightPassedInfo . isInView && ! leftPassedInfo . isInView ) {
152
- return 1 ;
153
- } else if ( rightPassedInfo . isPassed ) {
154
- return 1 ;
155
- } else if ( leftPassedInfo . isPassed ) {
156
- return - 1 ;
157
- } else {
158
- return 0 ;
164
+ let currentSectionId : string | undefined = undefined ;
165
+
166
+ // If a section header is visible then the header at the top of the page wins
167
+ for ( const section of sections ) {
168
+ const passedInfo = passedElements [ section . id ] ?? { } ;
169
+ if ( passedInfo . isInView ) {
170
+ currentSectionId = section . id ;
171
+ break ;
172
+ }
173
+ }
174
+
175
+ // If no section header is in view then pick the last section header
176
+ // that was scrolled passed
177
+ if ( currentSectionId === undefined ) {
178
+ for ( const section of sections ) {
179
+ const passedInfo = passedElements [ section . id ] ?? { } ;
180
+ if ( passedInfo . isPassed ) {
181
+ currentSectionId = section . id ;
159
182
}
160
183
}
161
- ) [ 0 ] ?. id ;
184
+ }
162
185
163
186
return (
164
- < Container
187
+ < div
165
188
className = { classNames (
166
- 'container' ,
167
189
styles . container ,
168
190
isSticky ? styles . containerSticky : ''
169
191
) }
170
192
style = { {
171
- top : headerHeight ,
193
+ top : isScrollingUp ? headerHeight : '0px' ,
172
194
backgroundColor :
173
195
isSticky && stickyBackgroundColor ? stickyBackgroundColor : undefined ,
174
196
} }
175
197
>
176
- < Row className = "justify-content-center" >
177
- < Col md = { 11 } >
178
- < nav
179
- ref = { stickyDivRef }
180
- className = { classnames ( 'd-flex flex-row' , styles . nav ) }
181
- style = { {
182
- gap : '40px' ,
183
- height : '49px' ,
184
- } }
185
- >
186
- { isSticky && (
187
- < Link
188
- className = { classnames ( styles . stickyHeader ) }
189
- // # is removed from link so we have to use onclick to scroll to the top
190
- to = "#"
191
- onClick = { e => {
192
- e . preventDefault ( ) ;
193
- window . scrollTo ( {
194
- top : 0 ,
195
- behavior : 'smooth' ,
196
- } ) ;
197
- } }
198
- >
199
- { title }
200
- </ Link >
201
- ) }
202
- < div
203
- className = "d-flex flex-row"
198
+ < Container >
199
+ < Row className = "justify-content-center" >
200
+ < Col md = { 11 } >
201
+ < nav
202
+ ref = { stickyDivRef }
203
+ className = { classnames ( 'd-flex flex-row' , styles . nav ) }
204
204
style = { {
205
- gap : '10px' ,
205
+ gap : '40px' ,
206
+ height : '49px' ,
206
207
} }
207
208
>
208
- { sections . map ( ( { id, label } ) => {
209
- const isInSection = currentSectionId === id ;
210
- return (
211
- < Link
212
- key = { id }
213
- to = { `#${ id } ` }
214
- className = { classNames (
215
- styles . stickySection ,
216
- isInSection ? styles . stickySectionSelected : ''
217
- ) }
218
- >
219
- { label }
220
- </ Link >
221
- ) ;
222
- } ) }
223
- </ div >
224
- </ nav >
225
- </ Col >
226
- </ Row >
227
- </ Container >
209
+ { isSticky && (
210
+ < Link
211
+ className = { classnames ( styles . stickyHeader ) }
212
+ // # is removed from link so we have to use onclick to scroll to the top
213
+ to = "#"
214
+ onClick = { e => {
215
+ e . preventDefault ( ) ;
216
+ window . scrollTo ( {
217
+ top : 0 ,
218
+ behavior : 'smooth' ,
219
+ } ) ;
220
+ } }
221
+ >
222
+ { title }
223
+ </ Link >
224
+ ) }
225
+ < div
226
+ className = "d-flex flex-row"
227
+ style = { {
228
+ gap : '10px' ,
229
+ } }
230
+ >
231
+ { sections . map ( ( { id, label } ) => {
232
+ const isInSection = currentSectionId === id ;
233
+ return (
234
+ < Link
235
+ key = { id }
236
+ to = { `#${ id } ` }
237
+ className = { classNames (
238
+ styles . stickySection ,
239
+ isInSection ? styles . stickySectionSelected : ''
240
+ ) }
241
+ >
242
+ { label }
243
+ </ Link >
244
+ ) ;
245
+ } ) }
246
+ </ div >
247
+ </ nav >
248
+ </ Col >
249
+ </ Row >
250
+ </ Container >
251
+ </ div >
228
252
) ;
229
253
}
0 commit comments