Right to left (RTL) support in React - javascript

What would be the best way to implement RTL support in React apps? Is there a way to override default <p> and <span> tags (components) to add RTL support so that I don't have to rewrite components I already wrote to have RTL support? (for example, to have some global variable window.RTL, so when set to true to have all <p> and <span> tags flip text direction to RTL). I could probably change the build system, or make a babel plugin, that will replace all React.createElement("p" ...) with my own implementation of a p tag, but is there a better solution?
Thanks.

A more optimal way is to use CSS / HTML abilities for that:
direction CSS property
Unicode symbols ‏ / ‎
Attach .rtl / .ltr classes to body and use them to change order
In that cases order of your elements in HTML are the same for RTL and LTR. The difference in applied CSS rules.
If you really need the order of elements in React, you can create own component that reverses the list of children if RTL:
const Dir = React.createClass({
render: function() {
let children = (this.props.direction == 'rtl' && this.props.children && this.props.children.reverse) ? this.props.children.reverse() : null;
return <div>
{ children }
</div>;
}
});
// And use as:
// directionVariable = 'rtl'|'ltr'
<Dir direction={ directionVariable }>
<a>First</a>
<a>Second</a>
<a>Third</a>
</Dir>

Simple and easy, Just set dir attribute of <html> in index.js as follow:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
//HERE ->
document.getElementsByTagName('html')[0].setAttribute("dir", "rtl");
//Or you can do it where ever you want, base on user selected language, or something else
serviceWorker.unregister();
UPDATE: If your application is RTL at all, just add dir="rtl" in <html> tag, but if user can chose different languages that may be RTL or LTR you can do it like above example, and handle other things with CSS...
Just check User chosen language is RTL and document.getElementsByTagName('html')[0].setAttribute("dir", "rtl"); let CSS handle appearance stuff.

here's your solution:
first of all create an array of rtl language locales, like this:
const rtlLanguages = ["ar"]
here's how you can update your page direction based on your current language:
function setPageDirection(language) {
const dir = rtlLanguages.includes(language) ? "rtl" : "ltr"
document.documentElement.dir = dir
}
now when you set your new language, update the page direction using the function above, like this:
setPageDirection("en") // ltr
setPageDirection("ar") // rtl
NOTE: to automatically update the page direction during first render try storing your current language locale (for example: "en") in localStorage whenever you update your language, and then try using useEffect (or componentDidMount), and use localStorage.getItem to get your language and just call the setPageDirection function and provide the language

There is a special HTML tag to use RTL direction:
bdo {
unicode-bidi: bidi-override;
}
<bdo dir="rtl">
This text will go right-to-left.
</bdo>
https://www.w3schools.com/tags/tag_bdo.asp

if you use react-i18next
import React from 'react';
import { useTranslation } from 'react-i18next';
import './App.css';
function App() {
const { t, i18n } = useTranslation();
document.body.dir = i18n.dir();
return (
<div className="App">
{t('welcome')}
</div>
);
}
export default App;

Go to main.tsx or main.js depending on your project and wrap <App /> tag with a <div dir="rtl"></div> like this
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<div dir="rtl">
<App />
</div>
</React.StrictMode>
)

I'm using react-i18next to change the position from left to right when the Arabic language is chosen. I did the flowing, and it worked for me.
first i went to to the index.html and wrote this inside of body tag:
<body dir="ltr">
which will allow the page to left to write when the direction is chosen.
lastly i add this line in my App.js which is my main code:
document.body.dir = i18n.dir(); //this will change the direction of the dir in the index.html.

Related

How to make a container width bigger in React

I want to create an event for my button that is in in my AsideMenu component that take the current width and if it's equal to 5vw than it will make it to 10vw else it will put it at 5vw and it must overlap the div named home. This is the code:
Thank you
I know how to do it with it vanilla javascript but not in react
import React from 'react'
import AsideMenu from '../components/AsideMenu'
import './Home.css'
const Home = (props) => {
return (
<div className='assemble'>
<div >
<AsideMenu />
</div>
<div className='home'>
<h1>Home</h1>
</div>
</div>
)
}
export default Home```
You can use a useRef hook and get the current width. This video will explain it better for you. In case you get stuck, feel free to reach out. After using the useRef, you can then use a ternery statement in the return statement to actualize the code.

Is there a way to add a personal icon on Select ( Mantine)- not an image from Tabler Icons

I am new on Mantine and I`m trying to do a Search Component. In stead of using an image from tabler icons as is present in the mantine examples, I want to add a picture from my assets.
This is what I`ve tried
import { ReactComponent as SearchIcon } from '../../assets/search.svg';
import { IconHash } from '#tabler/icons';
<Select
className={classes.searchBar}
radius="xl"
placeholder="Cauta produse, servicii, sau parteneri"
itemComponent={SelectItem}
data={data}
searchable
icon={<SearchIcon />}
maxDropdownHeight={400}
nothingFound="Nobody here"
filter={(value, item) =>
item.label.toLowerCase().includes(value.toLowerCase().trim()) ||
item.description.toLowerCase().includes(value.toLowerCase().trim())
}
/>
looks like if I import Icon Hash, the type is a function
Of course you can. icon is of type ReactNode and thus accepts every react node you pass to it. You should make sure that your svg is a react component, though.
The return type from #tabler\icons is indeed a function (actually, a component) because they're meant to be used as react components. You can check an example here and configure your svg accordingly: https://github.com/tabler/tabler-icons/blob/master/icons-react/icons-js/123.js.

React adding conditional styling (&&)

the same newbie here.
I have the code which working as intended, all I want to do is add && with state variable (boolean) to make a text have line-through decoration while clicked (using crossedItemOnClick)
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Para(props) {
const {id, className, info, state} = props;
return (
<p onClick={props.crossedItemOnClick} id={id} className={className} style={{textDecorationLine: 'line-through'}}>{info}</p>
)
}
export default Para;
My whole page disappears if I change it to:
<p onClick={props.crossedItemOnClick} id={id} className={className} style={state && {textDecorationLine: 'line-through'}}>{info}</p>
I'd love to know what I'm doing wrong and why the page completely disappears. And of course explanation to learn if you'd be kind to.
Much thanks!
style attribute takes object as value in react. Correct way will be:
<p onClick={props.crossedItemOnClick} id={id} className={className} style={state ? {textDecorationLine: 'line-through'}:{}}>{info}</p>
assuming you have to apply the style when state is true.
That is because style prop expect object of type CSSProperties, and in case when your condition is false you will end up with something like style={false} which will cause your app to crash since you provided HTML element with invalid styling.
Easiest solution is to just rewrite that part to style={state ? {...someStyle} : {}}
Your approach is mostly correct, however, your condition should be applied on the style property directly like this if you want to use &&
style={{textDecorationLine: state && 'line-through'}}

Grommet TextArea exports multiline text as a single line in the image

I want to export Grommet TextArea as an image. The following code works if there is just a single line of text. But if I adjust the TextArea component in a way to make the text fill multiple lines, this won't be exported in the output image.
This is how it looks (1 - is the page which contains the multiline TextArea I want to export, 2 - the exported TextArea, containing just a single line of text)
Seems like I'm just missing something about HTML canvas. Or maybe there is some property in Grommet TextArea which helps with changing that behavior?
App.js:
import React, { Component } from "react";
import { TextArea } from "grommet";
import "./styles.css";
import { exportComponentAsPNG } from "react-component-export-image";
export default class App extends Component {
constructor(props) {
super(props);
this.textRef = React.createRef();
this.state = { text: "" };
}
render() {
const { text } = this.state;
return (
<div className="App">
<TextArea
ref={this.textRef}
value={text}
onChange={(event) => this.setState({ text: event.target.value })}
/>
<button onClick={() => exportComponentAsPNG(this.textRef)}>
Click
</button>
</div>
);
}
}
index.js:
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
index.html:
<div id="root"></div>
I think that the issue lay in the way the image is being exported. I created a quick test using only 'textarea' of HTML instead of the Grommet TextArea component and the issue still occurs:
I've verified that the behavior you've described is the expected behavior from react-component-export-image package by running the HTML example on their demo app, and it seems that this is their core functionality behavior. You might want to file an enhancement request that asks to support multiline screenshots. Here is the screenshot example from running the HTML example on their demo app:
The issue came out from html2canvas library, which is a core of react-component-export-image.
There are a couple of threads for this issue in the html2canvas community, including this one (the main one, I guess):
https://github.com/niklasvh/html2canvas/issues/2008
The best solution (which also helped in my case) is to use contenteditable div instead of textarea element

How to force Antd to append element as child element of div React renders to instead of to HTML body?

Edit:
I figured it out & posted answer below.
Original Question
I am trying to create a completely compartmentalized web application within a shadow-dom and I've been using Antd components and ran into the issue where Antd is appending drop-down options into the body tag instead of as a child of the element that React is rendering into.
To isolate this issue I've removed everything outside of just React.render & a single Antd element which still does the same thing.
I then used a different component, "react-date-picker", which works how I had hoped Antd did, where the component renders as a child of the div specified for react.
Unfortunately, Antd rendering to the body of the HTML instead of as a child makes using shadow-root pointless.
Essentially my question is:
Is this Antd's normal functionality? If not then what might I be screwing up to have this happen? If so then is there a built-in Antd option that I'm missing that will render Antd options as child elements? If that option doesn't exist within their libraries, is there a way for me to force Antd to render as a child of my shadow-root node?
Here is what I'm using to render the Antd DatePicker component:
import ReactDOM from 'react-dom';
import React from 'react';
import DatePicker from 'antd/lib/date-picker';
ReactDOM.render(<DatePicker/>, document.getElementById('entry-fields'));
Before clicking on the Antd date picker:
After clicking on it, drop down options are appended to <body> and not <div id="entry-fields>:
Here is what I'm using to render the react-date-picker component to demonstrate the functionality I expected / need:
import ReactDOM from 'react-dom';
import React from 'react';
import DatePicker from "react-datepicker";
class Example extends React.Component {
state = {
startDate: new Date()
};
handleChange = (date: any) => {
this.setState({
startDate: date
});
};
render() {
return (
<DatePicker
selected={this.state.startDate}
onChange={this.handleChange}
/>
);
}
}
ReactDOM.render(<Example/>, document.getElementById('entry-fields'));
Before clicking on the react-date-picker date picker:
After clicking on the react-date-picker date picker (the drop down options are appended as children of the element react is rendered onto):
Basically I expected Antd to render its options encapsulated within the React rendered into <div></div> but it is instead appending elements on the <body></body>.
I'm relatively inexperienced in the web-dev domain and have resorted to asking a question here after way too much time trying to find the answer on my own. I am getting extremely frustrated in web-dev in general where any question seems to yield hundreds of irrelevant medium blog posts that are not useful in any capacity... assuming that it's not just me not knowing what to search for yet to find the answers I need which could very well be the case.
Really appreciate any help in advance.
Not sure how I managed to miss this but Antd has a parameter called "getCalendarContainer" which if left blank will render options into the body of the document but if supplied with the correct parameters will render the child elements into the container of your choosing.
Going off this example: https://react-component.github.io/calendar/examples/getCalendarContainer.html
I got it working by adding this function to my component:
getCalendarContainer()
{
return this.d || document.getElementById('calendarContainer');
}
and adding this to the component in JSX:
<div id="calendarContainer" ref={n => (this.d = n as HTMLDivElement)} >
<DatePicker onChange={EntryFields.onDateChange} getCalendarContainer={this.getCalendarContainer}/>
</div>
and initializing the div tag to reference it in the component's constructor like this:
private d: HTMLDivElement;
constructor(props: any)
{
super(props);
this.d = document.createElement("div");
...
It's also worth noting that the above will not work immediately when using shadow-DOM since you need to access the node that the shadow-DOM is a child to and then use getElementById().
Something along these lines (but probably better written I hope lol)
getContainer() {
let elem = null;
let shadowContainer = document.getElementById('entryFieldsShadow') as HTMLInputElement;
if (shadowContainer != null) {
let shadowDom = shadowContainer.firstElementChild;
if (shadowDom != null) {
let shadowRoot = shadowDom.shadowRoot;
if (shadowRoot != null) {
elem = shadowRoot.getElementById("entryFieldsContainer")
}
}
}
return elem || this.d;
}
where the JSX with react-shadow's shadow root is included looks like this:
return (
<div id="entryFieldsShadow">
<root.div>
<div>
<div id="entryFieldsContainer"/>
<style type="text/css"> #import "static/1.css"; </style>
<Layout>
<Content>
{this.RowCols()}
</Content>
</Layout>
</div>
</root.div>
</div>
)
This solve my problems
<DatePicker
{...}
getCalendarContainer={triggerNode => triggerNode.parentNode}
/>

Categories

Resources