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
|
import React from 'react'
import {View} from 'react-native'
import {flushSync} from 'react-dom'
import {s} from '#/lib/styles'
export interface RenderTabBarFnProps {
selectedPage: number
onSelect?: (index: number) => void
tabBarAnchor?: JSX.Element
}
export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
interface Props {
initialPage?: number
renderTabBar: RenderTabBarFn
onPageSelected?: (index: number) => void
}
export const Pager = React.forwardRef(function PagerImpl(
{
children,
initialPage = 0,
renderTabBar,
onPageSelected,
}: React.PropsWithChildren<Props>,
ref,
) {
const [selectedPage, setSelectedPage] = React.useState(initialPage)
const scrollYs = React.useRef<Array<number | null>>([])
const anchorRef = React.useRef(null)
React.useImperativeHandle(ref, () => ({
setPage: (index: number) => {
onTabBarSelect(index)
},
}))
const onTabBarSelect = React.useCallback(
(index: number) => {
const scrollY = window.scrollY
// We want to determine if the tabbar is already "sticking" at the top (in which
// case we should preserve and restore scroll), or if it is somewhere below in the
// viewport (in which case a scroll jump would be jarring). We determine this by
// measuring where the "anchor" element is (which we place just above the tabbar).
let anchorTop = anchorRef.current
? (anchorRef.current as Element).getBoundingClientRect().top
: -scrollY // If there's no anchor, treat the top of the page as one.
const isSticking = anchorTop <= 5 // This would be 0 if browser scrollTo() was reliable.
if (isSticking) {
scrollYs.current[selectedPage] = window.scrollY
} else {
scrollYs.current[selectedPage] = null
}
flushSync(() => {
setSelectedPage(index)
onPageSelected?.(index)
})
if (isSticking) {
const restoredScrollY = scrollYs.current[index]
if (restoredScrollY != null) {
window.scrollTo(0, restoredScrollY)
} else {
window.scrollTo(0, scrollY + anchorTop)
}
}
},
[selectedPage, setSelectedPage, onPageSelected],
)
return (
<View style={s.hContentRegion}>
{renderTabBar({
selectedPage,
tabBarAnchor: <View ref={anchorRef} />,
onSelect: e => onTabBarSelect(e),
})}
{React.Children.map(children, (child, i) => (
<View style={selectedPage === i ? s.flex1 : s.hidden} key={`page-${i}`}>
{child}
</View>
))}
</View>
)
})
|