From ad9da82612a33a796bcb2c679dbff357f4829dc8 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 16 Mar 2023 19:54:32 -0500 Subject: [PATCH] Add tab bar to pager --- src/view/com/util/Pager.tsx | 72 +++++++++++++++++++++ src/view/com/util/TabBar.tsx | 119 +++++++++++++++++++++++++++++++++++ src/view/screens/Home.tsx | 14 ++--- 3 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 src/view/com/util/Pager.tsx create mode 100644 src/view/com/util/TabBar.tsx diff --git a/src/view/com/util/Pager.tsx b/src/view/com/util/Pager.tsx new file mode 100644 index 00000000..1a3ff642 --- /dev/null +++ b/src/view/com/util/Pager.tsx @@ -0,0 +1,72 @@ +import React from 'react' +import {Animated, StyleSheet, View} from 'react-native' +import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view' +import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' +import {TabBar} from './TabBar' + +export type PageSelectedEvent = PagerViewOnPageSelectedEvent +const AnimatedPagerView = Animated.createAnimatedComponent(PagerView) + +interface Props { + onPageSelected?: (e: PageSelectedEvent) => void +} +export const Pager = ({ + children, + onPageSelected, +}: React.PropsWithChildren) => { + const [selectedPage, setSelectedPage] = React.useState(0) + const position = useAnimatedValue(0) + const offset = useAnimatedValue(0) + const pagerView = React.useRef() + + const onPageSelectedInner = React.useCallback( + (e: PageSelectedEvent) => { + setSelectedPage(e.nativeEvent.position) + onPageSelected?.(e) + }, + [setSelectedPage, onPageSelected], + ) + + const onTabBarSelect = React.useCallback( + (index: number) => { + pagerView.current?.setPage(index) + }, + [pagerView], + ) + + return ( + + + + {children} + + + ) +} + +const styles = StyleSheet.create({ + tabBar: { + flexDirection: 'row', + }, +}) diff --git a/src/view/com/util/TabBar.tsx b/src/view/com/util/TabBar.tsx new file mode 100644 index 00000000..3a823e42 --- /dev/null +++ b/src/view/com/util/TabBar.tsx @@ -0,0 +1,119 @@ +import React, {createRef, useState, useMemo} from 'react' +import { + Animated, + StyleSheet, + TouchableWithoutFeedback, + View, +} from 'react-native' +import {Text} from './text/Text' +import {usePalette} from 'lib/hooks/usePalette' + +interface Layout { + x: number + width: number +} + +export function TabBar({ + selectedPage, + items, + position, + offset, + onSelect, +}: { + selectedPage: number + items: string[] + position: Animated.Value + offset: Animated.Value + onSelect?: (index: number) => void +}) { + const pal = usePalette('default') + const [itemLayouts, setItemLayouts] = useState( + items.map(() => ({x: 0, width: 0})), + ) + const itemRefs = useMemo( + () => Array.from({length: items.length}).map(() => createRef()), + [items.length], + ) + const panX = Animated.add(position, offset) + + const underlineStyle = { + backgroundColor: pal.colors.text, + left: panX.interpolate({ + inputRange: items.map((_item, i) => i), + outputRange: itemLayouts.map(l => l.x), + }), + width: panX.interpolate({ + inputRange: items.map((_item, i) => i), + outputRange: itemLayouts.map(l => l.width), + }), + } + + const onLayout = () => { + const promises = [] + for (let i = 0; i < items.length; i++) { + promises.push( + new Promise(resolve => { + itemRefs[i].current?.measure( + (x: number, _y: number, width: number) => { + resolve({x, width}) + }, + ) + }), + ) + } + Promise.all(promises).then((layouts: Layout[]) => { + setItemLayouts(layouts) + }) + } + + const onPressItem = (index: number) => { + onSelect?.(index) + } + + return ( + + + {items.map((item, i) => { + const selected = i === selectedPage + return ( + onPressItem(i)}> + + + {item} + + + + ) + })} + + ) +} + +const styles = StyleSheet.create({ + outer: { + flexDirection: 'row', + paddingHorizontal: 14, + }, + item: { + paddingTop: 8, + paddingBottom: 12, + marginRight: 14, + paddingHorizontal: 10, + }, + label: { + fontWeight: '600', + }, + labelSelected: { + fontWeight: '600', + }, + underline: { + position: 'absolute', + height: 4, + bottom: 0, + }, +}) diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 49915cd0..6c708e2f 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -18,7 +18,7 @@ import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {useAnalytics} from 'lib/analytics' import {ComposeIcon2} from 'lib/icons' -import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view' +import {Pager, PageSelectedEvent} from 'view/com/util/Pager' import {Text} from 'view/com/util/text/Text' const HEADER_HEIGHT = 42 @@ -27,7 +27,7 @@ type Props = NativeStackScreenProps export const HomeScreen = withAuthRequired((_opts: Props) => { const store = useStores() const onPageSelected = React.useCallback( - (e: PagerViewOnPageSelectedEvent) => { + (e: PageSelectedEvent) => { store.shell.setIsDrawerSwipeDisabled(e.nativeEvent.position > 0) }, [store], @@ -42,17 +42,17 @@ export const HomeScreen = withAuthRequired((_opts: Props) => { ) return ( - + First page Second page - + + Third page + + ) }) function MyPage({children}) {