1.0.1 • Published 1 year ago

rn-collapsible-tab-view-scrollable v1.0.1

Weekly downloads
-
License
MIT
Repository
-
Last release
1 year ago

react-native-collapsible-tab-view

Cloned from https://github.com/PedroBern/react-native-collapsible-tab-view and adapted to allow horizontal scroll and touchables at the top container element. To reduce the complexity and time to implement it the snap and header overscroll feature were removed.

Credits

The react-native-tab-view example app was used as template for the demos.

Demo

| Default | | | :-----: | |

|

Features

  • Animations and interactions on the UI thread
  • Highly customizable
  • Fully typed with TypeScript
  • Lazy support with fade-in animation
  • Interpolated 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 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

API reference

Core

Tabs.Container

Basic usage looks like this:

import { Tabs } from 'react-native-collapsible-tab-view'

const Example = () => {
  return (
    <Tabs.Container renderHeader={MyHeader}>
      <Tabs.Tab name="A">
        <ScreenA />
      </Tabs.Tab>
      <Tabs.Tab name="B">
        <ScreenB />
      </Tabs.Tab>
    </Tabs.Container>
  )
}

Props

nametypedefaultdescription
cancelLazyFadeInboolean \| undefined
cancelTranslationboolean \| undefined
containerStyleStyleProp<ViewStyle>
headerContainerStyleStyleProp<AnimateStyle<ViewStyle>>
headerHeightnumber \| undefinedIs optional, but will optimize the first render.
initialTabNamestring \| number \| undefined
lazyboolean \| undefinedIf lazy, will mount the screens only when the tab is visited. There is a default fade in transition.
minHeaderHeightnumber \| undefinedHeader minimum height when collapsed
onIndexChange((index: number) => void) \| undefinedCallback fired when the index changes. It receives the current index.
onTabChange(data: { prevIndex: number index: number prevTabName: T tabName: T }) => voidCallback fired when the tab changes. It receives the previous and current index and tabnames.
pagerPropsOmit<FlatListProps<number>, 'data' \| 'keyExtractor' \| 'renderItem' \| 'horizontal' \| 'pagingEnabled' \| 'onScroll' \| 'showsHorizontalScrollIndicator' \| 'getItemLayout'>Props passed to the horiztontal flatlist. 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
tabBarHeightnumber \| undefinedIs optional, but will optimize the first render.
widthnumber \| undefinedCustom width of the container. Defaults to the window width.

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

nametype
labelstring \| undefined
nameT

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

nametype
cancelLazyFadeInboolean \| undefined
startMountedboolean \| undefined

Tabs.FlatList

Use like a regular FlatList.

Tabs.ScrollView

Use like a regular ScrollView.

Tabs.SectionList

Use like a regular SectionList.

Ref

You can pass a ref to Tabs.Container.

const ref = React.useRef()
<Tabs.Container ref={ref}>
methodtype
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

nametype
contentContainerStyle{ minHeight: number; paddingTop: number; }
progressViewOffsetnumber
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 = useFocusedTab()

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

nametypedefaultdescription
activeColorstring \| undefinednullColor applied to the label when active
inactiveColorstring \| undefinednullColor applied to the label when inactive
inactiveOpacitynumber \| undefined0.7
indexnumber
indexDecimalSharedValue<number>
labelstring
labelStyleStyleProp<AnimateStyle<TextStyle>>Style to apply to the tab item label
nameT
onLayout(((event: LayoutChangeEvent) => void) & ((event: LayoutChangeEvent) => void)) \| undefinedInvoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height}}}.
onPress(name: T) => void
pressColorstring \| undefined#DDDDDD
pressOpacitynumber \| undefinedPlatform.OS === 'ios' ? 0.2 : 1
scrollEnabledboolean \| undefined
styleStyleProp<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 and iOS SectionList stickySectionHeadersEnabled

When you use the stickyHeaderIndices prop on a FlatList or stickySectionHeadersEnabled on a SectionList, 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)
}