pedrobern / react-native-collapsible-tab-view Goto Github PK
View Code? Open in Web Editor NEWA cross-platform Collapsible Tab View component for React Native
License: MIT License
A cross-platform Collapsible Tab View component for React Native
License: MIT License
Currently I have 4 screens and set them each to have a width of the window. Despite this, when swiping from one to the other the next screen is visible but then when you get to the point of the index changing, it disappears. If you click to another tab the same thing happens, it's just blank.
I would expect to see the relevant screen on both swipe and when each tab is selected.
const { useTabsContext, ...Tabs } = createCollapsibleTabs()
const Page = () => {
const [index, setIndex] = useState(0);
const containerRef = useAnimatedRef()
const postsTabRef = useAnimatedRef()
const detailsTabRef = useAnimatedRef()
const itemsTabRef = useAnimatedRef()
const similarTabRef = useAnimatedRef()
let refs = {
Posts: postsTabRef,
Details: detailsTabRef,
Items: itemsTabRef,
Similar: similarTabRef
}
const [refMap, setRefMap] = React.useState(refs)
return (<Tabs.Container
containerRef={containerRef}
headerHeight={headerHeight} // optional
refMap={refMap}
scrollEnabled = {true}
onIndexChange={setIndex}
>
<Tabs.ScrollView style={{ width: windowWidth, height: 600, backgroundColor: 'red'}} contentContainerStyle={{ height: "100%" }}>
<View><Text>Hello</Text></View>
</Tabs.ScrollView>
<Tabs.ScrollView style={{ width: windowWidth, height: 600, backgroundColor: 'orange'}} />
<Tabs.ScrollView style={{ width: windowWidth, backgroundColor: 'yellow'}} />
<Tabs.ScrollView style={{ width: windowWidth, backgroundColor: 'green'}} />
</Tabs.Container>
)
}
I originally had the 4 <Tabs.ScrollView />
components as the ones that are actually use in my app. I've minimized down to the bare elements to try and see where I may be causing my error but even with this basic layout it isn't working.
I've tried alternative widths and flex: 1, etc. I can't figure out where the issue is.
Currently, the snap animation is handled with scrollTo
, would be better to run it with Animated
.
navigationState.index
?If you want to conditionally display content based on the current tab, or you just want to know for some logic which tab is currently focused, you used to be able to work this out from navigationState
.
In createCollapsibleTabs
, we should ensure that children exists as this can be undefined
:
Line 88:
const scrollY = useSharedValue([...new Array(children.length)].map(() => 0));
Line 106:
const [data] = React.useState(
[...new Array(children.length)].map((_, i) => i)
)
Add a default TabBar component.
The user must provide a custom tab bar.
Thank you for creating this!
I saw that you mentioned in satya164/react-native-tab-view#1096 (comment) that there would be integration with react-navigation
.
I'm really interested in this, and would love to help by testing it. Alternatively, if you can share the private implementation you mentioned, I might be able to implement and test that as well, before a polished public release.
I've just tried to implement the basic example but the header isn't displaying? I'm on:
"react-native": "0.63.3"
Using:
const [index, setIndex] = useState(0);
const [routes] = useState([
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
]);
const _renderHeader = () => (
<View style={styles.header}>
<Text style={styles.headerText}>COLLAPSIBLE</Text>
</View>
);
const _onTabPress = (tabProps) => {
tabProps.jumpTo(tabProps.route.key);
};
const _renderTabBar = (props) => {
return <TabBar {...props} onTabPress={_onTabPress} />;
};
return (
<CollapsibleTabView
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderHeader={_renderHeader}
renderScene={SceneMap({
first: () => null,
second: () => null,
})}
renderTabBar={_renderTabBar}
/>
);
Any ideas?
Thank you for your great library!
When I have scrolled down on one tab so that the header is hidden and then change to another tab (via swipe or tap on the tab) that is scrolled all the way to the top, the paddingTop is showing. It would usually be obscured by the header and tabs but the header is shrunk because of the interaction with the previous tab.
Is there known solution for this? Or a canonical approach?
Thank you very much for your time.
The tabview header is not collapsing on scroll at the tabview
I would expected the header to collapse as exemplified in the README, im not sure if I'm missing any configuration, but I followed the docs more than once to be sure about it
https://github.com/tcorreiaubi/CollapsibleTabViewsDemo
I've copied the base example project into my project, except I added the scrollEventThrottle={12} to both Tab.FlatList and Tab.ScrollView. However, the blue indicator on the tabbar has low frame rate when scrolling horizontally or on pressing.
The blue indicator should animate smoothly
import { View, Text, StyleSheet, Animated, Easing, ListRenderItem } from 'react-native'
import * as React from 'react'
import { useTabDismissOnHideActionListener } from '../../navigation/bottom-tab-navigator-utils';
import { useCallback, useEffect, useState } from 'react';
import Colors from '../../constants/Colors';
import Layout from '../../constants/Layout';
import {
RefComponent,
ContainerRef,
createCollapsibleTabs,
TabBarProps as TabProps,
} from 'react-native-collapsible-tab-view'
import TabBar from './MaterialTabBar'
import { default as Reanimated, EasingNode, Extrapolate, useAnimatedRef } from 'react-native-reanimated'
type TabNames = 'A' | 'B'
type HeaderProps = TabProps<TabNames>
const { useTabsContext, ...Tabs } = createCollapsibleTabs<TabNames>()
const {
add,
interpolate,
} = Reanimated
const HEADER_HEIGHT = 250
const TABBAR_HEIGHT = 48
const ChatScreen: React.FC<{
navigation: any
}> = (props) => {
const {
navigation,
} = props;
const {
show,
animated
} = useTabDismissOnHideActionListener(navigation)
const [focusAnimationRef] = useState(new Animated.Value(0))
const [focusAnimationAfterRef] = useState(new Animated.Value(0))
const containerRef = useAnimatedRef<ContainerRef>()
const tabARef = useAnimatedRef<RefComponent>()
const tabBRef = useAnimatedRef<RefComponent>()
const [refMap] = React.useState({
A: tabARef,
B: tabBRef,
})
const initialize = useCallback(() => {
Animated.sequence([
Animated.timing(focusAnimationRef, {
toValue: 1,
duration: 300,
easing: Easing.bezier(.3, 1, 1, 1),
useNativeDriver: true,
}),
Animated.spring(focusAnimationAfterRef, {
toValue: 1,
stiffness: 300,
damping: 200,
mass: 1,
useNativeDriver: true,
})
]).start()
}, [])
const reset = useCallback(() => {
focusAnimationRef.setValue(0)
focusAnimationAfterRef.setValue(0)
}, [])
useEffect(() => {
navigation.addListener('focus', initialize)
navigation.addListener('blur', reset)
return () => {
navigation.removeListener('focus', initialize)
navigation.removeListener('blur', reset)
}
}, [])
return (
<View style={styles.container}>
<Animated.View style={[styles.innerContainer, {
opacity: focusAnimationRef,
transform: [
{
translateX: focusAnimationRef.interpolate({
inputRange: [0, 1],
outputRange: [-Layout.width, 0],
extrapolate: 'clamp',
})
},
{
translateY: focusAnimationAfterRef.interpolate({
inputRange: [0, 1],
outputRange: [Layout.STATUS_BAR_HEIGHT, 0],
// extrapolate: 'clamp',
})
},
]
}]}>
<Tabs.Container
containerRef={containerRef}
TabBarComponent={TabBar}
HeaderComponent={Header}
headerHeight={HEADER_HEIGHT}
tabBarHeight={TABBAR_HEIGHT}
refMap={refMap}
// diffClampEnabled snapEnabled
>
<ScreenA />
<ScreenB />
</Tabs.Container>
</Animated.View>
</View>
);
}
const ScreenB = () => {
return (
<Tabs.ScrollView
name="B"
scrollEventThrottle={12}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
>
<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 + ''}
scrollEventThrottle={12}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
/>
)
}
const Header: React.FC<HeaderProps> = () => {
return <View pointerEvents={'none'} style={styles.header} />
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.themeColor,
},
innerContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: Colors.themeBlack,
borderRadius: Layout.MODAL_BORDER_RADIUS
},
////
box: {
height: 250,
width: '100%',
},
boxA: {
backgroundColor: 'white',
},
boxB: {
backgroundColor: '#D8D8D8',
},
header: {
height: HEADER_HEIGHT,
width: '100%',
backgroundColor: '#2196f3',
},
})
ChatScreen.displayName = 'ChatScreen'
export default ChatScreen
sort-keys-fix/sort-keys-fix
or sort-keys
pluginsCurrently, the refMap
uses the keys to create the labels.
Line 150 in src/MaterialTabBar/TabBar.tsx
getLabelText = (name) => name.toUpperCase()
.
.
.
{Object.keys(refMap).map((name, i) => {
return (
<TabItemComponent
key={name}
index={i}
name={name}
label={getLabelText(name)}
onPress={onTabPress}
onLayout={scrollEnabled ? onTabItemLayout : undefined}
scrollEnabled={scrollEnabled}
indexDecimal={indexDecimal}
{...tabItemProps}
/>
)
})}
However, if we changed the structure of refMap, it could take a label or component/icon to might allow for more customisation.
Something like:
const [refMap] = React.useState({
screen1: { ref: ref, label: () => <FontAwesomeIcon icon={'images'} size={22} /> },
screen2: { ref: ref, label: () => <Text>With Space</Text> }
})
Currently, there is no way to pass a ref
to the scrollable components. Would be nice to support it.
react-native-reanimated
v2 has quite a bit more set-up. It's not immediately clear that when installing the library, you have to be using v2 of the library.
Also, things like Direct Debugging are things that people who install v2 may not be expecting.
Also, we should bump the version in the example to 2.0.0-rc.2
@PedroBern Here is an interesting one. One of the Collapsible Scenes within my Tabs is a HOC component which fetches data from a Search Service (Algolia), the results of which are displayed in a FlatList
.
Due to the warning: VirualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead.
I have created a re-usable CollapsibleScene component that basically renders a FlatList with the content in the ListHeaderComponent
and a null renderItem
. (I could pass as children
and use as data={[children]}
too). Within this inner tabs' inner content, I have some components that wrap the FlatList
, with some connectors etc for the search and then the Flatlist
.
E.g.
export const AnimatedNestedEmptyContacts = React.forwardRef<
any,
React.PropsWithoutRef<CollapsibleScenePropsAndRef>
>((props, ref) => {
const [isRefreshing, startRefreshing] = useRefresh();
return (
<Animated.FlatList
ref={ref}
data={[]}
keyExtractor={(_, i) => String(i)}
renderItem={null}
ListHeaderComponent={
// Could be HOC Component Here, e.g. Algolia InstantSearch
<View style={styles.content}>
{/* Could be Connector Component Here, e.g. Algolia Configure */}
<FlatList
data={[]}
keyExtractor={(_, i) => String(i)}
renderItem={renderItem}
ItemSeparatorComponent={ItemSeparator}
refreshing={isRefreshing}
onRefresh={startRefreshing}
ListEmptyComponent={ListEmptyComponent}
/>
</View>
}
{...props}
/>
);
});
When the nested FlatList
has data, the outer FlatList
will scroll and the content moves with the tabs etc. However, I'm struggling to replicate the centered ListEmptyComponent
for this inner FlatList
. Also, when scrolling on the non-nested Tab and switching, doesn't keep the inner FlatList
aligned.
Please see video below:
So, taking a different tactic, I tried using useCollapsibleScene
further down, e.g.
export const AnimatedNonNestedEmptyContacts = () => {
const scenePropsAndRef = useCollapsibleScene('contactsNonNestedPopulated');
const [isRefreshing, startRefreshing] = useRefresh();
return (
// Could be HOC Component Here, e.g. Algolia InstantSearch
<View
style={{
flex: 1,
}}
>
{/* Could be Connector Component Here, e.g. Algolia Configure */}
<Animated.FlatList
data={[]}
keyExtractor={(_, i) => String(i)}
renderItem={renderItem}
ItemSeparatorComponent={ItemSeparator}
refreshing={isRefreshing}
onRefresh={startRefreshing}
ListEmptyComponent={ListEmptyComponent}
{...scenePropsAndRef}
/>
</View>
);
};
However, this also doesn't seem to be working.
Any ideas on how to solve this? I have made a PR for you to check out.
hi
I want to use some api on <FlatList />
, like scrollToIndex
, but I can't get the ref correctly.
<Animated.FlatList
ref={this.flatListRef}
{...scenePropsAndRef}
/>
componentDidMount() {
setTimeout(() => {
console.log(this.flatListRef.current); // null
}, 3000);
}
What might I have missed?
bundling failed: Error: Unable to resolve module `@react-navigation/material-top-tabs` from `node_modules/react-native-collapsible-tab-view/lib/module/MaterialTopTabsCollapsibleTabView.js`: @react-navigation/material-top-tabs could not be found within the project.
I used it without the integration with react-navigation, but the above error occurs.
Currently when I scroll on the header it doesn't move the position, despite adding pointer events.
Would expect it to scroll
const { useTabsContext, ...Tabs } = createCollapsibleTabs()
const Page = () => {
const [index, setIndex] = useState(0);
const containerRef = useAnimatedRef()
const postsTabRef = useAnimatedRef()
const detailsTabRef = useAnimatedRef()
const itemsTabRef = useAnimatedRef()
const similarTabRef = useAnimatedRef()
let refs = {
Posts: postsTabRef,
Details: detailsTabRef,
Items: itemsTabRef,
Similar: similarTabRef
}
const [refMap, setRefMap] = React.useState(refs)
const Header = () => {
return (<View pointerEvents="box-none" style={{ width: windowWidth }}>
<TouchableOpacity onPress={toggleImage}>
<Image source={{ uri: thing.image || "https://picsum.photos/500" }} style={{ flex: 1, alignSelf: 'center', width: 200, height: 200, borderRadius: 200*.42 }} />
</TouchableOpacity>
</View>
)
}
return (<Tabs.Container
containerRef={containerRef}
headerHeight={headerHeight} // optional
HeaderComponent={Header}
refMap={refMap}
scrollEnabled = {true}
onIndexChange={setIndex}
>
<Tabs.ScrollView style={{ width: windowWidth, height: 600, backgroundColor: 'red'}} contentContainerStyle={{ height: "100%" }}>
<View><Text>Hello</Text></View>
</Tabs.ScrollView>
<Tabs.ScrollView style={{ width: windowWidth, height: 600, backgroundColor: 'orange'}} />
<Tabs.ScrollView style={{ width: windowWidth, backgroundColor: 'yellow'}} />
<Tabs.ScrollView style={{ width: windowWidth, backgroundColor: 'green'}} />
</Tabs.Container>
)
}
N/A, just imagine a header trying to be scrolled and not going anywhere
I've tried adding the relevant pointer-events
Well, last question for today, is there any way to have some sort of header when the tab bar gets sticky? So if on scroll the header disappears and the title of the page is now out of sight, is there any place for some replacement title/part of the header to exist?
I can't figure out where it would go given the header is included in the component itself. This isn't a problem in the Example gifs where the title is just in the nav bar, but that's not always the case.
Any thoughts on this one?
Hi, love this plugin well written.
I'm trying to make a header with several animated objects in it that fade out but some will stay. So far i have not figured out where the final offset is calculated to keep some of the header without closing it compleately
I'm just investigating the rendered position when you include a ListEmptyComponent
. I believe the position is correct when you scroll to hide the header (i.e. the List EmptyComponent is in the middle of the screen). However, it displays too far down when the header is displayed (the default on page load).
For example in Contacts.tsx
.
const ListEmptyComponent = () => (
<View
style={{
alignItems: 'center',
flex: 1, // Without it will dispay at the very top
justifyContent: 'center',
}}
>
<Text>No results.</Text>
</View>
);
export default class Contacts extends React.Component {
render() {
return (
<FlatList
data={CONTACTS}
keyExtractor={(_, i) => String(i)}
renderItem={renderItem}
ItemSeparatorComponent={ItemSeparator}
/>
);
}
}
// used in Collapsible TabView examples
export const AnimatedContacts = React.forwardRef<
any,
React.PropsWithoutRef<CollapsibleScenePropsAndRef>
>((props, ref) => {
const [isRefreshing, startRefreshing] = useRefresh();
return (
<Animated.FlatList
ref={ref}
data={CONTACTS}
keyExtractor={(_, i) => String(i)}
renderItem={renderItem}
ItemSeparatorComponent={ItemSeparator}
ListEmptyComponent={ListEmptyComponent} // <--- Here
refreshing={isRefreshing}
onRefresh={startRefreshing}
{...props}
/>
);
});
And
I'm not using Typescript and can't figure out how to convert this to a Javascript example. It doesn't make sense for me to start using Typescript in the middle of this project. Does anybody have a javascript example?
Thank you!
Currently, the CollapsibleTabView
always starts with the header expanded. Would be nice to allow starting collapsed, or at any given offset.
A feature request to enable a ref on the CollapsibleTabView
when clicking on the react-navigation
bottom tab, to reset the scroll of all of the tabs to the top.
I noticed that there's some sort of loop happening when doing very small momentum scrolls within the header.
I'm not sure if this reproduces outside of my project, and I also use react-navigation
via a WIP PR I'm working on for #7
What seems to be happening is that onMomentumScrollEnd
triggers via the user gesture, then syncScrollOffset
calls scrollScene
which then also triggers onMomentumScrollEnd
and then there's a battle between the two triggering a loop.
This seems to have also been reported here: https://stackoverflow.com/questions/61715531/abrupt-scrolltooffset-calls-in-onmomentumscrollend-react-native
However, it seems to be possible to use https://reactnative.dev/docs/scrollview#snaptooffsets with values of [0, headerHeight]
to achieve the same behavior as the custom scroll snapping. Is there any reason not to use snapToOffsets
?
A current workaround is to set disableSnap
to true and add snapToOffsets
to my own list views
I don't know how to write TypeScript.
Could you please coding a "quick start" consisting of a react component?
I've been struggling for days.
I'm dying.
only 2days left.
I'm seeing the error:
Warning: Encountered two children with the same key, `undefined`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted โ the behavior is unsupported and could change in a future version.
Hey(once again), I was trying to force a tab to always start at a certain scroll position, this is a really specific behavior, what I tried to do was on the indexChange call the scrollTo on that tab to place it on the position where I need, example
onIndexChange={(onChangeData) => {
const {tabName} = onChangeData;
if (tabName === TabKey.MAP) {
(mapRef.current as ScrollView).scrollTo({
y: IMAGE_SLIDER_HEIGHT - HEADER_BAR_HEIGHT,
animated: true,
});
}
}}
This works great for the current tab but the others tab don't sync their position properly, should I call another function or just update some value from the context? Thanks once again
Support lazy tabs.
<CollapsibleTabView lazy ...>
do not sync scroll position of unmounted routes, as expected, because the routes are unmounted! But, when the routes are mounted, may start with a gap between the header and the content.
When mounting the lazy routes, they should know the header position and start with the correct scroll position.
The current workaround is to handle the laziness by yourself. What actually can be a good thing, since the lazy prop of react-native-tab-view
is pseudo-lazy. It loads all routes if you click on the last tab!
@PedroBern There may be an issue with the Indicator on the scrollable after #75 It's like the Tabs are in the wrong order?
I have a couple of pages that require tabs that do not require a header. I thought that the default of renderHeader
was () => null
so passing no header would still display the tabs as if we were using react-native-tab-view
.
<CollapsibleTabView<Route>
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={handleIndexChange}
// renderHeader={renderHeader}
headerHeight={HEADER_HEIGHT}
{...props}
/>
However, if you omit renderHeader
, the tabs do not show.
First of all, thanks for creating this awesome wrapper!
As you can see, when I scroll down the A tab and switch to B, the header gets stuck and there is empty space between the header and the content. I guess the expected behaviour would be that the header resets to max height when switching to tab B.
Need Option for changing Tab Bar Background Color
Currently default white color hard coded in the stylesheet. Please add an option.
You may want to implement a pull to refresh that is shared across all tabs. This might just be good to be included in the examples.
Currently, the examples load a pullToRefresh from each tab's scene independently. I.e. if you do not implement a pull to refresh on each tab, then depending on what tab you are on you you'll get different behaviour.
TAB
A shared pullToRefresh that implements a "refreshing" value that can be used trigger a useEffect
in each scene.
The RefreshControl be shown on top of the header where it should be shown at top of FlatList itself, is there any workaround so the RefreshControl stick at the top of FlatList
In some cases, where the collapsible header covers a significant amount of the vertical screen space, a user might prefer or even be forced to start their scroll gesture on the collapsible header. There are a few cases I can think of where this may happen:
Currently, when starting a scroll gesture on the header nothing happens at all. It would be nice if there was a (opt-in) way for these gestures to be passed or translated to the ScrollView
/ FlatList
inside the focused tab. This behavior would also be consistent with some native implementations collapsible headers (e.g the "Galaxy Store" App).
If anyone has ideas on how this could be implemented I would love to help and open a PR.
Large header | Android Split Screen |
---|---|
Configuring a flatlist with horizontal direction on a Tab.ScrollView is not working as expected, the content is being displayed vertically instead of horizontally
The Content to be displayed in the horizontal direction
https://github.com/tcorreiaubi/CollapsibleTabViewsDemo
When upgrading to 4.0.0-next.0
and running the basic version in a react-native application (not expo) as per:
import { Tabs } from 'react-native-collapsible-tab-view';
.
.
.
return (
<Tabs.Container HeaderComponent={_renderHeader}>
<Tabs.Tab name="A">
<Tabs.FlatList
data={[0, 1, 2, 3, 4]}
keyExtractor={(v) => v + ''}
renderItem={null}
/>
</Tabs.Tab>
<Tabs.Tab name="B">
<Tabs.ScrollView>{null}</Tabs.ScrollView>
</Tabs.Tab>
</Tabs.Container>
)
I see [Object Map Iterator], where the tabs are meant to be:
I also see:
I'm also having a problem scrolling the header. I will add additional notes here as I investigate.
Please provide an option for changing the Tab Bar Item Label Style.
In #64, you provided an option for changing the tab bar background, that is great.
But need an option to change the existing label style without using custom 'TabItemComponent' as you mentioned in #70.
Waiting for your valuable reply.
Hi @PedroBern I hope you had a nice Christmas and New Year.
I'm just looking into an issue when you use a custom tab bar. Everything works when you don't render a custom tab bar, however, when using a custom tab bar it seems like the preventDefault does not prevent clicking on the tab when the user is scrolling. Is this perhaps because the isGliding.current
is being passed instead of isGliding
?
Basically it becomes gittery.
The code below:
/**
*
* Wraps the tab bar with `Animated.View` to
* control the translateY property.
*
* Render the header with `renderHeader` prop.
*
* Render the default `<TabBar />` with additional
* `tabBarProps`, or a custom tab bar from the
* `renderTabBar` prop, inside the Animated wrapper.
*/
const renderTabBar = (
props: SceneRendererProps & {
navigationState: NavigationState<T>;
}
): React.ReactNode => {
return (
<Animated.View
pointerEvents="box-none"
style={[
styles.headerContainer,
{ transform: [{ translateY }] },
headerContainerStyle,
]}
onLayout={getHeaderHeight}
>
{renderHeader()}
{customRenderTabBar ? (
customRenderTabBar({
...props,
...tabBarProps,
isGliding: isGliding.current,
})
) : (
<TabBar
{...props}
{...tabBarProps}
onTabPress={(event) => {
if (isGliding.current && preventTabPressOnGliding) {
event.preventDefault();
}
tabBarProps?.onTabPress && tabBarProps.onTabPress(event);
}}
/>
)}
</Animated.View>
);
};
Within my custom TabBar, I'm using (shortened version):
import { TabBar as RNTVTabBar } from 'react-native-tab-view';
const TabBar = (
{ badges, isGliding, ...props },
) => {
return (
<RNTVTabBar
{...props}
.
.
onTabPress={(event) => {
if (isGliding) {
event.preventDefault();
}
props?.onTabPress && props.onTabPress(event);
}}
/>
);
};
This is a video showing it working with no custom tab bar:
This is a video showing it not working with a custom tab bar:
It would be nice to actually have the header collapse even if the tab content does not fill the page
Hello, first of all, amazing package. This is something the rn community definetly needs! One question though. Do you plan to support Sectionlist, or is there a way to make it work with sectionlist?
Getting the following error while trying to use Tabs.Container on iOS:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of TabItem
.
import {
createCollapsibleTabs,
} from 'react-native-collapsible-tab-view';
import {useAnimatedRef} from 'react-native-reanimated';
const {useTabsContext, ...Tabs} = createCollapsibleTabs();
// ....
function ProgramCreation({route, navigation}) {
const containerRef = useAnimatedRef();
const aboutRef = useAnimatedRef();
const feedRef = useAnimatedRef();
const [refMap] = useState({
A: aboutRef,
B: feedRef,
});
const Header = ({title, height = 200}) => {
return (
<View style={[{height}]}>
<Text style={styles.defaultText}>{title}</Text>
</View>
);
};
const AboutView = () => {
return (
<View/>
)
}
const FeedView = () => {
return (
<View/>
)
}
return (
<Tabs.Container
containerRef={containerRef}
HeaderComponent={Header}
headerHeight={200}
refMap={refMap}>
<AboutView />
<FeedView/>
</Tabs.Container>
);
}
And my babel.config.js
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['react-native-reanimated/plugin'],
};
I'm currently using bare react native. No expo or typescript.
I've tried installing this and react-native-reanimated using both npm and yarn...same result.
I was able to successfully run the example app through expo on my phone but no luck using my own app.
react-native: 0.62.2
react-native-collapsible-tab-view: 3.7.1,
react-native-reanimated: 2.0.0-rc.0 (also tried 2.0.0-rc.2)
Hi been a bit active on this repo today, hoping this is my last question (and thanks for the support so far).
I'm trying to conditionally set my ref map but it doesn't seem to like updates. Basically I'm trying to load pages that may or may not have content for the various pages, and if there's no content I don't want to show them.
Here's where I'm starting:
let refs = {
Posts: postsTabRef,
Details: detailsTabRef,
Items: itemsTabRef,
Similar: similarTabRef
}
Simple example of what I was trying/would like to achieve:
```
let refs = {}
refs['Posts'] = postsTabRef
if (details.length > 0){
refs['Details'] = detailsTabRef
}
if (items.length > 0){
refs['Items'] = itemsTabRef
}
if (similar.length > 0){
refs['Similar'] = similarTabRef
}
const [refMap, setRefMap] = React.useState(refs)
With this, was trying to check after an API call returns, could I update refMap and should I somehow ask it to refresh without re-rendering the children components?
I can currently update refs and it will render the right tabs, but when clicking them I'm currently getting scrollToIndex out of range, which just makes me think it renders first with some number of expected tabs, and then re-renders to show the tabs but on-click it doesn't recognize that tab actually belongs.
Any thoughts would be great!
It would be great if a test suite could verify that everything is working properly. The interactions are pretty finicky and things can break in odd places by doing minor changes in others.
This would make the library much more maintainable and easier to refactor in the future, while giving more peace of mind to users that upgrades are safe.
Should be relatively easy to add gray box e2e testing using:
https://github.com/wix/Detox
Tests could be added to initiate scrolls and touches, then assert whether things are positioned correctly.
Another possibility is to convert the Example app to use Storybook:
https://storybook.js.org/
https://www.learnstorybook.com/intro-to-storybook/react-native/en/get-started/
It would then be possible to use loki to do screenshot diff testing for the stories (this wouldn't really test dynamic interactions though, only static rendering):
https://github.com/oblador/loki
I tried to send a Pull Request, but was failed by the process.
Instead, I will send a diff of the js file.
Sorry about that.
Not work syncScrollOffsets.
Work syncScrollOffsets.
It seems to be a ref
problem.
The scrollToxxx
method is not being accessed.
diff --git a/node_modules/react-native-collapsible-tab-view/lib/module/CollapsibleTabView.js b/node_modules/react-native-collapsible-tab-view/lib/module/CollapsibleTabView.js
index f7a3837..4909fe3 100644
--- a/node_modules/react-native-collapsible-tab-view/lib/module/CollapsibleTabView.js
+++ b/node_modules/react-native-collapsible-tab-view/lib/module/CollapsibleTabView.js
@@ -88,14 +88,14 @@ const CollapsibleTabView = ({
if (newOffset !== null) {
scrollScene({
- ref: item.value,
+ ref: item.value.getNode(),
offset,
animated: false
});
listOffset.current[item.key] = offset;
} else if (itemOffset < headerHeight || !itemOffset) {
scrollScene({
- ref: item.value,
+ ref: item.value.getNode(),
offset: Math.min(offset, headerHeight),
animated: false
});
@@ -117,7 +117,7 @@ const CollapsibleTabView = ({
listRefArr.current.forEach(item => {
// scroll everything because we could be moving to a new tab
scrollScene({
- ref: item.value,
+ ref: item.value.getNode(),
offset: newOffset,
animated: true
});
@@ -169,14 +169,14 @@ const CollapsibleTabView = ({
const buildGetRef = React.useCallback(routeKey => ref => {
if (ref) {
- const found = listRefArr.current.find(e => e.key === routeKey);
-
- if (!found) {
- listRefArr.current.push({
+ listRefArr.current = listRefArr.current.filter((listRef) => {
+ return listRef.key !== routeKey
+ }).concat(
+ [{
key: routeKey,
value: ref
- });
- }
+ }]
+ );
}
}, []);
/**
Hi @PedroBern, I've finally gotten around to have a look at the new v3 implementation.
On running the example, I see: You specified
onScrollon a <ScrollView> but not
scrollEventThrottle. You will only receive one event. Using
16 you get all the events but be aware that it may cause frame drops, use a bigger number if you don't need as much precision.
I don't know if it's related yet, but I'm also seeing quite jittery behaviour. Please see video:
I haven't fully investigated this and will add more information if/when I find out more.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.