8

我正在构建一个 LTR 应用程序,我想添加 RTL 支持。该应用程序基于 Material UI React。由于我使用的是 CSS Flex Box,因此我可以将应用程序旋转到 RTL,只需将 dir="rtl" 添加到正文即可。我还在此处提到的主题中添加了 direction="rtl" 。

然而,并非一切都改变了。

让我们以此为例: 选择国家 LTR 正如您在此处看到的,我在文本元素的左侧进行了填充。在 RTL 版本中,由于一切都被颠倒了,所以向左填充在 UI 中没有任何影响,我的意思是它必须向右填充以显示两个元素之间的小空间: 选择国家 RTL

似乎我做错了什么,因为在此处的 Material UI 文档中,在添加此代码段并将组件包装在其周围后,此功能必须是开箱即用的。

这是我的父组件 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/核心/样式
  • @material-ui/样式

我应该使用第一个还是第二个?有什么区别 ?

谢谢你的时间。

编辑 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;
4

3 回答 3

5

我想我找到了解决我自己问题的方法,但是可以随意添加任何增强或更好的解决方案。

Material UI 默认使用 jss-rtl,最后一个是 rtl-css-js 的包装器。所以不需要直接使用 rtl-css-js,因为 Material UI 会完成这项工作。

我将我的父应用程序组件更改为:

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 之间切换,我们可以只专注于一个布局,所有的逆向工作都通过这个插件完成。

另外我想说按照官方文档中的说明对我不起作用!我发现的大部分解决方案都是基于此处的答案。

于 2019-05-27T22:32:49.063 回答
0

请大家忘记文档和周围写的所有内容,因为如果您使用的是 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

于 2022-01-22T20:53:59.403 回答
0
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,或者在更改默认样式引擎时使用其他提供者,例如(JSS 的 StylesProvider 和 styled-components 的 StyleSheetManager )
于 2022-02-13T00:13:19.093 回答