如何为 Material UI React 添加 RTL 支持
How to add RTL support for Material UI React
我正在构建 LTR 应用程序,我想添加 RTL 支持。
该应用程序基于 Material UI React。
我能够将应用程序旋转到 RTL,因为我使用的是 CSS Flex Box,只需将 dir="rtl" 添加到正文即可。我还在主题中添加了 direction="rtl" here.
然而并没有改变一切。
让我们以此为例:
正如您在这里看到的,我在文本元素中留有填充。
在 RTL 版本中,由于一切都被颠倒了,左边的填充在 UI 中没有效果,我的意思是它必须向右填充以显示两个元素之间的小 space:
似乎我做错了什么,因为在 Material UI 文档 here 中,此功能必须在添加此代码段并将组件包裹在其周围后开箱即用.
这是我的父组件 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;
这是我的组件示例(上图中的那个: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);
最后一个文件是 CLList 的 CSS :
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;
我希望标签的 paddingLeft 为 => paddingRight。这可能吗 ?它是开箱即用的功能吗?或者我应该只使用 RTL-CSS-JS 并在正文包含 dir="RTL" 时包装我的所有样式对象以自动更改样式?
我对这两个库也很困惑:
- @material-ui/core/styles
- @material-ui/styles
我应该使用第一个还是第二个?有什么区别?
谢谢你的时间。
编辑 1:
我在我的 CSS 对象上使用了 rtlCSSJS,我得到了预期的结果。但我不确定这是否是最好的方法。
CLList 的 CSS 现在看起来像这样:
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;
我想我找到了自己问题的解决方案,但请随时添加任何增强功能或更好的解决方案。
Material UI 默认使用 jss-rtl,最后一个是 rtl-css-js 的包装器。所以不需要直接使用 rtl-css-js 因为 Material UI 会完成工作。
我将我的 Parent App 组件更改为:
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;
并且我添加了 RTL 组件,它将连接到 Redux 以正确的方向定义正确的主题。我将语言数据保存在 Redux 中,根据这些数据我将定义为我的应用程序提供的主题。
这是 RTL 组件:
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);
现在所有的子组件都会根据语言在 RTL 和 LTR 之间切换,我们可以只关注一种布局,所有的反转工作都通过这个插件完成了。
另外我想按照官方的说明说documentation does not work for me ! Most of this solution that I found is based on the answer here。
拜托大家,忘记文档和周围写的所有东西,因为 none 其中既不清晰也不完整
如果你正在使用
v4.mui.com
我将向您展示如何使其成为 RTL,您将要实现 RTL 和 LTR 之间的切换
创建一个文件将其保存在 utils/stylesprovider.js
内容如下
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Suspense} from 'react';
import {createStore, applyMiddleware, compose} from 'redux'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import reducers from './reducers/'
import './i18n';
import RTL from "./utils/stylesprovider";
import {createTheme} from "@material-ui/core";
const store = createStore(reducers, applyMiddleware(thunk))
ReactDOM.render(
<RTL>
<Provider store={store} >
<Suspense fallback="...is loading">
<App/>
</Suspense>
</Provider>
</RTL>,
document.getElementById('root')
);
然后转到您的索引并将其制作成这样
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Suspense} from 'react';
import {createStore, applyMiddleware, compose} from 'redux'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import reducers from './reducers/'
import './i18n';
import RTL from "./utils/stylesprovider";
const store = createStore(reducers, applyMiddleware(thunk))
ReactDOM.render(
<RTL>
<Provider store={store} >
<Suspense fallback="...is loading">
<App/>
</Suspense>
</Provider>
</RTL>,
document.getElementById('root')
);
别忘了安装
npm 安装jss-rtl
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;
重要提示
- 我使用 MUI 版本 5
- MUI 版本 5 使用情感 css 作为默认样式引擎
- CacheProvider用于配置RTL或LTR
- ThemeProvider 必须包装在 CacheProvider 中
- 确保在将值传递给 CacheProvider 时使用 useMemo,或者如果您要在更改默认样式引擎时使用其他提供程序,例如( StylesProvider for JSS & StyleSheetManager for styled-components )
我正在构建 LTR 应用程序,我想添加 RTL 支持。 该应用程序基于 Material UI React。 我能够将应用程序旋转到 RTL,因为我使用的是 CSS Flex Box,只需将 dir="rtl" 添加到正文即可。我还在主题中添加了 direction="rtl" here.
然而并没有改变一切。
让我们以此为例:
似乎我做错了什么,因为在 Material UI 文档 here 中,此功能必须在添加此代码段并将组件包裹在其周围后开箱即用.
这是我的父组件 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;
这是我的组件示例(上图中的那个: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);
最后一个文件是 CLList 的 CSS :
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;
我希望标签的 paddingLeft 为 => paddingRight。这可能吗 ?它是开箱即用的功能吗?或者我应该只使用 RTL-CSS-JS 并在正文包含 dir="RTL" 时包装我的所有样式对象以自动更改样式?
我对这两个库也很困惑:
- @material-ui/core/styles
- @material-ui/styles
我应该使用第一个还是第二个?有什么区别?
谢谢你的时间。
编辑 1:
我在我的 CSS 对象上使用了 rtlCSSJS,我得到了预期的结果。但我不确定这是否是最好的方法。 CLList 的 CSS 现在看起来像这样:
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;
我想我找到了自己问题的解决方案,但请随时添加任何增强功能或更好的解决方案。
Material UI 默认使用 jss-rtl,最后一个是 rtl-css-js 的包装器。所以不需要直接使用 rtl-css-js 因为 Material UI 会完成工作。
我将我的 Parent App 组件更改为:
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;
并且我添加了 RTL 组件,它将连接到 Redux 以正确的方向定义正确的主题。我将语言数据保存在 Redux 中,根据这些数据我将定义为我的应用程序提供的主题。
这是 RTL 组件:
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);
现在所有的子组件都会根据语言在 RTL 和 LTR 之间切换,我们可以只关注一种布局,所有的反转工作都通过这个插件完成了。
另外我想按照官方的说明说documentation does not work for me ! Most of this solution that I found is based on the answer here。
拜托大家,忘记文档和周围写的所有东西,因为 none 其中既不清晰也不完整 如果你正在使用 v4.mui.com 我将向您展示如何使其成为 RTL,您将要实现 RTL 和 LTR 之间的切换 创建一个文件将其保存在 utils/stylesprovider.js
内容如下
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Suspense} from 'react';
import {createStore, applyMiddleware, compose} from 'redux'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import reducers from './reducers/'
import './i18n';
import RTL from "./utils/stylesprovider";
import {createTheme} from "@material-ui/core";
const store = createStore(reducers, applyMiddleware(thunk))
ReactDOM.render(
<RTL>
<Provider store={store} >
<Suspense fallback="...is loading">
<App/>
</Suspense>
</Provider>
</RTL>,
document.getElementById('root')
);
然后转到您的索引并将其制作成这样
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Suspense} from 'react';
import {createStore, applyMiddleware, compose} from 'redux'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import reducers from './reducers/'
import './i18n';
import RTL from "./utils/stylesprovider";
const store = createStore(reducers, applyMiddleware(thunk))
ReactDOM.render(
<RTL>
<Provider store={store} >
<Suspense fallback="...is loading">
<App/>
</Suspense>
</Provider>
</RTL>,
document.getElementById('root')
);
别忘了安装
npm 安装jss-rtl
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;
重要提示
- 我使用 MUI 版本 5
- MUI 版本 5 使用情感 css 作为默认样式引擎
- CacheProvider用于配置RTL或LTR
- ThemeProvider 必须包装在 CacheProvider 中
- 确保在将值传递给 CacheProvider 时使用 useMemo,或者如果您要在更改默认样式引擎时使用其他提供程序,例如( StylesProvider for JSS & StyleSheetManager for styled-components )