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/ready-paths-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-native-bottom-tabs': patch
---

feat: implement iOS 26 minimizeBehavior feature
4 changes: 2 additions & 2 deletions apps/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ PODS:
- React-jsiexecutor
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- react-native-bottom-tabs (0.9.1):
- react-native-bottom-tabs (0.9.2):
- DoubleConversion
- glog
- RCT-Folly (= 2024.11.18.00)
Expand Down Expand Up @@ -1931,7 +1931,7 @@ SPEC CHECKSUMS:
React-logger: 1935d6e6461e9c8be4c87af56c56a4876021171e
React-Mapbuffer: 212171f037e3b22e6c2df839aa826806da480b85
React-microtasksnativemodule: a0ffd165c39251512d8cf51e9e8f5719dabc38b6
react-native-bottom-tabs: 7c7ee94435e518759b414e89068956096a1adec7
react-native-bottom-tabs: 6ee03990297f7e37f5c1dd6f5259cee851733d4f
react-native-safe-area-context: e54b360402f089600c2fb0d825d1d3d918b99e15
React-nativeconfig: cb207ebba7cafce30657c7ad9f1587a8f32e4564
React-NativeModulesApple: 76a5d35322908fbc88871e6dd20433bea2b8b2db
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 @@ -47,6 +47,7 @@ export default function SFSymbols() {
return (
<TabView
sidebarAdaptable
minimizeBehavior="onScrollDown"
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
Expand Down
17 changes: 17 additions & 0 deletions docs/docs/docs/guides/standalone-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default function TabViewExample() {
renderScene={renderScene}
onIndexChange={setIndex}
labeled
minimizeBehavior="onScrollDown" // iOS 26+: Hide tab bar when scrolling down
/>
);
}
Expand Down Expand Up @@ -143,6 +144,22 @@ Supported properties:
Appearance attributes for the tab bar when a scroll view is at the bottom.
- Type: `'default' | 'opaque' | 'transparent'`

#### `minimizeBehavior` <Badge text="iOS 26+" type="info" />

Controls how the tab bar behaves when content is scrolled.
- Type: `'automatic' | 'onScrollDown' | 'onScrollUp' | 'never'`
- Default: `undefined` (uses system default)

Options:
- `automatic`: Platform determines the behavior
- `onScrollDown`: Tab bar minimizes when scrolling down
- `onScrollUp`: Tab bar minimizes when scrolling up
- `never`: Tab bar never minimizes

:::note
This feature requires iOS 26.0 or later and is only available on iOS. On older versions, this prop is ignored.
:::

#### `tabBarActiveTintColor`

Color for the active tab.
Expand Down
17 changes: 17 additions & 0 deletions docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ Tab views using the sidebar adaptable style have an appearance

Whether to enable haptic feedback on tab press. Defaults to false.

#### `minimizeBehavior` <Badge text="iOS 26+" type="info" />

Controls how the tab bar behaves when content is scrolled.
Copy link
Preview

Copilot AI Jul 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The documentation for minimizeBehavior is duplicated across multiple guides. Consider extracting this into a shared snippet or component to reduce duplication and keep docs in sync.

Copilot uses AI. Check for mistakes.


- Type: `'automatic' | 'onScrollDown' | 'onScrollUp' | 'never'`
- Default: `undefined` (uses system default)

Options:
- `automatic`: Platform determines the behavior
- `onScrollDown`: Tab bar minimizes when scrolling down
- `onScrollUp`: Tab bar minimizes when scrolling up
- `never`: Tab bar never minimizes

:::note
This feature requires iOS 26.0 or later and is only available on iOS. On older versions, this prop is ignored.
:::

#### `tabLabelStyle`

Object containing styles for the tab label.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,7 @@ class RCTTabViewManager(context: ReactApplicationContext) :

override fun setScrollEdgeAppearance(view: ReactBottomNavigationView?, value: String?) {
}

override fun setMinimizeBehavior(view: ReactBottomNavigationView?, value: String?) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ class RCTTabViewManager(context: ReactApplicationContext) : ViewGroupManager<Rea
fun setSidebarAdaptable(view: ReactBottomNavigationView, flag: Boolean) {
}

@ReactProp(name = "minimizeBehavior")
fun setMinimizeBehavior(view: ReactBottomNavigationView, flag: Boolean) {
}

@ReactProp(name = "hapticFeedbackEnabled")
fun setHapticFeedbackEnabled(view: ReactBottomNavigationView, value: Boolean) {
tabViewImpl.setHapticFeedbackEnabled(view, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
if (oldViewProps.sidebarAdaptable != newViewProps.sidebarAdaptable) {
_tabViewProvider.sidebarAdaptable = newViewProps.sidebarAdaptable;
}

if (oldViewProps.minimizeBehavior != newViewProps.minimizeBehavior) {
_tabViewProvider.minimizeBehavior = RCTNSStringFromString(newViewProps.minimizeBehavior);
}

if (oldViewProps.disablePageAnimations != newViewProps.disablePageAnimations) {
_tabViewProvider.disablePageAnimations = newViewProps.disablePageAnimations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ @implementation RCTTabView
RCT_EXPORT_VIEW_PROPERTY(onNativeLayout, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(selectedPage, NSString)
RCT_EXPORT_VIEW_PROPERTY(tabViewStyle, NSString)
RCT_EXPORT_VIEW_PROPERTY(minimizeBehavior, NSString)
RCT_EXPORT_VIEW_PROPERTY(icons, NSArray<RCTImageSource *>);
RCT_EXPORT_VIEW_PROPERTY(sidebarAdaptable, BOOL)
RCT_EXPORT_VIEW_PROPERTY(labeled, BOOL)
Expand Down
18 changes: 18 additions & 0 deletions packages/react-native-bottom-tabs/ios/TabViewImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
onLayout(size)
}
}
.tabBarMinimizeBehavior(props.minimizeBehavior)
#if !os(tvOS) && !os(macOS) && !os(visionOS)
.onTabItemEvent { index, isLongPress in
let item = props.filteredItems[safe: index]
Expand Down Expand Up @@ -155,7 +156,7 @@
attributes[.font] = RCTFont.update(
nil,
withFamily: family,
size: NSNumber(value: size),

Check warning on line 159 in packages/react-native-bottom-tabs/ios/TabViewImpl.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
weight: weight,
style: nil,
variant: nil,
Expand Down Expand Up @@ -333,6 +334,23 @@
}
}

@ViewBuilder
func tabBarMinimizeBehavior(_ behavior: MinimizeBehavior?) -> some View {
#if compiler(>=6.2)
if #available(iOS 26.0, *) {
if let behavior {
self.tabBarMinimizeBehavior(behavior.convert())
} else {
self
}
} else {
self
}
#else
self
#endif
}

@ViewBuilder
func hideTabBar(_ flag: Bool) -> some View {
#if !os(macOS)
Expand Down
24 changes: 24 additions & 0 deletions packages/react-native-bottom-tabs/ios/TabViewProps.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
import SwiftUI

internal enum MinimizeBehavior: String {
case automatic
case never
case onScrollUp
case onScrollDown

#if compiler(>=6.2)
@available(iOS 26.0, *)
func convert() -> TabBarMinimizeBehavior {
switch self {
case .automatic:
return .automatic
case .never:
return .never
case .onScrollUp:
return .onScrollUp
case .onScrollDown:
return .onScrollDown
}
}
#endif
}

/**
Props that component accepts. SwiftUI view gets re-rendered when ObservableObject changes.
*/
Expand All @@ -10,6 +33,7 @@ class TabViewProps: ObservableObject {
@Published var icons: [Int: PlatformImage] = [:]
@Published var sidebarAdaptable: Bool?
@Published var labeled: Bool = false
@Published var minimizeBehavior: MinimizeBehavior?
@Published var scrollEdgeAppearance: String?
@Published var barTintColor: PlatformColor?
@Published var activeTintColor: PlatformColor?
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native-bottom-tabs/ios/TabViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ public final class TabInfo: NSObject {
}
}

@objc public var minimizeBehavior: NSString? {
didSet {
props.minimizeBehavior = MinimizeBehavior(rawValue: minimizeBehavior as? String ?? "")
}
}

@objc public var translucent: Bool = true {
didSet {
props.translucent = translucent
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native-bottom-tabs/src/TabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
* Describes the appearance attributes for the tabBar to use when an observable scroll view is scrolled to the bottom. (iOS only)
*/
scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent';

/**
* Behavior for minimizing the tab bar. (iOS 26+)
*/
minimizeBehavior?: 'automatic' | 'onScrollDown' | 'onScrollUp' | 'never';
/**
* Active tab color.
*/
Expand Down Expand Up @@ -222,7 +227,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 230 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 226 column 10
}

const icons = React.useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface TabViewProps extends ViewProps {
disablePageAnimations?: boolean;
activeIndicatorColor?: ColorValue;
hapticFeedbackEnabled?: boolean;
minimizeBehavior?: string;
fontFamily?: string;
fontWeight?: string;
fontSize?: Int32;
Expand Down
Loading