Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/public-foxes-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'react-native-bottom-tabs': minor
'@bottom-tabs/react-navigation': minor
---

feat: add ios tab roles
100 changes: 50 additions & 50 deletions apps/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1902,71 +1902,71 @@ SPEC CHECKSUMS:
FBLazyVector: 79c4b7ec726447eec5f8593379466bd9fde1aa14
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
RCTDeprecation: 664055db806cce35c3c1b43c84414dd66e117ae6
RCTRequired: dc9a83fa1012054f94430d210337ca3a1afe6fc0
RCTTypeSafety: 031cefa254a1df313a196f105b8fcffdab1c5ab6
React: 8edfc46c315852ec88ea4a29d5e79019af3dc667
React-callinvoker: 4450b01574dfc7a8f074f7e29e6965ac04859c8f
React-Core: a318cda2bd04acffe4f70703098625b201aa2237
React-CoreModules: ebe93fa403bbd4d0909de105ffd34eeaad355083
React-cxxreact: 6fe3b8f8e8baf6a22fc39c8c4b4a0e1b5ae3e374
React-Core: 1fcd0d52ae09bdf7cf1fe96dd94b082208e43b86
React-CoreModules: 78e04d2319b1b61e0d4ed7fcd3e366d461819279
React-cxxreact: f9ca69323c1a9c22756ad1a4ed629fb6b44b2a18
React-debug: b0f7271aeacc2eb9e34f863397dcfc204ef721c0
React-defaultsnativemodule: 07704c5e30a5e66065a46aa11d4014941917a8ee
React-domnativemodule: dc3492f56861c82e658ea7db60316f7f1a8bace7
React-Fabric: 7a3d2abb0607f100881cc6c8e54484324c109260
React-FabricComponents: 4206a041e4671277d45deaf89e52f20951a8dd7d
React-FabricImage: b05580c4f17de740c38dd5e54d289e913f5bc5df
React-defaultsnativemodule: e790bf1d1a300a23504257f306a6629d2a60d845
React-domnativemodule: adcfebf6c3b7882b28a061ed2789777f3d337a18
React-Fabric: ecf387c8cca0e7ed2ef5104cd2725f38409163b2
React-FabricComponents: f7cd4fab1308f52c418b474dc877b94f7acc5672
React-FabricImage: e5c94c5679f2fec2261e76809aa86765fb4e0322
React-featureflags: 23d3dcdac6c9badeeb631db8a0883c7a3108d580
React-featureflagsnativemodule: 75c84559ad5fe3f26ccccdd34ee0320bbf191e4b
React-graphics: 61380f6d01a225af9a3808dfd0f16622d2b6f90d
React-idlecallbacksnativemodule: 58ef763402c13067c0e85c615caf7b389d661435
React-ImageManager: bab699b4ed44ce23b23d5bcab1cdc376eb69d583
React-featureflagsnativemodule: 7fc7346e83f792b6cbc851be03cbf201601a81fc
React-graphics: 348400b8ba57611d552af6db5dc7d42ccf132d08
React-idlecallbacksnativemodule: 8a111e8e0be17ef628ea58ce1c1b1587e331fc51
React-ImageManager: ee8526b1af93152133709104c6d649d5dada63b3
React-jsc: 1f6b8e576f2858c5479683647c081de145ce8055
React-jserrorhandler: f46bec9688c2fd853d101e7fb39ee48e162d5077
React-jsi: 917f26392eaec18d7ce4e197eb87f680ca87e426
React-jsiexecutor: 73a715d55e8ee66377ffe831063c5b7bb1a81448
React-jsinspector: e4ba333f8ed2bf14f0f5459482a28ea922145169
React-jsitracing: 838bbd073e24e84cf936354f085721cbc9204d70
React-logger: 1935d6e6461e9c8be4c87af56c56a4876021171e
React-Mapbuffer: 212171f037e3b22e6c2df839aa826806da480b85
React-microtasksnativemodule: a0ffd165c39251512d8cf51e9e8f5719dabc38b6
react-native-bottom-tabs: 6ee03990297f7e37f5c1dd6f5259cee851733d4f
react-native-safe-area-context: e54b360402f089600c2fb0d825d1d3d918b99e15
React-jserrorhandler: 51806588d8259e44cab7f35e72468be6ab8f6798
React-jsi: a75033c737fbcb46d80c80fc20b9475bfbf8d2bf
React-jsiexecutor: ed2125b6786f75b40cc5e3da791d7ab78a13e711
React-jsinspector: 22c5bd5e056328a95678e8f8df9e757171dd21fd
React-jsitracing: 9e7066f99151f99ed588f2055e011845b12a1bf6
React-logger: e7eeebaed32b88dcc29b10901aa8c5822dc397c4
React-Mapbuffer: 73dd1210c4ecf0dfb4e2d4e06f2a13f824a801a9
React-microtasksnativemodule: dece61f766f8d326099d217603b1ebb50d6bb707
react-native-bottom-tabs: 9cfaa0350a9632efff1e7ce16e732e32f06c6117
react-native-safe-area-context: 7f3dfe7a0e269ffccac6f1a8377e85e8237b47be
React-nativeconfig: cb207ebba7cafce30657c7ad9f1587a8f32e4564
React-NativeModulesApple: 76a5d35322908fbc88871e6dd20433bea2b8b2db
React-perflogger: 8152bab3f0eb4b8751f282f9af7caed2c823a9ea
React-performancetimeline: 3ef4a640b56f9c7ec5f52bd93217b9b607c37cf4
React-NativeModulesApple: 38f252170af5351c88bc2e94d697359cd8c031e6
React-perflogger: c4c3b7c18f8a50cdbe2bcdd2f15705ba029a5a02
React-performancetimeline: 38bda258bd9f9da19b27615e8edfbec064aa42cc
React-RCTActionSheet: 0fdf55fb8724856d63ca8c63cdb4e2325e15e8ec
React-RCTAnimation: b93f5a1675cc2599e96851fec13c909fdfb1d6bb
React-RCTAppDelegate: e3127aff7db7100ee0000e3f67956e9c6cbaa13f
React-RCTBlob: 53dc2afa8ccdc1b6d6885d81f6862fcb918a1875
React-RCTFabric: 0a4c2a18d0ef3368f900dc08ea15ab532dd3dcf3
React-RCTFBReactNativeSpec: ec50e74af2993fb51c1f9991cc7226fea21aaa26
React-RCTImage: 028171a4d7017ea96a2e605c817cd76f01ed3836
React-RCTLinking: e3f5431ab5f8f56b82387d41a2c484a278a8e645
React-RCTNetwork: 6de20da228ffe8bd9c9e3bafe3f7d1dfe1d7bd55
React-RCTSettings: 433c9f6a070bcecbe5a44d5009326b4d6f3b0667
React-RCTText: 46249950f8d8738b90a60883d19b5bef09f0a296
React-RCTVibration: 8f41e85ab6d40c7db6111ca9e8c7492c8de374fb
React-RCTAnimation: b2fcc7c462f1fb5e195a5547f6e405ec9a60d80f
React-RCTAppDelegate: a569d1037a16f4911bcf4d0b874598c7722fe2d5
React-RCTBlob: f6620374c96915ce1762405b1504e607e239c518
React-RCTFabric: d74ed998450607ebaf701e38969c04d85a54a1b2
React-RCTFBReactNativeSpec: cf67e0c357ed8de018a1ed5b33a8b258de651f5e
React-RCTImage: f189ae651e3c97879b4cdefcba1d4cffe55439da
React-RCTLinking: 759ac5e4aed95ac3c29849f98ff3f3b5ece830ed
React-RCTNetwork: ce1f38434a70eb1e228344f7632e636c3ceca03b
React-RCTSettings: 3602ea3adf9009f6d09461bf05f7e392414c32d8
React-RCTText: e48b4b54eab3f4cfea9be1228b5ef9ad3b8172c1
React-RCTVibration: 2e4dc335dd1e57c7004bcc07e7f5319e5968d5cf
React-rendererconsistency: c766ce7261ab6ed6be7bc155c403e29436d4f156
React-rendererdebug: 1f619b295f346242842f3accee23e8394b995d3c
React-rendererdebug: f8bf864b2646944c3f7c41555dbed0b5d7aea5d1
React-rncore: cafe45e14d870bbecbbf4bd89e12ef3b596e1f2d
React-RuntimeApple: 51303fe6715be3596bf0479c1b34ce56d61ac81f
React-RuntimeCore: a0fe52c5f42a65f9d636ee4fbee14322865eb530
React-RuntimeApple: 6b67a8f0109a5289ccef380d14ba099aeadcef0e
React-RuntimeCore: 056d99b829e1de4afed419e17e95639cf72799f1
React-runtimeexecutor: 201311bdafb53b5c30292782c8ee90193af86d91
React-runtimescheduler: 89a12fb995740bf1c1d768d3c6732e709913dbeb
React-runtimescheduler: 32e558eb10b88ea398bb974b74d2230e5a71f30e
React-timing: 127d8598b5a15ae5b29ebd0ec474d590285c6f2f
React-utils: 5157cba7e171651af2113558b0c6cd562d4271bf
ReactAppDependencyProvider: e7e92253013754a8c35ebdbf8ad700f4e8956f62
ReactCodegen: 6efd314e2f59c2eae0898c6d1e0d933876a1666c
ReactCommon: cec0154a884747940be235f16acbd4fc9c959f89
ReactNativeHost: 9796a3872d3b2777a87acbe62d666dec521eda7b
ReactTestApp-DevSupport: ba03e8b8d2c87ed4b631ae8dc25765925f37e4a7
React-utils: d5269d138fd5b7b93a7f03e697f25d482e64d399
ReactAppDependencyProvider: 41e9fb63606c32cce924653d2d410cb01ec81286
ReactCodegen: 9cf993a8cfdffca67d5abe1ba056020fc48fc0d4
ReactCommon: ede76856e587ac3fd7ce70ca2387e571bc947d14
ReactNativeHost: e48e75303e422f7966cf7dc4b68d4b59d4570217
ReactTestApp-DevSupport: f23bacc6d21da29a7d8d248bb6ee8cc9ad241a48
ReactTestApp-Resources: 4f6dff3b157f879757cd750caccd1d34a7eda647
RNGestureHandler: 3bd32689c176c81dd57bb1e3b3804e7352994000
RNScreens: be44c347c9ae035bc78da28e283f484fa37e916f
RNVectorIcons: 5987b681d1ad97637f67e4e7af2902b9d4c3f5d6
RNGestureHandler: 5efafa1473ccab9c4530bb7310032b784ecc84a6
RNScreens: 749bdbb62d3dc57bd1373c64eaf5342ea0768178
RNVectorIcons: 3bcc7d69519bcac82308d2464579c4203a451c28
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Expand Down
1 change: 1 addition & 0 deletions apps/example/src/Examples/SFSymbols.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default function SFSymbols() {
? require('../../assets/icons/person_dark.png')
: { sfSymbol: 'person.fill' },
title: 'Contacts',
role: 'search',
},
]);

Expand Down
7 changes: 7 additions & 0 deletions docs/docs/docs/guides/standalone-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ Each route in the `routes` array can have the following properties:
- `activeTintColor`: Custom active tint color for this specific tab
- `lazy`: Whether to lazy load this tab's content
- `freezeOnBlur`: Whether to freeze the tab's content when it's not visible
- `role`: A value that defines the purpose of the tab

### Helper Props

Expand Down Expand Up @@ -260,3 +261,9 @@ Function to determine if a tab should be hidden.
Function to get the test ID for a tab item.

- Default: Uses `route.testID`

#### `getRole`

Function to get the role for a tab item.

- Default: Uses `route.role`
8 changes: 8 additions & 0 deletions docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,14 @@ It's working separately from `enableFreeze()` in `react-native-screens`. So sett

Test ID for the tab item. This can be used to find the tab item in the native view hierarchy.

#### `role` <Badge text="iOS 18+" type="info" />

A value that defines the purpose of the tab. This can be used to pin and separate search tabs

Available options:

- `search` - The search role.

### Events

The navigator can emit events on certain actions. Supported events are:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
lhs.badge == rhs.badge &&
lhs.activeTintColor == rhs.activeTintColor &&
lhs.hidden == rhs.hidden &&
lhs.testID == rhs.testID;
lhs.testID == rhs.testID &&
lhs.role == rhs.role;
}

bool operator!=(const RNCTabViewItemsStruct& lhs, const RNCTabViewItemsStruct& rhs) {
Expand Down Expand Up @@ -201,7 +202,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
sfSymbol:RCTNSStringFromStringNilIfEmpty(item.sfSymbol)
activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor)
hidden:item.hidden
testID:RCTNSStringFromStringNilIfEmpty(item.testID)];
testID:RCTNSStringFromStringNilIfEmpty(item.testID)
role:RCTNSStringFromStringNilIfEmpty(item.role)];

[result addObject:tabInfo];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct NewTabView: AnyTabView {
onSelect: onSelect
)

Tab(value: tabData.key) {
Tab(value: tabData.key, role: tabData.role?.convert()) {
child
.ignoresSafeArea(.container, edges: .all)
.tabAppear(using: context)
Expand Down
12 changes: 12 additions & 0 deletions packages/react-native-bottom-tabs/ios/TabViewProps.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
#endif
}

public enum TabBarRole: String {
case search

Check warning on line 28 in packages/react-native-bottom-tabs/ios/TabViewProps.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
@available(iOS 18, macOS 15, visionOS 2, tvOS 18, *)
func convert() -> TabRole {
switch self {
case .search:

Check warning on line 32 in packages/react-native-bottom-tabs/ios/TabViewProps.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Switch and Case Statement Alignment Violation: Case statements should vertically aligned with their closing brace (switch_case_alignment)
return .search
}
}
}

/**
Props that component accepts. SwiftUI view gets re-rendered when ObservableObject changes.
*/
Expand Down Expand Up @@ -58,7 +70,7 @@

var filteredItems: [TabInfo] {
items.filter {
!$0.hidden || $0.key == selectedPage

Check warning on line 73 in packages/react-native-bottom-tabs/ios/TabViewProps.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Anonymous Argument in Multiline Closure Violation: Use named arguments in multiline closures (anonymous_argument_in_multiline_closure)

Check warning on line 73 in packages/react-native-bottom-tabs/ios/TabViewProps.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Anonymous Argument in Multiline Closure Violation: Use named arguments in multiline closures (anonymous_argument_in_multiline_closure)
}
}
}
8 changes: 6 additions & 2 deletions packages/react-native-bottom-tabs/ios/TabViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public final class TabInfo: NSObject {
public let activeTintColor: PlatformColor?
public let hidden: Bool
public let testID: String?
public let role: TabBarRole?

public init(
key: String,
Expand All @@ -21,7 +22,8 @@ public final class TabInfo: NSObject {
sfSymbol: String,
activeTintColor: PlatformColor?,
hidden: Bool,
testID: String?
testID: String?,
role: String?
) {
self.key = key
self.title = title
Expand All @@ -30,6 +32,7 @@ public final class TabInfo: NSObject {
self.activeTintColor = activeTintColor
self.hidden = hidden
self.testID = testID
self.role = TabBarRole(rawValue: role ?? "")
super.init()
}
}
Expand Down Expand Up @@ -273,7 +276,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 ?? "",
role: itemDict["role"] as? String
)
)
}
Expand Down
10 changes: 9 additions & 1 deletion packages/react-native-bottom-tabs/src/TabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import type { ImageSource } from 'react-native/Libraries/Image/ImageSource';
import NativeTabView from './TabViewNativeComponent';
import useLatestCallback from 'use-latest-callback';
import type { BaseRoute, NavigationState } from './types';
import type { BaseRoute, NavigationState, TabRole } from './types';
import DelayedFreeze from './DelayedFreeze';

const isAppleSymbol = (icon: any): icon is { sfSymbol: string } =>
Expand Down Expand Up @@ -124,6 +124,11 @@
*/
getTestID?: (props: { route: Route }) => string | undefined;

/**
* Get role for the tab, uses `route.role` by default. (iOS only)
*/
getRole?: (props: { route: Route }) => TabRole | undefined;

/**
* Custom tab bar to render. Set to `null` to hide the tab bar completely.
*/
Expand Down Expand Up @@ -190,6 +195,7 @@
getHidden = ({ route }: { route: Route }) => route.hidden,
getActiveTintColor = ({ route }: { route: Route }) => route.activeTintColor,
getTestID = ({ route }: { route: Route }) => route.testID,
getRole = ({ route }: { route: Route }) => route.role,
hapticFeedbackEnabled = false,
// Android's native behavior is to show labels when there are less than 4 tabs. We leave it as undefined to use the platform default behavior.
labeled = Platform.OS !== 'android' ? true : undefined,
Expand Down Expand Up @@ -227,7 +233,7 @@

if (!loaded.includes(focusedKey)) {
// Set the current tab to be loaded if it was not loaded before
setLoaded((loaded) => [...loaded, focusedKey]);

Check warning on line 236 in packages/react-native-bottom-tabs/src/TabView.tsx

View workflow job for this annotation

GitHub Actions / lint

'loaded' is already declared in the upper scope on line 232 column 10
}

const icons = React.useMemo(
Expand Down Expand Up @@ -261,6 +267,7 @@
activeTintColor: processColor(getActiveTintColor({ route })),
hidden: getHidden?.({ route }),
testID: getTestID?.({ route }),
role: getRole?.({ route }),
};
}),
[
Expand All @@ -271,6 +278,7 @@
getActiveTintColor,
getHidden,
getTestID,
getRole,
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type TabViewItems = ReadonlyArray<{
activeTintColor?: ProcessedColorValue | null;
hidden?: boolean;
testID?: string;
role?: string;
}>;

export interface TabViewProps extends ViewProps {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-bottom-tabs/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export { BottomTabBarHeightContext } from './utils/BottomTabBarHeightContext';
/**
* Types
*/
export type { AppleIcon } from './types';
export type { AppleIcon, TabRole } from './types';
3 changes: 3 additions & 0 deletions packages/react-native-bottom-tabs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type IconSource = string | ImageSourcePropType;

export type AppleIcon = { sfSymbol: SFSymbol };

export type TabRole = 'search';

export type BaseRoute = {
key: string;
title?: string;
Expand All @@ -15,6 +17,7 @@ export type BaseRoute = {
activeTintColor?: string;
hidden?: boolean;
testID?: string;
role?: TabRole;
freezeOnBlur?: boolean;
};

Expand Down
8 changes: 7 additions & 1 deletion packages/react-navigation/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
} from '@react-navigation/native';
import type { ImageSourcePropType } from 'react-native';
import type TabView from 'react-native-bottom-tabs';
import type { AppleIcon } from 'react-native-bottom-tabs';
import type { AppleIcon, TabRole } from 'react-native-bottom-tabs';

export type NativeBottomTabNavigationEventMap = {
/**
Expand Down Expand Up @@ -92,6 +92,11 @@ export type NativeBottomTabNavigationOptions = {
*/
tabBarButtonTestID?: string;

/**
* Role for the tab. (iOS only)
*/
role?: TabRole;

/**
* Whether inactive screens should be suspended from re-rendering. Defaults to `false`.
*/
Expand Down Expand Up @@ -131,6 +136,7 @@ export type NativeBottomTabNavigationConfig = Partial<
| 'onTabLongPress'
| 'getActiveTintColor'
| 'getTestID'
| 'getRole'
| 'tabBar'
| 'getFreezeOnBlur'
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default function NativeBottomTabView({
getTestID={({ route }) =>
descriptors[route.key]?.options.tabBarButtonTestID
}
getRole={({ route }) => descriptors[route.key]?.options.role}
tabBar={
tabBar ? () => tabBar({ state, descriptors, navigation }) : undefined
}
Expand Down
Loading