Bluesky app fork with some witchin' additions 馃挮
1import {Children, type JSX, useImperativeHandle, useRef, useState} from 'react'
2import {View} from 'react-native'
3import {flushSync} from 'react-dom'
4
5import {s} from '#/lib/styles'
6import {atoms as a} from '#/alf'
7
8export interface PagerRef {
9 setPage: (index: number) => void
10}
11
12export interface RenderTabBarFnProps {
13 selectedPage: number
14 onSelect?: (index: number) => void
15 tabBarAnchor?: JSX.Element
16}
17export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
18
19interface Props {
20 ref?: React.Ref<PagerRef>
21 initialPage?: number
22 renderTabBar: RenderTabBarFn
23 onPageSelected?: (index: number) => void
24}
25
26export function Pager({
27 ref,
28 children,
29 initialPage = 0,
30 renderTabBar,
31 onPageSelected,
32}: React.PropsWithChildren<Props>) {
33 const [selectedPage, setSelectedPage] = useState(initialPage)
34 const scrollYs = useRef<Array<number | null>>([])
35 const anchorRef = useRef(null)
36
37 useImperativeHandle(ref, () => ({
38 setPage: (index: number) => {
39 onTabBarSelect(index)
40 },
41 }))
42
43 const onTabBarSelect = (index: number) => {
44 const scrollY = window.scrollY
45 // We want to determine if the tabbar is already "sticking" at the top (in which
46 // case we should preserve and restore scroll), or if it is somewhere below in the
47 // viewport (in which case a scroll jump would be jarring). We determine this by
48 // measuring where the "anchor" element is (which we place just above the tabbar).
49 let anchorTop = anchorRef.current
50 ? (anchorRef.current as Element).getBoundingClientRect().top
51 : -scrollY // If there's no anchor, treat the top of the page as one.
52 const isSticking = anchorTop <= 5 // This would be 0 if browser scrollTo() was reliable.
53
54 if (isSticking) {
55 scrollYs.current[selectedPage] = window.scrollY
56 } else {
57 scrollYs.current[selectedPage] = null
58 }
59 flushSync(() => {
60 setSelectedPage(index)
61 onPageSelected?.(index)
62 })
63 if (isSticking) {
64 const restoredScrollY = scrollYs.current[index]
65 if (restoredScrollY != null) {
66 window.scrollTo(0, restoredScrollY)
67 } else {
68 window.scrollTo(0, scrollY + anchorTop)
69 }
70 }
71 }
72
73 return (
74 <View style={s.hContentRegion}>
75 {renderTabBar({
76 selectedPage,
77 tabBarAnchor: <View ref={anchorRef} />,
78 onSelect: e => onTabBarSelect(e),
79 })}
80 {Children.map(children, (child, i) => (
81 <View
82 style={selectedPage === i ? a.flex_1 : a.hidden}
83 key={`page-${i}`}>
84 {child}
85 </View>
86 ))}
87 </View>
88 )
89}