Skip to content
5 changes: 5 additions & 0 deletions apps/example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -72,6 +73,9 @@ const FourTabsActiveIndicatorColor = () => {
const UnlabeledTabs = () => {
return <LabeledTabs showLabels={false} />;
};
const FourTabsRightToLeft = () => {
return <FourTabsRTL layoutDirection={'rightToLeft'} />;
};

const examples = [
{
Expand Down Expand Up @@ -161,6 +165,7 @@ const examples = [
name: 'Bottom Accessory View',
screenOptions: { headerShown: false },
},
{ component: FourTabsRightToLeft, name: 'Four Tabs - RTL', platform: 'ios' },
];

function App() {
Expand Down
94 changes: 94 additions & 0 deletions apps/example/src/Examples/FourTabsRTL.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import TabView, { SceneMap } from 'react-native-bottom-tabs';
import React, { useState } 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';

interface Props {
disablePageAnimations?: boolean;
scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent';
backgroundColor?: ColorValue;
translucent?: boolean;
hideOneTab?: boolean;
rippleColor?: ColorValue;
activeIndicatorColor?: ColorValue;
layoutDirection?: 'leftToRight' | 'rightToLeft';
}

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 = 'leftToRight',
}: Props) {
React.useLayoutEffect(() => {
if (layoutDirection === 'rightToLeft') {
I18nManager.allowRTL(true);
I18nManager.forceRTL(true);
}
return () => {
if (layoutDirection === 'rightToLeft') {
I18nManager.allowRTL(false);
I18nManager.forceRTL(false);
}
};
}, [layoutDirection]);
const [index, setIndex] = useState(0);
const [routes] = 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 (
<TabView
sidebarAdaptable
disablePageAnimations={disablePageAnimations}
scrollEdgeAppearance={scrollEdgeAppearance}
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
tabBarStyle={{ backgroundColor }}
translucent={translucent}
rippleColor={rippleColor}
activeIndicatorColor={activeIndicatorColor}
layoutDirection={layoutDirection}
/>
);
}
1 change: 1 addition & 0 deletions apps/example/src/Examples/NativeBottomTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function NativeBottomTabs() {
initialRouteName="Chat"
labeled={true}
hapticFeedbackEnabled={false}
layoutDirection="leftToRight"
tabBarInactiveTintColor="#C57B57"
tabBarActiveTintColor="#F7DBA7"
tabBarStyle={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ struct NewTabView: AnyTabView {

@ViewBuilder
var body: some View {
var effectiveLayoutDirection: LayoutDirection {
if let layoutDirectionString = props.layoutDirection {
return layoutDirectionString == "rightToLeft" ? .rightToLeft : .leftToRight
}
return .leftToRight
}
TabView(selection: $props.selectedPage) {
ForEach(props.children) { child in
if let index = props.children.firstIndex(of: child),
Expand Down Expand Up @@ -49,6 +55,7 @@ struct NewTabView: AnyTabView {
}
}
}
.environment(\.layoutDirection, effectiveLayoutDirection)
.measureView { size in
onLayout(size)
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-bottom-tabs/ios/TabViewProps.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native-bottom-tabs/ios/TabViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
}

@objc public protocol TabViewProviderDelegate {
func onPageSelected(key: String, reactTag: NSNumber?)

Check warning on line 42 in packages/react-native-bottom-tabs/ios/TabViewProvider.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)
func onLongPress(key: String, reactTag: NSNumber?)

Check warning on line 43 in packages/react-native-bottom-tabs/ios/TabViewProvider.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)
func onTabBarMeasured(height: Int, reactTag: NSNumber?)

Check warning on line 44 in packages/react-native-bottom-tabs/ios/TabViewProvider.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)
func onLayout(size: CGSize, reactTag: NSNumber?)
}

Expand Down Expand Up @@ -95,6 +95,12 @@
}
}

@objc public var layoutDirection: NSString? {
didSet {
props.layoutDirection = layoutDirection as? String
}
}

@objc public var scrollEdgeAppearance: NSString? {
didSet {
props.scrollEdgeAppearance = scrollEdgeAppearance as? String
Expand Down Expand Up @@ -155,6 +161,8 @@
}
}



@objc public var itemsData: [TabInfo] = [] {
didSet {
props.items = itemsData
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native-bottom-tabs/src/TabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@
* @platform ios
*/
renderBottomAccessoryView?: BottomAccessoryViewProps['renderBottomAccessoryView'];
/**
* The direction of the layout. (iOS only)
* @platform ios
* @default 'leftToRight'
*/
layoutDirection?: 'leftToRight' | 'rightToLeft';
}

const ANDROID_MAX_TABS = 100;
Expand Down Expand Up @@ -239,6 +245,7 @@
tabBarStyle,
tabLabelStyle,
renderBottomAccessoryView,
layoutDirection = 'leftToRight',
...props
}: Props<Route>) => {
// @ts-ignore
Expand Down Expand Up @@ -269,7 +276,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 279 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 275 column 10
}

const icons = React.useMemo(
Expand Down Expand Up @@ -398,6 +405,7 @@
onTabBarMeasured={handleTabBarMeasured}
onNativeLayout={handleNativeLayout}
hapticFeedbackEnabled={hapticFeedbackEnabled}
layoutDirection={layoutDirection}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
barTintColor={tabBarStyle?.backgroundColor}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';

Check warning on line 1 in packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts

View workflow job for this annotation

GitHub Actions / lint

'react-native/Libraries/Utilities/codegenNativeComponent' React Native deep imports are deprecated. Please use the top level import instead
import type { ColorValue, ProcessedColorValue, ViewProps } from 'react-native';
import type {
DirectEventHandler,
Expand All @@ -7,7 +7,7 @@
WithDefault,
} from 'react-native/Libraries/Types/CodegenTypes';
//@ts-ignore
import type { ImageSource } from 'react-native/Libraries/Image/ImageSource';

Check warning on line 10 in packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts

View workflow job for this annotation

GitHub Actions / lint

'react-native/Libraries/Image/ImageSource' React Native deep imports are deprecated. Please use the top level import instead

export type OnPageSelectedEventData = Readonly<{
key: string;
Expand Down Expand Up @@ -56,6 +56,7 @@
disablePageAnimations?: boolean;
activeIndicatorColor?: ColorValue;
hapticFeedbackEnabled?: boolean;
layoutDirection?: string;
minimizeBehavior?: string;
fontFamily?: string;
fontWeight?: string;
Expand Down
Loading