From 8daf3db3ddb90133a791addc938b227b5f055274 Mon Sep 17 00:00:00 2001 From: Rafael Bulsing Date: Tue, 29 Apr 2025 16:54:35 -0300 Subject: [PATCH 1/5] feat: add support for custom header items --- src/TabScreen.tsx | 2 + src/TabsHeaderItem.tsx | 160 +++++++++++++++++++++++------------------ 2 files changed, 94 insertions(+), 68 deletions(-) diff --git a/src/TabScreen.tsx b/src/TabScreen.tsx index 4e10082..07dbe29 100644 --- a/src/TabScreen.tsx +++ b/src/TabScreen.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import type { GestureResponderEvent } from 'react-native'; +import type TabsHeaderItem from './TabsHeaderItem'; export interface TabScreenProps { label: string; @@ -9,6 +10,7 @@ export interface TabScreenProps { onPress?: (event: GestureResponderEvent) => void; onPressIn?: (event: GestureResponderEvent) => void; disabled?: boolean; + TabHeaderItem?: typeof TabsHeaderItem | undefined; } export default function TabScreen({ children }: TabScreenProps) { diff --git a/src/TabsHeaderItem.tsx b/src/TabsHeaderItem.tsx index 2ea61db..259e917 100644 --- a/src/TabsHeaderItem.tsx +++ b/src/TabsHeaderItem.tsx @@ -74,6 +74,97 @@ export default function TabsHeaderItem({ const badgeWithoutContent = typeof tab.props.badge === 'boolean'; + const HeaderItem = tab.props.TabHeaderItem ? ( + } + active={active} + onTabLayout={onTabLayout} + goTo={goTo} + activeColor={activeColor} + textColor={textColor} + position={position} + offset={offset} + childrenCount={childrenCount} + uppercase={uppercase} + iconPosition={iconPosition} + showTextLabel={showTextLabel} + mode={mode} + tabLabelStyle={tabLabelStyle} + /> + ) : ( + <> + {tab.props.icon ? ( + + {tab.props.icon ? ( + Platform.OS === 'android' ? ( + + + + ) : ( + + ) + ) : null} + + ) : null} + {badgeIsFilled ? ( + + {badgeWithoutContent ? ( + + ) : ( + + {tab.props.badge as any} + + )} + + ) : null} + {showTextLabel ? ( + + {uppercase && !theme.isV3 + ? tab.props.label.toUpperCase() + : tab.props.label} + + ) : null} + + ); return ( - {tab.props.icon ? ( - - {tab.props.icon ? ( - Platform.OS === 'android' ? ( - - - - ) : ( - - ) - ) : null} - - ) : null} - {badgeIsFilled ? ( - - {badgeWithoutContent ? ( - - ) : ( - - {tab.props.badge as any} - - )} - - ) : null} - {showTextLabel ? ( - - {uppercase && !theme.isV3 - ? tab.props.label.toUpperCase() - : tab.props.label} - - ) : null} + {HeaderItem} From 8a8520adf96d1432c1f5c6488813feab08ae84a1 Mon Sep 17 00:00:00 2001 From: Rafael Bulsing Date: Wed, 30 Apr 2025 09:47:42 -0300 Subject: [PATCH 2/5] fix: adapt to custom headers of any height --- src/TabsHeader.tsx | 4 ---- src/TabsHeaderItem.tsx | 9 +++++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/TabsHeader.tsx b/src/TabsHeader.tsx index 1dd29e3..020042e 100644 --- a/src/TabsHeader.tsx +++ b/src/TabsHeader.tsx @@ -174,7 +174,6 @@ export default function TabsHeader({ style={[ { backgroundColor, elevation }, restStyle, - styles.tabs, iconPosition === 'top' && styles.tabsTopIcon, ]} onLayout={onTabsLayout} @@ -260,9 +259,6 @@ const styles = StyleSheet.create({ scrollablePadding: { width: 52, }, - tabs: { - height: 48, - }, tabsTopIcon: { height: 72, }, diff --git a/src/TabsHeaderItem.tsx b/src/TabsHeaderItem.tsx index 259e917..942658e 100644 --- a/src/TabsHeaderItem.tsx +++ b/src/TabsHeaderItem.tsx @@ -212,10 +212,15 @@ const styles = StyleSheet.create({ left: 0, top: -2, }, - tabRoot: { position: 'relative' }, + tabRoot: { + position: 'relative', + justifyContent: 'center', + flexDirection: 'row', + alignItems: 'stretch', + }, tabRootFixed: { flex: 1 }, touchableRipple: { - height: 48, + minHeight: 48, justifyContent: 'center', alignItems: 'center', overflow: 'hidden', From f9cd087bbad2a6b28158bf36b68b4262cc998637 Mon Sep 17 00:00:00 2001 From: Rafael Bulsing Date: Wed, 30 Apr 2025 09:49:30 -0300 Subject: [PATCH 3/5] refactor: rename custom header prop name --- src/TabScreen.tsx | 2 +- src/TabsHeaderItem.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/TabScreen.tsx b/src/TabScreen.tsx index 07dbe29..8ed544d 100644 --- a/src/TabScreen.tsx +++ b/src/TabScreen.tsx @@ -10,7 +10,7 @@ export interface TabScreenProps { onPress?: (event: GestureResponderEvent) => void; onPressIn?: (event: GestureResponderEvent) => void; disabled?: boolean; - TabHeaderItem?: typeof TabsHeaderItem | undefined; + Header?: typeof TabsHeaderItem | undefined; } export default function TabScreen({ children }: TabScreenProps) { diff --git a/src/TabsHeaderItem.tsx b/src/TabsHeaderItem.tsx index 942658e..4e6fab6 100644 --- a/src/TabsHeaderItem.tsx +++ b/src/TabsHeaderItem.tsx @@ -74,8 +74,8 @@ export default function TabsHeaderItem({ const badgeWithoutContent = typeof tab.props.badge === 'boolean'; - const HeaderItem = tab.props.TabHeaderItem ? ( - } From d6dc67be241e59d43893c70b81746d355e182300 Mon Sep 17 00:00:00 2001 From: Rafael Bulsing Date: Wed, 30 Apr 2025 10:21:29 -0300 Subject: [PATCH 4/5] feat: allow custom header for the entire tab group --- src/Swiper.native.tsx | 2 ++ src/Swiper.tsx | 2 ++ src/TabScreen.tsx | 2 +- src/Tabs.tsx | 4 ++++ src/TabsHeader.tsx | 2 ++ src/TabsHeaderItem.tsx | 8 ++++++-- src/utils.tsx | 3 +++ 7 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Swiper.native.tsx b/src/Swiper.native.tsx index 0af89cd..7d04176 100644 --- a/src/Swiper.native.tsx +++ b/src/Swiper.native.tsx @@ -26,6 +26,7 @@ function SwiperNative(props: SwiperProps) { disableSwipe, tabHeaderStyle, tabLabelStyle, + TabHeaderComponent, } = props; const { index, goTo } = React.useContext(TabsContext); const indexRef = React.useRef(index || 0); @@ -81,6 +82,7 @@ function SwiperNative(props: SwiperProps) { mode, tabHeaderStyle, tabLabelStyle, + TabHeaderComponent, }; return ( <> diff --git a/src/Swiper.tsx b/src/Swiper.tsx index d3e80d6..ca776a3 100644 --- a/src/Swiper.tsx +++ b/src/Swiper.tsx @@ -17,6 +17,7 @@ function Swiper(props: SwiperProps) { mode, tabHeaderStyle, tabLabelStyle, + TabHeaderComponent, } = props; const index = useTabIndex(); @@ -41,6 +42,7 @@ function Swiper(props: SwiperProps) { mode, tabHeaderStyle, tabLabelStyle, + TabHeaderComponent, }; return ( diff --git a/src/TabScreen.tsx b/src/TabScreen.tsx index 8ed544d..8285538 100644 --- a/src/TabScreen.tsx +++ b/src/TabScreen.tsx @@ -10,7 +10,7 @@ export interface TabScreenProps { onPress?: (event: GestureResponderEvent) => void; onPressIn?: (event: GestureResponderEvent) => void; disabled?: boolean; - Header?: typeof TabsHeaderItem | undefined; + TabHeaderComponent?: typeof TabsHeaderItem | undefined; } export default function TabScreen({ children }: TabScreenProps) { diff --git a/src/Tabs.tsx b/src/Tabs.tsx index 3be6a3f..a1c0e88 100644 --- a/src/Tabs.tsx +++ b/src/Tabs.tsx @@ -5,6 +5,7 @@ import Swiper from './Swiper'; import type { MD3LightTheme } from 'react-native-paper'; import type { IconPosition, Mode } from './utils'; +import type TabsHeaderItem from './TabsHeaderItem'; function Tabs({ theme, @@ -18,6 +19,7 @@ function Tabs({ disableSwipe = false, tabHeaderStyle, tabLabelStyle, + TabHeaderComponent, ...rest }: { children: any; @@ -32,6 +34,7 @@ function Tabs({ disableSwipe?: boolean; tabHeaderStyle?: ViewStyle | undefined; tabLabelStyle?: TextStyle | undefined; + TabHeaderComponent?: typeof TabsHeaderItem; }) { const children = React.Children.toArray(rest.children).filter(Boolean); @@ -48,6 +51,7 @@ function Tabs({ disableSwipe={disableSwipe} tabHeaderStyle={tabHeaderStyle} tabLabelStyle={tabLabelStyle} + TabHeaderComponent={TabHeaderComponent} > {children} diff --git a/src/TabsHeader.tsx b/src/TabsHeader.tsx index 020042e..36ad40c 100644 --- a/src/TabsHeader.tsx +++ b/src/TabsHeader.tsx @@ -27,6 +27,7 @@ export default function TabsHeader({ mode, tabHeaderStyle, tabLabelStyle, + TabHeaderComponent, children, }: SwiperRenderProps) { const { index, goTo } = React.useContext(TabsContext); @@ -217,6 +218,7 @@ export default function TabsHeader({ showTextLabel={showTextLabel} mode={mode} tabLabelStyle={tabLabelStyle} + TabHeaderComponent={TabHeaderComponent} /> ))} ; tabIndex: number; @@ -48,6 +49,7 @@ export default function TabsHeaderItem({ showTextLabel?: boolean; mode: Mode; tabLabelStyle?: TextStyle | undefined; + TabHeaderComponent?: typeof TabsHeaderItem; }) { const baseColor = theme.colors.primary; const rippleColor = React.useMemo( @@ -74,8 +76,10 @@ export default function TabsHeaderItem({ const badgeWithoutContent = typeof tab.props.badge === 'boolean'; - const HeaderItem = tab.props.Header ? ( - } diff --git a/src/utils.tsx b/src/utils.tsx index 1c5437b..9db50e9 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -8,6 +8,7 @@ import type { } from 'react-native'; import type { MD3LightTheme } from 'react-native-paper'; import type { MutableRefObject, RefObject } from 'react'; +import type TabsHeaderItem from './TabsHeaderItem'; export type AnimatedViewStyle = Animated.AnimatedProps>; export type AnimatedTextStyle = Animated.AnimatedProps>; export type Mode = 'fixed' | 'scrollable'; @@ -27,6 +28,7 @@ export interface SwiperRenderProps { mode: Mode; tabHeaderStyle: ViewStyle | undefined; tabLabelStyle: TextStyle | undefined; + TabHeaderComponent?: typeof TabsHeaderItem; } export interface SwiperProps { @@ -42,6 +44,7 @@ export interface SwiperProps { disableSwipe?: boolean; tabHeaderStyle: ViewStyle | undefined; tabLabelStyle: TextStyle | undefined; + TabHeaderComponent?: typeof TabsHeaderItem; } export interface OffsetScrollArgs { From b2c50174246422a14ef1fc62050640df818389cb Mon Sep 17 00:00:00 2001 From: Rafael Bulsing Date: Wed, 30 Apr 2025 10:22:35 -0300 Subject: [PATCH 5/5] docs: add TabHeaderComponent to docs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 22f6c31..1409701 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ function Example() { // disableSwipe={false} // (default=false) disable swipe to left/right gestures // tabHeaderStyle // style object, can be animated properties as well in // tabLabelStyle // style object + // TabHeaderComponent={MyCustomHeader} // render a custom header > @@ -108,6 +109,7 @@ function Example() { // onPress={() => { // console.log('onPress explore'); // }} + // TabHeaderComponent={MyCustomHeader} // render a custom header >