简体   繁体   English

如何为 Material UI React 添加 RTL 支持

[英]How to add RTL support for Material UI React

I'm building an LTR application and I want to add RTL support.我正在构建 LTR 应用程序,我想添加 RTL 支持。 The application is based on top of Material UI React.该应用程序基于 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.我能够将应用程序旋转到 RTL,因为我使用的是 CSS Flex Box,只需将 dir="rtl" 添加到正文即可。 I also added direction="rtl" to the theme as mentioned here .我还在此处提到的主题中添加了 direction="rtl" 。

However not everything was changed.然而,并非一切都改变了。

Let's take this as an example:让我们以此为例: 选择国家 LTR 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:在 RTL 版本中,由于一切都被颠倒了,左边的填充在 UI 中没有影响,我的意思是它必须向右填充以显示两个元素之间的小空间: 选择国家 RTL

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.看来我做错了什么,因为在此处的 Material UI 文档中,此功能必须在添加此代码段并将组件包裹在其周围后开箱即用。

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):这是我的组件的一个例子(上图中的那个: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:最后一个文件是 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;

I'm expecting that paddingLeft of label to be => paddingRight.我期望 label 的 paddingLeft 为 => 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?或者我应该只使用RTL-CSS-JS并在正文包含 dir="RTL" 时包装我所有的 styles object 以自动更改样式?

I'm also so confused between these two libraries:我对这两个库也很困惑:

  • @material-ui/core/styles @material-ui/核心/样式
  • @material-ui/styles @material-ui/样式

Should I use the first or the second one?我应该使用第一个还是第二个? What is the difference?有什么区别?

Thanks for you time.谢谢你的时间。

EDIT 1:编辑 1:

I used rtlCSSJS on my CSS Object and I get the expected result.我在我的 CSS Object上使用了 rtlCSSJS,我得到了预期的结果。 But I'm not sure if this is the best way to do it.但我不确定这是否是最好的方法。 The CSS of CLList now look like this: 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;

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. Material UI 默认使用 jss-rtl,最后一个是 rtl-css-js 的包装器。 So there is no need to use rtl-css-js directly in since Material UI will do the job.所以不需要直接使用 rtl-css-js,因为 Material UI 会完成这项工作。

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;
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重要笔记

  • I use MUI version 5我使用 MUI 版本 5
  • MUI version 5 uses emotion css as default styling engine MUI 版本 5 使用 emotion css 作为默认样式引擎
  • CacheProvider is used to configure the RTL or LTR CacheProvider 用于配置 RTL 或 LTR
  • ThemeProvider must be wrapped inside CacheProvider ThemeProvider 必须包裹在 CacheProvider 里面
  • make sure to use useMemo when passing the value to CacheProvider or if you're going to use other Providers when changing default styling engine like ( StylesProvider for JSS & StyleSheetManager for styled-components )确保在将值传递给 CacheProvider 时使用useMemo ,或者如果您要在更改默认样式引擎时使用其他提供程序,例如(JSS 的 StylesProvider 和样式组件的 StyleSheetManager)

Please, everyone, forget about the documentation and about everything written around because none of them neither clear nor complete if you are using v4.mui.com I will show you how to make it RTL and you are going to implement the switching between RTL and LTR create a file save it in utils/stylesprovider.js请大家忘记文档和周围写的所有内容,因为如果您使用的是 v4.mui.com,它们都不清楚也不完整。我将向您展示如何使其成为 RTL,您将实现 RTL 和LTR 创建文件保存在 utils/stylesprovider.js

with the following content具有以下内容

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')
);

then go to your index and make it something like this然后去你的索引,让它像这样

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')
);

don't forget to install不要忘记安装

npm install jss-rtl npm 安装 jss-rtl

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM