Skip to content

Commit

Permalink
feat: add an animation option to customize animations
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Mar 7, 2024
1 parent 4101318 commit f0b7e6f
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 36 deletions.
19 changes: 5 additions & 14 deletions example/src/Screens/BottomTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useActionSheet } from '@expo/react-native-action-sheet';
import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
import {
createBottomTabNavigator,
TransitionPresets,
useBottomTabBarHeight,
} from '@react-navigation/bottom-tabs';
import {
Expand Down Expand Up @@ -85,11 +84,7 @@ const AlbumsScreen = () => {

const Tab = createBottomTabNavigator<BottomTabParams>();

const animations = {
shifting: TransitionPresets.ShiftingTransition,
fade: TransitionPresets.FadeTransition,
none: null,
} as const;
const animations = ['none', 'fade', 'shifting'] as const;

export function BottomTabs({
navigation,
Expand All @@ -105,7 +100,7 @@ export function BottomTabs({
const dimensions = useWindowDimensions();

const [animation, setAnimation] =
React.useState<keyof typeof animations>('none');
React.useState<(typeof animations)[number]>('none');
const [isCompact, setIsCompact] = React.useState(false);

const isLargeScreen = dimensions.width >= 1024;
Expand All @@ -120,13 +115,9 @@ export function BottomTabs({
headerRight: ({ tintColor }) => (
<HeaderButton
onPress={() => {
const options = Object.keys(
animations
) as (keyof typeof animations)[];

showActionSheetWithOptions(
{
options: options.map((option) => {
options: animations.map((option) => {
if (option === animation) {
return `${option} (current)`;
}
Expand All @@ -136,7 +127,7 @@ export function BottomTabs({
},
(index) => {
if (index != null) {
setAnimation(options[index]);
setAnimation(animations[index]);
}
}
);
Expand All @@ -152,7 +143,7 @@ export function BottomTabs({
tabBarPosition: isLargeScreen ? 'left' : 'bottom',
tabBarLabelPosition:
isLargeScreen && isCompact ? 'below-icon' : undefined,
...animations[animation],
animation,
}}
>
<Tab.Screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
/**
* Simple cross fade animation
*/
export function forCrossFade({
export function forFade({
current,
}: BottomTabSceneInterpolationProps): BottomTabSceneInterpolatedStyle {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { BottomTabTransitionPreset } from '../types';
import { forCrossFade, forShifting } from './SceneStyleInterpolators';
import { forFade, forShifting } from './SceneStyleInterpolators';
import { CrossFadeSpec, ShiftingSpec } from './TransitionSpecs';

export const FadeTransition: BottomTabTransitionPreset = {
transitionSpec: CrossFadeSpec,
sceneStyleInterpolator: forCrossFade,
sceneStyleInterpolator: forFade,
};

export const ShiftingTransition: BottomTabTransitionPreset = {
Expand Down
12 changes: 9 additions & 3 deletions packages/bottom-tabs/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export type TabBarVisibilityAnimationConfig =
| TimingKeyboardAnimationConfig
| SpringKeyboardAnimationConfig;

export type TabAnimationName = 'none' | 'fade' | 'shifting';

export type BottomTabNavigationOptions = HeaderOptions & {
/**
* Title text for the screen.
Expand Down Expand Up @@ -263,10 +265,14 @@ export type BottomTabNavigationOptions = HeaderOptions & {
freezeOnBlur?: boolean;

/**
* Whether transition animations should be enabled when switching tabs.
* Defaults to `false`.
* How the screen should animate when switching tabs.
*
* Supported values:
* - 'none': don't animate the screen (default)
* - 'fade': cross-fade the screens.
* - 'shifting': shift the screens slightly shift to left/right.
*/
animationEnabled?: boolean;
animation?: TabAnimationName;

/**
* Function which specifies interpolated styles for bottom-tab scenes.
Expand Down
45 changes: 29 additions & 16 deletions packages/bottom-tabs/src/views/BottomTabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import * as React from 'react';
import { Animated, Platform, StyleSheet } from 'react-native';
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';

import {
FadeTransition,
ShiftingTransition,
} from '../TransitionConfigs/TransitionPresets';
import type {
BottomTabBarProps,
BottomTabDescriptorMap,
Expand All @@ -38,14 +42,26 @@ const STATE_INACTIVE = 0;
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
const STATE_ON_TOP = 2;

const NAMED_TRANSITIONS_PRESETS = {
fade: FadeTransition,
shifting: ShiftingTransition,
none: {
sceneStyleInterpolator: undefined,
transitionSpec: {
animation: 'timing',
config: { duration: 150 },
},
},
} as const;

const hasAnimation = (options: BottomTabNavigationOptions) => {
const { animationEnabled, transitionSpec } = options;
const { animation, transitionSpec } = options;

if (animationEnabled === false || !transitionSpec) {
return false;
if (animation) {
return animation !== 'none';
}

return true;
return !transitionSpec;
};

export function BottomTabView(props: Props) {
Expand Down Expand Up @@ -81,21 +97,16 @@ export function BottomTabView(props: Props) {
state.routes
.map((route, index) => {
const { options } = descriptors[route.key];
const { transitionSpec } = options;

const animationEnabled = hasAnimation(options);
const {
animation = 'none',
transitionSpec = NAMED_TRANSITIONS_PRESETS[animation]
.transitionSpec ??
NAMED_TRANSITIONS_PRESETS.none.transitionSpec,
} = options;

const toValue =
index === state.index ? 0 : index >= state.index ? 1 : -1;

if (!animationEnabled || !transitionSpec) {
return Animated.timing(tabAnims[route.key], {
toValue,
duration: 0,
useNativeDriver: true,
});
}

return Animated[transitionSpec.animation](tabAnims[route.key], {
...transitionSpec.config,
toValue,
Expand Down Expand Up @@ -178,7 +189,9 @@ export function BottomTabView(props: Props) {
const {
lazy = true,
unmountOnBlur,
sceneStyleInterpolator,
animation = 'none',
sceneStyleInterpolator = NAMED_TRANSITIONS_PRESETS[animation]
.sceneStyleInterpolator,
} = descriptor.options;
const isFocused = state.index === index;

Expand Down

0 comments on commit f0b7e6f

Please sign in to comment.