-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtab-navigation.tsx
111 lines (93 loc) · 3.64 KB
/
tab-navigation.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import React, { useEffect, useRef, useState } from 'react'
import { ResultListGroup } from './types'
import { useTranslation } from 'react-i18next'
interface TabNavigationProps {
resultList: ResultListGroup[]
sectionRefs: HTMLDivElement[]
}
// Stores the location of previous Y coordinates for observed elements
const previousYArray: { [key: string]: number } = {}
// Stores the location of previous ratios for observed elements
const previousRatioArray: { [key: string]: number } = {}
export const TabNavigation = ({ resultList, sectionRefs }: TabNavigationProps) => {
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState<string>('')
const activeTabRef = useRef<string>('')
activeTabRef.current = activeTab
const [tabOrder, setTabOrder] = useState<string[]>([])
const tabOrderRef = useRef<string[]>([])
tabOrderRef.current = tabOrder
const observerCallback = (entries: IntersectionObserverEntry[]) => {
const tabChanges: string[] = []
entries.forEach((entry: IntersectionObserverEntry) => {
const currentY = entry.boundingClientRect.y
const currentRatio = entry.intersectionRatio
const isIntersecting = entry.isIntersecting
const targetId = entry.target?.getAttribute('id') || ''
const targetTabOrder = tabOrderRef.current.indexOf(targetId)
const previousY = previousYArray[targetId] || 0
const previousRatio = previousRatioArray[targetId] || 0
if (currentY < previousY) {
// scrolling down
if (currentRatio > previousRatio && isIntersecting) {
// Target is entering the screen from the bottom
tabChanges.push(targetId)
}
} else if (currentY > previousY) {
// scrolling up
if (currentRatio > previousRatio && isIntersecting) {
// target is entering screen from the top
tabChanges.push(targetId)
} else if (activeTabRef.current === targetId) {
// target is leaving screen from the bottom
tabChanges.push(tabOrderRef.current[targetTabOrder - 1])
}
}
// Save current components y position and ratio so we can use these the determine the scrolling direction
previousYArray[targetId] = currentY
previousRatioArray[targetId] = currentRatio
})
// In case of multiple intersections, choose the first one in the array
if (tabChanges.length > 0) {
setActiveTab(tabChanges[0])
}
}
useEffect(() => {
setTabOrder(resultList.map(resultGroup => resultGroup.groupName))
setActiveTab(resultList[0].groupName)
}, [resultList])
useEffect(() => {
const observerOptions = {
root: null,
rootMargin: '0px',
threshold: 1
}
const observer = new IntersectionObserver(observerCallback, observerOptions)
sectionRefs.forEach(ref => observer.observe(ref))
return () => observer.disconnect()
}, [sectionRefs])
if (!resultList.length) {
return null
}
return (
<div className="tab-navigation">
{resultList.map(({ groupName, students }) => (
<a
className={activeTab === groupName ? 'active' : undefined}
onClick={() => scrollTo(groupName)}
key={groupName}
>
{t(`results.groups.${groupName}.navigation`)} ({students.length})
</a>
))}
</div>
)
}
const scrollTo = (elementId: string) => {
const element = document.getElementById(elementId)
const header = document.querySelector('.tab-navigation')
if (!element || !header) return
let scrollPoint = element.getBoundingClientRect().top + window.scrollY - 20
scrollPoint -= header.getBoundingClientRect().height
window.scrollTo({ top: scrollPoint, behavior: 'auto' })
}