diff --git a/README.md b/README.md index 10105a63..017546cc 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ All props are optional, but if you are not rendering a header, you'd be probably | `headerContainerStyle?` | Styles applied to header and tabbar container. | `undefined` | | `preventTabPressOnGliding?` | Prevent tab press if screen is gliding. Ignored if `renderTabBar` is provided. | `true` | | `disableSnap?` | Disable the snap animation. | `false` | -| `renderTabBar?` | Same as [renderTabBar](https://github.com/satya164/react-native-tab-view#rendertabbar) of the original [TabView](https://github.com/satya164/react-native-tab-view#tabview), but with the additional `isGliding` property. | `undefined` | +| `renderTabBar?` | Same as [renderTabBar](https://github.com/satya164/react-native-tab-view#rendertabbar) of the original [TabView](https://github.com/satya164/react-native-tab-view#tabview), but with the additional `isGliding` and `preventTabPressOnGliding` properties. | `undefined` | | `snapThreshold?` | Percentage of header height to make the snap effect. A number between 0 and 1. | `0.5` | | `snapTimeout?` | How long to wait before initiating the snap effect, in milliseconds. | `250` | | `onHeaderHeightChange?` | Callback fired when the `headerHeight` state value inside `CollapsibleTabView` will be updated in the `onLayout` event from the tab/header container.

Useful to call layout animations. Example:

() => {LayoutAnimation.configureNext(preset)};
| `undefined` | diff --git a/example/src/App.tsx b/example/src/App.tsx index bc08b93e..8cede50d 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -22,6 +22,7 @@ import CollapsibleTabViewSmallContentExample from './CollapsibleTabViewSmallCont import CollapsibleTabViewNoSnapExample from './CollapsibleTabViewNoSnapExample'; import CollapsibleTabViewResizeExample from './CollapsibleTabViewResizeExample'; import CollapsibleTabViewDemoExample from './CollapsibleTabViewDemoExample'; +import CollapsibleTabViewCustomTabBarExample from './CollapsibleTabViewCustomTabBarExample'; import CollapsibleTabViewCenteredEmptyListExample from './CollapsibleTabViewCenteredEmptyListExample'; import MaterialTopTabsCollapsibleTabViewDemoExample from './NavigationExample'; import CollapsibleTabViewNoUpfrontHeightExample from './CollapsibleTabViewNoUpfrontHeightExample'; @@ -42,6 +43,7 @@ const EXAMPLE_COMPONENTS: ExampleComponentType[] = [ CollapsibleTabViewNoSnapExample, CollapsibleTabViewResizeExample, CollapsibleTabViewDemoExample, + CollapsibleTabViewCustomTabBarExample, CollapsibleTabViewSmallContentExample, CollapsibleTabViewNoUpfrontHeightExample, MaterialTopTabsCollapsibleTabViewDemoExample, diff --git a/example/src/CollapsibleTabViewCustomTabBarExample.tsx b/example/src/CollapsibleTabViewCustomTabBarExample.tsx new file mode 100644 index 00000000..e669d748 --- /dev/null +++ b/example/src/CollapsibleTabViewCustomTabBarExample.tsx @@ -0,0 +1,128 @@ +import * as React from 'react'; +import { StyleSheet, View, Text, Animated } from 'react-native'; +import { + CollapsibleTabView, + RenderTabBarProps, + useCollapsibleScene, +} from 'react-native-collapsible-tab-view'; +import { SceneMap, TabBarProps, TabBar } from 'react-native-tab-view'; +import { ExampleComponentType } from './types'; + +type Route = { + key: string; + title: string; + icon?: string; +}; + +const SomeRoute: React.FC<{ routeKey: string; color: string }> = ({ + routeKey, + color, +}) => { + const scrollPropsAndRef = useCollapsibleScene(routeKey); + + return ( + + + + ); +}; + +const FirstScene = () => ; +const SecondScene = () => ; + +const HEADER_HEIGHT = 250; + +const renderHeader = () => ( + + COLLAPSIBLE + +); + +const renderScene = SceneMap({ + first: FirstScene, + second: SecondScene, +}); + +const renderTabBar = ( + props: RenderTabBarProps> +): React.ReactNode => ( + { + if (props?.isGliding?.current && props?.preventTabPressOnGliding) { + event.preventDefault(); + } + props?.onTabPress && props.onTabPress(event); + }} + renderLabel={({ color, focused, route }) => ( + + + {route.icon ?? route.title} + + + )} + /> +); + +const CollapsibleTabViewExample: ExampleComponentType = () => { + const [index, setIndex] = React.useState(0); + const [routes] = React.useState([ + { key: 'first', title: 'First', icon: '👋' }, // or FontAwesome/badge + { key: 'second', title: 'Second', icon: '🤯' }, + ]); + + const handleIndexChange = (index: number) => { + setIndex(index); + }; + + return ( + > + navigationState={{ index, routes }} + renderScene={renderScene} + onIndexChange={handleIndexChange} + renderHeader={renderHeader} // optional + renderTabBar={renderTabBar} // optional + headerHeight={HEADER_HEIGHT} // optional, will be computed. + /> + ); +}; + +const styles = StyleSheet.create({ + header: { + height: HEADER_HEIGHT, + backgroundColor: '#2196f3', + justifyContent: 'center', + alignItems: 'center', + elevation: 4, + }, + headerText: { + color: 'white', + fontSize: 24, + }, + content: { + height: 1500, + }, + label: { + alignContent: 'center', + flex: 1, + justifyContent: 'center', + }, + title: {}, +}); + +CollapsibleTabViewExample.title = 'Custom Tab Bar demo'; +CollapsibleTabViewExample.backgroundColor = '#2196f3'; +CollapsibleTabViewExample.appbarElevation = 0; + +export default CollapsibleTabViewExample; diff --git a/src/CollapsibleTabView.tsx b/src/CollapsibleTabView.tsx index f7a7e8ea..cb8be52f 100644 --- a/src/CollapsibleTabView.tsx +++ b/src/CollapsibleTabView.tsx @@ -20,7 +20,19 @@ import { CollapsibleContextProvider } from './CollapsibleTabViewContext'; import scrollScene from './scrollScene'; import type { ScrollRef, GetRef } from './types'; -export type Props = Partial> & +type PTabBarProps = Partial>; + +export type RenderTabBarProps = { + navigationState: NavigationState; + isGliding: React.MutableRefObject; + preventTabPressOnGliding: boolean; +} & SceneRendererProps & + P; + +export type Props< + T extends Route, + P extends object = PTabBarProps +> = Partial, 'renderTabBar'>> & Pick, 'onIndexChange' | 'navigationState' | 'renderScene'> & { /** * Optionally controlled animated value. @@ -38,9 +50,9 @@ export type Props = Partial> & /** * Props passed to the tab bar component. */ - tabBarProps?: Partial>; + tabBarProps?: P; /** - * Header rendered on top of the tab bar. Defaul is `() => null` + * Header rendered on top of the tab bar. Default is `() => null` */ renderHeader?: () => React.ReactNode; /** @@ -57,14 +69,9 @@ export type Props = Partial> & disableSnap?: boolean; /** * Same as `renderTab` of `TabViewProps`, but with the additional - * `isGliding` property. + * `isGliding` and `preventTabPressOnGliding` properties. */ - renderTabBar?: ( - props: SceneRendererProps & { - navigationState: NavigationState; - isGliding: boolean; - } - ) => React.ReactNode; + renderTabBar?: (props: RenderTabBarProps) => React.ReactNode; /** * Callback fired when the `headerHeight` state value inside * `CollapsibleTabView` will be updated in the `onLayout` event @@ -99,7 +106,10 @@ export type Props = Partial> & * `CollapsibleTabView` wraps the `TabView` and take care of animations / * scroll value computations. It should be used with `useCollapsibleScene`. */ -const CollapsibleTabView = ({ +const CollapsibleTabView = < + T extends Route, + P extends object = PTabBarProps +>({ animatedValue = new Animated.Value(0), navigationState: { index, routes }, renderHeader = () => null, @@ -115,7 +125,7 @@ const CollapsibleTabView = ({ snapTimeout = 250, routeKeyProp = 'key', ...tabViewProps -}: React.PropsWithoutRef>): React.ReactElement => { +}: React.PropsWithoutRef>): React.ReactElement => { const [headerHeight, setHeaderHeight] = React.useState( Math.max(initialHeaderHeight, 0) ); @@ -363,10 +373,12 @@ const CollapsibleTabView = ({ > {renderHeader()} {customRenderTabBar ? ( + // @ts-ignore customRenderTabBar({ ...props, ...tabBarProps, - isGliding: isGliding.current, + isGliding, + preventTabPressOnGliding, }) ) : ( ({ if (isGliding.current && preventTabPressOnGliding) { event.preventDefault(); } + // @ts-ignore tabBarProps?.onTabPress && tabBarProps.onTabPress(event); }} /> diff --git a/src/index.tsx b/src/index.tsx index bfcd48c8..1fd3bb05 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,8 @@ export { default as CollapsibleTabView } from './CollapsibleTabView'; -export type { Props as CollapsibleTabViewProps } from './CollapsibleTabView'; +export type { + Props as CollapsibleTabViewProps, + RenderTabBarProps, +} from './CollapsibleTabView'; export { default as useCollapsibleScene } from './useCollapsibleScene'; export type { CollapsibleScenePropsAndRef } from './types';