Skip to content

Commit

Permalink
feat: support react navigation 4 (#5)
Browse files Browse the repository at this point in the history
* feat: add support to react-navigation 4

* docs: added react navigation 4 usage

* chore: remove debug console
  • Loading branch information
gorhom authored Mar 31, 2020
1 parent 672df3a commit 6910c3e
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 74 deletions.
174 changes: 120 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Animated TabBar [![npm](https://img.shields.io/npm/v/@gorhom/animated-tabbar)](https://www.npmjs.com/package/@gorhom/animated-tabbar)
# Animated TabBar [![npm](https://badgen.net/npm/v/@gorhom/animated-tabbar)](https://www.npmjs.com/package/@gorhom/animated-tabbar)

a 60fps animated tab bar to be used with `React Navigation` created with `Reanimated` 😎, inspired by [Aurélien Salomon](https://dribbble.com/aureliensalomon) works on [Dribbble](https://dribbble.com/shots/5925052-Google-Bottom-Bar-Navigation-Pattern-Mobile-UX-Design).
a 60fps animated tab bar to be used with `React Navigation v4 & v5` created with `Reanimated` 😎, inspired by [Aurélien Salomon](https://dribbble.com/aureliensalomon) works on [Dribbble](https://dribbble.com/shots/5925052-Google-Bottom-Bar-Navigation-Pattern-Mobile-UX-Design).

<p align="center">
<img src="./preview.gif" width="600" height="336">
Expand All @@ -18,66 +18,132 @@ npm install @gorhom/animated-tabbar
## Usage

```tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import AnimatedTabBar, {TabsConfigsType} from '@gorhom/animated-tabbar';

const tabs: TabsConfigsType = {
Home: {
labelStyle: {
color: '#5B37B7',
<details>
<summary>React Navigation v5</summary>

```tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import AnimatedTabBar, {TabsConfigsType} from '@gorhom/animated-tabbar';

const tabs: TabsConfigsType = {
Home: {
labelStyle: {
color: '#5B37B7',
},
icon: {
component: /* ICON COMPONENT */,
activeColor: 'rgba(91,55,183,1)',
inactiveColor: 'rgba(0,0,0,1)',
},
background: {
activeColor: 'rgba(223,215,243,1)',
inactiveColor: 'rgba(223,215,243,0)',
},
},
icon: {
component: /* ICON COMPONENT */,
activeColor: 'rgba(91,55,183,1)',
inactiveColor: 'rgba(0,0,0,1)',
Profile: {
labelStyle: {
color: '#1194AA',
},
icon: {
component: /* ICON COMPONENT */,
activeColor: 'rgba(17,148,170,1)',
inactiveColor: 'rgba(0,0,0,1)',
},
background: {
activeColor: 'rgba(207,235,239,1)',
inactiveColor: 'rgba(207,235,239,0)',
},
},
background: {
activeColor: 'rgba(223,215,243,1)',
inactiveColor: 'rgba(223,215,243,0)',
};

const Tab = createBottomTabNavigator();

export default function App() {
return (
<NavigationContainer>
<Tab.Navigator
tabBar={props => (
<AnimatedTabBar tabs={tabs} {...props} />
)}
>
<Tab.Screen
name="Home"
component={HomeScreen}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
/>
</Tab.Navigator>
</NavigationContainer>
)
}
```
</details>

<details>
<summary>React Navigation v4</summary>

```tsx
import React from 'react';
import {createAppContainer} from 'react-navigation';
import {createBottomTabNavigator} from 'react-navigation-tabs';
import {createStackNavigator} from 'react-navigation-stack';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import AnimatedTabBar, {TabsConfigsType} from '@gorhom/animated-tabbar';

const tabs: TabsConfigsType = {
Home: {
labelStyle: {
color: '#5B37B7',
},
icon: {
component: /* ICON COMPONENT */,
activeColor: 'rgba(91,55,183,1)',
inactiveColor: 'rgba(0,0,0,1)',
},
background: {
activeColor: 'rgba(223,215,243,1)',
inactiveColor: 'rgba(223,215,243,0)',
},
},
},
Profile: {
labelStyle: {
color: '#1194AA',
Profile: {
labelStyle: {
color: '#1194AA',
},
icon: {
component: /* ICON COMPONENT */,
activeColor: 'rgba(17,148,170,1)',
inactiveColor: 'rgba(0,0,0,1)',
},
background: {
activeColor: 'rgba(207,235,239,1)',
inactiveColor: 'rgba(207,235,239,0)',
},
},
icon: {
component: /* ICON COMPONENT */,
activeColor: 'rgba(17,148,170,1)',
inactiveColor: 'rgba(0,0,0,1)',
};

const TabNavigator = createBottomTabNavigator(
{
Home: HomeScreen,
Profile: ProfileScreen,
},
background: {
activeColor: 'rgba(207,235,239,1)',
inactiveColor: 'rgba(207,235,239,0)',
{
tabBarComponent: props => <AnimatedTabBar tabs={tabs} {...props} />,
},
},
};
);

const Tab = createBottomTabNavigator();
const AppContainer = createAppContainer(TabNavigator);

export default function App() {
return (
<NavigationContainer>
<Tab.Navigator
tabBar={props => (
<AnimatedTabBar tabs={tabs} {...props} />
)}
>
<Tab.Screen
name="Home"
component={HomeScreen}
/>
<Tab.Screen
name="Profile"
component={HomeScreen}
/>
</Tab.Navigator>
</NavigationContainer>
)
}
```
export default () => (
<SafeAreaProvider>
<AppContainer />
</SafeAreaProvider>
);
```
</details>

### Animated Icon

Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@
"typescript": "^3.8.3"
},
"peerDependencies": {
"@react-navigation/bottom-tabs": ">=5.0.0",
"@react-navigation/native": ">=5.0.0",
"@react-navigation/stack": ">=5.0.0",
"react": "*",
"react-native": "*",
"react-native-gesture-handler": ">=1.6.0",
Expand Down
72 changes: 55 additions & 17 deletions src/AnimatedTabBar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useCallback, useMemo, useEffect } from 'react';
import React, { useMemo, useEffect } from 'react';
import { View } from 'react-native';
import Animated, { useCode, onChange, call } from 'react-native-reanimated';
import { useValues } from 'react-native-redash';
import { useSafeArea } from 'react-native-safe-area-context';
import { CommonActions } from '@react-navigation/native';
import { CommonActions, Route } from '@react-navigation/native';
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
import { AnimatedTabBarItem } from './item';
import { TabsConfigsType, AnimationConfigProps } from './types';
Expand All @@ -24,10 +24,28 @@ interface AnimatedTabBarProps extends BottomTabBarProps, AnimationConfigProps {

export const AnimatedTabBar = (props: AnimatedTabBarProps) => {
// props
const { state, navigation, descriptors, tabs, duration, easing } = props;
const { routes } = state;
const { navigation, tabs, duration, easing } = props;

// variables
const isReactNavigation5 = props.state ? true : false;
// @ts-ignore
const {
routes,
index: navigationIndex,
key: navigationKey,
}: { routes: Route<string>[]; index: number; key: string } = useMemo(() => {
if (isReactNavigation5) {
return props.state;
} else {
return {
// @ts-ignore
index: props.navigation.state.index,
// @ts-ignore
routes: props.navigation.state.routes,
ket: '',
};
}
}, [props, isReactNavigation5]);
const safeArea = useSafeArea();
const [selectedIndex] = useValues([0], []);

Expand All @@ -43,9 +61,27 @@ export const AnimatedTabBar = (props: AnimatedTabBarProps) => {
);

// callbacks
const handleSelectedIndexChange = useCallback(
index => {
const { key, name } = state.routes[index];
const getRouteLabel = (route: Route<string>) => {
if (isReactNavigation5) {
const { descriptors } = props;
const { options } = descriptors[route.key];
return options.title !== undefined ? options.title : route.name;
} else {
return route.key;
}
};

const getRouteTabConfigs = (route: Route<string>) => {
if (isReactNavigation5) {
return tabs[route.name];
} else {
return tabs[route.key];
}
};

const handleSelectedIndexChange = (index: number) => {
if (isReactNavigation5) {
const { key, name } = routes[index];
const event = navigation.emit({
type: 'tabPress',
target: key,
Expand All @@ -55,18 +91,21 @@ export const AnimatedTabBar = (props: AnimatedTabBarProps) => {
if (!event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(name),
target: state.key,
target: navigationKey,
});
}
},
[state, navigation]
);
} else {
// @ts-ignore
const { onTabPress } = props;
onTabPress({ route: routes[index] });
}
};

// effects
useEffect(() => {
// @ts-ignore
selectedIndex.setValue(state.index);
}, [state, selectedIndex]);
selectedIndex.setValue(navigationIndex);
}, [navigationIndex, selectedIndex]);

useCode(
() =>
Expand All @@ -83,9 +122,8 @@ export const AnimatedTabBar = (props: AnimatedTabBarProps) => {
return (
<View style={containerStyle}>
{routes.map((route, index) => {
const { options } = descriptors[route.key];
const tabConfigs = tabs[route.name];
const label = options.title !== undefined ? options.title : route.name;
const configs = getRouteTabConfigs(route);
const label = getRouteLabel(route);
return (
<AnimatedTabBarItem
key={route.key}
Expand All @@ -94,7 +132,7 @@ export const AnimatedTabBar = (props: AnimatedTabBarProps) => {
label={label}
duration={duration}
easing={easing}
{...tabConfigs}
{...configs}
/>
);
})}
Expand Down

0 comments on commit 6910c3e

Please sign in to comment.