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