Trim down both versions

This commit is contained in:
Dan Abramov
2023-12-07 23:12:41 +00:00
parent cd9a92fcd7
commit 68cdd9897c
2 changed files with 19 additions and 165 deletions
+1 -14
View File
@@ -19,7 +19,6 @@ import Animated, {
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {TabBar} from './TabBar' import {TabBar} from './TabBar'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll' import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
@@ -246,7 +245,6 @@ let PagerTabBar = ({
onCurrentPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void
onSelect?: (index: number) => void onSelect?: (index: number) => void
}): React.ReactNode => { }): React.ReactNode => {
const {isMobile} = useWebMediaQueries()
const headerTransform = useAnimatedStyle(() => ({ const headerTransform = useAnimatedStyle(() => ({
transform: [ transform: [
{ {
@@ -257,10 +255,7 @@ let PagerTabBar = ({
return ( return (
<Animated.View <Animated.View
pointerEvents="box-none" pointerEvents="box-none"
style={[ style={[styles.tabBarMobile, headerTransform]}>
isMobile ? styles.tabBarMobile : styles.tabBarDesktop,
headerTransform,
]}>
<View onLayout={onHeaderOnlyLayout} pointerEvents="box-none"> <View onLayout={onHeaderOnlyLayout} pointerEvents="box-none">
{renderHeader?.()} {renderHeader?.()}
</View> </View>
@@ -333,14 +328,6 @@ const styles = StyleSheet.create({
left: 0, left: 0,
width: '100%', width: '100%',
}, },
tabBarDesktop: {
position: 'absolute',
zIndex: 1,
top: 0,
// @ts-ignore Web only -prf
left: 'calc(50% - 299px)',
width: 598,
},
}) })
function noop() { function noop() {
+18 -151
View File
@@ -1,28 +1,16 @@
import * as React from 'react' import * as React from 'react'
import { import {
LayoutChangeEvent,
FlatList, FlatList,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
View, View,
NativeScrollEvent, NativeScrollEvent,
} from 'react-native' } from 'react-native'
import Animated, { import {useSharedValue, runOnJS, useAnimatedRef} from 'react-native-reanimated'
useAnimatedStyle,
useSharedValue,
runOnJS,
runOnUI,
scrollTo,
useAnimatedRef,
AnimatedRef,
SharedValue,
} from 'react-native-reanimated'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {TabBar} from './TabBar' import {TabBar} from './TabBar'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll' import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {isWeb} from '#/platform/detection'
const SCROLLED_DOWN_LIMIT = 200 const SCROLLED_DOWN_LIMIT = 200
@@ -52,7 +40,6 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
children, children,
testID, testID,
items, items,
isHeaderReady,
renderHeader, renderHeader,
initialPage, initialPage,
onPageSelected, onPageSelected,
@@ -61,92 +48,25 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
ref, ref,
) { ) {
const [currentPage, setCurrentPage] = React.useState(0) const [currentPage, setCurrentPage] = React.useState(0)
const [tabBarHeight, setTabBarHeight] = React.useState(0)
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
const [isScrolledDown, setIsScrolledDown] = React.useState(false) const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const scrollY = useSharedValue(0) const scrollY = useSharedValue(0)
const headerHeight = headerOnlyHeight + tabBarHeight
// capture the header bar sizing
const onTabBarLayout = React.useCallback(
(evt: LayoutChangeEvent) => {
const height = evt.nativeEvent.layout.height
if (height > 0) {
// The rounding is necessary to prevent jumps on iOS
setTabBarHeight(Math.round(height))
}
},
[setTabBarHeight],
)
const onHeaderOnlyLayout = React.useCallback(
(evt: LayoutChangeEvent) => {
const height = evt.nativeEvent.layout.height
if (height > 0) {
// The rounding is necessary to prevent jumps on iOS
setHeaderOnlyHeight(Math.round(height))
}
},
[setHeaderOnlyHeight],
)
const renderTabBar = React.useCallback( const renderTabBar = React.useCallback(
(props: RenderTabBarFnProps) => { (props: RenderTabBarFnProps) => {
return ( return (
<PagerTabBar <PagerTabBar
headerOnlyHeight={headerOnlyHeight}
items={items} items={items}
isHeaderReady={isHeaderReady}
renderHeader={renderHeader} renderHeader={renderHeader}
currentPage={currentPage} currentPage={currentPage}
onCurrentPageSelected={onCurrentPageSelected} onCurrentPageSelected={onCurrentPageSelected}
onTabBarLayout={onTabBarLayout}
onHeaderOnlyLayout={onHeaderOnlyLayout}
onSelect={props.onSelect} onSelect={props.onSelect}
scrollY={scrollY}
testID={testID} testID={testID}
/> />
) )
}, },
[ [items, renderHeader, currentPage, onCurrentPageSelected, testID],
headerOnlyHeight,
items,
isHeaderReady,
renderHeader,
currentPage,
onCurrentPageSelected,
onTabBarLayout,
onHeaderOnlyLayout,
scrollY,
testID,
],
) )
const scrollRefs = useSharedValue<AnimatedRef<any>[]>([])
const registerRef = (scrollRef: AnimatedRef<any>, index: number) => {
scrollRefs.modify(refs => {
'worklet'
refs[index] = scrollRef
return refs
})
}
const lastForcedScrollY = useSharedValue(0)
const adjustScrollForOtherPages = () => {
'worklet'
const currentScrollY = scrollY.value
const forcedScrollY = Math.min(currentScrollY, headerOnlyHeight)
if (lastForcedScrollY.value !== forcedScrollY) {
lastForcedScrollY.value = forcedScrollY
const refs = scrollRefs.value
for (let i = 0; i < refs.length; i++) {
if (i !== currentPage) {
// This needs to run on the UI thread.
scrollTo(refs[i], 0, forcedScrollY, false)
}
}
}
}
const throttleTimeout = React.useRef<ReturnType<typeof setTimeout> | null>( const throttleTimeout = React.useRef<ReturnType<typeof setTimeout> | null>(
null, null,
) )
@@ -155,15 +75,13 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
throttleTimeout.current = setTimeout(() => { throttleTimeout.current = setTimeout(() => {
throttleTimeout.current = null throttleTimeout.current = null
runOnUI(adjustScrollForOtherPages)()
const nextIsScrolledDown = scrollY.value > SCROLLED_DOWN_LIMIT const nextIsScrolledDown = scrollY.value > SCROLLED_DOWN_LIMIT
if (isScrolledDown !== nextIsScrolledDown) { if (isScrolledDown !== nextIsScrolledDown) {
React.startTransition(() => { React.startTransition(() => {
setIsScrolledDown(nextIsScrolledDown) setIsScrolledDown(nextIsScrolledDown)
}) })
} }
}, 80 /* Sync often enough you're unlikely to catch it unsynced */) }, 80)
} }
}) })
@@ -201,17 +119,12 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
{toArray(children) {toArray(children)
.filter(Boolean) .filter(Boolean)
.map((child, i) => { .map((child, i) => {
const isReady =
isHeaderReady && headerOnlyHeight > 0 && tabBarHeight > 0
return ( return (
<View key={i} collapsable={false}> <View key={i} collapsable={false}>
<PagerItem <PagerItem
headerHeight={headerHeight}
isReady={isReady}
isFocused={i === currentPage} isFocused={i === currentPage}
isScrolledDown={isScrolledDown} isScrolledDown={isScrolledDown}
onScrollWorklet={i === currentPage ? onScrollWorklet : noop} onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
registerRef={(r: AnimatedRef<any>) => registerRef(r, i)}
renderTab={child} renderTab={child}
/> />
</View> </View>
@@ -224,62 +137,23 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
let PagerTabBar = ({ let PagerTabBar = ({
currentPage, currentPage,
headerOnlyHeight,
isHeaderReady,
items, items,
scrollY,
testID, testID,
renderHeader, renderHeader,
onHeaderOnlyLayout,
onTabBarLayout,
onCurrentPageSelected, onCurrentPageSelected,
onSelect, onSelect,
}: { }: {
currentPage: number currentPage: number
headerOnlyHeight: number
isHeaderReady: boolean
items: string[] items: string[]
testID?: string testID?: string
scrollY: SharedValue<number>
renderHeader?: () => JSX.Element renderHeader?: () => JSX.Element
onHeaderOnlyLayout: (e: LayoutChangeEvent) => void
onTabBarLayout: (e: LayoutChangeEvent) => void
onCurrentPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void
onSelect?: (index: number) => void onSelect?: (index: number) => void
}): React.ReactNode => { }): React.ReactNode => {
const {isMobile} = useWebMediaQueries()
const headerTransform = useAnimatedStyle(() => ({
transform: [
{
translateY: Math.min(Math.min(scrollY.value, headerOnlyHeight) * -1, 0),
},
],
}))
return ( return (
<> <>
<View <View style={styles.headerContainer}>{renderHeader?.()}</View>
onLayout={onHeaderOnlyLayout} <View style={styles.tabBarContainer}>
pointerEvents="box-none"
style={{
width: '100%',
marginLeft: 'auto',
marginRight: 'auto',
width: 598,
}}>
{renderHeader?.()}
</View>
<View
onLayout={onTabBarLayout}
style={[
{
// Render it immediately to measure it early since its size doesn't depend on the content.
// However, keep it invisible until the header above stabilizes in order to prevent jumps.
opacity: isHeaderReady ? 1 : 0,
pointerEvents: isHeaderReady ? 'auto' : 'none',
},
isWeb ? {position: 'sticky', top: 0, zIndex: 1} : null,
{width: '100%', marginLeft: 'auto', marginRight: 'auto', width: 598},
]}>
<TabBar <TabBar
testID={testID} testID={testID}
items={items} items={items}
@@ -294,36 +168,26 @@ let PagerTabBar = ({
PagerTabBar = React.memo(PagerTabBar) PagerTabBar = React.memo(PagerTabBar)
function PagerItem({ function PagerItem({
headerHeight,
isReady,
isFocused, isFocused,
isScrolledDown, isScrolledDown,
onScrollWorklet, onScrollWorklet,
renderTab, renderTab,
registerRef,
}: { }: {
headerHeight: number
isFocused: boolean isFocused: boolean
isReady: boolean
isScrolledDown: boolean isScrolledDown: boolean
registerRef: (scrollRef: AnimatedRef<any>) => void
onScrollWorklet: (e: NativeScrollEvent) => void onScrollWorklet: (e: NativeScrollEvent) => void
renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null
}) { }) {
const scrollElRef = useAnimatedRef() const scrollElRef = useAnimatedRef()
registerRef(scrollElRef)
const scrollHandler = React.useMemo( const scrollHandler = React.useMemo(
() => ({onScroll: onScrollWorklet}), () => ({onScroll: onScrollWorklet}),
[onScrollWorklet], [onScrollWorklet],
) )
if (renderTab == null) {
if (!isReady || renderTab == null) {
return null return null
} }
return renderTab({ return renderTab({
headerHeight: isWeb ? 0 : headerHeight, headerHeight: 0,
isFocused, isFocused,
isScrolledDown, isScrolledDown,
onScroll: scrollHandler, onScroll: scrollHandler,
@@ -334,16 +198,19 @@ function PagerItem({
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tabBarMobile: { headerContainer: {
zIndex: 1, marginLeft: 'auto',
top: 0, marginRight: 'auto',
left: 0, width: 598,
width: '100%',
}, },
tabBarDesktop: { tabBarContainer: {
zIndex: 1, // @ts-ignore web-only
position: 'sticky',
top: 0, top: 0,
// @ts-ignore Web only -prf zIndex: 1,
marginLeft: 'auto',
marginRight: 'auto',
width: 598,
}, },
}) })