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
5 changes: 5 additions & 0 deletions .changeset/ios-prominent-tab-role.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-native-bottom-tabs': minor
---

Add the `prominent` tab role (iOS 27+), and fix iOS 27 tabs keeping their selected tint after switching tabs or minimizing the tab bar
2 changes: 1 addition & 1 deletion docs/docs/docs/guides/standalone-usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,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
- `role`: A value that defines the purpose of the tab on iOS: `'search'` (iOS 18+) or `'prominent'` (iOS 27+, displayed as a regular tab on earlier versions)
- `style`: Style object for the component wrapping the screen content
- `preventsDefault`: Whether to prevent default tab switching behavior when pressed

Expand Down
5 changes: 3 additions & 2 deletions docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -398,11 +398,12 @@ Test ID for the tab item. This can be used to find the tab item in the native vi

#### `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
A value that defines the purpose of the tab.

Available options:

- `search` - The search role.
- `search` - Marks the tab as the search tab, which the system can pin to the trailing end of the tab bar. On iOS 27+, it may also receive the prominent treatment when no tab has the `prominent` role.
- `prominent` <Badge text="iOS 27+" type="info" /> - Gives the tab more visual emphasis than the others. Only one tab can be prominent. Requires building with the iOS 27 SDK, and on earlier iOS versions the tab is displayed as a regular tab.

#### `sceneStyle`

Expand Down
2 changes: 2 additions & 0 deletions packages/example-shared/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import SixTabs from './Examples/SixTabs';
import FourTabsRTL from './Examples/FourTabsRTL';
import MaterialBottomTabs from './Examples/MaterialBottomTabs';
import SFSymbols from './Examples/SFSymbols';
import Roles from './Examples/Roles';
import LabeledTabs from './Examples/Labeled';
import NativeBottomTabs from './Examples/NativeBottomTabs';
import TintColorsExample from './Examples/TintColors';
Expand Down Expand Up @@ -99,6 +100,7 @@ const examples = [
{ component: FourTabs, name: 'Four Tabs' },
{ component: FiveTabs, name: 'Five Tabs' },
{ component: SixTabs, name: 'Six Tabs' },
{ component: Roles, name: 'Roles', platform: 'ios' },
{
component: SFSymbols,
name: 'SF Symbols',
Expand Down
107 changes: 107 additions & 0 deletions packages/example-shared/src/Examples/Roles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useMemo, useState } from 'react';
import { Button, StyleSheet, Switch, Text, View } from 'react-native';
import TabView, { SceneMap } from 'react-native-bottom-tabs';
import type { TabRole } from 'react-native-bottom-tabs';
import { Article } from '../Screens/Article';
import { Albums } from '../Screens/Albums';
import { Contacts } from '../Screens/Contacts';

const renderScene = SceneMap({
article: Article,
albums: Albums,
role: Contacts,
});

export default function Roles() {
const [role, setRole] = useState<TabRole>('search');
const [bakedTintColors, setBakedTintColors] = useState(false);
const [index, setIndex] = useState(0);
const routes = useMemo(
() => [
{
key: 'article',
title: 'Article',
focusedIcon: { sfSymbol: 'document' },
},
{
key: 'albums',
title: 'Albums',
focusedIcon: { sfSymbol: 'square.grid.2x2' },
},
{
key: 'role',
title: role === 'search' ? 'Search' : 'Contacts',
focusedIcon: {
sfSymbol:
role === 'search' ? 'magnifyingglass' : 'person.crop.circle',
},
role,
testID: `roles-${role}-tab`,
},
],
[role]
);

const selectRole = (nextRole: TabRole) => {
setIndex(0);
setRole(nextRole);
};

return (
<View style={styles.container}>
<View style={styles.controls}>
<View style={styles.row}>
<Button
title="Search"
disabled={role === 'search'}
onPress={() => selectRole('search')}
/>
<Button
title="Prominent"
disabled={role === 'prominent'}
onPress={() => selectRole('prominent')}
/>
</View>
<Text>
{role === 'search'
? 'Search role (iOS 18+). May receive prominent styling when no tab has the prominent role.'
: 'Prominent role (iOS 27+). Emphasizes the Contacts tab; falls back to an ordinary tab on older iOS versions.'}
</Text>
<View style={styles.row}>
<Text>Experimental baked tint colors</Text>
<Switch
accessibilityLabel="Experimental baked tint colors"
value={bakedTintColors}
onValueChange={setBakedTintColors}
/>
</View>
</View>
<TabView
key={role}
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
labeled
tabBarActiveTintColor="red"
tabBarInactiveTintColor="orange"
experimental_bakedTintColors={bakedTintColors}
minimizeBehavior='onScrollDown'
/>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
},
controls: {
padding: 12,
gap: 12,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
});
13 changes: 9 additions & 4 deletions packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ struct NewTabView: AnyTabView {
let isFocused = props.selectedPage == tabData.key

if !tabData.hidden || isFocused {
let icon = props.icons[index]
#if os(macOS)
let labelImage: PlatformImage? = nil
#else
let labelImage = props.itemImages.images(for: tabData, props: props)?.labelImage
#endif

let context = TabAppearContext(
index: index,
Expand All @@ -38,18 +42,19 @@ struct NewTabView: AnyTabView {
onSelect: onSelect
)

Tab(value: tabData.key, role: tabData.role?.convert()) {
Tab(value: tabData.key, role: tabData.role.flatMap { $0.convert() }) {
Comment thread
troZee marked this conversation as resolved.
RepresentableView(view: child.view)
.ignoresSafeArea(.container, edges: .all)
.tabAppear(using: context)
.hideTabBar(props.tabBarHidden)
} label: {
TabItem(
title: tabData.title,
icon: icon,
icon: labelImage ?? props.icons[index],
sfSymbol: tabData.sfSymbol,
labeled: props.labeled,
iconRenderingMode: tabData.iconRenderingMode
// Rendered label images already have their final rendering mode.
iconRenderingMode: labelImage == nil ? tabData.iconRenderingMode : nil
)
}
#if !os(tvOS)
Expand Down
Loading
Loading