README
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.
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 | revealHeaderOnScroll | revealHeaderOnScroll + 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@next
Then, add Reanimated v2, follow the official installation guide.
Quick Start
import React from 'react'
import { View, StyleSheet, ListRenderItem } from 'react-native'
import { Tabs } from 'react-native-collapsible-tab-view'
const HEADER_HEIGHT = 250
const Header = () => {
return <View style={styles.header} />
}
const Example: React.FC = () => {
const renderItem: ListRenderItem<number> = React.useCallback(({ index }) => {
return (
<View style={[styles.box, index % 2 === 0 ? styles.boxB : styles.boxA]} />
)
}, [])
return (
<Tabs.Container
HeaderComponent={Header}
headerHeight={HEADER_HEIGHT} // optional
>
<Tabs.Tab name="A">
<Tabs.FlatList
data={[0, 1, 2, 3, 4]}
renderItem={renderItem}
keyExtractor={(v) => v + ''}
/>
</Tabs.Tab>
<Tabs.Tab name="B">
<Tabs.ScrollView>
<View style={[styles.box, styles.boxA]} />
<View style={[styles.box, styles.boxB]} />
</Tabs.ScrollView>
</Tabs.Tab>
</Tabs.Container>
)
}
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
Guides
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
Core
Tabs.Tab
Wrap your screens with Tabs.Tab
. Basic usage looks like this:
<Tabs.Container ...>
<Tabs.Tab name="A" label="First Tab">
<ScreenA />
</Tabs.Tab>
<Tabs.Tab name="B">
<ScreenA />
</Tabs.Tab>
</Tabs.Container>
Props
name | type |
---|---|
label | string \| undefined |
name | T |
Tabs.Lazy
Typically used internally, but if you want to mix lazy and regular screens you can wrap the lazy ones with this component.
Props
name | type |
---|---|
cancelLazyFadeIn | boolean \| undefined |
startMounted | boolean \| undefined |
Tabs.FlatList
Use like a regular FlatList.
Tabs.ScrollView
Use like a regular ScrollView.
Ref
You can pass a ref to Tabs.Container
.
const ref = React.useRef()
<Tabs.Container ref={ref}>
method | type |
---|---|
jumpToTab | (name: T) => boolean |
setIndex | (index: number) => boolean |
getFocusedTab | () => T |
getCurrentIndex | () => number |
Hooks
useCollapsibleStyle
Hook to access some key styles that make the whole think work. You can use this to get the progessViewOffset and pass to the refresh control of scroll view.
const {
contentContainerStyle,
progressViewOffset,
style,
} = useCollapsibleStyle()
Values
name | type |
---|---|
contentContainerStyle | { minHeight: number; paddingTop: number; } |
progressViewOffset | number |
style | { width: number; } |
useAnimatedTabIndex
Returns an animated value representing the current tab index, as a floating point number.
const tabIndex = useAnimatedTabIndex()
useFocusedTab
Returns the currently focused tab name.
const focusedTab = useAnimatedTabIndex()
useHeaderMeasurements
Returns the top distance and the header height. See the animated header example in the example folder.
const { top, height } = useHeaderMeasurements()
Default Tab Bar
MaterialTabItem
Any additional props are passed to the pressable component.
Props
name | type | default | description |
---|---|---|---|
activeColor | string \| undefined |
null |
Color applied to the label when active |
inactiveColor | string \| undefined |
null |
Color applied to the label when inactive |
inactiveOpacity | number \| undefined |
0.7 |
|
index | number |
||
indexDecimal | SharedValue<number> |
||
label | string |
||
labelStyle | StyleProp<AnimateStyle<TextStyle>> |
Style to apply to the tab item label | |
name | T |
||
onLayout | (((event: LayoutChangeEvent) => void) & ((event: LayoutChangeEvent) => void)) \| undefined |
Invoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height}}}. | |
onPress | (name: T) => void |
||
pressColor | string \| undefined |
#DDDDDD |
|
pressOpacity | number \| undefined |
Platform.OS === 'ios' ? 0.2 : 1 |
|
scrollEnabled | boolean \| undefined |
||
style | StyleProp<ViewStyle> |
Either view styles or a function that receives a boolean reflecting whether the component is currently pressed and returns view styles. |
Known issues
Android FlatList pull to refresh
See this open issue. We use scrollTo to synchronize the unfocused tabs, it's supposed to work only with ScrollView
, but works great with FlatList
, until the RefreshControl
is added. Note that this happens only to android.
Workaround: see the Android Shared Pull To Refresh
example in the expo app. You can have a single pull to refresh for the Tabs.Container
.
iOS FlatList stickyHeaderIndices
When you use the stickyHeaderIndices prop on a FlatList, the sticky elements don't scroll up when the header collapses. This happens only on iOS.
See #136.
ref.setIndex
This isn't an issue, but you need to know. When using containerRef.current.setIndex(i)
, if setting to the current index, the screen will scroll to the top. You can prevent this behavior like this:
const index = pageRef.current?.getCurrentIndex()
if (index !== nextIndex) {
pageRef.current?.setIndex(nextIndex)
}
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.
Documentation changes
Edit the README_TEMPLATE, or update the docstrings inside the src
folder, and run:
yarn docs