diff --git a/apps/example/app.json b/apps/example/app.json index 88ffbe8..a477cbd 100644 --- a/apps/example/app.json +++ b/apps/example/app.json @@ -16,12 +16,12 @@ "android": [ "dist/res", "dist/main.android.jsbundle", - "./node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf" + "../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf" ], "ios": [ "dist/assets", "dist/main.ios.jsbundle", - "./node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf" + "../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf" ], "macos": [ "dist/assets", diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock index fd71fc1..38125d7 100644 --- a/apps/example/ios/Podfile.lock +++ b/apps/example/ios/Podfile.lock @@ -1209,32 +1209,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-bottom-tabs (0.7.3): - - DoubleConversion - - glog - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - react-native-bottom-tabs/common (= 0.7.3) - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SDWebImage (>= 5.19.1) - - SDWebImageSVGCoder (>= 1.7.0) - - SwiftUIIntrospect (~> 1.0) - - Yoga - - react-native-bottom-tabs/common (0.7.3): + - react-native-bottom-tabs (0.8.3): - DoubleConversion - glog - RCT-Folly (= 2024.01.01.00) @@ -1914,68 +1889,68 @@ SPEC CHECKSUMS: FBLazyVector: 430e10366de01d1e3d57374500b1b150fe482e6d fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: 69ef571f3de08433d766d614c73a9838a06bf7eb - RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 + RCT-Folly: 34124ae2e667a0e5f0ea378db071d27548124321 RCTDeprecation: 726d24248aeab6d7180dac71a936bbca6a994ed1 RCTRequired: a94e7febda6db0345d207e854323c37e3a31d93b RCTTypeSafety: 28e24a6e44f5cbf912c66dde6ab7e07d1059a205 React: c2830fa483b0334bda284e46a8579ebbe0c5447e React-callinvoker: 4aecde929540c26b841a4493f70ebf6016691eb8 - React-Core: 1e3c04337857fa7fb7559f73f6f29a2a83a84b9c - React-CoreModules: 9fac2d31803c0ed03e4ddaa17f1481714f8633a5 - React-cxxreact: c72a7a8066fc4323ea85a3137de50c8a10a69794 + React-Core: 65374ea054f3f00eaa3c8bb5e989cb1ba8128844 + React-CoreModules: f53e0674e1747fa41c83bc970e82add97b14ad87 + React-cxxreact: bb77e88b645c5378ecd0c30c94f965a8294001d8 React-debug: 3d21f69d8def0656f8b8ec25c0f05954f4d862c5 - React-defaultsnativemodule: 95882787871a9e80337f464e4643f1c5e0a39198 - React-domnativemodule: dc8aac826a90479ca4e05f096f1f8cd35c216f31 - React-Fabric: 9347fa5c8fbfac6d5276dd9e52c91058467d0960 - React-FabricComponents: 68b9f8c4a7189c055a7eb67b182e8d98c4f75f47 - React-FabricImage: 062e20f8b360ca008f44d00a639951c8c37ba2aa + React-defaultsnativemodule: 7af17cb26da34dc11b9f285d20d500a7e02f4f31 + React-domnativemodule: c1068e2e275e192459690bd27d680cacf2396962 + React-Fabric: 8c9cb59af7be7270b7ac5da739c260219bf2d36d + React-FabricComponents: 7c4879ad6944d75e4e3e2752e93b564fe361ab88 + React-FabricImage: 19fabe4f6100ab7ea7a97641a9e36db72152cea4 React-featureflags: ee1abd6f71555604a36cda6476e3c502ca9a48e5 - React-featureflagsnativemodule: 209f660bc398849cfb81712c93010276f25f337b - React-graphics: d7dd9c8d75cad5af19e19911fa370f78f2febd96 - React-idlecallbacksnativemodule: 17a0e379a7e3c9bc9653b6902f22f4208658d687 - React-ImageManager: ab7a7d17dd0ff1ef1d4e1e88197d1119da9957ce + React-featureflagsnativemodule: 22efd12eeeebb24c4259685763d8cb8fa8dfce32 + React-graphics: f5c4cf3abc5aa083e28fe7a866bd95fb3bbbc1e0 + React-idlecallbacksnativemodule: 1abfbc956bbf8164fee194c9fb730640b1b5b876 + React-ImageManager: cb78d7a24f45f8f9a5a1640b52fce4c9f637f98d React-jsc: 4d3352be620f3fe2272238298aaccc9323b01824 - React-jserrorhandler: d9e867bb83b868472f3f7601883f0403b3e3942d - React-jsi: 490deef195fd3f01d57dc89dda8233a84bd54b83 - React-jsiexecutor: 13bcb5e11822b2a6b69dbb175a24a39e24a02312 - React-jsinspector: 5b93e72babcbfcbf84dd19576652c6b949d144af - React-jsitracing: 0e8c0aadb1fcec6b1e4f2a66ee3b0da80f0f8615 - React-logger: d79b704bf215af194f5213a6b7deec50ba8e6a9b - React-Mapbuffer: b982d5bba94a8bc073bda48f0d27c9b28417fae3 - React-microtasksnativemodule: 8fa285fed833a04a754bf575f8ded65fc240b88d - react-native-bottom-tabs: b6b3dc2e971c860a0a6d763701929d1899f666a0 - react-native-safe-area-context: 73505107f7c673cd550a561aeb6271f152c483b6 + React-jserrorhandler: dfe9b96e99a93d4f4858bad66d5bc4813a87a21a + React-jsi: b187c826e5bda25afb36ede4c54c146cd50c9d6c + React-jsiexecutor: ac8478b6c5f53bcf411a66bf4461e923dafeb0bd + React-jsinspector: 3ddb69299335dfeceb01ad4b9c3eb2b5945cd149 + React-jsitracing: cac972ccc097db399df8044e49add8e5b25cb34a + React-logger: 80d87daf2f98bf95ab668b79062c1e0c3f0c2f8a + React-Mapbuffer: acffb35a53a5f474ede09f082ac609b41aafab2e + React-microtasksnativemodule: 8316f77469bf116d89e800e87de19eda7830339c + react-native-bottom-tabs: 8aab64074997748a2459dc8707827bdbc4dc9252 + react-native-safe-area-context: f2beaaf96e5c342a9c1dbdd5bc4eeb717424fb80 React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794 - React-NativeModulesApple: b8465afc883f5bf3fe8bac3767e394d581a5f123 + React-NativeModulesApple: f2fae67dde85ef5d8985bfa04eaa120b52b1c24a React-perflogger: 59e1a3182dca2cee7b9f1f7aab204018d46d1914 - React-performancetimeline: a9d05533ff834c6aa1f532e05e571f3fd2e3c1ed + React-performancetimeline: 3e3f5c5576fe1cc2dd5fcfb1ae2046d5dceda3d7 React-RCTActionSheet: d80e68d3baa163e4012a47c1f42ddd8bcd9672cc - React-RCTAnimation: bde981f6bd7f8493696564da9b3bd05721d3b3cc - React-RCTAppDelegate: e5865dbf46ddec6d9ed9310a05094d13f9bb043f - React-RCTBlob: e492d54533e61a81f2601494a6f393b3e15e33b9 - React-RCTFabric: e6ef266a60e885a0548b0f7a5e9737f42ccd596b - React-RCTImage: 90448d2882464af6015ed57c98f463f8748be465 - React-RCTLinking: 1bd95d0a704c271d21d758e0f0388cced768d77d - React-RCTNetwork: 218af6e63eb9b47935cc5a775b7a1396cf10ff91 - React-RCTSettings: e10b8e42b0fce8a70fbf169de32a2ae03243ef6b - React-RCTText: e7bf9f4997a1a0b45c052d4ad9a0fe653061cf29 - React-RCTVibration: 5b70b7f11e48d1c57e0d4832c2097478adbabe93 + React-RCTAnimation: 051f0781709c5ed80ba8aa2b421dfb1d72a03162 + React-RCTAppDelegate: 0b2a626838fb828acdaf0789a18d681cf9664a61 + React-RCTBlob: e949797c162421e363f93bfd8b546b7e632ba847 + React-RCTFabric: 9398c6720ec94e047737909e00452e30594164bc + React-RCTImage: b73149c0cd54b641dba2d6250aaf168fee784d9f + React-RCTLinking: 23e519712285427e50372fbc6e0265d422abf462 + React-RCTNetwork: a5d06d122588031989115f293654b13353753630 + React-RCTSettings: 87d03b5d94e6eadd1e8c1d16a62f790751aafb55 + React-RCTText: 75e9dd39684f4bcd1836134ac2348efaca7437b3 + React-RCTVibration: 033c161fe875e6fa096d0d9733c2e2501682e3d4 React-rendererconsistency: f620c6e003e3c4593e6349d8242b8aeb3d4633f0 - React-rendererdebug: e697680f4dd117becc5daf9ea9800067abcee91c + React-rendererdebug: 5be7b834677b2a7a263f4d2545f0d4966cafad82 React-rncore: c22bd84cc2f38947f0414fab6646db22ff4f80cd - React-RuntimeApple: 352013c169b30fd6a1c83acc39c16ac27fecf42e - React-RuntimeCore: 704ebf1cc6bc7f5b72da4a7740a4d1520c66bee5 + React-RuntimeApple: a686e45ca18a703447a5cf173ae1ad51e58abe84 + React-RuntimeCore: 1c68670cd7edebfd41affffb1f4464712a428e73 React-runtimeexecutor: ea90d8e3a9e0f4326939858dafc6ab17c031a5d3 - React-runtimescheduler: 86b04703f6cb40f5d30a639a28aaee405032e75a - React-utils: 546831c4f1be57fac614f68de34ac8763e67db55 - ReactCodegen: 54f07f54275a16f8e3a32cbdd62bf008b0869c9c - ReactCommon: 8377a2a5504f72e284ce1b1cd207d8455bdbfdf3 - ReactNativeHost: a3cd2bc15b6deac7439318607ce5637d8a93a117 - ReactTestApp-DevSupport: ce66fc1bbcf598d7e90616db390a0274c13e14e7 + React-runtimescheduler: 7774e739c3a7855db01f34ed1714c0bc0be5f041 + React-utils: 76fd4e86f1f3a4217b86a70fd067ffad9984c669 + ReactCodegen: 2e8616099824063496ecff2abe18d223a59b89ce + ReactCommon: 7d4342b3f8c0ed7e0ac14ae4da79d1fc5e650e0c + ReactNativeHost: bfb0e02bf61df8dfa78e9c7a8a0129519e6f0c74 + ReactTestApp-DevSupport: 4ebd8dbae375a3a265d37c59179d281f2f1609d8 ReactTestApp-Resources: 9c387cfe7185736e6a9045e5aa3e085367be6aa3 - RNGestureHandler: 492b1d415a25506d1dc612e6a14932b1e697d835 - RNScreens: 16a61c0a9fe4cd69af6489b8d10ba580b5e22ed0 - RNVectorIcons: a1344e212e80e6e0f4537a9960148201175f4225 + RNGestureHandler: 4895ef926ab1c16473384aa8b3e5f39c318ed2b6 + RNScreens: a4747a8a6847adfd9426b2d5b10a8fc68c130fdf + RNVectorIcons: 0d15b83d2906dbc2a786ff58eb46475576e2cf96 SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d diff --git a/apps/example/package.json b/apps/example/package.json index a271ca5..edabb2b 100644 --- a/apps/example/package.json +++ b/apps/example/package.json @@ -18,7 +18,7 @@ "dependencies": { "@bottom-tabs/react-navigation": "*", "@callstack/react-native-visionos": "^0.75.0", - "@react-navigation/bottom-tabs": "^6.6.1", + "@react-navigation/bottom-tabs": "7.2.0", "@react-navigation/native": "7.0.4", "@react-navigation/native-stack": "^7.1.1", "@react-navigation/stack": "^7.0.6", diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index 23f5994..597dcec 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -28,6 +28,7 @@ import NativeBottomTabsEmbeddedStacks from './Examples/NativeBottomTabsEmbeddedS import NativeBottomTabsSVGs from './Examples/NativeBottomTabsSVGs'; import NativeBottomTabsRemoteIcons from './Examples/NativeBottomTabsRemoteIcons'; import NativeBottomTabsUnmounting from './Examples/NativeBottomTabsUnmounting'; +import NativeBottomTabsCustomTabBar from './Examples/NativeBottomTabsCustomTabBar'; const FourTabsIgnoreSafeArea = () => { return ; @@ -136,6 +137,10 @@ const examples = [ component: NativeBottomTabsRemoteIcons, name: 'Native Bottom Tabs with SVG Remote Icons', }, + { + component: NativeBottomTabsCustomTabBar, + name: 'Native Bottom Tabs with Custom Tab Bar', + }, { component: NativeBottomTabs, name: 'Native Bottom Tabs' }, { component: JSBottomTabs, name: 'JS Bottom Tabs' }, { diff --git a/apps/example/src/Examples/NativeBottomTabsCustomTabBar.tsx b/apps/example/src/Examples/NativeBottomTabsCustomTabBar.tsx new file mode 100644 index 0000000..4ee47b1 --- /dev/null +++ b/apps/example/src/Examples/NativeBottomTabsCustomTabBar.tsx @@ -0,0 +1,37 @@ +import { Article } from '../Screens/Article'; +import { Albums } from '../Screens/Albums'; +import { Contacts } from '../Screens/Contacts'; +import { Chat } from '../Screens/Chat'; +import { + createNativeBottomTabNavigator, + type BottomTabBarProps, +} from '@bottom-tabs/react-navigation'; +import { BottomTabBar } from '@react-navigation/bottom-tabs'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +const Tab = createNativeBottomTabNavigator(); + +const CustomTabBar = (props: BottomTabBarProps) => { + const insets = useSafeAreaInsets(); + // @ts-ignore Typescript thinks that props don't match but they are actually the same under the hood + return ; +}; + +function NativeBottomTabsCustomTabBar() { + return ( + + + + + + + ); +} + +export default NativeBottomTabsCustomTabBar; diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt index 0ab1771..7099884 100644 --- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -80,7 +80,7 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { )) post { - addOnLayoutChangeListener { _, left, top, right, bottom, + layoutHolder.addOnLayoutChangeListener { _, left, top, right, bottom, _, _, _, _ -> val newWidth = right - left val newHeight = bottom - top @@ -168,6 +168,13 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { TransitionManager.beginDelayedTransition(layoutHolder, fadeThrough) } + val selectedItem = items[itemId] + if (selectedItem.tabBarHidden) { + bottomNavigation.visibility = GONE + } else { + bottomNavigation.visibility = VISIBLE + } + layoutHolder.forEachIndexed { index, view -> if (itemId == index) { toggleViewVisibility(view, true) @@ -196,6 +203,7 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { selectedItem.let { onTabSelectedListener?.invoke(selectedItem.key) emitHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) + } } diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewImpl.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewImpl.kt index e8693eb..da48046 100644 --- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewImpl.kt +++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewImpl.kt @@ -16,6 +16,7 @@ data class TabInfo( val activeTintColor: Int?, val hidden: Boolean, val testID: String?, + val tabBarHidden: Boolean ) class RCTTabViewImpl { @@ -34,7 +35,8 @@ class RCTTabViewImpl { badge = item.getString("badge") ?: "", activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null, hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false, - testID = item.getString("testID") + testID = item.getString("testID"), + tabBarHidden = if (item.hasKey("tabBarHidden")) item.getBoolean("tabBarHidden") else false, ) ) } diff --git a/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt b/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt index df3866b..66f5e57 100644 --- a/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt +++ b/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt @@ -140,7 +140,6 @@ class RCTTabViewManager(context: ReactApplicationContext) : override fun setDisablePageAnimations(view: ReactBottomNavigationView?, value: Boolean) { view?.disablePageAnimations = value } - // iOS Methods override fun setTranslucent(view: ReactBottomNavigationView?, value: Boolean) { } diff --git a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm index 18daaab..8c4eba4 100644 --- a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm +++ b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm @@ -146,19 +146,19 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & if (oldViewProps.inactiveTintColor != newViewProps.inactiveTintColor) { _tabViewProvider.inactiveTintColor = RCTUIColorFromSharedColor(newViewProps.inactiveTintColor); } - + if (oldViewProps.hapticFeedbackEnabled != newViewProps.hapticFeedbackEnabled) { _tabViewProvider.hapticFeedbackEnabled = newViewProps.hapticFeedbackEnabled; } - + if (oldViewProps.fontSize != newViewProps.fontSize) { _tabViewProvider.fontSize = [NSNumber numberWithInt:newViewProps.fontSize]; } - + if (oldViewProps.fontWeight != newViewProps.fontWeight) { _tabViewProvider.fontWeight = RCTNSStringFromStringNilIfEmpty(newViewProps.fontWeight); } - + if (oldViewProps.fontFamily != newViewProps.fontFamily) { _tabViewProvider.fontFamily = RCTNSStringFromStringNilIfEmpty(newViewProps.fontFamily); } @@ -173,7 +173,8 @@ bool areTabItemsEqual(const RNCTabViewItemsStruct& lhs, const RNCTabViewItemsStr lhs.badge == rhs.badge && lhs.activeTintColor == rhs.activeTintColor && lhs.hidden == rhs.hidden && - lhs.testID == rhs.testID; + lhs.testID == rhs.testID && + lhs.tabBarHidden == rhs.tabBarHidden; } bool haveTabItemsChanged(const std::vector& oldItems, @@ -202,7 +203,8 @@ bool haveTabItemsChanged(const std::vector& oldItems, sfSymbol:RCTNSStringFromStringNilIfEmpty(item.sfSymbol) activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor) hidden:item.hidden - testID:RCTNSStringFromStringNilIfEmpty(item.testID)]; + testID:RCTNSStringFromStringNilIfEmpty(item.testID) + tabBarHidden:item.tabBarHidden]; [result addObject:tabInfo]; } diff --git a/packages/react-native-bottom-tabs/ios/TabViewImpl.swift b/packages/react-native-bottom-tabs/ios/TabViewImpl.swift index 63da9f9..9d75ed3 100644 --- a/packages/react-native-bottom-tabs/ios/TabViewImpl.swift +++ b/packages/react-native-bottom-tabs/ios/TabViewImpl.swift @@ -31,9 +31,8 @@ struct TabViewImpl: View { } #if !os(tvOS) && !os(macOS) && !os(visionOS) .onTabItemEvent({ index, isLongPress in - guard let key = props.items.filter({ - !$0.hidden || $0.key == props.selectedPage - })[safe: index]?.key else { return } + let item = props.filteredItems[safe: index] + guard let key = item?.key else { return } if isLongPress { onLongPress(key) @@ -52,6 +51,7 @@ struct TabViewImpl: View { onTabBarMeasured( Int(tabController.tabBar.frame.size.height) ) + hideTabBarIfNeeded() #endif }) #if !os(macOS) @@ -71,6 +71,7 @@ struct TabViewImpl: View { #if os(tvOS) || os(macOS) || os(visionOS) onSelect(newValue) #endif + hideTabBarIfNeeded() } } @@ -128,6 +129,12 @@ struct TabViewImpl: View { } #endif } + + func hideTabBarIfNeeded() { + let item = props.filteredItems.first {$0.key == props.selectedPage} + let tabBarHidden = item?.tabBarHidden ?? false + tabBar?.isHidden = tabBarHidden + } } #if !os(macOS) @@ -209,15 +216,15 @@ private func configureStandardAppearance(tabBar: UITabBar, props: TabViewProps) default: appearance.configureWithDefaultBackground() } - + if props.translucent == false { appearance.configureWithOpaqueBackground() } - + if props.barTintColor != nil { appearance.backgroundColor = props.barTintColor } - + // Configure item appearance let itemAppearance = UITabBarItemAppearance() let fontSize = props.fontSize != nil ? CGFloat(props.fontSize!) : tabBarDefaultFontSize diff --git a/packages/react-native-bottom-tabs/ios/TabViewProps.swift b/packages/react-native-bottom-tabs/ios/TabViewProps.swift index d5426bd..9eefe28 100644 --- a/packages/react-native-bottom-tabs/ios/TabViewProps.swift +++ b/packages/react-native-bottom-tabs/ios/TabViewProps.swift @@ -31,4 +31,10 @@ class TabViewProps: ObservableObject { return activeTintColor } + + var filteredItems: [TabInfo] { + items.filter({ + !$0.hidden || $0.key == selectedPage + }) + } } diff --git a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift index 707a240..4c46f6e 100644 --- a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift +++ b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift @@ -13,6 +13,7 @@ public final class TabInfo: NSObject { public let activeTintColor: PlatformColor? public let hidden: Bool public let testID: String? + public let tabBarHidden: Bool public init( key: String, @@ -21,7 +22,8 @@ public final class TabInfo: NSObject { sfSymbol: String, activeTintColor: PlatformColor?, hidden: Bool, - testID: String? + testID: String?, + tabBarHidden: Bool ) { self.key = key self.title = title @@ -30,6 +32,7 @@ public final class TabInfo: NSObject { self.activeTintColor = activeTintColor self.hidden = hidden self.testID = testID + self.tabBarHidden = tabBarHidden super.init() } } @@ -268,7 +271,8 @@ public final class TabInfo: NSObject { sfSymbol: itemDict["sfSymbol"] as? String ?? "", activeTintColor: RCTConvert.uiColor(itemDict["activeTintColor"] as? NSNumber), hidden: itemDict["hidden"] as? Bool ?? false, - testID: itemDict["testID"] as? String ?? "" + testID: itemDict["testID"] as? String ?? "", + tabBarHidden: itemDict["tabBarHidden"] as? Bool ?? false ) ) } diff --git a/packages/react-native-bottom-tabs/src/TabView.tsx b/packages/react-native-bottom-tabs/src/TabView.tsx index 570b163..0d74a42 100644 --- a/packages/react-native-bottom-tabs/src/TabView.tsx +++ b/packages/react-native-bottom-tabs/src/TabView.tsx @@ -116,6 +116,14 @@ interface Props { */ getTestID?: (props: { route: Route }) => string | undefined; + /** + * Get tabBarHidden for the tab, uses `route.tabBarHidden` by default. + * If `true`, the tab bar will be hidden. + */ + getTabBarHidden?: (props: { route: Route }) => boolean | undefined; + + tabBar?: () => React.ReactNode; + tabBarStyle?: { /** * Background color of the tab bar. @@ -172,8 +180,10 @@ const TabView = ({ getHidden = ({ route }: { route: Route }) => route.hidden, getActiveTintColor = ({ route }: { route: Route }) => route.activeTintColor, getTestID = ({ route }: { route: Route }) => route.testID, - tabBarStyle, + getTabBarHidden = ({ route }: { route: Route }) => route.tabBarHidden, hapticFeedbackEnabled = false, + tabBar: renderCustomTabBar, + tabBarStyle, tabLabelStyle, ...props }: Props) => { @@ -238,6 +248,7 @@ const TabView = ({ activeTintColor: processColor(getActiveTintColor({ route })), hidden: getHidden?.({ route }), testID: getTestID?.({ route }), + tabBarHidden: !!renderCustomTabBar || getTabBarHidden?.({ route }), }; }), [ @@ -248,6 +259,8 @@ const TabView = ({ getActiveTintColor, getHidden, getTestID, + getTabBarHidden, + renderCustomTabBar, ] ); @@ -333,6 +346,7 @@ const TabView = ({ ); })} + {renderCustomTabBar?.() ?? null} ); }; @@ -341,6 +355,7 @@ const styles = StyleSheet.create({ fullWidth: { width: '100%', height: '100%', + flex: 1, }, }); diff --git a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts index 0a818b5..a6860f0 100644 --- a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts +++ b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts @@ -30,6 +30,7 @@ export type TabViewItems = ReadonlyArray<{ activeTintColor?: ProcessedColorValue | null; hidden?: boolean; testID?: string; + tabBarHidden?: boolean; }>; export interface TabViewProps extends ViewProps { diff --git a/packages/react-native-bottom-tabs/src/types.ts b/packages/react-native-bottom-tabs/src/types.ts index 5993a61..824f622 100644 --- a/packages/react-native-bottom-tabs/src/types.ts +++ b/packages/react-native-bottom-tabs/src/types.ts @@ -15,6 +15,7 @@ export type BaseRoute = { activeTintColor?: string; hidden?: boolean; testID?: string; + tabBarHidden?: boolean; }; export type NavigationState = { diff --git a/packages/react-navigation/src/index.tsx b/packages/react-navigation/src/index.tsx index a04aa54..85e8063 100644 --- a/packages/react-navigation/src/index.tsx +++ b/packages/react-navigation/src/index.tsx @@ -16,4 +16,5 @@ export type { NativeBottomTabNavigationOptions, NativeBottomTabNavigationProp, NativeBottomTabScreenProps, + BottomTabBarProps, } from './types'; diff --git a/packages/react-navigation/src/types.ts b/packages/react-navigation/src/types.ts index eb1c3eb..45fb656 100644 --- a/packages/react-navigation/src/types.ts +++ b/packages/react-navigation/src/types.ts @@ -91,6 +91,11 @@ export type NativeBottomTabNavigationOptions = { * TestID for the tab. */ tabBarButtonTestID?: string; + + /** + * Whether to show the tab bar. + */ + tabBarHidden?: boolean; }; export type NativeBottomTabDescriptor = Descriptor< @@ -104,6 +109,15 @@ export type NativeBottomTabDescriptorMap = Record< NativeBottomTabDescriptor >; +export type BottomTabBarProps = { + state: TabNavigationState; + descriptors: NativeBottomTabDescriptorMap; + navigation: NavigationHelpers< + ParamListBase, + NativeBottomTabNavigationEventMap + >; +}; + export type NativeBottomTabNavigationConfig = Partial< Omit< React.ComponentProps, @@ -117,5 +131,9 @@ export type NativeBottomTabNavigationConfig = Partial< | 'onTabLongPress' | 'getActiveTintColor' | 'getTestID' + | 'getTabBarHidden' + | 'tabBar' > ->; +> & { + tabBar?: (props: BottomTabBarProps) => React.ReactNode; +}; diff --git a/packages/react-navigation/src/views/NativeBottomTabView.tsx b/packages/react-navigation/src/views/NativeBottomTabView.tsx index 8c633f4..802a2f2 100644 --- a/packages/react-navigation/src/views/NativeBottomTabView.tsx +++ b/packages/react-navigation/src/views/NativeBottomTabView.tsx @@ -21,6 +21,7 @@ export default function NativeBottomTabView({ state, navigation, descriptors, + tabBar, ...rest }: Props) { return ( @@ -48,6 +49,15 @@ export default function NativeBottomTabView({ getTestID={({ route }) => descriptors[route.key]?.options.tabBarButtonTestID } + {...(tabBar + ? { + tabBar: () => tabBar({ state, descriptors, navigation }), + } + : null)} + getTabBarHidden={({ route }) => { + const options = descriptors[route.key]?.options; + return options?.tabBarHidden === true; + }} getIcon={({ route, focused }) => { const options = descriptors[route.key]?.options; diff --git a/yarn.lock b/yarn.lock index 4114497..2447b9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5305,24 +5305,7 @@ __metadata: languageName: node linkType: hard -"@react-navigation/bottom-tabs@npm:^6.6.1": - version: 6.6.1 - resolution: "@react-navigation/bottom-tabs@npm:6.6.1" - dependencies: - "@react-navigation/elements": "npm:^1.3.31" - color: "npm:^4.2.3" - warn-once: "npm:^0.1.0" - peerDependencies: - "@react-navigation/native": ^6.0.0 - react: "*" - react-native: "*" - react-native-safe-area-context: ">= 3.0.0" - react-native-screens: ">= 3.0.0" - checksum: 10/572f67c1ea26ac52a0c599064957ec6ac5cd0eee810d2e3cd5b4f4beb1016fbd5c5e8eb54c4d62836e1afe90c5ef922b31a288f41e0c442123dcfc417aa8fd7f - languageName: node - linkType: hard - -"@react-navigation/bottom-tabs@npm:^7.2.0": +"@react-navigation/bottom-tabs@npm:7.2.0, @react-navigation/bottom-tabs@npm:^7.2.0": version: 7.2.0 resolution: "@react-navigation/bottom-tabs@npm:7.2.0" dependencies: @@ -5405,7 +5388,7 @@ __metadata: languageName: node linkType: hard -"@react-navigation/elements@npm:^1.3.30, @react-navigation/elements@npm:^1.3.31": +"@react-navigation/elements@npm:^1.3.30": version: 1.3.31 resolution: "@react-navigation/elements@npm:1.3.31" peerDependencies: @@ -17090,7 +17073,7 @@ __metadata: "@react-native/babel-preset": "npm:0.75.4" "@react-native/metro-config": "npm:0.75.4" "@react-native/typescript-config": "npm:0.75.4" - "@react-navigation/bottom-tabs": "npm:^6.6.1" + "@react-navigation/bottom-tabs": "npm:7.2.0" "@react-navigation/native": "npm:7.0.4" "@react-navigation/native-stack": "npm:^7.1.1" "@react-navigation/stack": "npm:^7.0.6"