From a3509506725583ccafe6dbf0566d540a387bf4ac Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 22 Sep 2025 22:26:39 +0800 Subject: [PATCH] feat(tabbar): add onTabItemPress event and enhance TabBar functionality - Introduced onTabItemPress event in TabBarRootView to handle tab item presses. - Updated TabBarModule to include the new event in the definition. - Modified TabBarRootWrapper to accept onTabItemPress and selectedIndex props. - Enhanced TabRoot to utilize the onTabItemPress callback for scroll-to-top functionality. This update improves user interaction with the tab bar by allowing for additional actions on tab item presses. Signed-off-by: Innei --- .../ios/Modules/TabBar/TabBarModule.swift | 40 +++++++++---------- .../ios/Modules/TabBar/TabBarRootView.swift | 9 +++++ .../src/lib/navigation/bottom-tab/TabRoot.tsx | 14 +++++++ .../src/lib/navigation/bottom-tab/native.tsx | 7 +++- .../src/lib/navigation/bottom-tab/types.ts | 13 +++++- 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/apps/mobile/native/ios/Modules/TabBar/TabBarModule.swift b/apps/mobile/native/ios/Modules/TabBar/TabBarModule.swift index 597ed9b02..af7d77346 100644 --- a/apps/mobile/native/ios/Modules/TabBar/TabBarModule.swift +++ b/apps/mobile/native/ios/Modules/TabBar/TabBarModule.swift @@ -9,29 +9,29 @@ import ExpoModulesCore public class TabBarModule: Module { - public func definition() -> ModuleDefinition { - Name("TabBarRoot") + public func definition() -> ModuleDefinition { + Name("TabBarRoot") - Function("switchTab") { [weak self] (reactTag: Int, index: Int) in - DispatchQueue.main.async { - guard let bridge = self?.appContext?.reactBridge else { - return - } - - if let sourceView = bridge.uiManager.view(forReactTag: NSNumber(value: reactTag)) as! TabBarRootView? - - { - sourceView.switchToTab(index: index) - } - } + Function("switchTab") { [weak self] (reactTag: Int, index: Int) in + DispatchQueue.main.async { + guard let bridge = self?.appContext?.reactBridge else { + return } - View(TabBarRootView.self) { - Prop("selectedIndex") { (view, index: Int) in - view.switchToTab(index: index) - } - - Events("onTabIndexChange") + if let sourceView = bridge.uiManager.view(forReactTag: NSNumber(value: reactTag)) + as! TabBarRootView? + { + sourceView.switchToTab(index: index) } + } } + + View(TabBarRootView.self) { + Prop("selectedIndex") { (view, index: Int) in + view.switchToTab(index: index) + } + + Events("onTabIndexChange", "onTabItemPress") + } + } } diff --git a/apps/mobile/native/ios/Modules/TabBar/TabBarRootView.swift b/apps/mobile/native/ios/Modules/TabBar/TabBarRootView.swift index 749296375..273d264ca 100644 --- a/apps/mobile/native/ios/Modules/TabBar/TabBarRootView.swift +++ b/apps/mobile/native/ios/Modules/TabBar/TabBarRootView.swift @@ -37,6 +37,7 @@ class TabBarRootView: ExpoView { private var tabViewControllers: [UIViewController] = [] private let onTabIndexChange = EventDispatcher() + private let onTabItemPress = EventDispatcher() required init(appContext: AppContext? = nil) { super.init(appContext: appContext) @@ -133,6 +134,14 @@ class TabBarRootView: ExpoView { // MARK: - UITabBarControllerDelegate extension TabBarRootView: UITabBarControllerDelegate { + func tabBarController( + _ tabBarController: UITabBarController, shouldSelect viewController: UIViewController + ) -> Bool { + if let index = tabViewControllers.firstIndex(of: viewController) { + onTabItemPress(["index": index, "currentIndex": tabBarController.selectedIndex]) + } + return true + } func tabBarController( _ tabBarController: UITabBarController, didSelect viewController: UIViewController ) { diff --git a/apps/mobile/src/lib/navigation/bottom-tab/TabRoot.tsx b/apps/mobile/src/lib/navigation/bottom-tab/TabRoot.tsx index d75137262..88c87f684 100644 --- a/apps/mobile/src/lib/navigation/bottom-tab/TabRoot.tsx +++ b/apps/mobile/src/lib/navigation/bottom-tab/TabRoot.tsx @@ -1,9 +1,12 @@ +import { useTypeScriptHappyCallback } from "@follow/hooks" import { useAtom } from "jotai" import type { FC, PropsWithChildren } from "react" import * as React from "react" import { use, useCallback, useMemo } from "react" import { StyleSheet } from "react-native" +import { useNavigationScrollToTop } from "@/src/components/layouts/tabbar/hooks" + import { BottomTabContext } from "./BottomTabContext" import { TabBarRootWrapper } from "./native" import { TabScreen } from "./TabScreen" @@ -13,6 +16,8 @@ export const TabRoot: FC = ({ children }) => { const { currentIndexAtom } = use(BottomTabContext) const [tabIndex, setTabIndex] = useAtom(currentIndexAtom) + const scrollToTop = useNavigationScrollToTop() + const MapChildren = useMemo(() => { let cnt = 0 return React.Children.map(children, (child) => { @@ -33,6 +38,15 @@ export const TabRoot: FC = ({ children }) => { }, [setTabIndex], )} + onTabItemPress={useTypeScriptHappyCallback( + (e) => { + const { index, currentIndex } = e.nativeEvent + if (index === currentIndex) { + scrollToTop() + } + }, + [scrollToTop], + )} selectedIndex={tabIndex} > {MapChildren} diff --git a/apps/mobile/src/lib/navigation/bottom-tab/native.tsx b/apps/mobile/src/lib/navigation/bottom-tab/native.tsx index 758d78a53..9cd632869 100644 --- a/apps/mobile/src/lib/navigation/bottom-tab/native.tsx +++ b/apps/mobile/src/lib/navigation/bottom-tab/native.tsx @@ -16,6 +16,11 @@ export const TabScreenWrapper = ({ title, icon, activeIcon, ...rest }: TabScreen return } -export const TabBarRootWrapper = (props: TabBarRootWrapperProps) => { +export const TabBarRootWrapper = ({ + onTabIndexChange, + onTabItemPress, + selectedIndex, + ...props +}: TabBarRootWrapperProps) => { return } diff --git a/apps/mobile/src/lib/navigation/bottom-tab/types.ts b/apps/mobile/src/lib/navigation/bottom-tab/types.ts index 8eeb24f2d..7a028102c 100644 --- a/apps/mobile/src/lib/navigation/bottom-tab/types.ts +++ b/apps/mobile/src/lib/navigation/bottom-tab/types.ts @@ -28,7 +28,16 @@ export interface ResolvedTabScreenProps extends Omit React.ReactNode } -export type TabBarRootWrapperProps = { +export interface TabBarRootWrapperProps extends ViewProps { + /** + * + * iOS only + */ onTabIndexChange: (e: NativeSyntheticEvent<{ index: number }>) => void + /** + * iOS only + */ + onTabItemPress?: (e: NativeSyntheticEvent<{ index: number; currentIndex: number }>) => void + selectedIndex: number -} & ViewProps +}