Как добавить поддержку RTL для Material UI React
Я создаю приложение LTR и хочу добавить поддержку RTL. Приложение основано на материале пользовательского интерфейса React. Я могу повернуть приложение в RTL, так как я использую CSS Flex Box, просто добавив dir="rtl" в тело. Я также добавил direction="rtl" к теме, как упомянуто здесь.
Однако не все было изменено.
Давайте возьмем это в качестве примера: Как вы можете видеть здесь, у меня есть отступ слева от текстового элемента. В версии 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);
И последний файл - это 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" для автоматического изменения стиля?
Я также так запутался между этими двумя библиотеками:
- @ материал-UI / ядро / стили
- @ материал-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;
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 в соответствии с языком, и мы можем сосредоточиться только на одной компоновке, вся работа по обращению выполняется благодаря этим плагинам.
Также я хочу сказать, что следующие инструкции в официальной документации не работают для меня! Большая часть этого решения, которое я нашел, основано на ответе здесь.
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 для стилизованных компонентов )
Пожалуйста, все, забудьте о документации и обо всем, что написано вокруг, потому что ни одна из них не является ни ясной, ни полной, если вы используете 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