Add navigation element animations
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
101
src/components/hideableBottomNavigationBar.tsx
Normal file
101
src/components/hideableBottomNavigationBar.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { BottomNavigation } from 'react-native-paper';
|
||||
import { Animated, StyleSheet } from 'react-native';
|
||||
import {
|
||||
CommonActions,
|
||||
NavigationHelpers,
|
||||
ParamListBase,
|
||||
TabNavigationState,
|
||||
} from '@react-navigation/native';
|
||||
import { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import { BottomTabNavigationEventMap } from '@react-navigation/bottom-tabs';
|
||||
import { BottomTabDescriptorMap } from '@react-navigation/bottom-tabs/lib/typescript/src/types';
|
||||
import { ROUTE } from '../types';
|
||||
|
||||
const hideableBottomNavigationBarStyles = StyleSheet.create({
|
||||
bar: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const HideableBottomNavigationBar = ({
|
||||
navigation,
|
||||
state,
|
||||
descriptors,
|
||||
insets,
|
||||
visible = true,
|
||||
routeCallback,
|
||||
}: {
|
||||
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
insets: EdgeInsets;
|
||||
visible?: boolean;
|
||||
routeCallback?: (route: ROUTE) => void;
|
||||
}) => {
|
||||
const visibleAnim = useRef(new Animated.Value(visible ? 0 : 1)).current;
|
||||
|
||||
useEffect(() => {
|
||||
Animated.timing(visibleAnim, {
|
||||
toValue: visible ? 0 : 1,
|
||||
duration: visible ? 200 : 150,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}, [visible, visibleAnim]);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={{
|
||||
marginBottom: insets.bottom,
|
||||
transform: [
|
||||
{
|
||||
translateY: visibleAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 80],
|
||||
}),
|
||||
},
|
||||
],
|
||||
...hideableBottomNavigationBarStyles.bar,
|
||||
}}>
|
||||
<BottomNavigation.Bar
|
||||
navigationState={state}
|
||||
safeAreaInsets={insets}
|
||||
onTabPress={({ route, preventDefault }) => {
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
if (event.defaultPrevented) {
|
||||
preventDefault();
|
||||
} else {
|
||||
navigation.dispatch({
|
||||
...CommonActions.navigate(route.name, route.params),
|
||||
target: state.key,
|
||||
});
|
||||
if (routeCallback) routeCallback(route.name as ROUTE);
|
||||
}
|
||||
}}
|
||||
renderIcon={({ route, focused, color }) => {
|
||||
const { options } = descriptors[route.key];
|
||||
if (options.tabBarIcon) {
|
||||
return options.tabBarIcon({
|
||||
focused,
|
||||
color,
|
||||
size: 22,
|
||||
});
|
||||
}
|
||||
}}
|
||||
getLabelText={({ route }) => {
|
||||
const { options } = descriptors[route.key];
|
||||
return options.title ?? route.name;
|
||||
}}
|
||||
/>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
export default HideableBottomNavigationBar;
|
@@ -1,6 +1,5 @@
|
||||
export { default as FloatingActionButton } from './floatingActionButton';
|
||||
export { default as HideableBottomNavigationBar } from './hideableBottomNavigationBar';
|
||||
export { default as LoadingView } from './loadingView';
|
||||
export { default as RootScrollView } from './rootScrollView';
|
||||
export { default as RootView } from './rootView';
|
||||
export { default as TagChip } from './tagChip';
|
||||
export { default as TagPreview } from './tagPreview';
|
||||
|
@@ -1,15 +1,21 @@
|
||||
import React from 'react';
|
||||
import { ActivityIndicator } from 'react-native';
|
||||
import { ActivityIndicator, View } from 'react-native';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
import { RootView } from '.';
|
||||
import styles from '../styles';
|
||||
|
||||
const LoadingView = () => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<RootView centered>
|
||||
<View
|
||||
style={[
|
||||
styles.centered,
|
||||
styles.flex,
|
||||
styles.fullSize,
|
||||
{ backgroundColor: colors.background },
|
||||
]}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
</RootView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -1,47 +0,0 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { StyleProp, ScrollView, ViewStyle } from 'react-native';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
import styles from '../styles';
|
||||
import { useDimensions } from '../contexts';
|
||||
|
||||
const RootScrollView = ({
|
||||
children,
|
||||
style,
|
||||
centered,
|
||||
padded,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
centered?: boolean;
|
||||
padded?: boolean;
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
padded &&
|
||||
orientation == 'portrait' && [
|
||||
styles.paddingHorizontal,
|
||||
styles.paddingTop,
|
||||
],
|
||||
padded &&
|
||||
orientation == 'landscape' && [
|
||||
styles.paddingHorizontal,
|
||||
styles.smallPaddingTop,
|
||||
],
|
||||
centered && [styles.centered, styles.flex],
|
||||
styles.fullSize,
|
||||
centered && [styles.centered, styles.flex],
|
||||
styles.fullSize,
|
||||
{ backgroundColor: colors.background },
|
||||
style,
|
||||
]}
|
||||
nestedScrollEnabled>
|
||||
{children}
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootScrollView;
|
@@ -1,44 +0,0 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { StyleProp, View, ViewStyle } from 'react-native';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
import styles from '../styles';
|
||||
import { useDimensions } from '../contexts';
|
||||
|
||||
const RootView = ({
|
||||
children,
|
||||
style,
|
||||
centered,
|
||||
padded,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
centered?: boolean;
|
||||
padded?: boolean;
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
padded &&
|
||||
orientation == 'portrait' && [
|
||||
styles.paddingHorizontal,
|
||||
styles.paddingTop,
|
||||
],
|
||||
padded &&
|
||||
orientation == 'landscape' && [
|
||||
styles.paddingHorizontal,
|
||||
styles.smallPaddingTop,
|
||||
],
|
||||
centered && [styles.centered, styles.flex],
|
||||
styles.fullSize,
|
||||
{ backgroundColor: colors.background },
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootView;
|
Reference in New Issue
Block a user