Skip to content

Commit 3572fc2

Browse files
ahmedawaad1804Ahmed AwaadCopilotokwasniewski
authored
feat: Add layoutDirection prop to TabView for RTL support (#494)
* feat: Add `layoutDirection` prop to `TabView` for iOS RTL support, including a new example. * Update packages/react-native-bottom-tabs/ios/TabViewProvider.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor: update layoutDirection prop to use 'locale' for consistency across components * feat(android): add support for layout direction in ReactBottomNavigationView * fix(iOS): enhance layout direction handling in NewTabView for better RTL support * Update packages/react-native-bottom-tabs/ios/TabViewProps.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/react-native-bottom-tabs/ios/TabViewProvider.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: update layoutDirection prop in FourTabsRTL to use 'rtl' * fix: add to legacy tabview --------- Co-authored-by: Ahmed Awaad <ashahin@bankaljazira.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Oskar Kwaśniewski <oskarkwasniewski@icloud.com>
1 parent 9d593f2 commit 3572fc2

File tree

17 files changed

+168
-3
lines changed

17 files changed

+168
-3
lines changed

apps/example/src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
2323
import JSBottomTabs from './Examples/JSBottomTabs';
2424
import ThreeTabs from './Examples/ThreeTabs';
2525
import FourTabs from './Examples/FourTabs';
26+
import FourTabsRTL from './Examples/FourTabsRTL';
2627
import MaterialBottomTabs from './Examples/MaterialBottomTabs';
2728
import SFSymbols from './Examples/SFSymbols';
2829
import LabeledTabs from './Examples/Labeled';
@@ -72,6 +73,9 @@ const FourTabsActiveIndicatorColor = () => {
7273
const UnlabeledTabs = () => {
7374
return <LabeledTabs showLabels={false} />;
7475
};
76+
const FourTabsRightToLeft = () => {
77+
return <FourTabsRTL layoutDirection={'rtl'} />;
78+
};
7579

7680
const examples = [
7781
{
@@ -161,6 +165,7 @@ const examples = [
161165
name: 'Bottom Accessory View',
162166
screenOptions: { headerShown: false },
163167
},
168+
{ component: FourTabsRightToLeft, name: 'Four Tabs - RTL' },
164169
];
165170

166171
function App() {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import TabView, { SceneMap } from 'react-native-bottom-tabs';
2+
import React from 'react';
3+
import { Article } from '../Screens/Article';
4+
import { Albums } from '../Screens/Albums';
5+
import { Contacts } from '../Screens/Contacts';
6+
import { Chat } from '../Screens/Chat';
7+
import { I18nManager, type ColorValue } from 'react-native';
8+
import type { LayoutDirection } from 'react-native-bottom-tabs';
9+
10+
interface Props {
11+
disablePageAnimations?: boolean;
12+
scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent';
13+
backgroundColor?: ColorValue;
14+
translucent?: boolean;
15+
hideOneTab?: boolean;
16+
rippleColor?: ColorValue;
17+
activeIndicatorColor?: ColorValue;
18+
layoutDirection?: LayoutDirection;
19+
}
20+
21+
const renderScene = SceneMap({
22+
article: Article,
23+
albums: Albums,
24+
contacts: Contacts,
25+
chat: Chat,
26+
});
27+
28+
export default function FourTabsRTL({
29+
disablePageAnimations = false,
30+
scrollEdgeAppearance = 'default',
31+
backgroundColor,
32+
translucent = true,
33+
hideOneTab = false,
34+
rippleColor,
35+
activeIndicatorColor,
36+
layoutDirection = 'locale',
37+
}: Props) {
38+
React.useLayoutEffect(() => {
39+
if (layoutDirection === 'rtl') {
40+
I18nManager.allowRTL(true);
41+
I18nManager.forceRTL(true);
42+
}
43+
return () => {
44+
if (layoutDirection === 'rtl') {
45+
I18nManager.allowRTL(false);
46+
I18nManager.forceRTL(false);
47+
}
48+
};
49+
}, [layoutDirection]);
50+
const [index, setIndex] = React.useState(0);
51+
const [routes] = React.useState([
52+
{
53+
key: 'article',
54+
title: 'المقالات',
55+
focusedIcon: require('../../assets/icons/article_dark.png'),
56+
unfocusedIcon: require('../../assets/icons/chat_dark.png'),
57+
badge: '!',
58+
},
59+
{
60+
key: 'albums',
61+
title: 'البومات',
62+
focusedIcon: require('../../assets/icons/grid_dark.png'),
63+
badge: '5',
64+
hidden: hideOneTab,
65+
},
66+
{
67+
key: 'contacts',
68+
focusedIcon: require('../../assets/icons/person_dark.png'),
69+
title: 'المتراسلين',
70+
badge: ' ',
71+
},
72+
{
73+
key: 'chat',
74+
focusedIcon: require('../../assets/icons/chat_dark.png'),
75+
title: 'المحادثات',
76+
role: 'search',
77+
},
78+
]);
79+
80+
return (
81+
<TabView
82+
sidebarAdaptable
83+
disablePageAnimations={disablePageAnimations}
84+
scrollEdgeAppearance={scrollEdgeAppearance}
85+
navigationState={{ index, routes }}
86+
onIndexChange={setIndex}
87+
renderScene={renderScene}
88+
tabBarStyle={{ backgroundColor }}
89+
translucent={translucent}
90+
rippleColor={rippleColor}
91+
activeIndicatorColor={activeIndicatorColor}
92+
layoutDirection={layoutDirection}
93+
/>
94+
);
95+
}

apps/example/src/Examples/MaterialBottomTabs.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as React from 'react';
21
import { createMaterialBottomTabNavigator } from 'react-native-paper/react-navigation';
32
import { Article } from '../Screens/Article';
43
import { Albums } from '../Screens/Albums';

apps/example/src/Examples/NativeBottomTabs.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function NativeBottomTabs() {
1515
initialRouteName="Chat"
1616
labeled={true}
1717
hapticFeedbackEnabled={false}
18+
layoutDirection="ltr"
1819
tabBarInactiveTintColor="#C57B57"
1920
tabBarActiveTintColor="#F7DBA7"
2021
tabBarStyle={{

packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) {
127127
layout(left, top, right, bottom)
128128
}
129129

130+
fun applyDirection(dir: Int) {
131+
bottomNavigation.layoutDirection = dir
132+
}
133+
130134
override fun requestLayout() {
131135
super.requestLayout()
132136
@Suppress("SENSELESS_COMPARISON") // layoutCallback can be null here since this method can be called in init

packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewManager.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ class RCTTabViewManager(context: ReactApplicationContext) :
167167
view.isHapticFeedbackEnabled = value
168168
}
169169

170+
override fun setLayoutDirection(view: ReactBottomNavigationView, value: String?) {
171+
val direction = when (value) {
172+
"rtl" -> View.LAYOUT_DIRECTION_RTL
173+
"ltr" -> View.LAYOUT_DIRECTION_LTR
174+
else -> View.LAYOUT_DIRECTION_LOCALE
175+
}
176+
view.applyDirection(direction)
177+
}
178+
170179
override fun setFontFamily(view: ReactBottomNavigationView?, value: String?) {
171180
view?.setFontFamily(value)
172181
}

packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
160160
_tabViewProvider.hapticFeedbackEnabled = newViewProps.hapticFeedbackEnabled;
161161
}
162162

163+
if (oldViewProps.layoutDirection != newViewProps.layoutDirection) {
164+
_tabViewProvider.layoutDirection = RCTNSStringFromStringNilIfEmpty(newViewProps.layoutDirection);
165+
}
166+
163167
if (oldViewProps.fontSize != newViewProps.fontSize) {
164168
_tabViewProvider.fontSize = [NSNumber numberWithInt:newViewProps.fontSize];
165169
}

packages/react-native-bottom-tabs/ios/TabView/LegacyTabView.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ struct LegacyTabView: AnyTabView {
77
var onSelect: (String) -> Void
88
var updateTabBarAppearance: () -> Void
99

10+
private var effectiveLayoutDirection: LayoutDirection {
11+
let dir = props.layoutDirection ?? "locale"
12+
if let mapped = ["rtl": LayoutDirection.rightToLeft,
13+
"ltr": LayoutDirection.leftToRight][dir] {
14+
return mapped
15+
}
16+
let system = UIView.userInterfaceLayoutDirection(for: .unspecified)
17+
return system == .rightToLeft ? .rightToLeft : .leftToRight
18+
}
19+
1020
@ViewBuilder
1121
var body: some View {
1222
TabView(selection: $props.selectedPage) {
@@ -19,6 +29,7 @@ struct LegacyTabView: AnyTabView {
1929
onLayout(size)
2030
}
2131
}
32+
.environment(\.layoutDirection, effectiveLayoutDirection)
2233
.hideTabBar(props.tabBarHidden)
2334
}
2435

packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ struct NewTabView: AnyTabView {
99
var onSelect: (String) -> Void
1010
var updateTabBarAppearance: () -> Void
1111

12+
private var effectiveLayoutDirection: LayoutDirection {
13+
let dir = props.layoutDirection ?? "locale"
14+
if let mapped = ["rtl": LayoutDirection.rightToLeft,
15+
"ltr": LayoutDirection.leftToRight][dir] {
16+
return mapped
17+
}
18+
let system = UIView.userInterfaceLayoutDirection(for: .unspecified)
19+
return system == .rightToLeft ? .rightToLeft : .leftToRight
20+
}
21+
1222
@ViewBuilder
1323
var body: some View {
1424
TabView(selection: $props.selectedPage) {
@@ -49,6 +59,7 @@ struct NewTabView: AnyTabView {
4959
}
5060
}
5161
}
62+
.environment(\.layoutDirection, effectiveLayoutDirection)
5263
.measureView { size in
5364
onLayout(size)
5465
}

packages/react-native-bottom-tabs/ios/TabViewProps.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class TabViewProps: ObservableObject {
6666
@Published var translucent: Bool = true
6767
@Published var disablePageAnimations: Bool = false
6868
@Published var hapticFeedbackEnabled: Bool = false
69+
@Published var layoutDirection: String?
6970
@Published var fontSize: Int?
7071
@Published var fontFamily: String?
7172
@Published var fontWeight: String?

0 commit comments

Comments
 (0)