If you are looking for the integration with react-native-tab-view and/or react-navigation, you need to use the v2, we are currenlty on v3.
Collapsible Tab View for React Native, with Reanimated.
- View it with Expo.
- Checkout the examples for the source code of the Expo app.
Credits
The react-native-tab-view example app was used as template for the demos.
Default | Snap | DiffClamp | DiffClamp + Snap |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
- Animations and interactions on the UI thread
- Highly customizable
- Fully typed with TypeScript
- Lazy support with fade-in animation
- DiffClamp header
- Interpolated header
- Scroll snap (with interpolated header)
- Animated snap (with diffClamp header)
- Scrollable tabs, inspired by the react-native-tab-view tab bar
- Support horizontal and vertical window
Open a Terminal in the project root and run:
yarn add react-native-collapsible-tab-view
Then, add Reanimated v2, follow the official installation guide.
import React from 'react'
import { View, StyleSheet, ListRenderItem } from 'react-native'
import {
RefComponent,
ContainerRef,
createCollapsibleTabs,
TabBarProps as TabProps,
} from 'react-native-collapsible-tab-view'
import { useAnimatedRef } from 'react-native-reanimated'
type TabNames = 'A' | 'B'
type HeaderProps = TabProps<TabNames>
const { useTabsContext, ...Tabs } = createCollapsibleTabs<TabNames>()
const HEADER_HEIGHT = 250
const Example: React.FC = () => {
const containerRef = useAnimatedRef<ContainerRef>()
const tabARef = useAnimatedRef<RefComponent>()
const tabBRef = useAnimatedRef<RefComponent>()
const [refMap] = React.useState({
A: tabARef,
B: tabBRef,
})
return (
<Tabs.Container
containerRef={containerRef}
HeaderComponent={Header}
headerHeight={HEADER_HEIGHT} // optional
refMap={refMap}
>
<ScreenA />
<ScreenB />
</Tabs.Container>
)
}
const ScreenB = () => {
return (
<Tabs.ScrollView name="B">
<View style={[styles.box, styles.boxA]} />
<View style={[styles.box, styles.boxB]} />
</Tabs.ScrollView>
)
}
const renderItem: ListRenderItem<number> = ({ index }) => {
return (
<View style={[styles.box, index % 2 === 0 ? styles.boxB : styles.boxA]} />
)
}
const ScreenA = () => {
return (
<Tabs.FlatList
name="A"
data={[0, 1, 2, 3, 4]}
renderItem={renderItem}
keyExtractor={(v) => v + ''}
/>
)
}
const Header: React.FC<HeaderProps> = () => {
return <View style={styles.header} />
}
const styles = StyleSheet.create({
box: {
height: 250,
width: '100%',
},
boxA: {
backgroundColor: 'white',
},
boxB: {
backgroundColor: '#D8D8D8',
},
header: {
height: HEADER_HEIGHT,
width: '100%',
backgroundColor: '#2196f3',
},
})
export default Example
If you want to allow scrolling from the header:
-
If the
HeaderComponent
doesn't contain touchables setpointerEvents='none'
-
If
HeaderComponent
does contain touchables setpointerEvents='box-none'
for them to work.Note: With this setting any child component that should not respond to touches (e.g.
<Image />
) needs to havepointerEvents
set to'none'
. Otherwise it can become the target of a touch gesture on iOS devices and thereby preventing scrolling.
Basic usage looks like this:
import { createCollapsibleTabs, TabBarProps } from 'react-native-collapsible-tab-view'
type MyTabs = 'tab0' | 'tab1'
const {
Container,
FlatList,
ScrollView,
useTabsContext
} = createCollapsibleTabs<MyTabs>()
// or
const { useTabsContext, ...Tabs } = createCollapsibleTabs<MyTabs>()
// use like this:
<Tabs.Container {...props} />
<Tabs.FlatList name='tab0' {...props} />
<Tabs.ScrollView name='tab1' {...props} />
// if you want to provide a cutom tab bar
// TabBarComponent prop of the Tabs.Container
const { useTabsContext, ...Tabs } = createCollapsibleTabs<
MyTabs,
TabBarProps<MyTabs> & MyPropsType
>()
Basic usage looks like this:
import {
RefComponent,
ContainerRef,
TabBarProps,
} from 'react-native-collapsible-tab-view'
import { useAnimatedRef } from 'react-native-reanimated'
type MyTabs = 'article' | 'contacts' | 'albums'
const MyHeader: React.FC<TabBarProps<MyTabs>> = (props) => {...}
const Example: React.FC<Props> = () => {
const containerRef = useAnimatedRef<ContainerRef>()
const tab0Ref = useAnimatedRef<RefComponent>()
const tab1Ref = useAnimatedRef<RefComponent>()
const [refMap] = React.useState({
tab0: tab0Ref,
tab1: tab1Ref,
})
return (
<Tabs.Container
containerRef={containerRef}
HeaderComponent={MyHeader}
headerHeight={HEADER_HEIGHT} // optional
refMap={refMap}
>
{ /* components returning Tabs.ScrollView || Tabs.FlatList */ }
</Tabs.Container>
)
}
prop | description | default |
---|---|---|
containerRef |
Must be provided with useAnimatedRef<ContainerRef>() . |
|
refMap |
Map of tab names and refs, must be the same order as the container children. | |
children |
Array of react elements. Each child should have a Tabs.ScrollView or Tabs.FlatList inside. |
|
initialTabName? |
Initial tab name. | |
headerHeight? |
If you don't provide the header height, the pager will fade-in after getting it with onLayout . |
|
tabBarHeight? |
- | 48 |
snapEnabled? |
Enable snapping. Do scroll snapping if !diffClampEnabled , otherwise, do animated snapping. |
false |
diffClampEnabled? |
Enable diff clamp. | false |
snapThreshold? |
Percentage of header height to make the snap effect. A number between 0 and 1. | 0.5 |
HeaderComponent? |
React component to render above the tabbar. | |
TabBarComponent? |
React component to render above tab scenes. | MaterialTabBar |
headerContainerStyle? |
Styles applied to the header and tabbar container | |
containerStyle? |
Styles applied to the view container. | |
cancelTranslation? |
This will cancel the collapsible effect, and render a static tabbar / header. | false |
lazy? |
Mount the screen only when it's focused. It has a default fade in animation. | false |
cancelLazyFadeIn? |
Cancel the fade in animation if lazy={true} |
false |
tabBarProps? |
Props passed to the TabBarComponent . |
|
pagerProps? |
Props passed to the horizontal FlatList. |
Basic usage looks like this:
const ScrollViewScreen: React.FC<object> = () => {
return (
<Tabs.ScrollView name="myTabName0" {...scrollViewProps}>
{children}
</Tabs.ScrollView>
)
}
const FlatListScreen: React.FC<object> = () => {
return <Tabs.FlatList<ItemType> name="myTabName1" {...flatListProps} />
}
prop | description |
---|---|
name | Name of the tab. Must be key of the refMap object provided to the Tabs.Container . |
{...rest} | ScrollView or FlatList props. |
A hook to access the context.
// iside your component
const { focusedTab, ...rest } = useTabsContext()
prop | description | type |
---|---|---|
headerHeight | number |
|
tabBarHeight | number |
|
snapEnabled | boolean |
|
diffClampEnabled | boolean |
|
snapThreshold | number |
|
refMap | Record<T, Ref> |
|
scrollYCurrent | Scroll position of current tab. | Animated.SharedValue<number> |
tabNames | Tab names, same as the keys of refMap |
Animated.SharedValue<T[]> |
index | Current tab index. | Animated.SharedValue<number> |
scrollY | Array of the scroll position of each tab. | Animated.SharedValue<number[]> |
scrollX | Scroll x position of the tabs container. | Animated.SharedValue<number> |
oldAccScrollY | Previous accumulted scrollY. | Animated.SharedValue<number> |
accScrollY | Current accumulated scrollY | Animated.SharedValue<number> |
offset | Offset. | Animated.SharedValue<number> |
isScrolling | If is scrolling. | Animated.SharedValue<boolean> |
focusedTab | Current focused tab name. | Animated.SharedValue<T> |
accDiffClamp | Accumulated diffClamp value. | Animated.SharedValue<number> |
containerHeight? | number |
While developing, you can run the example app to test your changes.
Please follow the angular commit message format.
Make sure your code passes TypeScript and ESLint. Run the following to verify:
yarn typescript
yarn lint
To fix formatting errors, run the following:
yarn lint -- --fix
Remember to add tests for your change if possible.