diff --git a/docs/docs/guides/05-react-native-web.md b/docs/docs/guides/05-react-native-web.md
index 14b2f14a7a..154b479a11 100644
--- a/docs/docs/guides/05-react-native-web.md
+++ b/docs/docs/guides/05-react-native-web.md
@@ -1,8 +1,8 @@
---
-title: Using on the Web
+title: Usage on the Web
---
-# Using on the Web
+# Usage on the Web
## Pre-requisites
@@ -270,6 +270,19 @@ You can also load these fonts using [`css-loader`](https://github.com/webpack-co
The default theme in React Native Paper uses the Roboto font. You can add them to your project following [the instructions on its Google Fonts page](https://fonts.google.com/specimen/Roboto?selection.family=Roboto:100,300,400,500).
+## RTL support for Web
+Since `react-native-web` does not support `I18nManager`, in order to support RTL layouts you have to define text direction manually:
+
+```jsx
+export default function Main() {
+ return (
+
+
+
+ );
+}
+```
+
## We're done!
You can run `webpack-dev-server` to run the webpack server and open your project in the browser. You can add the following script in your `package.json` under the `"scripts"` section to make it easier:
diff --git a/docs/src/components/GetStartedButtons.tsx b/docs/src/components/GetStartedButtons.tsx
index 4debe5b5ac..964dc3166a 100644
--- a/docs/src/components/GetStartedButtons.tsx
+++ b/docs/src/components/GetStartedButtons.tsx
@@ -21,7 +21,7 @@ const styles = StyleSheet.create({
paddingBottom: 16,
},
button: {
- marginRight: 16,
+ marginEnd: 16,
},
});
@@ -70,7 +70,7 @@ const Shimmer = () => {
(0);
- const preferences = React.useContext(PreferencesContext);
+ const preferences = usePreferences();
const _setDrawerItem = (index: number) => setDrawerItemIndex(index);
const { isV3, colors } = useExampleTheme();
const isIOS = Platform.OS === 'ios';
- if (!preferences) throw new Error('PreferencesContext not provided');
-
const {
toggleShouldUseDeviceColors,
toggleTheme,
- toggleRtl: toggleRTL,
toggleThemeVersion,
toggleCollapsed,
toggleCustomFont,
toggleRippleEffect,
+ toggleRTL,
customFontLoaded,
rippleEffectEnabled,
collapsed,
- rtl: isRTL,
+ isRTL,
theme: { dark: isDarkTheme },
shouldUseDeviceColors,
} = preferences;
- const _handleToggleRTL = () => {
- toggleRTL();
- I18nManager.forceRTL(!isRTL);
- if (isWeb) {
- Updates.reloadAsync();
- }
- };
-
const coloredLabelTheme = {
colors: isV3
? {
@@ -190,16 +180,14 @@ function DrawerItems() {
- {!isWeb && (
-
-
- RTL
-
-
-
+
+
+ RTL
+
+
-
- )}
+
+
diff --git a/example/src/ExampleList.tsx b/example/src/ExampleList.tsx
index 3645b741e4..4bce0dce78 100644
--- a/example/src/ExampleList.tsx
+++ b/example/src/ExampleList.tsx
@@ -157,8 +157,8 @@ export default function ExampleList({ navigation }: Props) {
contentContainerStyle={{
backgroundColor: colors.background,
paddingBottom: safeArea.bottom,
- paddingLeft: safeArea.left,
- paddingRight: safeArea.right,
+ paddingStart: safeArea.left,
+ paddingEnd: safeArea.right,
}}
style={{
backgroundColor: colors.background,
diff --git a/example/src/Examples/AnimatedFABExample/AnimatedFABExample.tsx b/example/src/Examples/AnimatedFABExample/AnimatedFABExample.tsx
index b2f475be0e..b05d68800b 100644
--- a/example/src/Examples/AnimatedFABExample/AnimatedFABExample.tsx
+++ b/example/src/Examples/AnimatedFABExample/AnimatedFABExample.tsx
@@ -11,6 +11,7 @@ import {
} from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
+import { useLocale } from '../../../../src/core/Localization';
import CustomFAB from './CustomFAB';
import CustomFABControls, {
Controls,
@@ -33,6 +34,7 @@ type Item = {
const AnimatedFABExample = () => {
const { colors, isV3 } = useExampleTheme();
+ const { localeProps } = useLocale();
const isIOS = Platform.OS === 'ios';
@@ -155,6 +157,7 @@ const AnimatedFABExample = () => {
]}
contentContainerStyle={styles.container}
onScroll={onScroll}
+ {...localeProps}
/>
{
const { colors, isV3 } = useExampleTheme();
const [selectedMode, setSelectedMode] = React.useState('elevated' as Mode);
const [isSelected, setIsSelected] = React.useState(false);
- const preferences = React.useContext(PreferencesContext);
+ const preferences = usePreferences();
const modes = isV3
? ['elevated', 'outlined', 'contained']
@@ -188,7 +189,7 @@ const CardExample = () => {
{
- preferences?.toggleTheme();
+ preferences.toggleTheme();
}}
mode={selectedMode}
>
diff --git a/example/src/Examples/ChipExample.tsx b/example/src/Examples/ChipExample.tsx
index f161197fa7..6ee7362f8a 100644
--- a/example/src/Examples/ChipExample.tsx
+++ b/example/src/Examples/ChipExample.tsx
@@ -354,8 +354,8 @@ const styles = StyleSheet.create({
},
tiny: {
marginVertical: 2,
- marginRight: 2,
- marginLeft: 2,
+ marginEnd: 2,
+ marginStart: 2,
minHeight: 19,
lineHeight: 19,
},
diff --git a/example/src/Examples/Dialogs/DialogWithLoadingIndicator.tsx b/example/src/Examples/Dialogs/DialogWithLoadingIndicator.tsx
index ac9754fb77..7c1f6fc1d4 100644
--- a/example/src/Examples/Dialogs/DialogWithLoadingIndicator.tsx
+++ b/example/src/Examples/Dialogs/DialogWithLoadingIndicator.tsx
@@ -25,7 +25,7 @@ const DialogWithLoadingIndicator = ({
Loading.....
@@ -40,8 +40,8 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
},
- marginRight: {
- marginRight: 16,
+ marginEnd: {
+ marginEnd: 16,
},
});
diff --git a/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx b/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx
index 966422369e..8893cece09 100644
--- a/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx
+++ b/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx
@@ -106,6 +106,6 @@ const styles = StyleSheet.create({
paddingVertical: 8,
},
text: {
- paddingLeft: 8,
+ paddingStart: 8,
},
});
diff --git a/example/src/Examples/MenuExample.tsx b/example/src/Examples/MenuExample.tsx
index 6559f3d8f0..3f3cd142e5 100644
--- a/example/src/Examples/MenuExample.tsx
+++ b/example/src/Examples/MenuExample.tsx
@@ -185,7 +185,10 @@ const styles = StyleSheet.create({
md3Divider: {
marginVertical: 8,
},
- bottomMenu: { width: '40%' },
+ bottomMenu: {
+ width: '50%',
+ paddingStart: 20,
+ },
contentContainer: {
justifyContent: 'space-between',
flex: 1,
diff --git a/example/src/Examples/SnackbarExample.tsx b/example/src/Examples/SnackbarExample.tsx
index 4a6511a9b9..d220043a05 100644
--- a/example/src/Examples/SnackbarExample.tsx
+++ b/example/src/Examples/SnackbarExample.tsx
@@ -3,7 +3,8 @@ import { StyleSheet, View } from 'react-native';
import { Snackbar, Button, List, Text, Switch } from 'react-native-paper';
-import { PreferencesContext, useExampleTheme } from '..';
+import { useExampleTheme } from '..';
+import { usePreferences } from '../PreferencesContext';
import ScreenWrapper from '../ScreenWrapper';
const SHORT_MESSAGE = 'Single-line snackbar';
@@ -11,7 +12,7 @@ const LONG_MESSAGE =
'Snackbar with longer message which does not fit in one line';
const SnackbarExample = () => {
- const preferences = React.useContext(PreferencesContext);
+ const preferences = usePreferences();
const theme = useExampleTheme();
const [options, setOptions] = React.useState({
@@ -33,7 +34,7 @@ const SnackbarExample = () => {
const action = {
label: showLongerAction ? 'Toggle Theme' : 'Action',
onPress: () => {
- preferences?.toggleTheme();
+ preferences.toggleTheme();
},
};
diff --git a/example/src/Examples/SurfaceExample.tsx b/example/src/Examples/SurfaceExample.tsx
index 8fdc698141..a2e9f9646f 100644
--- a/example/src/Examples/SurfaceExample.tsx
+++ b/example/src/Examples/SurfaceExample.tsx
@@ -53,10 +53,10 @@ const SurfaceExample = () => {
- Left
+ Start
- Right
+ End
diff --git a/example/src/Examples/TeamDetails.tsx b/example/src/Examples/TeamDetails.tsx
index 540507ffe8..61ed4856bc 100644
--- a/example/src/Examples/TeamDetails.tsx
+++ b/example/src/Examples/TeamDetails.tsx
@@ -23,6 +23,7 @@ import {
} from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import { useLocale } from '../../../src/core/Localization';
import { colorThemes, teamResultsData } from '../../utils';
import ScreenWrapper from '../ScreenWrapper';
@@ -180,6 +181,7 @@ const Results = () => {
const ThemeBasedOnSourceColor = ({ navigation, route }: Props) => {
const insets = useSafeAreaInsets();
const [index, setIndex] = React.useState(0);
+ const { direction } = useLocale();
const { params } = route;
const { sourceColor, headerTitle, darkMode } = params;
@@ -217,7 +219,7 @@ const ThemeBasedOnSourceColor = ({ navigation, route }: Props) => {
const colorScheme = darkMode ? 'dark' : systemColorScheme;
return (
-
+
navigation.goBack()} />
@@ -264,7 +266,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
},
score: {
- marginRight: 16,
+ marginEnd: 16,
},
fab: {
position: 'absolute',
@@ -282,10 +284,10 @@ const styles = StyleSheet.create({
flexDirection: 'row',
},
chipsContent: {
- paddingLeft: 8,
+ paddingStart: 8,
paddingVertical: 8,
},
chip: {
- marginRight: 8,
+ marginEnd: 8,
},
});
diff --git a/example/src/Examples/TextInputExample.tsx b/example/src/Examples/TextInputExample.tsx
index 530c22f3ba..f547a49e8a 100644
--- a/example/src/Examples/TextInputExample.tsx
+++ b/example/src/Examples/TextInputExample.tsx
@@ -701,7 +701,7 @@ const styles = StyleSheet.create({
margin: 8,
},
inputContentStyle: {
- paddingLeft: 50,
+ paddingStart: 50,
fontWeight: 'bold',
fontStyle: 'italic',
},
diff --git a/example/src/PreferencesContext.ts b/example/src/PreferencesContext.ts
new file mode 100644
index 0000000000..c85c163d9e
--- /dev/null
+++ b/example/src/PreferencesContext.ts
@@ -0,0 +1,31 @@
+import * as React from 'react';
+
+import type { MD2Theme, MD3Theme } from 'react-native-paper';
+
+const PreferencesContext = React.createContext<{
+ toggleShouldUseDeviceColors?: () => void;
+ toggleTheme: () => void;
+ toggleRTL: () => void;
+ toggleThemeVersion: () => void;
+ toggleCollapsed: () => void;
+ toggleCustomFont: () => void;
+ toggleRippleEffect: () => void;
+ customFontLoaded: boolean;
+ rippleEffectEnabled: boolean;
+ collapsed: boolean;
+ isRTL: boolean;
+ theme: MD2Theme | MD3Theme;
+ shouldUseDeviceColors?: boolean;
+} | null>(null);
+
+export const usePreferences = () => {
+ const context = React.useContext(PreferencesContext);
+
+ if (!context) {
+ throw new Error('Context required');
+ }
+
+ return context;
+};
+
+export default PreferencesContext;
diff --git a/example/src/ScreenWrapper.tsx b/example/src/ScreenWrapper.tsx
index bb96250899..3291c81113 100644
--- a/example/src/ScreenWrapper.tsx
+++ b/example/src/ScreenWrapper.tsx
@@ -35,8 +35,8 @@ export default function ScreenWrapper({
{
backgroundColor: theme.colors.background,
paddingBottom: insets.bottom,
- paddingLeft: insets.left,
- paddingRight: insets.left,
+ paddingStart: insets.left,
+ paddingEnd: insets.left,
},
];
diff --git a/example/src/index.native.tsx b/example/src/index.native.tsx
index a7966a6cc8..177f48ddd2 100644
--- a/example/src/index.native.tsx
+++ b/example/src/index.native.tsx
@@ -29,28 +29,13 @@ import {
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
import DrawerItems from './DrawerItems';
+import PreferencesContext from './PreferencesContext';
import App from './RootNavigator';
import { deviceColorsSupported } from '../utils';
const PERSISTENCE_KEY = 'NAVIGATION_STATE';
const PREFERENCES_KEY = 'APP_PREFERENCES';
-export const PreferencesContext = React.createContext<{
- toggleShouldUseDeviceColors?: () => void;
- toggleTheme: () => void;
- toggleRtl: () => void;
- toggleThemeVersion: () => void;
- toggleCollapsed: () => void;
- toggleCustomFont: () => void;
- toggleRippleEffect: () => void;
- customFontLoaded: boolean;
- rippleEffectEnabled: boolean;
- collapsed: boolean;
- rtl: boolean;
- theme: MD2Theme | MD3Theme;
- shouldUseDeviceColors?: boolean;
-} | null>(null);
-
export const useExampleTheme = () => useTheme();
const Drawer = createDrawerNavigator<{ Home: undefined }>();
@@ -71,7 +56,7 @@ export default function PaperExample() {
React.useState(true);
const [isDarkMode, setIsDarkMode] = React.useState(false);
const [themeVersion, setThemeVersion] = React.useState<2 | 3>(3);
- const [rtl, setRtl] = React.useState(
+ const [isRTL, setIsRTL] = React.useState(
I18nManager.getConstants().isRTL
);
const [collapsed, setCollapsed] = React.useState(false);
@@ -122,7 +107,7 @@ export default function PaperExample() {
setIsDarkMode(preferences.theme === 'dark');
if (typeof preferences.rtl === 'boolean') {
- setRtl(preferences.rtl);
+ setIsRTL(preferences.rtl);
}
}
} catch (e) {
@@ -140,28 +125,28 @@ export default function PaperExample() {
PREFERENCES_KEY,
JSON.stringify({
theme: isDarkMode ? 'dark' : 'light',
- rtl,
+ rtl: isRTL,
})
);
} catch (e) {
// ignore error
}
- if (I18nManager.getConstants().isRTL !== rtl) {
- I18nManager.forceRTL(rtl);
+ if (I18nManager.getConstants().isRTL !== isRTL) {
+ I18nManager.forceRTL(isRTL);
Updates.reloadAsync();
}
};
savePrefs();
- }, [rtl, isDarkMode]);
+ }, [isRTL, isDarkMode]);
const preferences = React.useMemo(
() => ({
toggleShouldUseDeviceColors: () =>
setShouldUseDeviceColors((oldValue) => !oldValue),
toggleTheme: () => setIsDarkMode((oldValue) => !oldValue),
- toggleRtl: () => setRtl((rtl) => !rtl),
+ toggleRTL: () => setIsRTL((rtl) => !rtl),
toggleCollapsed: () => setCollapsed(!collapsed),
toggleCustomFont: () => setCustomFont(!customFontLoaded),
toggleRippleEffect: () => setRippleEffectEnabled(!rippleEffectEnabled),
@@ -174,12 +159,12 @@ export default function PaperExample() {
customFontLoaded,
rippleEffectEnabled,
collapsed,
- rtl,
+ isRTL,
theme,
shouldUseDeviceColors,
}),
[
- rtl,
+ isRTL,
theme,
collapsed,
customFontLoaded,
diff --git a/example/src/index.tsx b/example/src/index.tsx
index 4638ac2715..f8db09c265 100644
--- a/example/src/index.tsx
+++ b/example/src/index.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { I18nManager } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createDrawerNavigator } from '@react-navigation/drawer';
@@ -25,23 +26,12 @@ import {
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
import DrawerItems from './DrawerItems';
+import PreferencesContext from './PreferencesContext';
import App from './RootNavigator';
const PERSISTENCE_KEY = 'NAVIGATION_STATE';
const PREFERENCES_KEY = 'APP_PREFERENCES';
-export const PreferencesContext = React.createContext<{
- toggleTheme: () => void;
- toggleThemeVersion: () => void;
- toggleCollapsed: () => void;
- toggleCustomFont: () => void;
- toggleRippleEffect: () => void;
- customFontLoaded: boolean;
- rippleEffectEnabled: boolean;
- collapsed: boolean;
- theme: MD2Theme | MD3Theme;
-} | null>(null);
-
export const useExampleTheme = () => useTheme();
const Drawer = createDrawerNavigator<{ Home: undefined }>();
@@ -63,6 +53,7 @@ export default function PaperExample() {
const [collapsed, setCollapsed] = React.useState(false);
const [customFontLoaded, setCustomFont] = React.useState(false);
const [rippleEffectEnabled, setRippleEffectEnabled] = React.useState(true);
+ const [isRTL, setIsRTL] = React.useState(false);
const theme = React.useMemo(() => {
if (themeVersion === 2) {
@@ -99,6 +90,10 @@ export default function PaperExample() {
if (preferences) {
setIsDarkMode(preferences.theme === 'dark');
+
+ if (typeof preferences.rtl === 'boolean') {
+ setIsRTL(preferences.rtl);
+ }
}
} catch (e) {
// ignore error
@@ -115,15 +110,19 @@ export default function PaperExample() {
PREFERENCES_KEY,
JSON.stringify({
theme: isDarkMode ? 'dark' : 'light',
+ rtl: isRTL,
})
);
} catch (e) {
// ignore error
}
+
+ document.documentElement.setAttribute('dir', isRTL ? 'rtl' : 'ltr');
+ I18nManager.forceRTL(isRTL);
};
savePrefs();
- }, [isDarkMode]);
+ }, [isDarkMode, isRTL]);
const preferences = React.useMemo(
() => ({
@@ -131,6 +130,7 @@ export default function PaperExample() {
toggleCollapsed: () => setCollapsed(!collapsed),
toggleCustomFont: () => setCustomFont(!customFontLoaded),
toggleRippleEffect: () => setRippleEffectEnabled(!rippleEffectEnabled),
+ toggleRTL: () => setIsRTL((rtl) => !rtl),
toggleThemeVersion: () => {
setCustomFont(false);
setCollapsed(false);
@@ -140,9 +140,10 @@ export default function PaperExample() {
customFontLoaded,
rippleEffectEnabled,
collapsed,
+ isRTL,
theme,
}),
- [theme, collapsed, customFontLoaded, rippleEffectEnabled]
+ [customFontLoaded, rippleEffectEnabled, collapsed, isRTL, theme]
);
if (!isReady && !fontsLoaded) {
@@ -186,6 +187,7 @@ export default function PaperExample() {
@@ -206,6 +208,7 @@ export default function PaperExample() {
drawerStyle: collapsed && {
width: collapsedDrawerWidth,
},
+ // drawerPosition: isRTL ? 'right' : 'left',
}}
drawerContent={() => }
>
diff --git a/src/components/Appbar/Appbar.tsx b/src/components/Appbar/Appbar.tsx
index d6a69e7926..b8ff8277ac 100644
--- a/src/components/Appbar/Appbar.tsx
+++ b/src/components/Appbar/Appbar.tsx
@@ -245,8 +245,8 @@ const Appbar = ({
const insets = {
paddingBottom: safeAreaInsets?.bottom,
paddingTop: safeAreaInsets?.top,
- paddingLeft: safeAreaInsets?.left,
- paddingRight: safeAreaInsets?.right,
+ paddingStart: safeAreaInsets?.left,
+ paddingEnd: safeAreaInsets?.right,
};
return (
diff --git a/src/components/Appbar/AppbarBackIcon.tsx b/src/components/Appbar/AppbarBackIcon.tsx
index 14aa21ec03..9e6fb45710 100644
--- a/src/components/Appbar/AppbarBackIcon.tsx
+++ b/src/components/Appbar/AppbarBackIcon.tsx
@@ -1,9 +1,11 @@
import * as React from 'react';
-import { Platform, I18nManager, View, Image, StyleSheet } from 'react-native';
+import { Platform, View, Image, StyleSheet } from 'react-native';
+import { useLocale } from '../../core/Localization';
import MaterialCommunityIcon from '../MaterialCommunityIcon';
const AppbarBackIcon = ({ size, color }: { size: number; color: string }) => {
+ const { direction } = useLocale();
const iosIconSize = size - 3;
return Platform.OS === 'ios' ? (
@@ -13,7 +15,7 @@ const AppbarBackIcon = ({ size, color }: { size: number; color: string }) => {
{
width: size,
height: size,
- transform: [{ scaleX: I18nManager.getConstants().isRTL ? -1 : 1 }],
+ transform: [{ scaleX: direction === 'rtl' ? -1 : 1 }],
},
]}
>
@@ -31,7 +33,7 @@ const AppbarBackIcon = ({ size, color }: { size: number; color: string }) => {
name="arrow-left"
color={color}
size={size}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
);
};
diff --git a/src/components/Appbar/AppbarContent.tsx b/src/components/Appbar/AppbarContent.tsx
index 356bf2b60e..eed8ffaf0f 100644
--- a/src/components/Appbar/AppbarContent.tsx
+++ b/src/components/Appbar/AppbarContent.tsx
@@ -14,6 +14,7 @@ import {
import color from 'color';
import { modeTextVariant } from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import { white } from '../../styles/themes/v2/colors';
import type { $RemoveChildren, MD3TypescaleKey, ThemeProp } from '../../types';
@@ -111,6 +112,7 @@ const AppbarContent = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { localeProps } = useLocale();
const { isV3, colors } = theme;
const titleTextColor = titleColor
@@ -136,6 +138,7 @@ const AppbarContent = ({
style={[styles.container, isV3 && modeContainerStyles[mode], style]}
testID={testID}
{...rest}
+ {...localeProps}
>
{typeof title === 'string' ? (
{
const theme = useInternalTheme(themeOverrides);
+ const { localeProps } = useLocale();
const { current: opacity } = React.useRef(
new Animated.Value(visible ? 1 : 0)
);
@@ -119,6 +121,7 @@ const Badge = ({
styles.container,
restStyle,
]}
+ {...localeProps}
{...rest}
>
{children}
diff --git a/src/components/BottomNavigation/BottomNavigationBar.tsx b/src/components/BottomNavigation/BottomNavigationBar.tsx
index ef7ec26847..fe57a71295 100644
--- a/src/components/BottomNavigation/BottomNavigationBar.tsx
+++ b/src/components/BottomNavigation/BottomNavigationBar.tsx
@@ -1007,7 +1007,7 @@ const styles = StyleSheet.create({
},
badgeContainer: {
position: 'absolute',
- left: 0,
+ start: 0,
},
v3TouchableContainer: {
paddingTop: 12,
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index 3ed3ee791a..6d3c31c7f6 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -13,6 +13,7 @@ import {
import color from 'color';
import { ButtonMode, getButtonColors } from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { $Omit, ThemeProp } from '../../types';
import hasTouchHandler from '../../utils/hasTouchHandler';
@@ -172,6 +173,7 @@ const Button = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { localeProps } = useLocale();
const isMode = React.useCallback(
(modeToCompare: ButtonMode) => {
return mode === modeToCompare;
@@ -295,6 +297,7 @@ const Button = ({
] as ViewStyle
}
{...(isV3 && { elevation: elevation })}
+ {...localeProps}
>
{
const theme = useInternalTheme(themeOverrides);
+ const { direction, localeProps } = useLocale();
const { isV3 } = theme;
const { current: elevation } = React.useRef(
@@ -261,14 +263,14 @@ const Chip = ({
const elevationStyle = isV3 || Platform.OS === 'android' ? elevation : 0;
const multiplier = isV3 ? (compact ? 1.5 : 2) : 1;
const labelSpacings = {
- marginRight: onClose ? 0 : 8 * multiplier,
- marginLeft:
+ marginEnd: onClose ? 0 : 8 * multiplier,
+ marginStart:
avatar || icon || (selected && showSelectedCheck)
? 4 * multiplier
: 8 * multiplier,
};
const contentSpacings = {
- paddingRight: isV3 ? (onClose ? 34 : 0) : onClose ? 32 : 4,
+ paddingEnd: isV3 ? (onClose ? 34 : 0) : onClose ? 32 : 4,
};
const labelTextStyle = {
color: textColor,
@@ -294,6 +296,8 @@ const Chip = ({
{...rest}
testID={`${testID}-container`}
theme={theme}
+ // @ts-ignore
+ dir="rtl"
>
)}
@@ -403,7 +408,7 @@ const Chip = ({
name={isV3 ? 'close' : 'close-circle'}
size={iconSize}
color={iconColor}
- direction="ltr"
+ direction={direction}
/>
)}
@@ -429,25 +434,25 @@ const styles = StyleSheet.create({
content: {
flexDirection: 'row',
alignItems: 'center',
- paddingLeft: 4,
+ paddingStart: 4,
position: 'relative',
},
md3Content: {
- paddingLeft: 0,
+ paddingStart: 0,
},
icon: {
padding: 4,
alignSelf: 'center',
},
md3Icon: {
- paddingLeft: 8,
- paddingRight: 0,
+ paddingStart: 8,
+ paddingEnd: 0,
},
closeIcon: {
- marginRight: 4,
+ marginEnd: 4,
},
md3CloseIcon: {
- marginRight: 8,
+ marginEnd: 8,
padding: 0,
},
labelText: {
@@ -466,25 +471,25 @@ const styles = StyleSheet.create({
borderRadius: 12,
},
avatarWrapper: {
- marginRight: 4,
+ marginEnd: 4,
},
md3AvatarWrapper: {
- marginLeft: 4,
- marginRight: 0,
+ marginStart: 4,
+ marginEnd: 0,
},
md3SelectedIcon: {
- paddingLeft: 4,
+ paddingStart: 4,
},
// eslint-disable-next-line react-native/no-color-literals
avatarSelected: {
position: 'absolute',
top: 4,
- left: 4,
+ start: 4,
backgroundColor: 'rgba(0, 0, 0, .29)',
},
closeButtonStyle: {
position: 'absolute',
- right: 0,
+ end: 0,
height: '100%',
justifyContent: 'center',
alignItems: 'center',
diff --git a/src/components/DataTable/DataTablePagination.tsx b/src/components/DataTable/DataTablePagination.tsx
index dd72dbe54f..f09195209f 100644
--- a/src/components/DataTable/DataTablePagination.tsx
+++ b/src/components/DataTable/DataTablePagination.tsx
@@ -1,7 +1,6 @@
import * as React from 'react';
import {
ColorValue,
- I18nManager,
StyleProp,
StyleSheet,
View,
@@ -11,6 +10,7 @@ import {
import color from 'color';
import type { ThemeProp } from 'src/types';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import Button from '../Button/Button';
import IconButton from '../IconButton/IconButton';
@@ -107,6 +107,7 @@ const PaginationControls = ({
paginationControlRippleColor,
}: PaginationControlsProps) => {
const theme = useInternalTheme(themeOverrides);
+ const { direction } = useLocale();
const textColor = theme.isV3 ? theme.colors.onSurface : theme.colors.text;
@@ -119,7 +120,7 @@ const PaginationControls = ({
name="page-first"
color={color}
size={size}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
)}
iconColor={textColor}
@@ -136,7 +137,7 @@ const PaginationControls = ({
name="chevron-left"
color={color}
size={size}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
)}
iconColor={textColor}
@@ -152,7 +153,7 @@ const PaginationControls = ({
name="chevron-right"
color={color}
size={size}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
)}
iconColor={textColor}
@@ -169,7 +170,7 @@ const PaginationControls = ({
name="page-last"
color={color}
size={size}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
)}
iconColor={textColor}
@@ -377,7 +378,7 @@ const styles = StyleSheet.create({
justifyContent: 'flex-end',
flexDirection: 'row',
alignItems: 'center',
- paddingLeft: 16,
+ paddingStart: 16,
flexWrap: 'wrap',
},
optionsContainer: {
@@ -387,11 +388,11 @@ const styles = StyleSheet.create({
},
label: {
fontSize: 12,
- marginRight: 16,
+ marginEnd: 16,
},
button: {
textAlign: 'center',
- marginRight: 16,
+ marginEnd: 16,
},
iconsContainer: {
flexDirection: 'row',
diff --git a/src/components/DataTable/DataTableTitle.tsx b/src/components/DataTable/DataTableTitle.tsx
index 4b325bf82e..579c807b0c 100644
--- a/src/components/DataTable/DataTableTitle.tsx
+++ b/src/components/DataTable/DataTableTitle.tsx
@@ -2,7 +2,6 @@ import * as React from 'react';
import {
Animated,
GestureResponderEvent,
- I18nManager,
StyleProp,
StyleSheet,
TextStyle,
@@ -13,6 +12,7 @@ import {
import color from 'color';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp } from '../../types';
import MaterialCommunityIcon from '../MaterialCommunityIcon';
@@ -90,6 +90,7 @@ const DataTableTitle = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { direction } = useLocale();
const { current: spinAnim } = React.useRef(
new Animated.Value(sortDirection === 'ascending' ? 0 : 1)
);
@@ -117,7 +118,7 @@ const DataTableTitle = ({
name="arrow-up"
size={16}
color={textColor}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
) : null;
@@ -135,7 +136,7 @@ const DataTableTitle = ({
// if numberOfLines causes wrap, center is lost. Align directly, sensitive to numeric and RTL
numberOfLines > 1
? numeric
- ? I18nManager.getConstants().isRTL
+ ? direction === 'rtl'
? styles.leftText
: styles.rightText
: styles.centerText
@@ -186,7 +187,7 @@ const styles = StyleSheet.create({
},
sorted: {
- marginLeft: 8,
+ marginStart: 8,
},
icon: {
diff --git a/src/components/Dialog/DialogActions.tsx b/src/components/Dialog/DialogActions.tsx
index b8f7b0b16a..08e988fa51 100644
--- a/src/components/Dialog/DialogActions.tsx
+++ b/src/components/Dialog/DialogActions.tsx
@@ -61,7 +61,7 @@ const DialogActions = (props: Props) => {
uppercase: !isV3,
style: [
isV3 && {
- marginRight: i + 1 === actionsLength ? 0 : 8,
+ marginEnd: i + 1 === actionsLength ? 0 : 8,
},
child.props.style,
],
diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx
index be30d94c7c..3dc3a1512c 100644
--- a/src/components/Divider.tsx
+++ b/src/components/Divider.tsx
@@ -85,14 +85,14 @@ const Divider = ({
const styles = StyleSheet.create({
leftInset: {
- marginLeft: 72,
+ marginStart: 72,
},
v3LeftInset: {
- marginLeft: 16,
+ marginStart: 16,
},
horizontalInset: {
- marginLeft: 16,
- marginRight: 16,
+ marginStart: 16,
+ marginEnd: 16,
},
bold: {
height: 1,
diff --git a/src/components/Drawer/DrawerCollapsedItem.tsx b/src/components/Drawer/DrawerCollapsedItem.tsx
index 86ba1e2a58..354c161a8f 100644
--- a/src/components/Drawer/DrawerCollapsedItem.tsx
+++ b/src/components/Drawer/DrawerCollapsedItem.tsx
@@ -261,7 +261,7 @@ const styles = StyleSheet.create({
},
badgeContainer: {
position: 'absolute',
- left: 20,
+ start: 20,
bottom: 20,
zIndex: 2,
},
diff --git a/src/components/Drawer/DrawerItem.tsx b/src/components/Drawer/DrawerItem.tsx
index d01be2e3de..fce69fa56e 100644
--- a/src/components/Drawer/DrawerItem.tsx
+++ b/src/components/Drawer/DrawerItem.tsx
@@ -136,7 +136,7 @@ const DrawerItem = ({
styles.label,
{
color: contentColor,
- marginLeft: labelMargin,
+ marginStart: labelMargin,
...font,
},
]}
@@ -162,8 +162,8 @@ const styles = StyleSheet.create({
v3Container: {
justifyContent: 'center',
height: 56,
- marginLeft: 12,
- marginRight: 12,
+ marginStart: 12,
+ marginEnd: 12,
marginVertical: 0,
},
wrapper: {
@@ -172,8 +172,8 @@ const styles = StyleSheet.create({
padding: 8,
},
v3Wrapper: {
- marginLeft: 16,
- marginRight: 24,
+ marginStart: 16,
+ marginEnd: 24,
padding: 0,
},
content: {
@@ -182,7 +182,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
label: {
- marginRight: 32,
+ marginEnd: 32,
},
});
diff --git a/src/components/Drawer/DrawerSection.tsx b/src/components/Drawer/DrawerSection.tsx
index 16d2c2f662..9ed33f36e8 100644
--- a/src/components/Drawer/DrawerSection.tsx
+++ b/src/components/Drawer/DrawerSection.tsx
@@ -86,7 +86,7 @@ const DrawerSection = ({
style={[
{
color: titleColor,
- marginLeft: titleMargin,
+ marginStart: titleMargin,
...font,
},
]}
diff --git a/src/components/FAB/AnimatedFAB.tsx b/src/components/FAB/AnimatedFAB.tsx
index 757a143c7a..ec67d67544 100644
--- a/src/components/FAB/AnimatedFAB.tsx
+++ b/src/components/FAB/AnimatedFAB.tsx
@@ -9,7 +9,6 @@ import {
Animated,
Easing,
GestureResponderEvent,
- I18nManager,
Platform,
ScrollView,
StyleProp,
@@ -21,6 +20,7 @@ import {
import color from 'color';
import { getCombinedStyles, getFABColors } from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { $Omit, $RemoveChildren, ThemeProp } from '../../types';
import type { IconSource } from '../Icon';
@@ -129,7 +129,6 @@ const SCALE = 0.9;
* ScrollView,
* Text,
* SafeAreaView,
- * I18nManager,
* } from 'react-native';
* import { AnimatedFAB } from 'react-native-paper';
*
@@ -213,11 +212,11 @@ const AnimatedFAB = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { direction } = useLocale();
const uppercase: boolean = uppercaseProp ?? !theme.isV3;
const isIOS = Platform.OS === 'ios';
const isAnimatedFromRight = animateFrom === 'right';
const isIconStatic = iconMode === 'static';
- const { isRTL } = I18nManager;
const { current: visibility } = React.useRef(
new Animated.Value(visible ? 1 : 0)
);
@@ -307,6 +306,7 @@ const AnimatedFAB = ({
isIconStatic,
distance,
animFAB,
+ direction,
});
const font = isV3 ? theme.fonts.labelLarge : theme.fonts.medium;
@@ -463,9 +463,10 @@ const AnimatedFAB = ({
ellipsizeMode={'tail'}
style={[
{
- [isAnimatedFromRight || isRTL ? 'right' : 'left']: isIconStatic
- ? textWidth - SIZE + borderRadius / (isV3 ? 1 : 2)
- : borderRadius,
+ [isAnimatedFromRight || direction === 'rtl' ? 'right' : 'left']:
+ isIconStatic
+ ? textWidth - SIZE + borderRadius / (isV3 ? 1 : 2)
+ : borderRadius,
},
{
minWidth: textWidth,
diff --git a/src/components/FAB/FABGroup.tsx b/src/components/FAB/FABGroup.tsx
index 9aeae123f6..d865527d76 100644
--- a/src/components/FAB/FABGroup.tsx
+++ b/src/components/FAB/FABGroup.tsx
@@ -324,8 +324,8 @@ const FABGroup = ({
const { top, bottom, right, left } = useSafeAreaInsets();
const containerPaddings = {
paddingBottom: bottom,
- paddingRight: right,
- paddingLeft: left,
+ paddingEnd: right,
+ paddingStart: left,
paddingTop: top,
};
diff --git a/src/components/FAB/utils.ts b/src/components/FAB/utils.ts
index 3396acda4d..8522424507 100644
--- a/src/components/FAB/utils.ts
+++ b/src/components/FAB/utils.ts
@@ -1,7 +1,8 @@
-import { Animated, ColorValue, I18nManager, ViewStyle } from 'react-native';
+import type { Animated, ColorValue, ViewStyle } from 'react-native';
import color from 'color';
+import type { Direction } from '../../core/Localization';
import { black, white } from '../../styles/themes/v2/colors';
import type { InternalTheme } from '../../types';
import getContrastingColor from '../../utils/getContrastingColor';
@@ -11,6 +12,7 @@ type GetCombinedStylesProps = {
isIconStatic: boolean;
distance: number;
animFAB: Animated.Value;
+ direction: Direction;
};
type CombinedStyles = {
@@ -32,9 +34,9 @@ export const getCombinedStyles = ({
isIconStatic,
distance,
animFAB,
+ direction,
}: GetCombinedStylesProps): CombinedStyles => {
- const { isRTL } = I18nManager;
-
+ const isRTL = direction === 'rtl';
const defaultPositionStyles = { left: -distance, right: undefined };
const combinedStyles: CombinedStyles = {
diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx
index 84a4b617ae..650636463c 100644
--- a/src/components/Icon.tsx
+++ b/src/components/Icon.tsx
@@ -1,12 +1,8 @@
import * as React from 'react';
-import {
- I18nManager,
- Image,
- ImageSourcePropType,
- Platform,
-} from 'react-native';
+import { Image, ImageSourcePropType, Platform } from 'react-native';
import { accessibilityProps } from './MaterialCommunityIcon';
+import { useLocale } from '../core/Localization';
import { Consumer as SettingsConsumer } from '../core/settings';
import { useInternalTheme } from '../core/theming';
import type { ThemeProp } from '../types';
@@ -75,10 +71,11 @@ const Icon = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { direction: localeDirection } = useLocale();
const direction =
typeof source === 'object' && source.direction && source.source
? source.direction === 'auto'
- ? I18nManager.getConstants().isRTL
+ ? localeDirection
? 'rtl'
: 'ltr'
: source.direction
diff --git a/src/components/List/ListAccordion.tsx b/src/components/List/ListAccordion.tsx
index 0674ed5a01..bc847dbb60 100644
--- a/src/components/List/ListAccordion.tsx
+++ b/src/components/List/ListAccordion.tsx
@@ -2,7 +2,6 @@ import * as React from 'react';
import {
ColorValue,
GestureResponderEvent,
- I18nManager,
NativeSyntheticEvent,
StyleProp,
StyleSheet,
@@ -14,8 +13,9 @@ import {
} from 'react-native';
import { ListAccordionGroupContext } from './ListAccordionGroup';
-import type { Style } from './utils';
import { getAccordionColors, getLeftStyles } from './utils';
+import type { Style } from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp } from '../../types';
import MaterialCommunityIcon from '../MaterialCommunityIcon';
@@ -169,6 +169,7 @@ const ListAccordion = ({
pointerEvents = 'none',
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { direction } = useLocale();
const [expanded, setExpanded] = React.useState(
expandedProp || false
);
@@ -288,7 +289,7 @@ const ListAccordion = ({
name={isExpanded ? 'chevron-up' : 'chevron-down'}
color={theme.isV3 ? descriptionColor : titleColor}
size={24}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
)}
@@ -357,7 +358,7 @@ const styles = StyleSheet.create({
paddingLeft: 16,
},
child: {
- paddingLeft: 64,
+ paddingStart: 64,
},
childV3: {
paddingLeft: 40,
diff --git a/src/components/List/ListImage.tsx b/src/components/List/ListImage.tsx
index 9839be7b02..a94ea564f7 100644
--- a/src/components/List/ListImage.tsx
+++ b/src/components/List/ListImage.tsx
@@ -75,12 +75,12 @@ const styles = StyleSheet.create({
video: {
width: 100,
height: 64,
- marginLeft: 0,
+ marginStart: 0,
},
videoV3: {
width: 114,
height: 64,
- marginLeft: 0,
+ marginStart: 0,
},
});
diff --git a/src/components/List/ListItem.tsx b/src/components/List/ListItem.tsx
index c945251225..5f4fc63865 100644
--- a/src/components/List/ListItem.tsx
+++ b/src/components/List/ListItem.tsx
@@ -13,6 +13,7 @@ import {
import color from 'color';
import { Style, getLeftStyles, getRightStyles } from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { $RemoveChildren, EllipsizeProp, ThemeProp } from '../../types';
import TouchableRipple from '../TouchableRipple/TouchableRipple';
@@ -135,6 +136,7 @@ const ListItem = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { localeProps } = useLocale();
const [alignToTop, setAlignToTop] = React.useState(false);
const onDescriptionTextLayout = (
@@ -209,8 +211,9 @@ const ListItem = ({
style={[theme.isV3 ? styles.containerV3 : styles.container, style]}
onPress={onPress}
theme={theme}
+ {...localeProps}
>
-
+
{left
? left({
color: descriptionColor,
@@ -245,7 +248,7 @@ const styles = StyleSheet.create({
},
containerV3: {
paddingVertical: 8,
- paddingRight: 24,
+ paddingEnd: 24,
},
row: {
width: '100%',
@@ -264,10 +267,10 @@ const styles = StyleSheet.create({
},
item: {
marginVertical: 6,
- paddingLeft: 8,
+ paddingStart: 8,
},
itemV3: {
- paddingLeft: 16,
+ paddingStart: 16,
},
content: {
flexShrink: 1,
diff --git a/src/components/List/ListSection.tsx b/src/components/List/ListSection.tsx
index 59640c70da..0a7c325931 100644
--- a/src/components/List/ListSection.tsx
+++ b/src/components/List/ListSection.tsx
@@ -8,6 +8,7 @@ import {
} from 'react-native';
import ListSubheader from './ListSubheader';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp } from '../../types';
@@ -62,10 +63,11 @@ const ListSection = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { localeProps } = useLocale();
const viewProps = { ...rest, theme };
return (
-
+
{title ? (
{title}
diff --git a/src/components/List/utils.ts b/src/components/List/utils.ts
index 773150255d..7ac12ec2c2 100644
--- a/src/components/List/utils.ts
+++ b/src/components/List/utils.ts
@@ -13,8 +13,8 @@ type Description =
}) => React.ReactNode);
export type Style = {
- marginLeft?: number;
- marginRight?: number;
+ marginStart?: number;
+ marginEnd?: number;
marginVertical?: number;
alignSelf?: FlexAlignType;
};
@@ -25,25 +25,25 @@ export const getLeftStyles = (
isV3: boolean
) => {
const stylesV3 = {
- marginRight: 0,
- marginLeft: 16,
+ marginEnd: 0,
+ marginStart: 16,
alignSelf: alignToTop ? 'flex-start' : 'center',
};
if (!description) {
return {
- ...styles.iconMarginLeft,
+ ...styles.iconmarginStart,
...styles.marginVerticalNone,
...(isV3 && { ...stylesV3 }),
};
}
if (!isV3) {
- return styles.iconMarginLeft;
+ return styles.iconmarginStart;
}
return {
- ...styles.iconMarginLeft,
+ ...styles.iconmarginStart,
...stylesV3,
};
};
@@ -54,32 +54,32 @@ export const getRightStyles = (
isV3: boolean
) => {
const stylesV3 = {
- marginLeft: 16,
+ marginStart: 16,
alignSelf: alignToTop ? 'flex-start' : 'center',
};
if (!description) {
return {
- ...styles.iconMarginRight,
+ ...styles.iconmarginEnd,
...styles.marginVerticalNone,
...(isV3 && { ...stylesV3 }),
};
}
if (!isV3) {
- return styles.iconMarginRight;
+ return styles.iconmarginEnd;
}
return {
- ...styles.iconMarginRight,
+ ...styles.iconmarginEnd,
...stylesV3,
};
};
const styles = StyleSheet.create({
marginVerticalNone: { marginVertical: 0 },
- iconMarginLeft: { marginLeft: 0, marginRight: 16 },
- iconMarginRight: { marginRight: 0 },
+ iconmarginStart: { marginStart: 0, marginEnd: 16 },
+ iconmarginEnd: { marginEnd: 0 },
});
export const getAccordionColors = ({
diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx
index 0d8be6c9e1..72ad2172ad 100644
--- a/src/components/Menu/Menu.tsx
+++ b/src/components/Menu/Menu.tsx
@@ -150,9 +150,6 @@ const WINDOW_LAYOUT = Dimensions.get('window');
* wrapping is not necessary if you use Paper's `Modal` instead.
*/
class Menu extends React.Component {
- // @component ./MenuItem.tsx
- static Item = MenuItem;
-
static defaultProps = {
statusBarHeight: APPROX_STATUSBAR_HEIGHT,
overlayAccessibilityLabel: 'Close menu',
@@ -391,8 +388,7 @@ class Menu extends React.Component {
};
private keyboardDidShow = (e: RNKeyboardEvent) => {
- const keyboardHeight = e.endCoordinates.height;
- this.keyboardHeight = keyboardHeight;
+ this.keyboardHeight = e.endCoordinates.height;
};
private keyboardDidHide = () => {
@@ -452,7 +448,14 @@ class Menu extends React.Component {
];
// We need to translate menu while animating scale to imitate transform origin for scale animation
- const positionTransforms = [];
+ const positionTransforms: (
+ | {
+ translateX: Animated.AnimatedInterpolation;
+ }
+ | {
+ translateY: Animated.AnimatedInterpolation;
+ }
+ )[] = [];
// Check if menu fits horizontally and if not align it to right.
if (left <= windowLayout.width - menuLayout.width - SCREEN_INDENT) {
@@ -595,7 +598,7 @@ class Menu extends React.Component {
const positionStyle = {
top: this.isCoordinate(anchor) ? top : top + additionalVerticalValue,
- ...(I18nManager.getConstants().isRTL ? { right: left } : { left }),
+ ...(I18nManager.getConstants().isRTL ? { end: left } : { start: left }),
};
const pointerEvents = visible ? 'box-none' : 'none';
@@ -675,4 +678,7 @@ const styles = StyleSheet.create({
},
});
-export default withInternalTheme(Menu);
+export default Object.assign(withInternalTheme(Menu), {
+ // @component ./MenuItem.tsx
+ Item: MenuItem,
+});
diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx
index 320731efca..a430e605d0 100644
--- a/src/components/Menu/MenuItem.tsx
+++ b/src/components/Menu/MenuItem.tsx
@@ -16,6 +16,7 @@ import {
MAX_WIDTH,
MIN_WIDTH,
} from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp } from '../../types';
import Icon, { IconSource } from '../Icon';
@@ -125,6 +126,7 @@ const MenuItem = ({
titleMaxFontSizeMultiplier = 1.5,
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { localeProps } = useLocale();
const { titleColor, iconColor, rippleColor } = getMenuItemColor({
theme,
disabled,
@@ -189,6 +191,7 @@ const MenuItem = ({
contentStyle,
]}
pointerEvents="none"
+ {...localeProps}
>
& {
const INDETERMINATE_DURATION = 2000;
const INDETERMINATE_MAX_WIDTH = 0.6;
-const { isRTL } = I18nManager;
/**
* Progress bar is an indicator used to present progress of some activity in the app.
@@ -75,6 +74,7 @@ const ProgressBar = ({
...rest
}: Props) => {
const isWeb = Platform.OS === 'web';
+ const { direction } = useLocale();
const theme = useInternalTheme(themeOverrides);
const { current: timer } = React.useRef(
new Animated.Value(0)
@@ -218,17 +218,20 @@ const ProgressBar = ({
? {
inputRange: [0, 0.5, 1],
outputRange: [
- (isRTL ? 1 : -1) * 0.5 * width,
- (isRTL ? 1 : -1) *
+ (direction === 'rtl' ? 1 : -1) * 0.5 * width,
+ (direction === 'rtl' ? 1 : -1) *
0.5 *
INDETERMINATE_MAX_WIDTH *
width,
- (isRTL ? -1 : 1) * 0.7 * width,
+ (direction === 'rtl' ? -1 : 1) * 0.7 * width,
],
}
: {
inputRange: [0, 1],
- outputRange: [(isRTL ? 1 : -1) * 0.5 * width, 0],
+ outputRange: [
+ (direction === 'rtl' ? 1 : -1) * 0.5 * width,
+ 0,
+ ],
}
),
},
diff --git a/src/components/RadioButton/RadioButtonItem.tsx b/src/components/RadioButton/RadioButtonItem.tsx
index 2d577f9587..5824bdd257 100644
--- a/src/components/RadioButton/RadioButtonItem.tsx
+++ b/src/components/RadioButton/RadioButtonItem.tsx
@@ -13,6 +13,7 @@ import RadioButtonAndroid from './RadioButtonAndroid';
import { RadioButtonContext, RadioButtonContextType } from './RadioButtonGroup';
import RadioButtonIOS from './RadioButtonIOS';
import { handlePress, isChecked } from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp, MD3TypescaleKey } from '../../types';
import TouchableRipple from '../TouchableRipple/TouchableRipple';
@@ -135,6 +136,7 @@ const RadioButtonItem = ({
labelVariant = 'bodyLarge',
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { overwriteRTL } = useLocale();
const radioButtonProps = {
value,
disabled,
@@ -158,7 +160,12 @@ const RadioButtonItem = ({
const disabledTextColor = theme.isV3
? theme.colors.onSurfaceDisabled
: theme.colors.disabled;
- const textAlign = isLeading ? 'right' : 'left';
+
+ let textAlign = isLeading ? 'right' : 'left';
+
+ if (overwriteRTL) {
+ textAlign = isLeading ? 'left' : 'right';
+ }
const computedStyle = {
color: disabled ? disabledTextColor : textColor,
diff --git a/src/components/Searchbar.tsx b/src/components/Searchbar.tsx
index 08226c2855..5dcbeaa7b7 100644
--- a/src/components/Searchbar.tsx
+++ b/src/components/Searchbar.tsx
@@ -3,7 +3,6 @@ import {
Animated,
ColorValue,
GestureResponderEvent,
- I18nManager,
Platform,
StyleProp,
StyleSheet,
@@ -22,12 +21,13 @@ import type { IconSource } from './Icon';
import IconButton from './IconButton/IconButton';
import MaterialCommunityIcon from './MaterialCommunityIcon';
import Surface from './Surface';
+import { useLocale } from '../core/Localization';
import { useInternalTheme } from '../core/theming';
import type { ThemeProp } from '../types';
import { forwardRef } from '../utils/forwardRef';
interface Style {
- marginRight: number;
+ marginEnd: number;
}
export type Props = React.ComponentPropsWithRef & {
@@ -210,6 +210,7 @@ const Searchbar = forwardRef(
ref
) => {
const theme = useInternalTheme(themeOverrides);
+ const { direction, localeProps } = useLocale();
const root = React.useRef(null);
React.useImperativeHandle(ref, () => {
@@ -293,6 +294,7 @@ const Searchbar = forwardRef(
testID={`${testID}-container`}
{...(theme.isV3 && { elevation })}
theme={theme}
+ {...localeProps}
>
(
name="magnify"
color={color}
size={size}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
))
}
@@ -318,7 +320,9 @@ const Searchbar = forwardRef(
(
name={isV3 ? 'close' : 'close-circle-outline'}
color={color}
size={size}
- direction={I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}
+ direction={direction}
/>
))
}
@@ -418,17 +422,16 @@ const styles = StyleSheet.create({
input: {
flex: 1,
fontSize: 18,
- paddingLeft: 8,
+ paddingStart: 8,
alignSelf: 'stretch',
- textAlign: I18nManager.getConstants().isRTL ? 'right' : 'left',
minWidth: 0,
},
barModeInput: {
- paddingLeft: 0,
+ paddingStart: 0,
minHeight: 56,
},
viewModeInput: {
- paddingLeft: 0,
+ paddingStart: 0,
minHeight: 72,
},
elevation: {
@@ -441,12 +444,12 @@ const styles = StyleSheet.create({
marginHorizontal: 16,
},
rightStyle: {
- marginRight: 16,
+ marginEnd: 16,
},
v3ClearIcon: {
position: 'absolute',
right: 0,
- marginLeft: 16,
+ marginStart: 16,
},
v3ClearIconHidden: {
display: 'none',
diff --git a/src/components/SegmentedButtons/SegmentedButtonItem.tsx b/src/components/SegmentedButtons/SegmentedButtonItem.tsx
index 60a7f9f7c1..f94ad99715 100644
--- a/src/components/SegmentedButtons/SegmentedButtonItem.tsx
+++ b/src/components/SegmentedButtons/SegmentedButtonItem.tsx
@@ -18,6 +18,7 @@ import {
getSegmentedButtonColors,
getSegmentedButtonDensityPadding,
} from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { IconSource } from '../Icon';
import Icon from '../Icon';
@@ -113,6 +114,7 @@ const SegmentedButtonItem = ({
theme: themeOverrides,
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { overwriteRTL } = useLocale();
const checkScale = React.useRef(new Animated.Value(0)).current;
@@ -147,6 +149,7 @@ const SegmentedButtonItem = ({
const segmentBorderRadius = getSegmentedButtonBorderRadius({
theme,
segment,
+ overwriteRTL,
});
const rippleColor =
customRippleColor || color(textColor).alpha(0.12).rgb().string();
@@ -156,7 +159,7 @@ const SegmentedButtonItem = ({
const iconSize = isV3 ? 18 : 16;
const iconStyle = {
- marginRight: label ? 5 : showCheckedIcon ? 3 : 0,
+ marginEnd: label ? 5 : showCheckedIcon ? 3 : 0,
...(label && {
transform: [
{
diff --git a/src/components/SegmentedButtons/SegmentedButtons.tsx b/src/components/SegmentedButtons/SegmentedButtons.tsx
index e5ab79629f..9620b73855 100644
--- a/src/components/SegmentedButtons/SegmentedButtons.tsx
+++ b/src/components/SegmentedButtons/SegmentedButtons.tsx
@@ -12,6 +12,7 @@ import type { ThemeProp } from 'src/types';
import SegmentedButtonItem from './SegmentedButtonItem';
import { getDisabledSegmentedButtonStyle } from './utils';
+import { useLocale } from '../../core/Localization';
import { useInternalTheme } from '../../core/theming';
import type { IconSource } from '../Icon';
@@ -136,9 +137,10 @@ const SegmentedButtons = ({
theme: themeOverrides,
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { localeProps } = useLocale();
return (
-
+
{buttons.map((item, i) => {
const disabledChildStyle = getDisabledSegmentedButtonStyle({
theme,
diff --git a/src/components/SegmentedButtons/utils.ts b/src/components/SegmentedButtons/utils.ts
index 87d343c8a9..a21bc176c2 100644
--- a/src/components/SegmentedButtons/utils.ts
+++ b/src/components/SegmentedButtons/utils.ts
@@ -58,28 +58,48 @@ export const getDisabledSegmentedButtonStyle = ({
return {};
};
+const ltrCSSBorders = {
+ topEndRadius: 'borderTopRightRadius',
+ bottomEndRadius: 'borderBottomRightRadius',
+ endWidth: 'borderRightWidth',
+ topStartRadius: 'borderTopLeftRadius',
+ bottomStartRadius: 'borderBottomLeftRadius',
+};
+
+const rtlCSSBorders = {
+ topEndRadius: 'borderTopLeftRadius',
+ bottomEndRadius: 'borderBottomLeftRadius',
+ endWidth: 'borderLeftWidth',
+ topStartRadius: 'borderTopRightRadius',
+ bottomStartRadius: 'borderBottomRightRadius',
+};
+
export const getSegmentedButtonBorderRadius = ({
segment,
theme,
+ overwriteRTL,
}: {
theme: InternalTheme;
segment?: 'first' | 'last';
+ overwriteRTL?: boolean;
}): ViewStyle => {
+ const cssBorders = overwriteRTL ? rtlCSSBorders : ltrCSSBorders;
+
if (segment === 'first') {
return {
- borderTopRightRadius: 0,
- borderBottomRightRadius: 0,
- ...(theme.isV3 && { borderRightWidth: 0 }),
+ [cssBorders.topEndRadius]: 0,
+ [cssBorders.bottomEndRadius]: 0,
+ ...(theme.isV3 && { [cssBorders.endWidth]: 0 }),
};
} else if (segment === 'last') {
return {
- borderTopLeftRadius: 0,
- borderBottomLeftRadius: 0,
+ [cssBorders.topStartRadius]: 0,
+ [cssBorders.bottomStartRadius]: 0,
};
} else {
return {
borderRadius: 0,
- ...(theme.isV3 && { borderRightWidth: 0 }),
+ ...(theme.isV3 && { [cssBorders.endWidth]: 0 }),
};
}
};
diff --git a/src/components/Snackbar.tsx b/src/components/Snackbar.tsx
index 531d1b624e..f58360589e 100644
--- a/src/components/Snackbar.tsx
+++ b/src/components/Snackbar.tsx
@@ -3,7 +3,6 @@ import {
Animated,
ColorValue,
Easing,
- I18nManager,
StyleProp,
StyleSheet,
View,
@@ -19,6 +18,7 @@ import IconButton from './IconButton/IconButton';
import MaterialCommunityIcon from './MaterialCommunityIcon';
import Surface from './Surface';
import Text from './Typography/Text';
+import { useLocale } from '../core/Localization';
import { useInternalTheme } from '../core/theming';
import type { $Omit, $RemoveChildren, ThemeProp } from '../types';
@@ -156,6 +156,7 @@ const Snackbar = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
+ const { direction, localeProps } = useLocale();
const { bottom, right, left } = useSafeAreaInsets();
const { current: opacity } = React.useRef(
@@ -243,7 +244,7 @@ const Snackbar = ({
const isIconButton = isV3 && onIconPress;
- const marginLeft = action ? -12 : -16;
+ const marginStart = action ? -12 : -16;
const wrapperPaddings = {
paddingBottom: bottom,
@@ -301,11 +302,12 @@ const Snackbar = ({
]}
testID={testID}
{...(isV3 && { elevation })}
+ {...localeProps}
{...rest}
>
{renderChildrenWithWrapper()}
{(action || isIconButton) && (
-
+
{action ? (
);
expect(getByTestId('compact-button-icon-container')).toHaveStyle({
- marginLeft: 6,
- marginRight: 0,
+ marginStart: 6,
+ marginEnd: 0,
});
});
@@ -211,8 +211,8 @@ describe('button icon styles', () => {
);
expect(getByTestId('compact-button-icon-container')).toHaveStyle({
- marginLeft: 8,
- marginRight: 0,
+ marginStart: 8,
+ marginEnd: 0,
});
})
);
@@ -224,8 +224,8 @@ describe('button icon styles', () => {
);
expect(getByTestId('compact-button-icon-container')).toHaveStyle({
- marginLeft: 12,
- marginRight: -8,
+ marginStart: 12,
+ marginEnd: -8,
});
});
@@ -238,8 +238,8 @@ describe('button icon styles', () => {
);
expect(getByTestId('compact-button-icon-container')).toHaveStyle({
- marginLeft: 16,
- marginRight: -16,
+ marginStart: 16,
+ marginEnd: -16,
});
})
);
diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx
index eef2dd9fea..ffb6094f9d 100644
--- a/src/components/__tests__/Dialog.test.tsx
+++ b/src/components/__tests__/Dialog.test.tsx
@@ -142,8 +142,8 @@ describe('DialogActions', () => {
paddingBottom: 24,
paddingHorizontal: 24,
});
- expect(dialogActionButtons[0]).toHaveStyle({ marginRight: 8 });
- expect(dialogActionButtons[1]).toHaveStyle({ marginRight: 0 });
+ expect(dialogActionButtons[0]).toHaveStyle({ marginEnd: 8 });
+ expect(dialogActionButtons[1]).toHaveStyle({ marginEnd: 0 });
});
it('should apply custom styles', () => {
diff --git a/src/components/__tests__/Drawer/__snapshots__/DrawerSection.test.tsx.snap b/src/components/__tests__/Drawer/__snapshots__/DrawerSection.test.tsx.snap
index bcd35191f0..50bad5e9bf 100644
--- a/src/components/__tests__/Drawer/__snapshots__/DrawerSection.test.tsx.snap
+++ b/src/components/__tests__/Drawer/__snapshots__/DrawerSection.test.tsx.snap
@@ -21,8 +21,8 @@ exports[`DrawerSection renders properly 1`] = `
},
undefined,
{
- "marginLeft": 16,
- "marginRight": 16,
+ "marginEnd": 16,
+ "marginStart": 16,
},
{
"height": 1,
diff --git a/src/components/__tests__/FABGroup.test.tsx b/src/components/__tests__/FABGroup.test.tsx
index 0d1019698d..edbad8adbf 100644
--- a/src/components/__tests__/FABGroup.test.tsx
+++ b/src/components/__tests__/FABGroup.test.tsx
@@ -147,7 +147,7 @@ describe('FABActions - labelStyle - containerStyle', () => {
containerStyle: {
padding: 16,
backgroundColor: '#687456',
- marginLeft: 16,
+ marginStart: 16,
},
onPress() {},
icon: '',
diff --git a/src/components/__tests__/ListImage.test.tsx b/src/components/__tests__/ListImage.test.tsx
index 5248019277..283cc6afa5 100644
--- a/src/components/__tests__/ListImage.test.tsx
+++ b/src/components/__tests__/ListImage.test.tsx
@@ -13,7 +13,7 @@ const styles = StyleSheet.create({
video: {
width: 114,
height: 64,
- marginLeft: 0,
+ marginStart: 0,
},
container: {
width: 30,
diff --git a/src/components/__tests__/ListUtils.test.tsx b/src/components/__tests__/ListUtils.test.tsx
index 4aeefb6ca9..98fa2b0141 100644
--- a/src/components/__tests__/ListUtils.test.tsx
+++ b/src/components/__tests__/ListUtils.test.tsx
@@ -6,20 +6,20 @@ import Text from '../Typography/Text';
const styles = StyleSheet.create({
leftItem: {
- marginLeft: 0,
- marginRight: 16,
+ marginStart: 0,
+ marginEnd: 16,
},
leftItemV3: {
- marginLeft: 16,
- marginRight: 0,
+ marginStart: 16,
+ marginEnd: 0,
alignSelf: 'center',
},
rightItem: {
- marginRight: 0,
+ marginEnd: 0,
},
rightItemV3: {
- marginLeft: 16,
- marginRight: 0,
+ marginStart: 16,
+ marginEnd: 0,
alignSelf: 'center',
},
});
diff --git a/src/components/__tests__/Menu.test.tsx b/src/components/__tests__/Menu.test.tsx
index b27027b0fc..88c8e62cb0 100644
--- a/src/components/__tests__/Menu.test.tsx
+++ b/src/components/__tests__/Menu.test.tsx
@@ -103,7 +103,6 @@ it('uses the default anchorPosition of top', async () => {
const menu = screen.getByTestId('menu-view');
expect(menu).toHaveStyle({
position: 'absolute',
- left: 100,
top: 100,
});
});
@@ -143,7 +142,6 @@ it('respects anchorPosition bottom', async () => {
const menu = screen.getByTestId('menu-view');
expect(menu).toHaveStyle({
position: 'absolute',
- left: 100,
top: 132,
});
});
diff --git a/src/components/__tests__/Searchbar.test.tsx b/src/components/__tests__/Searchbar.test.tsx
index 090df9964b..ce6db22d5c 100644
--- a/src/components/__tests__/Searchbar.test.tsx
+++ b/src/components/__tests__/Searchbar.test.tsx
@@ -124,7 +124,7 @@ it('renders clear icon wrapper, with appropriate style for v3', () => {
expect(getByTestId('search-bar-icon-wrapper')).toHaveStyle({
position: 'absolute',
right: 0,
- marginLeft: 16,
+ marginStart: 16,
});
update(
diff --git a/src/components/__tests__/Snackbar.test.tsx b/src/components/__tests__/Snackbar.test.tsx
index b9e7eec9e1..e78e1d528c 100644
--- a/src/components/__tests__/Snackbar.test.tsx
+++ b/src/components/__tests__/Snackbar.test.tsx
@@ -15,7 +15,7 @@ const styles = StyleSheet.create({
backgroundColor: red200,
padding: 15,
},
- text: { color: white, marginLeft: 10, flexWrap: 'wrap', flexShrink: 1 },
+ text: { color: white, marginStart: 10, flexWrap: 'wrap', flexShrink: 1 },
});
// Make sure any animation finishes before checking the snapshot results
diff --git a/src/components/__tests__/Surface.test.tsx b/src/components/__tests__/Surface.test.tsx
index 8d8e639779..9034b9869b 100644
--- a/src/components/__tests__/Surface.test.tsx
+++ b/src/components/__tests__/Surface.test.tsx
@@ -76,8 +76,8 @@ describe('Surface', () => {
${'width'} | ${'42%'}
${'height'} | ${'32.5%'}
${'margin'} | ${13}
- ${'marginLeft'} | ${13.1}
- ${'marginRight'} | ${13.2}
+ ${'marginStart'} | ${13.1}
+ ${'marginEnd'} | ${13.2}
${'marginTop'} | ${13.3}
${'marginBottom'} | ${13.4}
${'marginHorizontal'} | ${13.5}
@@ -107,8 +107,8 @@ describe('Surface', () => {
it.each`
property | value
${'padding'} | ${12}
- ${'paddingLeft'} | ${12.1}
- ${'paddingRight'} | ${12.2}
+ ${'paddingStart'} | ${12.1}
+ ${'paddingEnd'} | ${12.2}
${'paddingTop'} | ${12.3}
${'paddingBottom'} | ${12.4}
${'paddingHorizontal'} | ${12.5}
diff --git a/src/components/__tests__/TextInput.test.tsx b/src/components/__tests__/TextInput.test.tsx
index cf9b4feb83..394a7916bc 100644
--- a/src/components/__tests__/TextInput.test.tsx
+++ b/src/components/__tests__/TextInput.test.tsx
@@ -1,6 +1,6 @@
/* eslint-disable react-native/no-inline-styles */
import * as React from 'react';
-import { StyleSheet, Text, Platform, I18nManager } from 'react-native';
+import { StyleSheet, Text, Platform } from 'react-native';
import { fireEvent, render } from '@testing-library/react-native';
import color from 'color';
@@ -27,7 +27,7 @@ const style = StyleSheet.create({
lineHeight: 22,
},
contentStyle: {
- paddingLeft: 20,
+ paddingStart: 20,
},
});
@@ -194,7 +194,7 @@ it('correctly applies a component as the text label', () => {
expect(toJSON()).toMatchSnapshot();
});
-it('correctly applies paddingLeft from contentStyleProp', () => {
+it('correctly applies paddingStart from contentStyleProp', () => {
const { toJSON } = render(
{
expect(getByTestId('text-input').props.placeholder).toBe(' ');
});
-it('correctly applies padding offset to input label on Android when RTL', () => {
- Platform.OS = 'android';
- I18nManager.isRTL = true;
-
- const { getByTestId } = render(
-
- }
- right={
-
- }
- />
- );
-
- expect(getByTestId('text-input-flat-label-active')).toHaveStyle({
- paddingLeft: 56,
- paddingRight: 16,
- });
-
- I18nManager.isRTL = false;
-});
-
-it('correctly applies padding offset to input label on Android when LTR', () => {
- Platform.OS = 'android';
-
- const { getByTestId } = render(
-
- }
- right={
-
- }
- />
- );
-
- expect(getByTestId('text-input-flat-label-active')).toHaveStyle({
- paddingLeft: 16,
- paddingRight: 56,
- });
-});
-
it('calls onLayout on right-side affix adornment', () => {
const onLayoutMock = jest.fn();
const nativeEventMock = {
diff --git a/src/components/__tests__/__snapshots__/BottomNavigation.test.tsx.snap b/src/components/__tests__/__snapshots__/BottomNavigation.test.tsx.snap
index e304d8827c..7743b34861 100644
--- a/src/components/__tests__/__snapshots__/BottomNavigation.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/BottomNavigation.test.tsx.snap
@@ -239,8 +239,8 @@ exports[`allows customizing Route's type via generics 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -481,8 +481,8 @@ exports[`allows customizing Route's type via generics 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -957,8 +957,8 @@ exports[`hides labels in non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -1154,8 +1154,8 @@ exports[`hides labels in non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -1351,8 +1351,8 @@ exports[`hides labels in non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -1707,8 +1707,8 @@ exports[`hides labels in shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -1904,8 +1904,8 @@ exports[`hides labels in shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -2101,8 +2101,8 @@ exports[`hides labels in shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -2588,8 +2588,8 @@ exports[`renders bottom navigation with getLazy 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -2904,8 +2904,8 @@ exports[`renders bottom navigation with getLazy 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -3220,8 +3220,8 @@ exports[`renders bottom navigation with getLazy 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -3536,8 +3536,8 @@ exports[`renders bottom navigation with getLazy 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -3852,8 +3852,8 @@ exports[`renders bottom navigation with getLazy 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -4332,8 +4332,8 @@ exports[`renders bottom navigation with scene animation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -4598,8 +4598,8 @@ exports[`renders bottom navigation with scene animation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -4864,8 +4864,8 @@ exports[`renders bottom navigation with scene animation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -5130,8 +5130,8 @@ exports[`renders bottom navigation with scene animation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -5396,8 +5396,8 @@ exports[`renders bottom navigation with scene animation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -5750,8 +5750,8 @@ exports[`renders custom icon and label in non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -5927,8 +5927,8 @@ exports[`renders custom icon and label in non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -6104,8 +6104,8 @@ exports[`renders custom icon and label in non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -6445,8 +6445,8 @@ exports[`renders custom icon and label in shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -6609,8 +6609,8 @@ exports[`renders custom icon and label in shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -6773,8 +6773,8 @@ exports[`renders custom icon and label in shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -6937,8 +6937,8 @@ exports[`renders custom icon and label in shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -7101,8 +7101,8 @@ exports[`renders custom icon and label in shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -7485,8 +7485,8 @@ exports[`renders custom icon and label with custom colors in non-shifting bottom
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -7801,8 +7801,8 @@ exports[`renders custom icon and label with custom colors in non-shifting bottom
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -8117,8 +8117,8 @@ exports[`renders custom icon and label with custom colors in non-shifting bottom
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -8597,8 +8597,8 @@ exports[`renders custom icon and label with custom colors in shifting bottom nav
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -8863,8 +8863,8 @@ exports[`renders custom icon and label with custom colors in shifting bottom nav
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -9129,8 +9129,8 @@ exports[`renders custom icon and label with custom colors in shifting bottom nav
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -9549,8 +9549,8 @@ exports[`renders non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -9865,8 +9865,8 @@ exports[`renders non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -10181,8 +10181,8 @@ exports[`renders non-shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -10661,8 +10661,8 @@ exports[`renders shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -10927,8 +10927,8 @@ exports[`renders shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -11193,8 +11193,8 @@ exports[`renders shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -11459,8 +11459,8 @@ exports[`renders shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
@@ -11725,8 +11725,8 @@ exports[`renders shifting bottom navigation 1`] = `
style={
[
{
- "left": 0,
"position": "absolute",
+ "start": 0,
},
{
"right": 0,
diff --git a/src/components/__tests__/__snapshots__/Button.test.tsx.snap b/src/components/__tests__/__snapshots__/Button.test.tsx.snap
index c023baed3b..952289dd19 100644
--- a/src/components/__tests__/__snapshots__/Button.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/Button.test.tsx.snap
@@ -851,16 +851,16 @@ exports[`renders button with icon 1`] = `
style={
[
{
- "marginLeft": 12,
- "marginRight": -4,
+ "marginEnd": -4,
+ "marginStart": 12,
},
{
- "marginLeft": 16,
- "marginRight": -16,
+ "marginEnd": -16,
+ "marginStart": 16,
},
{
- "marginLeft": 12,
- "marginRight": -8,
+ "marginEnd": -8,
+ "marginStart": 12,
},
]
}
@@ -1059,16 +1059,16 @@ exports[`renders button with icon in reverse order 1`] = `
style={
[
{
- "marginLeft": -4,
- "marginRight": 12,
+ "marginEnd": 12,
+ "marginStart": -4,
},
{
- "marginLeft": -16,
- "marginRight": 16,
+ "marginEnd": 16,
+ "marginStart": -16,
},
{
- "marginLeft": -8,
- "marginRight": 12,
+ "marginEnd": 12,
+ "marginStart": -8,
},
]
}
@@ -1578,16 +1578,16 @@ exports[`renders loading button 1`] = `
},
[
{
- "marginLeft": 12,
- "marginRight": -4,
+ "marginEnd": -4,
+ "marginStart": 12,
},
{
- "marginLeft": 16,
- "marginRight": -16,
+ "marginEnd": -16,
+ "marginStart": 16,
},
{
- "marginLeft": 12,
- "marginRight": -8,
+ "marginEnd": -8,
+ "marginStart": 12,
},
],
]
diff --git a/src/components/__tests__/__snapshots__/Chip.test.tsx.snap b/src/components/__tests__/__snapshots__/Chip.test.tsx.snap
index f75512145c..ac57550e7b 100644
--- a/src/components/__tests__/__snapshots__/Chip.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/Chip.test.tsx.snap
@@ -20,6 +20,7 @@ exports[`renders chip with close button 1`] = `
>
@@ -244,10 +245,10 @@ exports[`renders chip with close button 1`] = `
"padding": 4,
},
{
- "marginRight": 4,
+ "marginEnd": 4,
},
{
- "marginRight": 8,
+ "marginEnd": 8,
"padding": 0,
},
]
@@ -315,6 +316,7 @@ exports[`renders chip with custom close button 1`] = `
>
@@ -539,10 +541,10 @@ exports[`renders chip with custom close button 1`] = `
"padding": 4,
},
{
- "marginRight": 4,
+ "marginEnd": 4,
},
{
- "marginRight": 8,
+ "marginEnd": 8,
"padding": 0,
},
]
@@ -610,6 +612,7 @@ exports[`renders chip with icon 1`] = `
>
@@ -523,7 +530,7 @@ exports[`renders list item with left and right items 1`] = `
style={
[
{
- "paddingLeft": 16,
+ "paddingStart": 16,
},
{
"flexGrow": 1,
@@ -607,8 +614,8 @@ exports[`renders list item with left and right items 1`] = `
},
{
"alignSelf": "center",
- "marginLeft": 16,
- "marginRight": 0,
+ "marginEnd": 0,
+ "marginStart": 16,
},
]
}
@@ -690,7 +697,7 @@ exports[`renders list item with left item 1`] = `
false,
[
{
- "paddingRight": 24,
+ "paddingEnd": 24,
"paddingVertical": 8,
},
undefined,
@@ -700,11 +707,13 @@ exports[`renders list item with left item 1`] = `
>
@@ -307,7 +307,7 @@ exports[`renders snackbar with action button 1`] = `
"minHeight": 48,
},
{
- "marginLeft": -12,
+ "marginStart": -12,
},
]
}
@@ -318,8 +318,8 @@ exports[`renders snackbar with action button 1`] = `
{
"backgroundColor": "transparent",
"borderRadius": 20,
- "marginLeft": 4,
- "marginRight": 8,
+ "marginEnd": 8,
+ "marginStart": 4,
"shadowColor": "#000",
"shadowOffset": {
"height": 0,
diff --git a/src/components/__tests__/__snapshots__/TextInput.test.tsx.snap b/src/components/__tests__/__snapshots__/TextInput.test.tsx.snap
index d89ec4b1e0..66abecb40d 100644
--- a/src/components/__tests__/__snapshots__/TextInput.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/TextInput.test.tsx.snap
@@ -818,7 +818,7 @@ exports[`correctly applies height to multiline Outline TextInput 1`] = `
`;
-exports[`correctly applies paddingLeft from contentStyleProp 1`] = `
+exports[`correctly applies paddingStart from contentStyleProp 1`] = `
(defaultDirection);
+const isWeb = Platform.OS === 'web';
+
+const useLocale = () => {
+ const direction = useContext(context);
+ const localeProps = direction === 'rtl' ? { dir: 'rtl' } : {};
+
+ // Since I18nManager is mocked in react-native-web (https://github.com/necolas/react-native-web/releases/tag/0.18.0)
+ // we have to rely on default react-native-web positioning for RTL languages.
+ // Most of the time this works out of the box, but in few specific cases
+ // we need to overwrite right/left ourselves.
+ const overwriteRTL = isWeb && direction === 'rtl';
+
+ return {
+ direction,
+ localeProps,
+ overwriteRTL,
+ };
+};
+
+export const { Provider: LocalizationProvider } = context;
+
+export { Direction, useLocale };
diff --git a/src/core/PaperProvider.tsx b/src/core/PaperProvider.tsx
index 355818a957..32621d2c22 100644
--- a/src/core/PaperProvider.tsx
+++ b/src/core/PaperProvider.tsx
@@ -3,9 +3,12 @@ import {
AccessibilityInfo,
Appearance,
ColorSchemeName,
+ I18nManager,
NativeEventSubscription,
+ Platform,
} from 'react-native';
+import { Direction, LocalizationProvider } from './Localization';
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
import { Provider as SettingsProvider, Settings } from './settings';
import { defaultThemesByVersion, ThemeProvider } from './theming';
@@ -18,6 +21,7 @@ export type Props = {
children: React.ReactNode;
theme?: ThemeProp;
settings?: Settings;
+ direction?: Direction;
};
const PaperProvider = (props: Props) => {
@@ -76,6 +80,25 @@ const PaperProvider = (props: Props) => {
};
}, [props.theme, isOnlyVersionInTheme]);
+ React.useEffect(() => {
+ if (!props.direction || !['rtl', 'ltr'].includes(props.direction)) {
+ return;
+ }
+
+ const isRTL = props.direction === 'rtl';
+
+ if (Platform.OS === 'web') {
+ const htmlDir = document.documentElement.getAttribute('dir');
+ if (isRTL && htmlDir !== 'rtl') {
+ document.documentElement.setAttribute('dir', 'rtl');
+ }
+ } else {
+ if (isRTL && !I18nManager.isRTL) {
+ I18nManager.forceRTL(isRTL);
+ }
+ }
+ }, [props.direction]);
+
const getTheme = () => {
const themeVersion = props.theme?.version || 3;
const scheme = colorScheme || 'light';
@@ -97,21 +120,27 @@ const PaperProvider = (props: Props) => {
};
};
- const { children, settings } = props;
+ const {
+ children,
+ settings,
+ direction = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr',
+ } = props;
return (
-
-
- {children}
-
-
+
+
+
+ {children}
+
+
+
);
};
diff --git a/src/react-navigation/views/MaterialBottomTabView.tsx b/src/react-navigation/views/MaterialBottomTabView.tsx
index df846ae2fb..b734303678 100644
--- a/src/react-navigation/views/MaterialBottomTabView.tsx
+++ b/src/react-navigation/views/MaterialBottomTabView.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { I18nManager, Platform, StyleSheet } from 'react-native';
+import { Platform, StyleSheet } from 'react-native';
import {
CommonActions,
@@ -12,6 +12,7 @@ import {
import BottomNavigation from '../../components/BottomNavigation/BottomNavigation';
import MaterialCommunityIcon from '../../components/MaterialCommunityIcon';
+import { useLocale } from '../../core/Localization';
import type {
MaterialBottomTabDescriptorMap,
MaterialBottomTabNavigationConfig,
@@ -29,6 +30,7 @@ export default function MaterialBottomTabView({
descriptors,
...rest
}: Props) {
+ const { direction } = useLocale();
const buildLink = useLinkBuilder();
return (
@@ -76,7 +78,7 @@ export default function MaterialBottomTabView({
if (typeof options.tabBarIcon === 'string') {
return (
{
backgroundColor: 'red',
marginTop: 1,
marginBottom: 2,
- marginLeft: 3,
+ marginStart: 3,
padding: 4,
borderTopLeftRadius: 5,
borderTopRightRadius: 6,