Skip to content

Commit 2deed20

Browse files
giolaqclaude
andcommitted
feat(rtl): add RTL (Right-to-Left) layout support
Add RTL-aware navigation and styling throughout the app: - Create RTL utility (utils/rtl.ts) with direction helpers - Convert spatial navigation direction checks to use RTL-aware helpers instead of hardcoded 'left'/'right' - Replace directional styles (marginLeft/Right, paddingLeft, left/right positioning) with logical equivalents (start/end, marginStart/End, paddingStart) - Set drawer position based on I18nManager.isRTL - Handle SeekBar thumb positioning for RTL Closes #41 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8eab9b3 commit 2deed20

13 files changed

Lines changed: 57 additions & 34 deletions

File tree

packages/shared-ui/src/components/CustomDrawerContent.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useMenuContext } from '../components/MenuContext';
77
import { safeZones, colors } from '../theme';
88
import { useCallback } from 'react';
99
import { Direction } from '@bam.tech/lrud';
10+
import { getCloseDrawerDirection } from '../utils/rtl';
1011

1112
export default function CustomDrawerContent(props: DrawerContentComponentProps) {
1213
const navigation = props.navigation;
@@ -20,7 +21,7 @@ export default function CustomDrawerContent(props: DrawerContentComponentProps)
2021

2122
const onDirectionHandledWithoutMovement = useCallback(
2223
(movement: Direction) => {
23-
if (movement === 'right') {
24+
if (movement === getCloseDrawerDirection()) {
2425
navigation.closeDrawer();
2526
toggleMenu(false);
2627
}
@@ -185,7 +186,7 @@ const drawerStyles = StyleSheet.create({
185186
icon: {
186187
width: scaledPixels(32),
187188
height: scaledPixels(32),
188-
marginRight: scaledPixels(20),
189+
marginEnd: scaledPixels(20),
189190
},
190191
menuText: {
191192
color: colors.text,

packages/shared-ui/src/components/player/Controls.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ const controlsStyles = StyleSheet.create({
3838
bottomControls: {
3939
position: "absolute",
4040
bottom: scaledPixels(safeZones.actionSafe.vertical),
41-
left: scaledPixels(safeZones.actionSafe.horizontal),
42-
right: scaledPixels(safeZones.actionSafe.horizontal),
41+
start: scaledPixels(safeZones.actionSafe.horizontal),
42+
end: scaledPixels(safeZones.actionSafe.horizontal),
4343
flexDirection: "row",
4444
alignItems: "center",
4545
},
4646
controlButton: {
47-
marginRight: scaledPixels(20),
47+
marginEnd: scaledPixels(20),
4848
},
4949
});
5050

packages/shared-ui/src/components/player/ExitButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const exitButtonStyles = StyleSheet.create({
2424
exitBtn: {
2525
position: "absolute",
2626
top: scaledPixels(safeZones.actionSafe.vertical),
27-
left: scaledPixels(safeZones.actionSafe.horizontal),
27+
start: scaledPixels(safeZones.actionSafe.horizontal),
2828
},
2929
});
3030

packages/shared-ui/src/components/player/SeekBar.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react";
2-
import { View, StyleSheet } from "react-native";
2+
import { View, StyleSheet, I18nManager } from "react-native";
33
import { scaledPixels } from "../../hooks/useScale";
44

55
interface SeekBarProps {
@@ -13,10 +13,14 @@ const SeekBar = React.memo(({ currentTime, duration }: SeekBarProps) => {
1313
return (currentTime / duration) * 100;
1414
}, [currentTime, duration]);
1515

16+
const thumbPosition = I18nManager.isRTL
17+
? { right: `${percentage}%` }
18+
: { left: `${percentage}%` };
19+
1620
return (
1721
<View style={seekBarStyles.seekbarContainer}>
1822
<View style={seekBarStyles.seekbarTrack} />
19-
<View style={[seekBarStyles.seekbarThumb, { left: `${percentage}%` }]} />
23+
<View style={[seekBarStyles.seekbarThumb, thumbPosition]} />
2024
</View>
2125
);
2226
});
@@ -26,7 +30,7 @@ const seekBarStyles = StyleSheet.create({
2630
flex: 1,
2731
height: scaledPixels(40),
2832
justifyContent: "center",
29-
marginRight: scaledPixels(80),
33+
marginEnd: scaledPixels(80),
3034
},
3135
seekbarTrack: {
3236
width: "100%",

packages/shared-ui/src/components/player/VideoOverlay.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,18 @@ const overlayStyles = StyleSheet.create({
8484
exitButton: {
8585
position: "absolute",
8686
top: scaledPixels(safeZones.actionSafe.vertical),
87-
left: scaledPixels(safeZones.actionSafe.horizontal),
87+
start: scaledPixels(safeZones.actionSafe.horizontal),
8888
},
8989
bottomControls: {
9090
position: "absolute",
9191
bottom: scaledPixels(safeZones.actionSafe.vertical),
92-
left: scaledPixels(safeZones.actionSafe.horizontal),
93-
right: scaledPixels(safeZones.actionSafe.horizontal),
92+
start: scaledPixels(safeZones.actionSafe.horizontal),
93+
end: scaledPixels(safeZones.actionSafe.horizontal),
9494
flexDirection: "row",
9595
alignItems: "center",
9696
},
9797
controlButton: {
98-
marginRight: scaledPixels(20),
98+
marginEnd: scaledPixels(20),
9999
},
100100
});
101101

packages/shared-ui/src/components/player/VideoOverlay.vega.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,18 @@ const overlayStyles = StyleSheet.create({
9898
exitButton: {
9999
position: "absolute",
100100
top: scaledPixels(safeZones.actionSafe.vertical),
101-
left: scaledPixels(safeZones.actionSafe.horizontal),
101+
start: scaledPixels(safeZones.actionSafe.horizontal),
102102
},
103103
bottomControls: {
104104
position: "absolute",
105105
bottom: scaledPixels(safeZones.actionSafe.vertical),
106-
left: scaledPixels(safeZones.actionSafe.horizontal),
107-
right: scaledPixels(safeZones.actionSafe.horizontal),
106+
start: scaledPixels(safeZones.actionSafe.horizontal),
107+
end: scaledPixels(safeZones.actionSafe.horizontal),
108108
flexDirection: "row",
109109
alignItems: "center",
110110
},
111111
controlButton: {
112-
marginRight: scaledPixels(20),
112+
marginEnd: scaledPixels(20),
113113
},
114114
});
115115

packages/shared-ui/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export { default as SettingsScreen } from './screens/SettingsScreen';
2121

2222
// Utils
2323
export { VideoHandler } from './utils/VideoHandler';
24+
export { isRTL, getOpenDrawerDirection, getCloseDrawerDirection } from './utils/rtl';
2425

2526
// Navigation
2627
export { default as RootNavigator } from './navigation/RootNavigator';

packages/shared-ui/src/navigation/DrawerNavigator.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useRef } from 'react';
2-
import { StyleSheet, View, Platform } from 'react-native';
2+
import { StyleSheet, View, Platform, I18nManager } from 'react-native';
33
import { GestureHandlerRootView } from 'react-native-gesture-handler';
44
import { createDrawerNavigator } from '@react-navigation/drawer';
55
import { useNavigation, DrawerActions, NavigationProp } from '@react-navigation/native';
@@ -52,10 +52,9 @@ export default function DrawerNavigator() {
5252
drawerInactiveTintColor: '#bdc3c7',
5353
drawerStyle: styles.drawerStyle,
5454
drawerLabelStyle: styles.drawerLabelStyle,
55-
// Use 'front' type to allow drawer to open/close (collapse/expand)
56-
// Disable swipe gestures since we use remote control navigation
5755
drawerType: 'front',
5856
swipeEnabled: false,
57+
drawerPosition: I18nManager.isRTL ? 'right' : 'left',
5958
}}
6059
>
6160
<Drawer.Screen
@@ -115,6 +114,6 @@ const drawerStyles = StyleSheet.create({
115114
drawerLabelStyle: {
116115
fontSize: scaledPixels(18),
117116
fontWeight: 'bold',
118-
marginLeft: scaledPixels(10),
117+
marginStart: scaledPixels(10),
119118
},
120119
});

packages/shared-ui/src/screens/ExploreScreen.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { DrawerActions, useIsFocused } from '@react-navigation/native';
77
import { Direction } from '@bam.tech/lrud';
88
import { useCallback, useState } from 'react';
99
import { safeZones } from '../theme';
10+
import { getOpenDrawerDirection } from '../utils/rtl';
1011

1112
export default function ExploreScreen() {
1213
const styles = exploreStyles;
@@ -18,7 +19,7 @@ export default function ExploreScreen() {
1819

1920
const onDirectionHandledWithoutMovement = useCallback(
2021
(movement: Direction) => {
21-
if (movement === 'left' && focusedIndex === 0) {
22+
if (movement === getOpenDrawerDirection() && focusedIndex === 0) {
2223
navigation.dispatch(DrawerActions.openDrawer());
2324
toggleMenu(true);
2425
}

packages/shared-ui/src/screens/HomeScreen.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { RootStackParamList } from '../navigation/types';
1919
import { fetchMoviesData, CardData } from '../data/moviesData';
2020
import { colors, safeZones } from '../theme';
2121
import PlatformLinearGradient from '../components/PlatformLinearGradient';
22+
import { getOpenDrawerDirection } from '../utils/rtl';
2223

2324
type HomeScreenNavigationProp = NativeStackNavigationProp<RootStackParamList, 'DrawerNavigator'>;
2425

@@ -120,7 +121,7 @@ export default function HomeScreen() {
120121

121122
const onDirectionHandledWithoutMovement = useCallback(
122123
(movement: Direction) => {
123-
if (movement === 'left' && focusedIndex === 0) {
124+
if (movement === getOpenDrawerDirection() && focusedIndex === 0) {
124125
navigation.dispatch(DrawerActions.openDrawer());
125126
toggleMenu(true);
126127
}
@@ -241,13 +242,13 @@ const gridStyles = StyleSheet.create({
241242
thumbnailTextContainer: {
242243
position: 'absolute',
243244
bottom: 0,
244-
left: 0,
245-
right: 0,
245+
start: 0,
246+
end: 0,
246247
backgroundColor: colors.scrimDark,
247248
paddingHorizontal: scaledPixels(16),
248249
paddingVertical: scaledPixels(12),
249-
borderBottomLeftRadius: scaledPixels(8),
250-
borderBottomRightRadius: scaledPixels(8),
250+
borderBottomStartRadius: scaledPixels(8),
251+
borderBottomEndRadius: scaledPixels(8),
251252
},
252253
thumbnailText: {
253254
color: colors.text,
@@ -259,7 +260,7 @@ const gridStyles = StyleSheet.create({
259260
highlightThumbnail: {
260261
width: scaledPixels(420),
261262
height: scaledPixels(260),
262-
marginRight: scaledPixels(20),
263+
marginEnd: scaledPixels(20),
263264
backgroundColor: colors.card,
264265
borderRadius: scaledPixels(12),
265266
borderWidth: scaledPixels(5),
@@ -302,21 +303,21 @@ const gridStyles = StyleSheet.create({
302303
},
303304
gradientLeft: {
304305
position: 'absolute',
305-
left: 0,
306+
start: 0,
306307
top: 0,
307308
bottom: 0,
308309
width: '65%',
309310
},
310311
headerTextContainer: {
311312
position: 'absolute',
312-
left: scaledPixels(safeZones.titleSafe.horizontal),
313+
start: scaledPixels(safeZones.titleSafe.horizontal),
313314
top: scaledPixels(safeZones.titleSafe.vertical),
314315
bottom: scaledPixels(safeZones.titleSafe.vertical),
315316
justifyContent: 'center',
316317
width: '55%',
317318
},
318319
highlightsList: {
319-
paddingLeft: scaledPixels(20),
320+
paddingStart: scaledPixels(20),
320321
},
321322
cardImage: {
322323
width: '100%',

0 commit comments

Comments
 (0)