I'm building an LTR application and I want to add RTL support. The application is based on top of Material UI React. I'm able to rotate the application to RTL since I'm using CSS Flex Box, just by adding dir="rtl" to the body. I also added direction="rtl" to the theme as mentioned here.
However not everything was changed.
Let's take this as an example :
As you can see here I have padding left to the text element.
In the RTL version, since everything was reversed the padding left has no effect in the UI, I mean it must be padding right to show the small space between the two element :
It seems that I'm doing something wrong because in Material UI documentation here this feature must be out of the box after adding this snippet and wrap the component around it.
This is my Parent component App :
import React, { PureComponent } from "react";
import { theme } from "./styling/theme";
import Routes from "./Routes";
// Redux
import { Provider } from "react-redux";
import store from "./app/store";
import LoadingBar from "react-redux-loading-bar";
// CSS
import { MuiThemeProvider } from "@material-ui/core/styles";
// import { ThemeProvider } from "@material-ui/styles";
import { create } from "jss";
import rtl from "jss-rtl";
import JssProvider from "react-jss/lib/JssProvider";
// import { StylesProvider, jssPreset } from "@material-ui/styles";
import { createGenerateClassName, jssPreset } from "@material-ui/core/styles";
import { themeObject, colors } from "./styling/theme";
// Helpers
import get from "lodash/get";
// Configure JSS
const jss = create({ plugins: [...jssPreset().plugins, rtl()] });
const generateClassName = createGenerateClassName();
function RTL(props) {
return (
<JssProvider jss={jss} generateClassName={generateClassName}>
{
props.children
}
</JssProvider>
);
}
class App extends PureComponent {
render() {
const isRtl = get(store, "classified.language.rtl", false);
return (
<Provider store={store}>
<RTL>
<MuiThemeProvider
theme={
isRtl
? { ...theme, direction: "rtl" }
: { ...theme, direction: "ltr" }
}
>
<LoadingBar
style={{
backgroundColor: colors.primary[500],
height: themeObject.spacing.unit,
zIndex: 9999
}}
/>
<Routes />
</MuiThemeProvider>
</RTL>
</Provider>
);
}
}
export default App;
And this is an example of my components (The one in the pictures above : CLList) :
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withStyles } from "@material-ui/core/styles";
// Helpers
import isFunction from "lodash/isFunction";
import cloneDeep from "lodash/cloneDeep";
import styles from "./CLList.styles";
const defaultImg = "IMAGE_URL_HERE";
class CLList extends Component {
static propTypes = {
classes: PropTypes.object.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
img: PropTypes.string,
name: PropTypes.string
})
).isRequired,
onClick: PropTypes.func
};
render() {
const { classes, items, onClick } = this.props;
return (
<ul className={classes.list}>
{items.map((item, key) => (
<li
className={classes.item}
onClick={() => isFunction(onClick) && onClick(cloneDeep(item))}
key={key}
>
<img
className={classes.image}
src={item.img || defaultImg}
alt={item.name}
title={item.name}
/>
<span className={classes.label}>{item.name}</span>
</li>
))}
</ul>
);
}
}
export default withStyles(styles)(CLList);
And the last file is the CSS for CLList :
import { colors } from "../..";
const styles = theme => ({
list: {
display: "flex",
flexDirection: "column",
listStyle: "none",
padding: 5,
margin: 0,
"& > li:not(:last-child)": {
marginBottom: 10
}
},
item: {
flex: 1,
display: "flex",
cursor: "pointer",
"&:hover": {
backgroundColor: colors.primary[50]
}
},
image: {
flex: "0 0 15%",
maxWidth: "40px",
maxHeight: "40px"
},
label: {
flex: "1",
alignSelf: "center",
paddingLeft: 20
}
});
export default styles;
I'm expecting that paddingLeft of label to be => paddingRight. Is this possible ? Is it a feature out of the box ? Or should I just use RTL-CSS-JS and wrap all my styles object when the body contains dir="RTL" to change automatically the style ?
I'm also so confused between these two libraries :
Should I use the first or the second one ? What is the difference ?
Thanks for you time.
EDIT 1 :
I used rtlCSSJS on my CSS Object and I get the expected result. But I'm not sure if this is the best way to do it. The CSS of CLList now look like this :
import rtlCSSJS from "rtl-css-js";
import { colors } from "../..";
const defaultDir = document.body.getAttribute("dir");
const styles = theme =>
defaultDir === 'rtl' ? rtlCSSJS({...CSS_HERE....}) : {...CSS_HERE....};
export default styles;
Right To Left (RTL) can be enabled for Syncfusion Angular UI components by calling enableRtl with true . This will render all the Syncfusion Angular UI components in right to left direction. We can enable the feature by setting the property enableRtl value as true.
In order to support the habits of languages such as Arabic and Hebrew, the UI of the language read from right to left (RTL).
I think I found a solution to my own question, however feel free to add any enhancement or better solution.
Material UI is using jss-rtl by default and this last one is a wrapper for rtl-css-js. So there is no need to use rtl-css-js directly in since Material UI will do the job.
I changed my Parent App component to :
import React, { PureComponent } from "react";
import Routes from "./Routes";
import RTL from "./RTL";
// Redux
import { Provider } from "react-redux";
import store from "./app/store";
import LoadingBar from "react-redux-loading-bar";
import { themeObject, colors } from "./styling/theme";
class App extends PureComponent {
render() {
return (
<Provider store={store}>
<RTL>
<>
<LoadingBar
// className="loading"
style={{
backgroundColor: colors.primary[500],
height: themeObject.spacing.unit,
zIndex: 9999
}}
/>
<Routes />
</>
</RTL>
</Provider>
);
}
}
export default App;
And I added RTL component which will connect to Redux to define the correct theme with the right direction. I'm saving the language data in Redux, and according the this data I will define the theme provided for my application.
This is the RTL component :
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
// Redux
import { connect } from "react-redux";
// CSS
import { MuiThemeProvider, createMuiTheme } from "@material-ui/core/styles";
import { create } from "jss";
import rtl from "jss-rtl";
import JssProvider from "react-jss/lib/JssProvider";
import { createGenerateClassName, jssPreset } from "@material-ui/core/styles";
// Theme
import { themeObject } from "./styling/theme";
// Helpers
import get from "lodash/get";
// Configure JSS
const jss = create({ plugins: [...jssPreset().plugins, rtl()] });
const generateClassName = createGenerateClassName();
const G_isRtl = document.body.getAttribute("dir") === "rtl";
class RTL extends PureComponent {
static propTypes = {
children: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.node
]),
language: PropTypes.object
};
render() {
const { children, language } = this.props;
const isRtl = get(language, "rtl", G_isRtl);
const theme = createMuiTheme({
...themeObject,
direction: isRtl ? "rtl" : "ltr"
});
return (
<JssProvider jss={jss} generateClassName={generateClassName}>
<MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>
</JssProvider>
);
}
}
const mapStateToProps = ({ classified }) => ({
language: classified.language
});
export default connect(mapStateToProps)(RTL);
All the children components now will switch between RTL and LTR according to the language and we can focus on only one layout, all the reversing job is done thanks to this Plugins.
Also I want to say folowing the instructions in the official documentation does not work for me ! Most of this solution that I found is based on the answer here.
import React, { useState, createContext, useMemo, useEffect } from 'react';
import PropTypes from 'prop-types';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { SnackbarProvider } from 'notistack';
import { Box } from '@mui/material';
import languageList from 'shared/languageList';
import { useTranslation } from 'react-i18next';
import rtlPlugin from 'stylis-plugin-rtl';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { prefixer } from 'stylis';
export const AppThemeContext = createContext({});
const AppTheme = ({ children }) => {
const { i18n } = useTranslation();
const [dir, setDir] = useState(i18n.language === 'ar' ? 'rtl' : 'ltr');
const [language, setLanguage] = useState(i18n.language);
const toggleLanguage = async (language) => {
setLanguage(language.value);
switch (language.value) {
case 'ar':
document.body.setAttribute('dir', 'rtl');
setDir('rtl');
await i18n.changeLanguage('ar');
break;
case 'en':
document.body.setAttribute('dir', 'ltr');
setDir('ltr');
await i18n.changeLanguage('en');
break;
}
};
const theme = useMemo(() => {
const arabicFont = '""serif", "Arial", "sans-serif"';
const englishFont = '"Roboto","Helvetica","Arial",sans-serif';
const typography = {
button: {
textTransform: 'capitalize',
},
fontSize: dir === 'rtl' ? 15 : 14,
fontFamily: dir === 'rtl' ? arabicFont : englishFont,
};
return createTheme({
direction: dir,
typography,
});
}, [dir, colorMode]);
const direction = useMemo(() => {
return dir === 'ltr' ? 'left' : 'right';
}, [dir]);
// this is the most important part
const cacheRtl = useMemo(() => {
if (dir === 'rtl') {
return createCache({
key: 'muirtl',
stylisPlugins: [prefixer, rtlPlugin],
});
} else {
return createCache({ key: 'css' });
}
}, [dir]);
useEffect(async () => {
await toggleLanguage({ value: language });
}, []);
const toggleColorMode = () =>
setColorMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
return (
<AppThemeContext.Provider
value={{
language,
toggleLanguage,
languageList,
direction,
colorMode,
toggleColorMode,
}}>
<Box component="main">
<CacheProvider value={cacheRtl}>
<ThemeProvider theme={theme}>
<SnackbarProvider maxSnack={3}>{children}</SnackbarProvider>
</ThemeProvider>
</CacheProvider>
</Box>
</AppThemeContext.Provider>
);
};
AppTheme.propTypes = {
children: PropTypes.any,
};
export default AppTheme;
Important notes
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With