react-native-collapsible-tab-view

repository·main·Indexed 22 days ago

https://github.com/pedrobern/react-native-collapsible-tab-view

A library for creating customizable tab views with collapsible headers in React Native using Reanimated. It supports smooth UI thread animations, lazy loading, and scroll snapping, and is compatible with scrollable components such as FlatList, SectionList, and ScrollView.

Tokens
11.7K
Snippets
41
Records
52
Agent score
77%

What's inside react-native-collapsible-tab-view

  1. Run the collapsible-tab-view-example locally

    main

    To run the project's example application on your own machine using Expo, follow these steps:

    1. Clone the repository.
    2. Navigate to the example directory: cd example.
    3. Install dependencies using yarn.
    4. Start the packager with yarn start.
    5. Use the Expo app on your mobile device to scan the provided QR code.
    # Clone the repository (assumed step)
    # Navigate to the example directory
    cd example
    
    # Install dependencies
    yarn
    
    # Start the packager
    yarn start
  2. Enable scrolling on the Header

    main

    To allow users to initiate scrolling by touching the header, you must configure the pointerEvents prop on your HeaderComponent:

    • If the header has NO touchable components: Set pointerEvents='none'.
    • If the header HAS touchable components: Set pointerEvents='box-none' to ensure buttons/links work while still allowing scroll gestures to pass through.

    Tip: If specific children (like an <Image />) should not intercept touches on iOS, set their pointerEvents to 'none' explicitly.

  3. Configure Metro for local development of react-native-collapsible-tab-view

    main

    When working within the example project to develop the library itself, the metro.config.js is configured to redirect the react-native-collapsible-tab-view dependency to the local src directory. This allows changes made in the library's source code to be reflected immediately in the example app without needing to rebuild or reinstall the package via npm/yarn.

    Key configurations used:

    • config.resolver.extraNodeModules: Uses a Proxy to redirect requests for react-native-collapsible-tab-view to the local ../src path, while falling back to standard node_modules for all other dependencies.
    • config.watchFolders: Includes the local ../src directory so Metro tracks file changes in the library source.
    • config.transformer.getTransformOptions: Disables experimentalImportSupport and inlineRequires for the transformer.
    const { getDefaultConfig } = require('expo/metro-config')
    const path = require('path')
    
    const extraNodeModules = {
      'react-native-collapsible-tab-view': path.resolve(`${__dirname}/../src`),
    }
    
    const config = getDefaultConfig(__dirname)
    
    config.transformer.getTransformOptions = async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    })
    
    config.resolver.extraNodeModules = new Proxy(extraNodeModules, {
      get: (target, name) =>
        //redirects dependencies referenced from src/ to local node_modules
        name in target
          ? target[name]
          : path.join(process.cwd(), `node_modules/${name}`),
    })
    
    config.watchFolders = [path.resolve(`${__dirname}/../src`)]
    
    module.exports = config
  4. Workaround for Android FlatList Pull to Refresh

    main

    On Android, adding a RefreshControl to a FlatList inside a tab can cause issues with synchronizing unfocused tabs due to how scrollTo is utilized.

    Workaround: Implement a single pull-to-refresh mechanism for the entire Tabs.Container instead of individual tabs. Refer to the Android Shared Pull To Refresh example in the Expo app for implementation details.

  5. Avoid unwanted scrolling when using ref.setIndex

    main

    When using containerRef.current.setIndex(i) to programmatically change tabs, calling setIndex with the index that is already active will cause the screen to scroll to the top. To prevent this, check if the nextIndex is different from the current index before calling the method.

    const index = pageRef.current?.getCurrentIndex()
    if (index !== nextIndex) {
      pageRef.current?.setIndex(nextIndex)
    }
  6. Quick Start with Tabs.Container and Tabs.Tab

    main

    The basic pattern for creating a collapsible tab view involves using Tabs.Container to wrap your header and tabs, and Tabs.Tab to define individual tab screens. You can use specialized scrollable components like Tabs.FlatList or Tabs.ScrollView inside each tab to ensure the header collapses correctly during scrolling.

    import React from 'react'
    import { View, StyleSheet, ListRenderItem } from 'react-native'
    import { Tabs } from 'react-native-collapsible-tab-view'
    
    const HEADER_HEIGHT = 250
    
    const DATA = [0, 1, 2, 3, 4]
    const identity = (v: unknown): string => v + ''
    
    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
          renderHeader={Header}
          headerHeight={HEADER_HEIGHT} // optional
        >
          <Tabs.Tab name="A">
            <Tabs.FlatList
              data={DATA}
              renderItem={renderItem}
              keyExtractor={identity}
            />
          </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
  7. Configure MaterialTabItem props

    main

    The MaterialTabItem is used within the default tab bar. You can customize its appearance and behavior using the following props:

    • activeColor: Color applied to the label when the tab is active.
    • inactiveColor: Color applied to the label when the tab is inactive.
    • inactiveOpacity: Opacity level for inactive tabs.
    • index: The current tab index.
    • indexDecimal: A SharedValue<number> for animated index transitions.
    • label: The text or a function returning a ReactNode for the tab label.
    • labelStyle: Style for the tab item label.
    • name: The identifier for the tab (type T).
    • onLayout: Invoked on mount and layout changes.
    • onPress: Callback function (name: T) => void triggered when the tab is pressed.
    • pressColor: Color applied during the press interaction.
    • pressOpacity: Opacity level during the press interaction.
    • scrollEnabled: Boolean to enable/disable scrolling of the tab bar.
    • style: View styles, or a function receiving a boolean (pressed state) that returns view styles.
  8. Tabs.Container Props Reference

    main

    The Tabs.Container component is the core orchestrator of the collapsible view. Use these props to configure behavior and styling.

    |name|type|default|description|
    |:----:|:----:|:----:|:----:|
    |`allowHeaderOverscroll`|`boolean \| undefined`|`false`|Whether the header moves down during overscrolling (for example on pull-to-refresh on iOS) or sticks to the top|
    |`cancelLazyFadeIn`|`boolean \| undefined`|||
    |`cancelTranslation`|`boolean \| undefined`|||
    |`containerStyle`|`StyleProp<ViewStyle>`||
    |`headerContainerStyle`|`StyleProp<AnimateStyle<ViewStyle>>`||
    |`headerHeight`|`number \| undefined`|\|Is optional, but will optimize the first render.|
    |`initialTabName`|`string \| undefined`||
    |`lazy`|`boolean \| undefined`|\|If lazy, will mount the screens only when the tab is visited. There is a default fade in transition.|
    |`minHeaderHeight`|`number \| undefined`|\|Header minimum height when collapsed|
    |`onIndexChange`|`((index: number) => void) \| undefined`|\|Callback fired when the index changes. It receives the current index.|
    |`onTabChange`|`(data: { prevIndex: number index: number prevTabName: T tabName: T }) => void`|\|Callback fired when the tab changes. It receives the previous and current index and tabnames.|
    |`pagerProps`|`Omit<FlatListProps<number>, 'data' \| 'keyExtractor' \| 'renderItem' \| 'horizontal' \| 'pagingEnabled' \| 'onScroll' \| 'showsHorizontalScrollIndicator' \| 'getItemLayout'>`|\|Props passed to the pager. If you want for example to disable swiping, you can pass `{ scrollEnabled: false }`|
    |`renderHeader`|`(props: TabBarProps<TabName>) => React.ReactElement \| null`||
    |`renderTabBar`|`(props: TabBarProps<TabName>) => React.ReactElement \| null`|`(props: TabBarProps<TabName>) => MaterialTabBar`|
    |`revealHeaderOnScroll`|`boolean \| undefined`|\|Reveal header when scrolling down. Implements diffClamp.|
    |`snapThreshold`|`number \| null \| undefined`|`null`|Percentage of header height to define as the snap point. A number between 0 and 1, or `null` to disable snapping.|
    |`tabBarHeight`|`number \| undefined`|\|Is optional, but will optimize the first render.|
    |`width`|`number \| undefined`|\|Custom width of the container. Defaults to the window width.|