diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx
index ba1f7606..78c8d6f9 100644
--- a/apps/example/src/App.tsx
+++ b/apps/example/src/App.tsx
@@ -23,6 +23,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
import JSBottomTabs from './Examples/JSBottomTabs';
import ThreeTabs from './Examples/ThreeTabs';
import FourTabs from './Examples/FourTabs';
+import FourTabsRTL from './Examples/FourTabsRTL';
import MaterialBottomTabs from './Examples/MaterialBottomTabs';
import SFSymbols from './Examples/SFSymbols';
import LabeledTabs from './Examples/Labeled';
@@ -72,6 +73,9 @@ const FourTabsActiveIndicatorColor = () => {
const UnlabeledTabs = () => {
return ;
};
+const FourTabsRightToLeft = () => {
+ return ;
+};
const examples = [
{
@@ -161,6 +165,7 @@ const examples = [
name: 'Bottom Accessory View',
screenOptions: { headerShown: false },
},
+ { component: FourTabsRightToLeft, name: 'Four Tabs - RTL' },
];
function App() {
diff --git a/apps/example/src/Examples/FourTabsRTL.tsx b/apps/example/src/Examples/FourTabsRTL.tsx
new file mode 100644
index 00000000..6ff9914d
--- /dev/null
+++ b/apps/example/src/Examples/FourTabsRTL.tsx
@@ -0,0 +1,95 @@
+import TabView, { SceneMap } from 'react-native-bottom-tabs';
+import React from 'react';
+import { Article } from '../Screens/Article';
+import { Albums } from '../Screens/Albums';
+import { Contacts } from '../Screens/Contacts';
+import { Chat } from '../Screens/Chat';
+import { I18nManager, type ColorValue } from 'react-native';
+import type { LayoutDirection } from 'react-native-bottom-tabs/src/types';
+
+interface Props {
+ disablePageAnimations?: boolean;
+ scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent';
+ backgroundColor?: ColorValue;
+ translucent?: boolean;
+ hideOneTab?: boolean;
+ rippleColor?: ColorValue;
+ activeIndicatorColor?: ColorValue;
+ layoutDirection?: LayoutDirection;
+}
+
+const renderScene = SceneMap({
+ article: Article,
+ albums: Albums,
+ contacts: Contacts,
+ chat: Chat,
+});
+
+export default function FourTabsRTL({
+ disablePageAnimations = false,
+ scrollEdgeAppearance = 'default',
+ backgroundColor,
+ translucent = true,
+ hideOneTab = false,
+ rippleColor,
+ activeIndicatorColor,
+ layoutDirection = 'locale',
+}: Props) {
+ React.useLayoutEffect(() => {
+ if (layoutDirection === 'rtl') {
+ I18nManager.allowRTL(true);
+ I18nManager.forceRTL(true);
+ }
+ return () => {
+ if (layoutDirection === 'rtl') {
+ I18nManager.allowRTL(false);
+ I18nManager.forceRTL(false);
+ }
+ };
+ }, [layoutDirection]);
+ const [index, setIndex] = React.useState(0);
+ const [routes] = React.useState([
+ {
+ key: 'article',
+ title: 'المقالات',
+ focusedIcon: require('../../assets/icons/article_dark.png'),
+ unfocusedIcon: require('../../assets/icons/chat_dark.png'),
+ badge: '!',
+ },
+ {
+ key: 'albums',
+ title: 'البومات',
+ focusedIcon: require('../../assets/icons/grid_dark.png'),
+ badge: '5',
+ hidden: hideOneTab,
+ },
+ {
+ key: 'contacts',
+ focusedIcon: require('../../assets/icons/person_dark.png'),
+ title: 'المتراسلين',
+ badge: ' ',
+ },
+ {
+ key: 'chat',
+ focusedIcon: require('../../assets/icons/chat_dark.png'),
+ title: 'المحادثات',
+ role: 'search',
+ },
+ ]);
+
+ return (
+
+ );
+}
diff --git a/apps/example/src/Examples/NativeBottomTabs.tsx b/apps/example/src/Examples/NativeBottomTabs.tsx
index 190db787..7e89aa3a 100644
--- a/apps/example/src/Examples/NativeBottomTabs.tsx
+++ b/apps/example/src/Examples/NativeBottomTabs.tsx
@@ -15,6 +15,7 @@ function NativeBottomTabs() {
initialRouteName="Chat"
labeled={true}
hapticFeedbackEnabled={false}
+ layoutDirection="leftToRight"
tabBarInactiveTintColor="#C57B57"
tabBarActiveTintColor="#F7DBA7"
tabBarStyle={{
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 23cd8759..1d0c4047 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
@@ -127,6 +127,10 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) {
layout(left, top, right, bottom)
}
+ fun applyDirection(dir: Int) {
+ bottomNavigation.layoutDirection = dir
+ }
+
override fun requestLayout() {
super.requestLayout()
@Suppress("SENSELESS_COMPARISON") // layoutCallback can be null here since this method can be called in init
diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt
index dad16c28..3f130e11 100644
--- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt
+++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt
@@ -167,6 +167,15 @@ class RCTTabViewManager(context: ReactApplicationContext) :
view.isHapticFeedbackEnabled = value
}
+ override fun setLayoutDirection(view: ReactBottomNavigationView, value: String?) {
+ val direction = when (value) {
+ "rtl" -> View.LAYOUT_DIRECTION_RTL
+ "ltr" -> View.LAYOUT_DIRECTION_LTR
+ else -> View.LAYOUT_DIRECTION_LOCALE
+ }
+ view.applyDirection(direction)
+ }
+
override fun setFontFamily(view: ReactBottomNavigationView?, value: String?) {
view?.setFontFamily(value)
}
diff --git a/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
index c41f9316..92cab0e7 100644
--- a/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
+++ b/packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
@@ -160,6 +160,10 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
_tabViewProvider.hapticFeedbackEnabled = newViewProps.hapticFeedbackEnabled;
}
+ if (oldViewProps.layoutDirection != newViewProps.layoutDirection) {
+ _tabViewProvider.layoutDirection = RCTNSStringFromStringNilIfEmpty(newViewProps.layoutDirection);
+ }
+
if (oldViewProps.fontSize != newViewProps.fontSize) {
_tabViewProvider.fontSize = [NSNumber numberWithInt:newViewProps.fontSize];
}
diff --git a/packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift b/packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift
index d6993158..52089075 100644
--- a/packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift
+++ b/packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift
@@ -11,6 +11,16 @@ struct NewTabView: AnyTabView {
@ViewBuilder
var body: some View {
+ var effectiveLayoutDirection: LayoutDirection {
+ let dir = props.layoutDirection ?? "locale"
+ if let mapped = ["rtl": LayoutDirection.rightToLeft,
+ "ltr": LayoutDirection.leftToRight][dir] {
+ return mapped
+ }
+ let system = UIView.userInterfaceLayoutDirection(for: .unspecified)
+ return system == .rightToLeft ? .rightToLeft : .leftToRight
+}
+
TabView(selection: $props.selectedPage) {
ForEach(props.children) { child in
if let index = props.children.firstIndex(of: child),
@@ -49,6 +59,7 @@ struct NewTabView: AnyTabView {
}
}
}
+ .environment(\.layoutDirection, effectiveLayoutDirection)
.measureView { size in
onLayout(size)
}
diff --git a/packages/react-native-bottom-tabs/ios/TabViewProps.swift b/packages/react-native-bottom-tabs/ios/TabViewProps.swift
index cd098c07..9cfb29a9 100644
--- a/packages/react-native-bottom-tabs/ios/TabViewProps.swift
+++ b/packages/react-native-bottom-tabs/ios/TabViewProps.swift
@@ -66,6 +66,7 @@ class TabViewProps: ObservableObject {
@Published var translucent: Bool = true
@Published var disablePageAnimations: Bool = false
@Published var hapticFeedbackEnabled: Bool = false
+ @Published var layoutDirection: String?
@Published var fontSize: Int?
@Published var fontFamily: String?
@Published var fontWeight: String?
diff --git a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
index 013032f0..deac524d 100644
--- a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
+++ b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
@@ -95,6 +95,11 @@ public final class TabInfo: NSObject {
}
}
+ @objc public var layoutDirection: NSString? {
+ didSet {
+ props.layoutDirection = layoutDirection as? String
+ }
+ }
@objc public var scrollEdgeAppearance: NSString? {
didSet {
props.scrollEdgeAppearance = scrollEdgeAppearance as? String
diff --git a/packages/react-native-bottom-tabs/src/TabView.tsx b/packages/react-native-bottom-tabs/src/TabView.tsx
index 41b9cb37..4e93f090 100644
--- a/packages/react-native-bottom-tabs/src/TabView.tsx
+++ b/packages/react-native-bottom-tabs/src/TabView.tsx
@@ -22,7 +22,13 @@ import { BottomTabBarHeightContext } from './utils/BottomTabBarHeightContext';
import type { ImageSource } from 'react-native/Libraries/Image/ImageSource';
import NativeTabView from './TabViewNativeComponent';
import useLatestCallback from 'use-latest-callback';
-import type { AppleIcon, BaseRoute, NavigationState, TabRole } from './types';
+import type {
+ AppleIcon,
+ BaseRoute,
+ LayoutDirection,
+ NavigationState,
+ TabRole,
+} from './types';
import DelayedFreeze from './DelayedFreeze';
import {
BottomAccessoryView,
@@ -201,6 +207,11 @@ interface Props {
* @platform ios
*/
renderBottomAccessoryView?: BottomAccessoryViewProps['renderBottomAccessoryView'];
+ /**
+ * The direction of the layout.
+ * @default 'locale'
+ */
+ layoutDirection?: LayoutDirection;
}
const ANDROID_MAX_TABS = 100;
@@ -239,6 +250,7 @@ const TabView = ({
tabBarStyle,
tabLabelStyle,
renderBottomAccessoryView,
+ layoutDirection = 'locale',
...props
}: Props) => {
// @ts-ignore
@@ -398,6 +410,7 @@ const TabView = ({
onTabBarMeasured={handleTabBarMeasured}
onNativeLayout={handleNativeLayout}
hapticFeedbackEnabled={hapticFeedbackEnabled}
+ layoutDirection={layoutDirection}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
barTintColor={tabBarStyle?.backgroundColor}
diff --git a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
index 7949e156..50082c55 100644
--- a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
+++ b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
@@ -56,6 +56,7 @@ export interface TabViewProps extends ViewProps {
disablePageAnimations?: boolean;
activeIndicatorColor?: ColorValue;
hapticFeedbackEnabled?: boolean;
+ layoutDirection?: string;
minimizeBehavior?: string;
fontFamily?: string;
fontWeight?: string;
diff --git a/packages/react-native-bottom-tabs/src/types.ts b/packages/react-native-bottom-tabs/src/types.ts
index 612dcd4f..83a9b2bb 100644
--- a/packages/react-native-bottom-tabs/src/types.ts
+++ b/packages/react-native-bottom-tabs/src/types.ts
@@ -7,6 +7,8 @@ export type AppleIcon = { sfSymbol: SFSymbol };
export type TabRole = 'search';
+export type LayoutDirection = 'ltr' | 'rtl' | 'locale';
+
export type BaseRoute = {
key: string;
title?: string;
diff --git a/packages/react-navigation/src/navigators/createNativeBottomTabNavigator.tsx b/packages/react-navigation/src/navigators/createNativeBottomTabNavigator.tsx
index b76bdeb0..98cdd6e0 100644
--- a/packages/react-navigation/src/navigators/createNativeBottomTabNavigator.tsx
+++ b/packages/react-navigation/src/navigators/createNativeBottomTabNavigator.tsx
@@ -43,6 +43,7 @@ function NativeBottomTabNavigator({
screenOptions,
tabBarActiveTintColor: customActiveTintColor,
tabBarInactiveTintColor: customInactiveTintColor,
+ layoutDirection = 'locale',
...rest
}: NativeBottomTabNavigatorProps) {
const { colors } = useTheme();
@@ -77,6 +78,7 @@ function NativeBottomTabNavigator({
);
}