Skip to content

Commit 5433e8f

Browse files
author
Ahmed Awaad
committed
feat: Add layoutDirection prop to TabView for iOS RTL support, including a new example.
1 parent a66a108 commit 5433e8f

File tree

9 files changed

+129
-0
lines changed

9 files changed

+129
-0
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={'rightToLeft'} />;
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', platform: 'ios' },
164169
];
165170

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

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="leftToRight"
1819
tabBarInactiveTintColor="#C57B57"
1920
tabBarActiveTintColor="#F7DBA7"
2021
tabBarStyle={{

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/NewTabView.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ struct NewTabView: AnyTabView {
1111

1212
@ViewBuilder
1313
var body: some View {
14+
var effectiveLayoutDirection: LayoutDirection {
15+
if let layoutDirectionString = props.layoutDirection {
16+
return layoutDirectionString == "rightToLeft" ? .rightToLeft : .leftToRight
17+
}
18+
return .leftToRight
19+
}
1420
TabView(selection: $props.selectedPage) {
1521
ForEach(props.children) { child in
1622
if let index = props.children.firstIndex(of: child),
@@ -49,6 +55,7 @@ struct NewTabView: AnyTabView {
4955
}
5056
}
5157
}
58+
.environment(\.layoutDirection, effectiveLayoutDirection)
5259
.measureView { size in
5360
onLayout(size)
5461
}

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?

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ public final class TabInfo: NSObject {
9595
}
9696
}
9797

98+
@objc public var layoutDirection: NSString? {
99+
didSet {
100+
props.layoutDirection = layoutDirection as? String
101+
}
102+
}
103+
98104
@objc public var scrollEdgeAppearance: NSString? {
99105
didSet {
100106
props.scrollEdgeAppearance = scrollEdgeAppearance as? String
@@ -155,6 +161,8 @@ public final class TabInfo: NSObject {
155161
}
156162
}
157163

164+
165+
158166
@objc public var itemsData: [TabInfo] = [] {
159167
didSet {
160168
props.items = itemsData

packages/react-native-bottom-tabs/src/TabView.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ interface Props<Route extends BaseRoute> {
201201
* @platform ios
202202
*/
203203
renderBottomAccessoryView?: BottomAccessoryViewProps['renderBottomAccessoryView'];
204+
/**
205+
* The direction of the layout. (iOS only)
206+
* @platform ios
207+
* @default 'leftToRight'
208+
*/
209+
layoutDirection?: 'leftToRight' | 'rightToLeft';
204210
}
205211

206212
const ANDROID_MAX_TABS = 100;
@@ -239,6 +245,7 @@ const TabView = <Route extends BaseRoute>({
239245
tabBarStyle,
240246
tabLabelStyle,
241247
renderBottomAccessoryView,
248+
layoutDirection = 'leftToRight',
242249
...props
243250
}: Props<Route>) => {
244251
// @ts-ignore
@@ -398,6 +405,7 @@ const TabView = <Route extends BaseRoute>({
398405
onTabBarMeasured={handleTabBarMeasured}
399406
onNativeLayout={handleNativeLayout}
400407
hapticFeedbackEnabled={hapticFeedbackEnabled}
408+
layoutDirection={layoutDirection}
401409
activeTintColor={activeTintColor}
402410
inactiveTintColor={inactiveTintColor}
403411
barTintColor={tabBarStyle?.backgroundColor}

packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface TabViewProps extends ViewProps {
5656
disablePageAnimations?: boolean;
5757
activeIndicatorColor?: ColorValue;
5858
hapticFeedbackEnabled?: boolean;
59+
layoutDirection?: string;
5960
minimizeBehavior?: string;
6061
fontFamily?: string;
6162
fontWeight?: string;

0 commit comments

Comments
 (0)