react-native-collapsible-tab-view
Expo app
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.
Demo
Default | Snap | DiffClamp | DiffClamp + Snap |
---|---|---|---|
Features
- 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
Installation
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.
Quick Start
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
Scroll on header
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.
API reference
createCollapsibleTabs
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
>()
Tabs.Container
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>
)
}
Props
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. |
Tabs.ScrollView
and Tabs.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} />
}
Props
prop | description |
---|---|
name | Name of the tab. Must be key of the refMap object provided to the Tabs.Container . |
{...rest} | ScrollView or FlatList props. |
useTabsContext
A hook to access the context.
// iside your component
const { focusedTab, ...rest } = useTabsContext()
Props
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 |
Contributing
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.