Skip to content

Commit 0569c6d

Browse files
outsource navigation effects
1 parent 87545a1 commit 0569c6d

File tree

4 files changed

+160
-100
lines changed

4 files changed

+160
-100
lines changed

src/components/UI/navigation/Navigation.tsx

Lines changed: 24 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useContext, useEffect, useRef, useState } from 'react'
1+
import { useContext, useRef, useState } from 'react'
22
import ButtonIcon from '../ButtonIcon'
3+
import DesktopSearch from './DesktopSearch'
4+
import MobileSearch from './MobileSearch'
35
import { HomeIcon } from './HomeIcon'
46
import { UserIcon } from './UserIcon'
5-
import MobileSearch from './MobileSearch'
67
import {
78
ContextDisableFocusTrap,
89
ContextFilteredStreamData,
@@ -14,9 +15,11 @@ import {
1415
ContextSearchText,
1516
ContextStreamData,
1617
} from '../../../App'
17-
import DesktopSearch from './DesktopSearch'
1818
import { getSearchFilter } from '../../../helper/getSearchFilter'
1919
import { setItemInStorage } from '../../../helper/setItemInStorage'
20+
import { useNavigationScrollY } from '../../../hooks/useNavigationScrollY'
21+
import { useCloseSearchResults } from '../../../hooks/useCloseSearchResults'
22+
import { useHideMobileSearch } from '../../../hooks/useHideMobileSearch'
2023

2124
const Navigation = () => {
2225
const contextScreenWidth = useContext(ContextScreenWidth)
@@ -253,103 +256,24 @@ const Navigation = () => {
253256
}, 0)
254257
}
255258

256-
useEffect(() => {
257-
let lastScrollY = window.scrollY
258-
let timer: NodeJS.Timeout
259-
260-
const handleScroll = () => {
261-
if (window.scrollY === 0) {
262-
setNavOpacity('opacity-100')
263-
} else if (window.scrollY < lastScrollY && !blockOpacity) {
264-
setNavOpacity('opacity-75')
265-
} else if (!blockOpacity) {
266-
setNavOpacity('opacity-95')
267-
}
268-
lastScrollY = window.scrollY
269-
270-
clearTimeout(timer)
271-
if (navOpacity !== 'opacity-100') {
272-
timer = setTimeout(() => {
273-
setNavOpacity('opacity-100')
274-
}, 500)
275-
}
276-
}
277-
278-
window.addEventListener('scroll', handleScroll)
279-
280-
return () => {
281-
window.removeEventListener('scroll', handleScroll)
282-
clearTimeout(timer)
283-
}
284-
}, [blockOpacity, navOpacity])
285-
286-
useEffect(() => {
287-
const handleClickOutside = (event: MouseEvent) => {
288-
if (
289-
((desktopSearchRef.current &&
290-
!desktopSearchRef.current.contains(event.target as Node)) ||
291-
(mobileSearchRef.current &&
292-
!mobileSearchRef.current.contains(
293-
event.target as Node
294-
))) &&
295-
searchResultsExpanded
296-
) {
297-
event.stopPropagation()
298-
setSearchResultsExpanded(false)
299-
}
300-
}
301-
302-
const handleEscape = (event: KeyboardEvent) => {
303-
if (
304-
((desktopSearchRef.current &&
305-
desktopSearchRef.current.contains(event.target as Node)) ||
306-
(mobileSearchRef.current &&
307-
mobileSearchRef.current.contains(
308-
event.target as Node
309-
))) &&
310-
searchResultsExpanded &&
311-
event.key === 'Escape'
312-
) {
313-
event.stopPropagation()
314-
setSearchResultsExpanded(false)
315-
if (!inputRef?.current?.onfocus) {
316-
if (
317-
contextScreenWidth === 'MOBILE' ||
318-
contextScreenWidth === 'TABLET_SMALL'
319-
) {
320-
buttonIconRef?.current?.focus()
321-
} else {
322-
userIconRef?.current?.focus()
323-
anchorRef?.current?.focus()
324-
}
325-
}
326-
}
327-
}
328-
329-
if (searchResultsExpanded) {
330-
document.addEventListener('keydown', handleEscape)
331-
document.addEventListener('mousedown', handleClickOutside)
332-
} else {
333-
document.removeEventListener('keydown', handleEscape)
334-
document.removeEventListener('mousedown', handleClickOutside)
335-
}
336-
337-
return () => {
338-
document.removeEventListener('keydown', handleEscape)
339-
document.removeEventListener('mousedown', handleClickOutside)
340-
}
341-
}, [contextScreenWidth, searchResultsExpanded])
342-
343-
useEffect(() => {
344-
if (
345-
(contextScreenWidth === 'MOBILE' ||
346-
contextScreenWidth === 'TABLET_SMALL') &&
347-
inputFocussed
348-
) {
349-
setHideSearch(false)
350-
setAriaPressed(true)
351-
}
352-
}, [contextScreenWidth, inputFocussed, setHideSearch])
259+
useNavigationScrollY(blockOpacity, navOpacity, setNavOpacity)
260+
useCloseSearchResults(
261+
anchorRef,
262+
buttonIconRef,
263+
contextScreenWidth,
264+
desktopSearchRef,
265+
inputRef,
266+
mobileSearchRef,
267+
searchResultsExpanded,
268+
setSearchResultsExpanded,
269+
userIconRef
270+
)
271+
useHideMobileSearch(
272+
contextScreenWidth,
273+
inputFocussed,
274+
setAriaPressed,
275+
setHideSearch
276+
)
353277

354278
return (
355279
<div className="sticky top-0 z-10" data-testid="navigation-container">

src/hooks/useCloseSearchResults.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useEffect } from 'react'
2+
3+
export const useCloseSearchResults = (
4+
anchorRef: React.RefObject<HTMLButtonElement>,
5+
buttonIconRef: React.RefObject<HTMLButtonElement>,
6+
contextScreenWidth: 'MOBILE' | 'TABLET_SMALL' | 'TABLET' | 'DESKTOP',
7+
desktopSearchRef: React.RefObject<HTMLDivElement>,
8+
inputRef: React.RefObject<HTMLInputElement>,
9+
mobileSearchRef: React.RefObject<HTMLDivElement>,
10+
searchResultsExpanded: boolean,
11+
setSearchResultsExpanded: (value: React.SetStateAction<boolean>) => void,
12+
userIconRef: React.RefObject<HTMLButtonElement>
13+
) => {
14+
useEffect(() => {
15+
const handleClickOutside = (event: MouseEvent) => {
16+
if (
17+
((desktopSearchRef.current &&
18+
!desktopSearchRef.current.contains(event.target as Node)) ||
19+
(mobileSearchRef.current &&
20+
!mobileSearchRef.current.contains(
21+
event.target as Node
22+
))) &&
23+
searchResultsExpanded
24+
) {
25+
event.stopPropagation()
26+
setSearchResultsExpanded(false)
27+
}
28+
}
29+
30+
const handleEscape = (event: KeyboardEvent) => {
31+
if (
32+
((desktopSearchRef.current &&
33+
desktopSearchRef.current.contains(event.target as Node)) ||
34+
(mobileSearchRef.current &&
35+
mobileSearchRef.current.contains(
36+
event.target as Node
37+
))) &&
38+
searchResultsExpanded &&
39+
event.key === 'Escape'
40+
) {
41+
event.stopPropagation()
42+
setSearchResultsExpanded(false)
43+
if (!inputRef?.current?.onfocus) {
44+
if (
45+
contextScreenWidth === 'MOBILE' ||
46+
contextScreenWidth === 'TABLET_SMALL'
47+
) {
48+
buttonIconRef?.current?.focus()
49+
} else {
50+
userIconRef?.current?.focus()
51+
anchorRef?.current?.focus()
52+
}
53+
}
54+
}
55+
}
56+
57+
if (searchResultsExpanded) {
58+
document.addEventListener('keydown', handleEscape)
59+
document.addEventListener('mousedown', handleClickOutside)
60+
} else {
61+
document.removeEventListener('keydown', handleEscape)
62+
document.removeEventListener('mousedown', handleClickOutside)
63+
}
64+
65+
return () => {
66+
document.removeEventListener('keydown', handleEscape)
67+
document.removeEventListener('mousedown', handleClickOutside)
68+
}
69+
}, [
70+
anchorRef,
71+
buttonIconRef,
72+
contextScreenWidth,
73+
desktopSearchRef,
74+
inputRef,
75+
mobileSearchRef,
76+
searchResultsExpanded,
77+
setSearchResultsExpanded,
78+
userIconRef,
79+
])
80+
}

src/hooks/useHideMobileSearch.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useEffect } from 'react'
2+
3+
export const useHideMobileSearch = (
4+
contextScreenWidth: 'MOBILE' | 'TABLET_SMALL' | 'TABLET' | 'DESKTOP',
5+
inputFocussed: boolean,
6+
setAriaPressed: (value: React.SetStateAction<boolean>) => void,
7+
setHideSearch: React.Dispatch<React.SetStateAction<boolean>>
8+
) => {
9+
useEffect(() => {
10+
if (
11+
(contextScreenWidth === 'MOBILE' ||
12+
contextScreenWidth === 'TABLET_SMALL') &&
13+
inputFocussed
14+
) {
15+
setHideSearch(false)
16+
setAriaPressed(true)
17+
}
18+
}, [contextScreenWidth, inputFocussed, setAriaPressed, setHideSearch])
19+
}

src/hooks/useNavigationScrollY.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useEffect } from 'react'
2+
3+
export const useNavigationScrollY = (
4+
blockOpacity: boolean,
5+
navOpacity: string,
6+
setNavOpacity: (value: React.SetStateAction<string>) => void
7+
) => {
8+
useEffect(() => {
9+
let lastScrollY = window.scrollY
10+
let timer: NodeJS.Timeout
11+
12+
const handleScroll = () => {
13+
if (window.scrollY === 0) {
14+
setNavOpacity('opacity-100')
15+
} else if (window.scrollY < lastScrollY && !blockOpacity) {
16+
setNavOpacity('opacity-75')
17+
} else if (!blockOpacity) {
18+
setNavOpacity('opacity-95')
19+
}
20+
lastScrollY = window.scrollY
21+
22+
clearTimeout(timer)
23+
if (navOpacity !== 'opacity-100') {
24+
timer = setTimeout(() => {
25+
setNavOpacity('opacity-100')
26+
}, 500)
27+
}
28+
}
29+
30+
window.addEventListener('scroll', handleScroll)
31+
32+
return () => {
33+
window.removeEventListener('scroll', handleScroll)
34+
clearTimeout(timer)
35+
}
36+
}, [blockOpacity, navOpacity, setNavOpacity])
37+
}

0 commit comments

Comments
 (0)