Add close animation to tabs selector

zio/stable
Paul Frazee 2022-09-09 16:45:37 -05:00
parent 530243859c
commit 5193a5b48e
2 changed files with 84 additions and 43 deletions

View File

@ -1,4 +1,4 @@
import React, {createRef, useRef, useMemo} from 'react' import React, {createRef, useRef, useMemo, useState} from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import { import {
ScrollView, ScrollView,
@ -8,6 +8,12 @@ import {
TouchableWithoutFeedback, TouchableWithoutFeedback,
View, View,
} from 'react-native' } from 'react-native'
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
runOnJS,
} from 'react-native-reanimated'
import {IconProp} from '@fortawesome/fontawesome-svg-core' import {IconProp} from '@fortawesome/fontawesome-svg-core'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import Swipeable from 'react-native-gesture-handler/Swipeable' import Swipeable from 'react-native-gesture-handler/Swipeable'
@ -16,14 +22,22 @@ import {useStores} from '../../../state'
import {s, colors, gradients} from '../../lib/styles' import {s, colors, gradients} from '../../lib/styles'
import {match} from '../../routes' import {match} from '../../routes'
const TAB_HEIGHT = 42
export const snapPoints = [500] export const snapPoints = [500]
export const Component = observer(() => { export const Component = observer(() => {
const store = useStores() const store = useStores()
const [closingTabIndex, setClosingTabIndex] = useState<number | undefined>(
undefined,
)
const closeInterp = useSharedValue<number>(0)
const tabsRef = useRef<ScrollView>(null) const tabsRef = useRef<ScrollView>(null)
const tabRefs = useMemo( const tabRefs = useMemo(
() => () =>
Array.from({length: store.nav.tabs.length}).map(() => createRef<View>()), Array.from({length: store.nav.tabs.length}).map(() =>
createRef<Animated.View>(),
),
[store.nav.tabs.length], [store.nav.tabs.length],
) )
@ -46,7 +60,15 @@ export const Component = observer(() => {
store.nav.setActiveTab(tabIndex) store.nav.setActiveTab(tabIndex)
onClose() onClose()
} }
const onCloseTab = (tabIndex: number) => store.nav.closeTab(tabIndex) const doCloseTab = (index: number) => store.nav.closeTab(index)
const onCloseTab = (tabIndex: number) => {
setClosingTabIndex(tabIndex)
closeInterp.value = 0
closeInterp.value = withTiming(1, {duration: 300}, () => {
runOnJS(setClosingTabIndex)(undefined)
runOnJS(doCloseTab)(tabIndex)
})
}
const onNavigate = (url: string) => { const onNavigate = (url: string) => {
store.nav.navigate(url) store.nav.navigate(url)
onClose() onClose()
@ -101,6 +123,11 @@ export const Component = observer(() => {
} }
const currentTabIndex = store.nav.tabIndex const currentTabIndex = store.nav.tabIndex
const closingTabAnimStyle = useAnimatedStyle(() => ({
height: TAB_HEIGHT * (1 - closeInterp.value),
opacity: 1 - closeInterp.value,
marginBottom: 4 * (1 - closeInterp.value),
}))
return ( return (
<View onLayout={onLayout}> <View onLayout={onLayout}>
<View style={[s.p10, styles.section]}> <View style={[s.p10, styles.section]}>
@ -154,6 +181,7 @@ export const Component = observer(() => {
{store.nav.tabs.map((tab, tabIndex) => { {store.nav.tabs.map((tab, tabIndex) => {
const {icon} = match(tab.current.url) const {icon} = match(tab.current.url)
const isActive = tabIndex === currentTabIndex const isActive = tabIndex === currentTabIndex
const isClosing = closingTabIndex === tabIndex
return ( return (
<Swipeable <Swipeable
key={tab.id} key={tab.id}
@ -162,7 +190,12 @@ export const Component = observer(() => {
leftThreshold={100} leftThreshold={100}
rightThreshold={100} rightThreshold={100}
onSwipeableWillOpen={() => onCloseTab(tabIndex)}> onSwipeableWillOpen={() => onCloseTab(tabIndex)}>
<View <Animated.View
style={[
styles.tabOuter,
isClosing ? closingTabAnimStyle : undefined,
]}>
<Animated.View
ref={tabRefs[tabIndex]} ref={tabRefs[tabIndex]}
style={[ style={[
styles.tab, styles.tab,
@ -171,12 +204,10 @@ export const Component = observer(() => {
]}> ]}>
<TouchableWithoutFeedback <TouchableWithoutFeedback
onPress={() => onPressChangeTab(tabIndex)}> onPress={() => onPressChangeTab(tabIndex)}>
<View style={styles.tabInner}>
<View style={styles.tabIcon}> <View style={styles.tabIcon}>
<FontAwesomeIcon size={20} icon={icon} /> <FontAwesomeIcon size={20} icon={icon} />
</View> </View>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback
onPress={() => onPressChangeTab(tabIndex)}>
<Text <Text
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
@ -187,6 +218,7 @@ export const Component = observer(() => {
]}> ]}>
{tab.current.title || tab.current.url} {tab.current.title || tab.current.url}
</Text> </Text>
</View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
<TouchableWithoutFeedback <TouchableWithoutFeedback
onPress={() => onCloseTab(tabIndex)}> onPress={() => onCloseTab(tabIndex)}>
@ -198,7 +230,8 @@ export const Component = observer(() => {
/> />
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
</View> </Animated.View>
</Animated.View>
</Swipeable> </Swipeable>
) )
})} })}
@ -247,14 +280,23 @@ const styles = StyleSheet.create({
tabs: { tabs: {
height: 240, height: 240,
}, },
tabOuter: {
height: TAB_HEIGHT + 4,
overflow: 'hidden',
},
tab: { tab: {
flexDirection: 'row', flexDirection: 'row',
height: TAB_HEIGHT,
backgroundColor: colors.gray1, backgroundColor: colors.gray1,
alignItems: 'center', alignItems: 'center',
borderRadius: 4, borderRadius: 4,
},
tabInner: {
flexDirection: 'row',
flex: 1,
alignItems: 'center',
paddingLeft: 12, paddingLeft: 12,
paddingRight: 16, paddingVertical: 12,
marginBottom: 4,
}, },
existing: { existing: {
borderColor: colors.gray4, borderColor: colors.gray4,
@ -269,14 +311,14 @@ const styles = StyleSheet.create({
tabText: { tabText: {
flex: 1, flex: 1,
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 12,
fontSize: 16, fontSize: 16,
}, },
tabTextActive: { tabTextActive: {
fontWeight: '500', fontWeight: '500',
}, },
tabClose: { tabClose: {
padding: 2, paddingVertical: 16,
paddingRight: 16,
}, },
tabCloseIcon: { tabCloseIcon: {
color: '#655', color: '#655',

View File

@ -2,7 +2,6 @@ Paul's todo list
- General - General
- Update to RN 0.70 - Update to RN 0.70
- Add close animation to tabs selector
- Composer - Composer
- Update the view after creating a post - Update the view after creating a post
- Profile - Profile