Skip to content

Commit 0cc2eba

Browse files
Revert "refactor: outsource useeffect"
1 parent adf6c2d commit 0cc2eba

26 files changed

+456
-610
lines changed

src/App.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { Dispatch, SetStateAction, createContext, useState } from 'react'
1+
import {
2+
Dispatch,
3+
SetStateAction,
4+
createContext,
5+
useEffect,
6+
useState,
7+
} from 'react'
28
import Footer from './components/UI/footer/Footer'
39
import StreamFeed from './components/streamFeed/StreamFeed'
410
import Navigation from './components/UI/navigation/Navigation'
511
import { StreamProps } from './types/StreamProps'
12+
import { getEnglishLanguageName } from './helper/getEnglishLanguageName'
613
import { getItemFromStorage } from './helper/getItemFromStorage'
7-
import { useDocumentTitle } from './hooks/useDocumentTitle'
814
import { useScreenWidth } from './hooks/useScreenWidth'
915

1016
export const ContextScreenWidth = createContext<
@@ -79,7 +85,11 @@ const App = () => {
7985
const [inputFocussed, setInputFocussed] = useState(false)
8086
const [hideSearch, setHideSearch] = useState(true)
8187

82-
useDocumentTitle(language, seoSearchText)
88+
useEffect(() => {
89+
document.title = `Twitch-App | ${getEnglishLanguageName(
90+
language
91+
)} Livestreams${seoSearchText ? ` | ${seoSearchText}` : ''}`
92+
}, [language, seoSearchText])
8393

8494
return (
8595
<div className="min-w-64 min-h-screen bg-zinc-800">

src/components/UI/navigation/DesktopSearch.tsx

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import { forwardRef, useContext, useRef } from 'react'
2-
import './search.css'
1+
import { forwardRef, useContext, useEffect, useRef } from 'react'
32
import {
43
ContextDisableFocusTrap,
54
ContextFocusInput,
65
ContextSearchResults,
76
ContextSearchText,
87
} from '../../../App'
8+
import { SearchProps } from '../../../types/SearchProps'
99
import Icon from '../Icon'
1010
import SearchResultSuggestion from './SearchResultSuggestion'
11-
import { SearchProps } from '../../../types/SearchProps'
12-
import { useFocusInput } from '../../../hooks/useFocusInput'
13-
import { useFocusTrapSearch } from '../../../hooks/useFocusTrapSearch'
11+
import './search.css'
1412

1513
const DesktopSearch = forwardRef<HTMLDivElement, SearchProps>(
1614
(
@@ -68,14 +66,68 @@ const DesktopSearch = forwardRef<HTMLDivElement, SearchProps>(
6866
const buttonRef = useRef<HTMLButtonElement | null>(null)
6967
const searchResultsRef = useRef<HTMLDivElement | null>(null)
7068

71-
useFocusTrapSearch(
72-
buttonRef,
69+
useEffect(() => {
70+
const handleFocusTrap = (e: KeyboardEvent) => {
71+
if (
72+
searchText.length === 0 ||
73+
focusTrapDisabled ||
74+
e.key !== 'Tab'
75+
)
76+
return
77+
78+
const focusableElements = [
79+
inputRef?.current,
80+
buttonRef.current,
81+
...(searchResultsRef.current
82+
? Array.from(
83+
searchResultsRef.current.querySelectorAll(
84+
'button'
85+
)
86+
)
87+
: []),
88+
].filter((el) => el !== null) as (
89+
| HTMLInputElement
90+
| HTMLButtonElement
91+
)[]
92+
93+
if (focusableElements.length === 0) return
94+
95+
const firstElement = focusableElements[0]
96+
const lastElement =
97+
focusableElements[focusableElements.length - 1]
98+
99+
if (e.shiftKey) {
100+
if (document.activeElement === firstElement) {
101+
e.preventDefault()
102+
lastElement.focus()
103+
}
104+
} else {
105+
if (document.activeElement === lastElement) {
106+
e.preventDefault()
107+
firstElement.focus()
108+
}
109+
}
110+
}
111+
112+
document.addEventListener('keydown', handleFocusTrap)
113+
114+
return () => {
115+
document.removeEventListener('keydown', handleFocusTrap)
116+
}
117+
}, [
73118
focusTrapDisabled,
74119
inputRef,
75-
searchResultsRef,
76-
searchText
77-
)
78-
useFocusInput(inputFocussed, inputRef, setInputFocussed)
120+
searchResults,
121+
searchResultsExpanded,
122+
searchText,
123+
])
124+
125+
useEffect(() => {
126+
if (inputFocussed && inputRef?.current) {
127+
inputRef.current.focus()
128+
setInputFocussed(false)
129+
}
130+
}, [inputFocussed, inputRef, setInputFocussed])
79131

80132
return (
81133
<div

src/components/UI/navigation/MobileSearch.tsx

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import { forwardRef, useContext, useRef } from 'react'
2-
import './search.css'
1+
import { forwardRef, useContext, useEffect, useRef } from 'react'
32
import {
43
ContextDisableFocusTrap,
54
ContextFocusInput,
65
ContextSearchResults,
76
ContextSearchText,
87
} from '../../../App'
8+
import { SearchProps } from '../../../types/SearchProps'
99
import Icon from '../Icon'
1010
import SearchResultSuggestion from './SearchResultSuggestion'
11-
import { SearchProps } from '../../../types/SearchProps'
12-
import { useFocusInput } from '../../../hooks/useFocusInput'
13-
import { useFocusTrapSearch } from '../../../hooks/useFocusTrapSearch'
11+
import './search.css'
1412

1513
const MobileSearch = forwardRef<HTMLDivElement, SearchProps>(
1614
(
@@ -68,14 +66,62 @@ const MobileSearch = forwardRef<HTMLDivElement, SearchProps>(
6866
const buttonRef = useRef<HTMLButtonElement | null>(null)
6967
const searchResultsRef = useRef<HTMLDivElement | null>(null)
7068

71-
useFocusTrapSearch(
72-
buttonRef,
69+
useEffect(() => {
70+
const handleFocusTrap = (e: KeyboardEvent) => {
71+
if (e.key !== 'Tab' || focusTrapDisabled) return
72+
73+
const focusableElements = [
74+
searchMobileRef?.current,
75+
buttonRef.current,
76+
...(searchResultsRef.current
77+
? Array.from(
78+
searchResultsRef.current.querySelectorAll(
79+
'button'
80+
)
81+
)
82+
: []),
83+
].filter((el) => el !== null) as (
84+
| HTMLInputElement
85+
| HTMLButtonElement
86+
)[]
87+
88+
if (focusableElements.length === 0) return
89+
90+
const firstElement = focusableElements[0]
91+
const lastElement =
92+
focusableElements[focusableElements.length - 1]
93+
94+
if (e.shiftKey) {
95+
if (document.activeElement === firstElement) {
96+
e.preventDefault()
97+
lastElement.focus()
98+
}
99+
} else {
100+
if (document.activeElement === lastElement) {
101+
e.preventDefault()
102+
firstElement.focus()
103+
}
104+
}
105+
}
106+
107+
document.addEventListener('keydown', handleFocusTrap)
108+
109+
return () => {
110+
document.removeEventListener('keydown', handleFocusTrap)
111+
}
112+
}, [
73113
focusTrapDisabled,
74114
searchMobileRef,
75-
searchResultsRef,
76-
searchText
77-
)
78-
useFocusInput(inputFocussed, searchMobileRef, setInputFocussed)
115+
searchResults,
116+
searchResultsExpanded,
117+
])
118+
119+
useEffect(() => {
120+
if (inputFocussed && searchMobileRef?.current) {
121+
searchMobileRef.current.focus()
122+
setInputFocussed(false)
123+
}
124+
}, [inputFocussed, searchMobileRef, setInputFocussed])
79125

80126
return (
81127
<>

src/components/UI/navigation/Navigation.tsx

Lines changed: 100 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { useContext, useRef, useState } from 'react'
1+
import { useContext, useEffect, useRef, useState } from 'react'
22
import ButtonIcon from '../ButtonIcon'
3-
import DesktopSearch from './DesktopSearch'
4-
import MobileSearch from './MobileSearch'
53
import { HomeIcon } from './HomeIcon'
64
import { UserIcon } from './UserIcon'
5+
import MobileSearch from './MobileSearch'
76
import {
87
ContextDisableFocusTrap,
98
ContextFilteredStreamData,
@@ -15,11 +14,9 @@ import {
1514
ContextSearchText,
1615
ContextStreamData,
1716
} 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'
2320

2421
const Navigation = () => {
2522
const contextScreenWidth = useContext(ContextScreenWidth)
@@ -256,24 +253,103 @@ const Navigation = () => {
256253
}, 0)
257254
}
258255

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-
)
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])
277353

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

0 commit comments

Comments
 (0)