diff --git a/FabricExample/src/constants/screenNames.ts b/FabricExample/src/constants/screenNames.ts
index 1e885a4d6..90ed9f947 100644
--- a/FabricExample/src/constants/screenNames.ts
+++ b/FabricExample/src/constants/screenNames.ts
@@ -20,4 +20,5 @@ export enum ScreenNames {
FOCUSED_INPUT_HANDLERS = "FOCUSED_INPUT_HANDLERS",
TOOLBAR = "TOOLBAR",
MODAL = "MODAL",
+ BOTTOM_TAB_BAR = "BOTTOM_TAB_BAR",
}
diff --git a/FabricExample/src/navigation/BottomTabBar/index.tsx b/FabricExample/src/navigation/BottomTabBar/index.tsx
new file mode 100644
index 000000000..618dbba22
--- /dev/null
+++ b/FabricExample/src/navigation/BottomTabBar/index.tsx
@@ -0,0 +1,69 @@
+import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
+import { createNativeStackNavigator } from "@react-navigation/native-stack";
+import * as React from "react";
+import { StyleSheet, Text, View } from "react-native";
+
+import KeyboardAnimation from "../../screens/Examples/KeyboardAnimation";
+
+const Tab = createBottomTabNavigator();
+const HomeStack = createNativeStackNavigator();
+const SettingsStack = createNativeStackNavigator();
+
+const HomeStackScreens = () => {
+ return (
+
+
+
+ );
+};
+
+const SettingsStackScreens = () => {
+ return (
+
+
+
+ );
+};
+
+export default function BottomTabBar() {
+ return (
+ ({
+ tabBarBackground: () => ,
+ tabBarIcon: () =>
+ route.name === "HomeStack" ? (
+ 🏠
+ ) : (
+ ⚙️
+ ),
+ tabBarLabel: () =>
+ route.name === "HomeStack" ? (
+ Home
+ ) : (
+ Settings
+ ),
+ headerShown: false,
+ })}
+ >
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ icon: {
+ color: "white",
+ fontSize: 20,
+ },
+ label: {
+ color: "white",
+ marginHorizontal: 20,
+ },
+ tabBarBackground: {
+ backgroundColor: "#2c2c2c",
+ width: "100%",
+ height: "100%",
+ },
+});
diff --git a/FabricExample/src/navigation/ExamplesStack/index.tsx b/FabricExample/src/navigation/ExamplesStack/index.tsx
index 7b84e76f0..735f8450f 100644
--- a/FabricExample/src/navigation/ExamplesStack/index.tsx
+++ b/FabricExample/src/navigation/ExamplesStack/index.tsx
@@ -19,6 +19,7 @@ import ReanimatedChat from "../../screens/Examples/ReanimatedChat";
import ReanimatedChatFlatList from "../../screens/Examples/ReanimatedChatFlatList";
import StatusBar from "../../screens/Examples/StatusBar";
import ToolbarExample from "../../screens/Examples/Toolbar";
+import BottomTabBar from "../BottomTabBar";
import NativeStack from "../NestedStack";
export type ExamplesStackParamList = {
@@ -40,6 +41,7 @@ export type ExamplesStackParamList = {
[ScreenNames.FOCUSED_INPUT_HANDLERS]: undefined;
[ScreenNames.TOOLBAR]: undefined;
[ScreenNames.MODAL]: undefined;
+ [ScreenNames.BOTTOM_TAB_BAR]: undefined;
};
const Stack = createStackNavigator();
@@ -100,6 +102,9 @@ const options = {
[ScreenNames.MODAL]: {
title: "Modal",
},
+ [ScreenNames.BOTTOM_TAB_BAR]: {
+ headerShown: false,
+ },
};
const ExamplesStack = () => (
@@ -194,6 +199,11 @@ const ExamplesStack = () => (
name={ScreenNames.MODAL}
options={options[ScreenNames.MODAL]}
/>
+
);
diff --git a/FabricExample/src/screens/Examples/Main/constants.ts b/FabricExample/src/screens/Examples/Main/constants.ts
index f4cadff69..3c46ee966 100644
--- a/FabricExample/src/screens/Examples/Main/constants.ts
+++ b/FabricExample/src/screens/Examples/Main/constants.ts
@@ -111,4 +111,10 @@ export const examples: Example[] = [
info: ScreenNames.MODAL,
icons: "🌎",
},
+ {
+ title: "Bottom tab bar",
+ testID: "bottom_tab_bar",
+ info: ScreenNames.BOTTOM_TAB_BAR,
+ icons: "🧱",
+ },
];
diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt
index 6e566fc88..9418cf1c8 100644
--- a/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt
+++ b/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt
@@ -1,6 +1,7 @@
package com.reactnativekeyboardcontroller.views
import android.annotation.SuppressLint
+import android.content.res.Configuration
import android.os.Handler
import android.os.Looper
import android.util.Log
@@ -68,6 +69,10 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
this.removeKeyboardCallbacks()
}
+
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ this.reApplyWindowInsets()
+ }
// endregion
// region State manager helpers
@@ -83,25 +88,23 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
val shouldApplyZeroPaddingTop = !active || this.isStatusBarTranslucent
val shouldApplyZeroPaddingBottom = !active || this.isNavigationBarTranslucent
+ val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
+ val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+
params.setMargins(
- 0,
+ navBarInsets.left,
if (shouldApplyZeroPaddingTop) {
0
} else {
- (
- insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.top
- ?: 0
- )
+ systemBarInsets.top
},
- 0,
+ navBarInsets.right,
if (shouldApplyZeroPaddingBottom) {
0
} else {
- insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom
- ?: 0
+ navBarInsets.bottom
},
)
-
content?.layoutParams = params
val defaultInsets = ViewCompat.onApplyWindowInsets(v, insets)
@@ -164,6 +167,11 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
// for more details
Handler(Looper.getMainLooper()).post { view.removeSelf() }
}
+
+ private fun reApplyWindowInsets() {
+ this.setupWindowInsets()
+ this.requestApplyInsetsWhenAttached()
+ }
// endregion
// region State managers
@@ -206,8 +214,7 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
fun forceStatusBarTranslucent(isStatusBarTranslucent: Boolean) {
if (active && this.isStatusBarTranslucent != isStatusBarTranslucent) {
this.isStatusBarTranslucent = isStatusBarTranslucent
- this.setupWindowInsets()
- this.requestApplyInsetsWhenAttached()
+ this.reApplyWindowInsets()
}
}
// endregion
diff --git a/e2e/kit/010-bottom-tab-bar-rotation.e2e.ts b/e2e/kit/010-bottom-tab-bar-rotation.e2e.ts
new file mode 100644
index 000000000..817ccf850
--- /dev/null
+++ b/e2e/kit/010-bottom-tab-bar-rotation.e2e.ts
@@ -0,0 +1,48 @@
+import { expectBitmapsToBeEqual } from "./asserts";
+import {
+ closeKeyboard,
+ scrollDownUntilElementIsVisible,
+ waitAndTap,
+ waitForExpect,
+} from "./helpers";
+
+describe("Bottom tab bar", () => {
+ it("should navigate to `Bottom tab bar` screen", async () => {
+ await scrollDownUntilElementIsVisible("main_scroll_view", "bottom_tab_bar");
+ await waitAndTap("bottom_tab_bar");
+ });
+
+ it("should have expected state in portrait mode", async () => {
+ await waitForExpect(async () => {
+ await expectBitmapsToBeEqual("BottomTabBarPortrait", 0.25);
+ });
+ });
+
+ it("should have expected state in landscape mode", async () => {
+ await device.setOrientation("landscape");
+ await waitForExpect(async () => {
+ await expectBitmapsToBeEqual("BottomTabBarLandscape");
+ });
+ });
+
+ it("should have expected state in portrait mode after rotation", async () => {
+ await device.setOrientation("portrait");
+ await waitForExpect(async () => {
+ await expectBitmapsToBeEqual("BottomTabBarPortraitAgain");
+ });
+ });
+
+ it("should have expected state when keyboard open", async () => {
+ await waitAndTap("keyboard_animation_text_input");
+ await waitForExpect(async () => {
+ await expectBitmapsToBeEqual("BottomTabBarKeyboardIsShown");
+ });
+ });
+
+ it("should have expected state when emoji keyboard closed", async () => {
+ await closeKeyboard("keyboard_animation_text_input");
+ await waitForExpect(async () => {
+ await expectBitmapsToBeEqual("BottomTabBarKeyboardIsHidden");
+ });
+ });
+});
diff --git a/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarKeyboardIsHidden.png b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarKeyboardIsHidden.png
new file mode 100644
index 000000000..2c6b19dd7
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarKeyboardIsHidden.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarKeyboardIsShown.png b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarKeyboardIsShown.png
new file mode 100644
index 000000000..804042470
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarKeyboardIsShown.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarLandscape.png b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarLandscape.png
new file mode 100644
index 000000000..5ead14256
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarLandscape.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarPortrait.png b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarPortrait.png
new file mode 100644
index 000000000..72a540826
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarPortrait.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarPortraitAgain.png b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarPortraitAgain.png
new file mode 100644
index 000000000..72a540826
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_28/BottomTabBarPortraitAgain.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarKeyboardIsHidden.png b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarKeyboardIsHidden.png
new file mode 100644
index 000000000..0671fe31b
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarKeyboardIsHidden.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarKeyboardIsShown.png b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarKeyboardIsShown.png
new file mode 100644
index 000000000..6d575b72e
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarKeyboardIsShown.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarLandscape.png b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarLandscape.png
new file mode 100644
index 000000000..bb6422f51
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarLandscape.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarPortrait.png b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarPortrait.png
new file mode 100644
index 000000000..c1102b3b4
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarPortrait.png differ
diff --git a/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarPortraitAgain.png b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarPortraitAgain.png
new file mode 100644
index 000000000..c1102b3b4
Binary files /dev/null and b/e2e/kit/assets/android/e2e_emulator_31/BottomTabBarPortraitAgain.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarKeyboardIsHidden.png b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarKeyboardIsHidden.png
new file mode 100644
index 000000000..56791e669
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarKeyboardIsHidden.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarKeyboardIsShown.png b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarKeyboardIsShown.png
new file mode 100644
index 000000000..974c65159
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarKeyboardIsShown.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarLandscape.png b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarLandscape.png
new file mode 100644
index 000000000..d02ca64a9
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarLandscape.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarPortrait.png b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarPortrait.png
new file mode 100644
index 000000000..0dfd0fab5
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarPortrait.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarPortraitAgain.png b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarPortraitAgain.png
new file mode 100644
index 000000000..45cd44f62
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14 Pro/BottomTabBarPortraitAgain.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14/BottomTabBarKeyboardIsHidden.png b/e2e/kit/assets/ios/iPhone 14/BottomTabBarKeyboardIsHidden.png
new file mode 100644
index 000000000..c12d46db6
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14/BottomTabBarKeyboardIsHidden.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14/BottomTabBarKeyboardIsShown.png b/e2e/kit/assets/ios/iPhone 14/BottomTabBarKeyboardIsShown.png
new file mode 100644
index 000000000..c18a2d7d2
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14/BottomTabBarKeyboardIsShown.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14/BottomTabBarLandscape.png b/e2e/kit/assets/ios/iPhone 14/BottomTabBarLandscape.png
new file mode 100644
index 000000000..35ce06a76
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14/BottomTabBarLandscape.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14/BottomTabBarPortrait.png b/e2e/kit/assets/ios/iPhone 14/BottomTabBarPortrait.png
new file mode 100644
index 000000000..c12d46db6
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14/BottomTabBarPortrait.png differ
diff --git a/e2e/kit/assets/ios/iPhone 14/BottomTabBarPortraitAgain.png b/e2e/kit/assets/ios/iPhone 14/BottomTabBarPortraitAgain.png
new file mode 100644
index 000000000..bd91591be
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 14/BottomTabBarPortraitAgain.png differ
diff --git a/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarKeyboardIsHidden.png b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarKeyboardIsHidden.png
new file mode 100644
index 000000000..f5e4327f9
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarKeyboardIsHidden.png differ
diff --git a/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarKeyboardIsShown.png b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarKeyboardIsShown.png
new file mode 100644
index 000000000..78a82f47e
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarKeyboardIsShown.png differ
diff --git a/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarLandscape.png b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarLandscape.png
new file mode 100644
index 000000000..6c2a30ae7
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarLandscape.png differ
diff --git a/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarPortrait.png b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarPortrait.png
new file mode 100644
index 000000000..f5e4327f9
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarPortrait.png differ
diff --git a/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarPortraitAgain.png b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarPortraitAgain.png
new file mode 100644
index 000000000..1c45b0384
Binary files /dev/null and b/e2e/kit/assets/ios/iPhone 15 Pro/BottomTabBarPortraitAgain.png differ
diff --git a/example/src/constants/screenNames.ts b/example/src/constants/screenNames.ts
index 1e885a4d6..90ed9f947 100644
--- a/example/src/constants/screenNames.ts
+++ b/example/src/constants/screenNames.ts
@@ -20,4 +20,5 @@ export enum ScreenNames {
FOCUSED_INPUT_HANDLERS = "FOCUSED_INPUT_HANDLERS",
TOOLBAR = "TOOLBAR",
MODAL = "MODAL",
+ BOTTOM_TAB_BAR = "BOTTOM_TAB_BAR",
}
diff --git a/example/src/navigation/BottomTabBar/index.tsx b/example/src/navigation/BottomTabBar/index.tsx
new file mode 100644
index 000000000..618dbba22
--- /dev/null
+++ b/example/src/navigation/BottomTabBar/index.tsx
@@ -0,0 +1,69 @@
+import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
+import { createNativeStackNavigator } from "@react-navigation/native-stack";
+import * as React from "react";
+import { StyleSheet, Text, View } from "react-native";
+
+import KeyboardAnimation from "../../screens/Examples/KeyboardAnimation";
+
+const Tab = createBottomTabNavigator();
+const HomeStack = createNativeStackNavigator();
+const SettingsStack = createNativeStackNavigator();
+
+const HomeStackScreens = () => {
+ return (
+
+
+
+ );
+};
+
+const SettingsStackScreens = () => {
+ return (
+
+
+
+ );
+};
+
+export default function BottomTabBar() {
+ return (
+ ({
+ tabBarBackground: () => ,
+ tabBarIcon: () =>
+ route.name === "HomeStack" ? (
+ 🏠
+ ) : (
+ ⚙️
+ ),
+ tabBarLabel: () =>
+ route.name === "HomeStack" ? (
+ Home
+ ) : (
+ Settings
+ ),
+ headerShown: false,
+ })}
+ >
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ icon: {
+ color: "white",
+ fontSize: 20,
+ },
+ label: {
+ color: "white",
+ marginHorizontal: 20,
+ },
+ tabBarBackground: {
+ backgroundColor: "#2c2c2c",
+ width: "100%",
+ height: "100%",
+ },
+});
diff --git a/example/src/navigation/ExamplesStack/index.tsx b/example/src/navigation/ExamplesStack/index.tsx
index 7b84e76f0..735f8450f 100644
--- a/example/src/navigation/ExamplesStack/index.tsx
+++ b/example/src/navigation/ExamplesStack/index.tsx
@@ -19,6 +19,7 @@ import ReanimatedChat from "../../screens/Examples/ReanimatedChat";
import ReanimatedChatFlatList from "../../screens/Examples/ReanimatedChatFlatList";
import StatusBar from "../../screens/Examples/StatusBar";
import ToolbarExample from "../../screens/Examples/Toolbar";
+import BottomTabBar from "../BottomTabBar";
import NativeStack from "../NestedStack";
export type ExamplesStackParamList = {
@@ -40,6 +41,7 @@ export type ExamplesStackParamList = {
[ScreenNames.FOCUSED_INPUT_HANDLERS]: undefined;
[ScreenNames.TOOLBAR]: undefined;
[ScreenNames.MODAL]: undefined;
+ [ScreenNames.BOTTOM_TAB_BAR]: undefined;
};
const Stack = createStackNavigator();
@@ -100,6 +102,9 @@ const options = {
[ScreenNames.MODAL]: {
title: "Modal",
},
+ [ScreenNames.BOTTOM_TAB_BAR]: {
+ headerShown: false,
+ },
};
const ExamplesStack = () => (
@@ -194,6 +199,11 @@ const ExamplesStack = () => (
name={ScreenNames.MODAL}
options={options[ScreenNames.MODAL]}
/>
+
);
diff --git a/example/src/screens/Examples/Main/constants.ts b/example/src/screens/Examples/Main/constants.ts
index f4cadff69..3c46ee966 100644
--- a/example/src/screens/Examples/Main/constants.ts
+++ b/example/src/screens/Examples/Main/constants.ts
@@ -111,4 +111,10 @@ export const examples: Example[] = [
info: ScreenNames.MODAL,
icons: "🌎",
},
+ {
+ title: "Bottom tab bar",
+ testID: "bottom_tab_bar",
+ info: ScreenNames.BOTTOM_TAB_BAR,
+ icons: "🧱",
+ },
];