PedroBern / react-native-collapsible-tab-view

A cross-platform Collapsible Tab View component for React Native
MIT License
792 stars 157 forks source link

Header scrollable with Touchable #361

Open nasirdeveloper opened 9 months ago

nasirdeveloper commented 9 months ago

Feature request

Tab header should scroll with whole content and header portion should have touchable(like: TouchableOpacity, TouchableRipple, Button)

If header content is large amount of content then tab portion was not able to see. Tab hides below the header content portion, Suppose there are less content in header part then tab portion is visible perfectly

We want smooth scroll on whole page (header portion and tab content) with touchables (The touchable part resides in header part but right now touchable not working on header part)

Current behavior

Currently you mentioned in your document about scroll of a whole page including header some of "pointer-events" property (e.g, 1. none 2. box-none)

But it does not work as we expected if header is scrollable , touchables are not working

please look up the issue It's really important issue

Thanks in advance

Screenshots (if applicable)

bpeck81 commented 5 months ago

@PedroBern Will this ever be addressed?

wyrainmonkey commented 4 months ago

me too How to solve this problem?

domenicoprestia commented 3 months ago

i need this too, how can we solve this?

antochan commented 3 months ago

Double clicking this!

mrtawil commented 1 month ago

+1

OTitaev commented 3 weeks ago

+1

yadormad commented 3 weeks ago

Found a solution, maybe someone can create pr with this approach

Basically, we need to make sure that the header from Tabs.Container is visible only when moving from one tab to another. When the transition is complete, you need to show the header from the flashlist (or the component you are using)

Here's what I did. First, let's apply the following patch to import useTabsContext and also make the background of the header container transparent.

diff --git a/node_modules/react-native-collapsible-tab-view/lib/typescript/src/index.d.ts b/node_modules/react-native-collapsible-tab-view/lib/typescript/src/index.d.ts
index 2a854a1..b3dd22e 100644
--- a/node_modules/react-native-collapsible-tab-view/lib/typescript/src/index.d.ts
+++ b/node_modules/react-native-collapsible-tab-view/lib/typescript/src/index.d.ts
@@ -32,7 +32,8 @@ export declare const Tabs: {
     }) => import("react").ReactElement<any, string | ((props: any) => import("react").ReactElement<any, any> | null) | (new (props: any) => import("react").Component<any, any, any>)>;
 };
 export { Container, Tab, Lazy, FlatList, ScrollView, SectionList, FlashList };
-export { useCurrentTabScrollY, useHeaderMeasurements, useFocusedTab, useAnimatedTabIndex, useCollapsibleStyle, } from './hooks';
+export { useCurrentTabScrollY, useHeaderMeasurements, useFocusedTab, useAnimatedTabIndex, useCollapsibleStyle,
+    useTabsContext, } from './hooks';
 export type { HeaderMeasurements } from './hooks';
 export { MaterialTabBar } from './MaterialTabBar/TabBar';
 export { MaterialTabItem } from './MaterialTabBar/TabItem';
diff --git a/node_modules/react-native-collapsible-tab-view/src/Container.tsx b/node_modules/react-native-collapsible-tab-view/src/Container.tsx
index 1023290..72961ae 100644
--- a/node_modules/react-native-collapsible-tab-view/src/Container.tsx
+++ b/node_modules/react-native-collapsible-tab-view/src/Container.tsx
@@ -465,7 +465,7 @@ const styles = StyleSheet.create({
     position: 'absolute',
     zIndex: 100,
     width: '100%',
-    backgroundColor: 'white',
+    backgroundColor: 'transparent',
     shadowColor: '#000000',
     shadowOffset: {
       width: 0,
@@ -476,6 +476,7 @@ const styles = StyleSheet.create({
     elevation: 4,
   },
   tabBarContainer: {
+    backgroundColor: 'white',
     zIndex: 1,
   },
   headerContainer: {
diff --git a/node_modules/react-native-collapsible-tab-view/src/index.tsx b/node_modules/react-native-collapsible-tab-view/src/index.tsx
index 019eb4b..7bc0f26 100644
--- a/node_modules/react-native-collapsible-tab-view/src/index.tsx
+++ b/node_modules/react-native-collapsible-tab-view/src/index.tsx
@@ -47,6 +47,7 @@ export {
   useFocusedTab,
   useAnimatedTabIndex,
   useCollapsibleStyle,
+  useTabsContext,
 } from './hooks'
 export type { HeaderMeasurements } from './hooks'

Next, I made a wrapper for the header:

import React, { FC, PropsWithChildren } from 'react'
import { Platform, StyleProp, ViewStyle } from 'react-native'

import { useTabsContext } from 'react-native-collapsible-tab-view'
import Animated, { useAnimatedStyle } from 'react-native-reanimated'

export interface ITabsHeaderWrapperProps {
    layoutType?: 'container' | 'flashList' | 'scrollView'
    scrollViewTopPadding?: number
}

export const TabsHeaderWrapper: FC<
    PropsWithChildren<ITabsHeaderWrapperProps>
> = ({ layoutType, scrollViewTopPadding, children }) => {
    const { indexDecimal, headerHeight, tabBarHeight, headerTranslateY } =
        useTabsContext()
    const isInsideTab = layoutType !== 'container'

    const animatedStyle = useAnimatedStyle(() => {
        // android sometimes keeps headerTranslateY positive after scroll to top
        // need to consider it to avoid header flickering while transitioning between tabs
        let translateY = 0
        if (Platform.OS === 'android') {
            translateY = headerTranslateY.value > 0 ? headerTranslateY.value : 0
        }
        const style: StyleProp<ViewStyle> = {}
        // if header is in flashlist, we need to shift it up on headerHeight + tabBarHeight value
        if (layoutType === 'flashList') {
            style.position = 'absolute'
            style.left = 0
            style.right = 0
            style.top =
                -(headerHeight.value ?? 0) - (tabBarHeight.value ?? 0) + translateY
            style.width = '100%'
        }
        // if header is in scrollview, we need to shift it up on scrollViewTopPadding value
        if (layoutType === 'scrollView') {
            style.position = 'absolute'
            style.left = 0
            style.right = 0
            style.top = -(scrollViewTopPadding ?? 0) + translateY
            style.width = '100%'
        }
        const isTransitioning = indexDecimal.value % 1 !== 0
        if (isInsideTab) {
            // if header is inside tab (e.g. flashList or scrollView), we need to hide it while transitioning
            style.opacity = isTransitioning ? 0 : 1
        } else {
            // if header in tab container (e.g. layoutType is container), we need to show it while transitioning
            style.opacity = isTransitioning ? 1 : 0
        }
        return style
    })
    return (
        <Animated.View
            style={animatedStyle}
            pointerEvents={isInsideTab ? 'auto' : 'none'}
        >
            {children}
        </Animated.View>
    )
}

And Here how we use it

<Tabs.Container
          allowHeaderOverscroll={true}
          renderHeader={() => (
              <TokenScreenHeaderWrapper layoutType="container">
                  <YourHeader />
              </TokenScreenHeaderWrapper>
          )}
          // your other props
          >
          <Tabs.Tab name="flash list tab">
              <Tabs.FlashList
                  ListHeaderComponent={
                      <TokenScreenHeaderWrapper layoutType="flashList">
                          <YourHeader />
                      </TokenScreenHeaderWrapper>
                  }
                  // your other props
              />
          </Tabs.Tab>
          <Tabs.Tab name="scroll view tab">
              <Tabs.ScrollView
                  style={{ paddingTop: SCROLL_VIEW_TOP_PADDING }}
                  // your other props
              >
                  <TokenScreenHeaderWrapper layoutType="scrollView" scrollViewTopPadding={SCROLL_VIEW_TOP_PADDING}>
                      <YourHeader />
                  </TokenScreenHeaderWrapper>
                  {/* your other content */}
              </Tabs.ScrollView>
          </Tabs.Tab>
</Tabs.Container>