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,43 +190,48 @@ export const Component = observer(() => {
leftThreshold={100} leftThreshold={100}
rightThreshold={100} rightThreshold={100}
onSwipeableWillOpen={() => onCloseTab(tabIndex)}> onSwipeableWillOpen={() => onCloseTab(tabIndex)}>
<View <Animated.View
ref={tabRefs[tabIndex]}
style={[ style={[
styles.tab, styles.tabOuter,
styles.existing, isClosing ? closingTabAnimStyle : undefined,
isActive && styles.active,
]}> ]}>
<TouchableWithoutFeedback <Animated.View
onPress={() => onPressChangeTab(tabIndex)}> ref={tabRefs[tabIndex]}
<View style={styles.tabIcon}> style={[
<FontAwesomeIcon size={20} icon={icon} /> styles.tab,
</View> styles.existing,
</TouchableWithoutFeedback> isActive && styles.active,
<TouchableWithoutFeedback ]}>
onPress={() => onPressChangeTab(tabIndex)}> <TouchableWithoutFeedback
<Text onPress={() => onPressChangeTab(tabIndex)}>
ellipsizeMode="tail" <View style={styles.tabInner}>
numberOfLines={1} <View style={styles.tabIcon}>
suppressHighlighting={true} <FontAwesomeIcon size={20} icon={icon} />
style={[ </View>
styles.tabText, <Text
isActive && styles.tabTextActive, ellipsizeMode="tail"
]}> numberOfLines={1}
{tab.current.title || tab.current.url} suppressHighlighting={true}
</Text> style={[
</TouchableWithoutFeedback> styles.tabText,
<TouchableWithoutFeedback isActive && styles.tabTextActive,
onPress={() => onCloseTab(tabIndex)}> ]}>
<View style={styles.tabClose}> {tab.current.title || tab.current.url}
<FontAwesomeIcon </Text>
size={14} </View>
icon="x" </TouchableWithoutFeedback>
style={styles.tabCloseIcon} <TouchableWithoutFeedback
/> onPress={() => onCloseTab(tabIndex)}>
</View> <View style={styles.tabClose}>
</TouchableWithoutFeedback> <FontAwesomeIcon
</View> size={14}
icon="x"
style={styles.tabCloseIcon}
/>
</View>
</TouchableWithoutFeedback>
</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