Skip to content

Commit

Permalink
Replace the <Tabs> component with the more effective <PagerWithHeader…
Browse files Browse the repository at this point in the history
…> component
  • Loading branch information
pfrazee committed Nov 1, 2023
1 parent a0a8f08 commit dce0fe9
Show file tree
Hide file tree
Showing 12 changed files with 476 additions and 677 deletions.
6 changes: 0 additions & 6 deletions src/view/com/feeds/FeedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,6 @@ export const FeedPage = observer(function FeedPageImpl({
store.shell.openComposer({})
}, [store, track])

const onPressTryAgain = React.useCallback(() => {
feed.refresh()
}, [feed])

const onPressLoadLatest = React.useCallback(() => {
scrollToTop()
feed.refresh()
Expand Down Expand Up @@ -179,10 +175,8 @@ export const FeedPage = observer(function FeedPageImpl({
<View testID={testID} style={s.h100pct}>
<Feed
testID={testID ? `${testID}-feed` : undefined}
key="default"
feed={feed}
scrollElRef={scrollElRef}
onPressTryAgain={onPressTryAgain}
onScroll={onMainScroll}
scrollEventThrottle={100}
renderEmptyState={renderEmptyState}
Expand Down
58 changes: 34 additions & 24 deletions src/view/com/lists/ListItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'

const HEADER_ITEM = {_reactKey: '__header__'}
const LOADING_ITEM = {_reactKey: '__loading__'}
Expand All @@ -32,19 +33,25 @@ export const ListItems = observer(function ListItemsImpl({
list,
style,
scrollElRef,
onScroll,
onPressTryAgain,
renderHeader,
renderEmptyState,
testID,
scrollEventThrottle,
headerOffset = 0,
desktopFixedHeightOffset,
}: {
list: ListModel
style?: StyleProp<ViewStyle>
scrollElRef?: MutableRefObject<RNFlatList<any> | null>
onScroll?: OnScrollCb
onPressTryAgain?: () => void
renderHeader: () => JSX.Element
renderEmptyState: () => JSX.Element
testID?: string
scrollEventThrottle?: number
headerOffset?: number
desktopFixedHeightOffset?: number
}) {
const pal = usePalette('default')
Expand Down Expand Up @@ -203,30 +210,33 @@ export const ListItems = observer(function ListItemsImpl({

return (
<View testID={testID} style={style}>
{data.length > 0 && (
<FlatList
testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef}
data={data}
keyExtractor={(item: any) => item._reactKey}
renderItem={renderItem}
ListFooterComponent={Footer}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
contentContainerStyle={s.contentContainer}
onEndReached={onEndReached}
onEndReachedThreshold={0.6}
removeClippedSubviews={true}
// @ts-ignore our .web version only -prf
desktopFixedHeight={desktopFixedHeightOffset || true}
/>
)}
<FlatList
testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef}
data={data}
keyExtractor={(item: any) => item._reactKey}
renderItem={renderItem}
ListFooterComponent={Footer}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
progressViewOffset={headerOffset}
/>
}
contentContainerStyle={s.contentContainer}
style={{paddingTop: headerOffset}}
onScroll={onScroll}
onEndReached={onEndReached}
onEndReachedThreshold={0.6}
scrollEventThrottle={scrollEventThrottle}
removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}}
// @ts-ignore our .web version only -prf
desktopFixedHeight={desktopFixedHeightOffset || true}
/>
</View>
)
})
6 changes: 2 additions & 4 deletions src/view/com/pager/Pager.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,14 @@ export const Pager = React.forwardRef(function PagerImpl(
)

return (
<View>
<View style={s.hContentRegion}>
{tabBarPosition === 'top' &&
renderTabBar({
selectedPage,
onSelect: onTabBarSelect,
})}
{React.Children.map(children, (child, i) => (
<View
style={selectedPage === i ? undefined : s.hidden}
key={`page-${i}`}>
<View style={selectedPage === i ? s.flex1 : s.hidden} key={`page-${i}`}>
{child}
</View>
))}
Expand Down
177 changes: 177 additions & 0 deletions src/view/com/pager/PagerWithHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import * as React from 'react'
import {
LayoutChangeEvent,
NativeScrollEvent,
NativeSyntheticEvent,
StyleSheet,
} from 'react-native'
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {TabBar} from './TabBar'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'

interface PagerWithHeaderChildParams {
headerHeight: number
onScroll: OnScrollCb
}

export interface PagerWithHeaderProps {
testID?: string
children: (((props: PagerWithHeaderChildParams) => JSX.Element) | null)[]
items: string[]
renderHeader?: () => JSX.Element
initialPage?: number
onPageSelected?: (index: number) => void
onCurrentPageSelected?: () => void
}
export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
function PageWithHeaderImpl(
{
children,
testID,
items,
renderHeader,
initialPage,
onPageSelected,
onCurrentPageSelected,
}: PagerWithHeaderProps,
ref,
) {
const {isMobile} = useWebMediaQueries()
const scrollYs = React.useRef<Record<number, number>>({})
const [currentPage, setCurrentPage] = React.useState(0)
const scrollY = useSharedValue(scrollYs.current[currentPage] || 0)
const [tabBarHeight, setTabBarHeight] = React.useState(0)
const [headerHeight, setHeaderHeight] = React.useState(0)
const headerTransform = useAnimatedStyle(
() => ({
transform: [
{
translateY: clampedInvert(
scrollY.value,
headerHeight,
tabBarHeight,
),
},
],
}),
[scrollY, headerHeight, tabBarHeight],
)

const onTabBarLayout = React.useCallback(
(evt: LayoutChangeEvent) => {
setTabBarHeight(evt.nativeEvent.layout.height)
},
[setTabBarHeight],
)

const onHeaderLayout = React.useCallback(
(evt: LayoutChangeEvent) => {
setHeaderHeight(evt.nativeEvent.layout.height)
},
[setHeaderHeight],
)

const renderTabBar = React.useCallback(
(props: RenderTabBarFnProps) => {
return (
<Animated.View
onLayout={onHeaderLayout}
style={[
isMobile ? styles.tabBarMobile : styles.tabBarDesktop,
headerTransform,
]}>
{renderHeader?.()}
<TabBar
items={items}
selectedPage={props.selectedPage}
onSelect={props.onSelect}
onPressSelected={onCurrentPageSelected}
onLayout={onTabBarLayout}
/>
</Animated.View>
)
},
[
items,
renderHeader,
headerTransform,
onCurrentPageSelected,
isMobile,
onTabBarLayout,
onHeaderLayout,
],
)

const childProps = React.useMemo<PagerWithHeaderChildParams>(() => {
return {
headerHeight,
onScroll(event: NativeSyntheticEvent<NativeScrollEvent>) {
scrollY.value = event.nativeEvent.contentOffset.y
scrollYs.current[currentPage] = event.nativeEvent.contentOffset.y
},
}
}, [headerHeight, scrollY, scrollYs, currentPage])

const onPageSelectedInner = React.useCallback(
(index: number) => {
setCurrentPage(index)
scrollY.value = withTiming(scrollYs.current[index] || 0, {
duration: 300,
})
onPageSelected?.(index)
},
[scrollY, onPageSelected, setCurrentPage, scrollYs],
)

return (
<Pager
ref={ref}
testID={testID}
initialPage={initialPage}
onPageSelected={onPageSelectedInner}
renderTabBar={renderTabBar}
tabBarPosition="top">
{children.filter(Boolean).map(child => {
// TODO- how can keys get passed here?
if (child) {
return child(childProps)
}
return null
})}
</Pager>
)
},
)

const styles = StyleSheet.create({
tabBarMobile: {
position: 'absolute',
zIndex: 1,
top: 0,
left: 0,
width: '100%',
},
tabBarDesktop: {
position: 'absolute',
zIndex: 1,
top: 0,
// @ts-ignore Web only -prf
left: 'calc(50% - 299px)',
width: 598,
},
})

function clampedInvert(
value: number,
headerHeight: number,
tabBarHeight: number,
) {
'worklet'
return Math.min(Math.max(value, 0), headerHeight - tabBarHeight) * -1
}
4 changes: 3 additions & 1 deletion src/view/com/pager/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface TabBarProps {
indicatorColor?: string
onSelect?: (index: number) => void
onPressSelected?: () => void
onLayout?: (evt: LayoutChangeEvent) => void
}

export function TabBar({
Expand All @@ -23,6 +24,7 @@ export function TabBar({
indicatorColor,
onSelect,
onPressSelected,
onLayout,
}: TabBarProps) {
const pal = usePalette('default')
const scrollElRef = useRef<ScrollView>(null)
Expand Down Expand Up @@ -66,7 +68,7 @@ export function TabBar({
const styles = isDesktop || isTablet ? desktopStyles : mobileStyles

return (
<View testID={testID} style={[pal.view, styles.outer]}>
<View testID={testID} style={[pal.view, styles.outer]} onLayout={onLayout}>
<DraggableScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
Expand Down
6 changes: 4 additions & 2 deletions src/view/com/posts/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export const Feed = observer(function Feed({
feed,
style,
scrollElRef,
onPressTryAgain,
onScroll,
scrollEventThrottle,
renderEmptyState,
Expand All @@ -43,7 +42,6 @@ export const Feed = observer(function Feed({
feed: PostsFeedModel
style?: StyleProp<ViewStyle>
scrollElRef?: MutableRefObject<FlatList<any> | null>
onPressTryAgain?: () => void
onScroll?: OnScrollCb
scrollEventThrottle?: number
renderEmptyState: () => JSX.Element
Expand Down Expand Up @@ -108,6 +106,10 @@ export const Feed = observer(function Feed({
}
}, [feed, track])

const onPressTryAgain = React.useCallback(() => {
feed.refresh()
}, [feed])

const onPressRetryLoadMore = React.useCallback(() => {
feed.retryLoadMore()
}, [feed])
Expand Down
2 changes: 1 addition & 1 deletion src/view/com/profile/ProfileSubpageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const ProfileSubpageHeader = observer(function HeaderImpl({
}, [store, avatar])

return (
<CenteredView>
<CenteredView style={pal.view}>
{isMobile && (
<View
style={[
Expand Down
Loading

0 comments on commit dce0fe9

Please sign in to comment.