Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 89 lines 2.6 kB view raw
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}