Move the feed selector to the header (#352)

* Reorg pager components into their own folder

* Move the feed selector to the header

* Fix lint
This commit is contained in:
Paul Frazee 2023-03-22 10:46:09 -05:00 committed by GitHub
parent 4f814207bc
commit 2dc774788b
9 changed files with 48 additions and 70 deletions

View file

@ -1,161 +0,0 @@
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'
import {isDesktopWeb} from 'platform/detection'
interface Layout {
x: number
width: number
}
export interface TabBarProps {
selectedPage: number
items: string[]
position: Animated.Value
offset: Animated.Value
indicatorPosition?: 'top' | 'bottom'
indicatorColor?: string
onSelect?: (index: number) => void
onPressSelected?: () => void
}
export function TabBar({
selectedPage,
items,
position,
offset,
indicatorPosition = 'bottom',
indicatorColor,
onSelect,
onPressSelected,
}: TabBarProps) {
const pal = usePalette('default')
const [itemLayouts, setItemLayouts] = useState<Layout[]>(
items.map(() => ({x: 0, width: 0})),
)
const itemRefs = useMemo(
() => Array.from({length: items.length}).map(() => createRef<View>()),
[items.length],
)
const panX = Animated.add(position, offset)
const indicatorStyle = {
backgroundColor: indicatorColor || pal.colors.link,
bottom:
indicatorPosition === 'bottom' ? (isDesktopWeb ? 0 : -1) : undefined,
top: indicatorPosition === 'top' ? (isDesktopWeb ? 0 : -1) : undefined,
transform: [
{
translateX: panX.interpolate({
inputRange: items.map((_item, i) => i),
outputRange: itemLayouts.map(l => l.x + l.width / 2),
}),
},
{
scaleX: 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<Layout>(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)
if (index === selectedPage) {
onPressSelected?.()
}
}
return (
<View style={[pal.view, styles.outer]} onLayout={onLayout}>
<Animated.View style={[styles.indicator, indicatorStyle]} />
{items.map((item, i) => {
const selected = i === selectedPage
return (
<TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}>
<View
style={
indicatorPosition === 'top' ? styles.itemTop : styles.itemBottom
}
ref={itemRefs[i]}>
<Text type="xl-bold" style={selected ? pal.text : pal.textLight}>
{item}
</Text>
</View>
</TouchableWithoutFeedback>
)
})}
</View>
)
}
const styles = isDesktopWeb
? StyleSheet.create({
outer: {
flexDirection: 'row',
paddingHorizontal: 18,
},
itemTop: {
paddingTop: 16,
paddingBottom: 14,
marginRight: 24,
},
itemBottom: {
paddingTop: 14,
paddingBottom: 16,
marginRight: 24,
},
indicator: {
position: 'absolute',
left: 0,
width: 1,
height: 3,
},
})
: StyleSheet.create({
outer: {
flexDirection: 'row',
paddingHorizontal: 14,
},
itemTop: {
paddingTop: 10,
paddingBottom: 10,
marginRight: 24,
},
itemBottom: {
paddingTop: 8,
paddingBottom: 12,
marginRight: 24,
},
indicator: {
position: 'absolute',
left: 0,
width: 1,
height: 3,
},
})

View file

@ -1,87 +0,0 @@
import React from 'react'
import {Animated, View} from 'react-native'
import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {s} from 'lib/styles'
export type PageSelectedEvent = PagerViewOnPageSelectedEvent
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
export interface RenderTabBarFnProps {
selectedPage: number
position: Animated.Value
offset: Animated.Value
onSelect?: (index: number) => void
}
export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
interface Props {
tabBarPosition?: 'top' | 'bottom'
initialPage?: number
renderTabBar: RenderTabBarFn
onPageSelected?: (index: number) => void
}
export const Pager = ({
children,
tabBarPosition = 'top',
initialPage = 0,
renderTabBar,
onPageSelected,
}: React.PropsWithChildren<Props>) => {
const [selectedPage, setSelectedPage] = React.useState(0)
const position = useAnimatedValue(0)
const offset = useAnimatedValue(0)
const pagerView = React.useRef<PagerView>()
const onPageSelectedInner = React.useCallback(
(e: PageSelectedEvent) => {
setSelectedPage(e.nativeEvent.position)
onPageSelected?.(e.nativeEvent.position)
},
[setSelectedPage, onPageSelected],
)
const onTabBarSelect = React.useCallback(
(index: number) => {
pagerView.current?.setPage(index)
},
[pagerView],
)
return (
<View>
{tabBarPosition === 'top' &&
renderTabBar({
selectedPage,
position,
offset,
onSelect: onTabBarSelect,
})}
<AnimatedPagerView
ref={pagerView}
style={s.h100pct}
initialPage={initialPage}
onPageSelected={onPageSelectedInner}
onPageScroll={Animated.event(
[
{
nativeEvent: {
position: position,
offset: offset,
},
},
],
{useNativeDriver: true},
)}>
{children}
</AnimatedPagerView>
{tabBarPosition === 'bottom' &&
renderTabBar({
selectedPage,
position,
offset,
onSelect: onTabBarSelect,
})}
</View>
)
}

View file

@ -1,69 +0,0 @@
import React from 'react'
import {Animated, View} from 'react-native'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {s} from 'lib/styles'
export interface RenderTabBarFnProps {
selectedPage: number
position: Animated.Value
offset: Animated.Value
onSelect?: (index: number) => void
}
export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
interface Props {
tabBarPosition?: 'top' | 'bottom'
initialPage?: number
renderTabBar: RenderTabBarFn
onPageSelected?: (index: number) => void
}
export const Pager = ({
children,
tabBarPosition = 'top',
initialPage = 0,
renderTabBar,
onPageSelected,
}: React.PropsWithChildren<Props>) => {
const [selectedPage, setSelectedPage] = React.useState(initialPage)
const position = useAnimatedValue(0)
const offset = useAnimatedValue(0)
const onTabBarSelect = React.useCallback(
(index: number) => {
setSelectedPage(index)
onPageSelected?.(index)
Animated.timing(position, {
toValue: index,
duration: 200,
useNativeDriver: true,
}).start()
},
[setSelectedPage, onPageSelected, position],
)
return (
<View>
{tabBarPosition === 'top' &&
renderTabBar({
selectedPage,
position,
offset,
onSelect: onTabBarSelect,
})}
{children.map((child, i) => (
<View
style={selectedPage === i ? undefined : s.hidden}
key={`page-${i}`}>
{child}
</View>
))}
{tabBarPosition === 'bottom' &&
renderTabBar({
selectedPage,
position,
offset,
onSelect: onTabBarSelect,
})}
</View>
)
}