Как добавить поддержку RTL для Material UI React

Я создаю приложение LTR и хочу добавить поддержку RTL. Приложение основано на Material UI React. Я могу повернуть приложение в RTL, так как я использую CSS Flex Box, просто добавив dir="rtl" в тело. Я также добавил в тему direction="rtl" как уже упоминалось здесь.

Однако не все было изменено.

Возьмем это в качестве примера: Как добавить поддержку RTL для Material UI React Как вы можете видеть здесь, у меня есть отступы слева от текстового элемента. В версии RTL, поскольку все было перевернуто, отступ слева не влияет на пользовательский интерфейс, я имею в виду, что он должен быть отступом справа, чтобы показать небольшое пространство между двумя элементами: Как добавить поддержку RTL для Material UI React

Кажется, я делаю что-то не так, потому что в документации Material UI здесь эта функция должна быть из коробки после добавления этого фрагмента и обтекания компонента вокруг него.

Это приложение моего родительского компонента:

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

И последний файл — это CSS для 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;

Я ожидаю, что paddingLeft метки будет => paddingRight. Это возможно ? Это функция из коробки? Или я должен просто использовать RTL-CSS-JS и обернуть все мои объекты стилей, когда тело содержит dir="RTL" для автоматического изменения стиля?

Я также так запутался между этими двумя библиотеками:

  • @material-ui/ядро/стили
  • @материал-интерфейс/стили

Должен ли я использовать первый или второй? В чем разница ?

Спасибо за ваше время.

РЕДАКТИРОВАТЬ 1:

Я использовал rtlCSSJS для своего объекта CSS и получил ожидаемый результат. Но я не уверен, что это лучший способ сделать это. CSS CLList теперь выглядит так:

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;
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
8
0
6 037
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Я думаю, что нашел решение своего вопроса, однако не стесняйтесь добавлять любые улучшения или лучшие решения.

Пользовательский интерфейс материалов по умолчанию использует 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 в зависимости от языка, и мы можем сосредоточиться только на одном макете, вся реверсивная работа выполняется благодаря этим плагинам.

Также хочу сказать, что по инструкции в официальном документация у меня не работает! Большая часть этого решения, которое я нашел, основана на ответе здесь.

Я думаю, что это решение для mui v4. а как же v5?

shadi 20.02.2022 10:52

Просьба всем забыть о документации и обо всем, что написано вокруг, потому что ни одна из них не является ни ясной, ни полной. если вы используете 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
  • обязательно используйте useMemo при передаче значения в CacheProvider или если вы собираетесь использовать другие провайдеры при изменении механизма стилей по умолчанию, например ( StylesProvider для JSS и StyleSheetManager для стилизованных компонентов )

Другие вопросы по теме