Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate RTL CSS file in create-react-app and switch between them based on change in state

I'm using create-react-app for a multi-language project. I want to use some library like "cssJanus" or "rtlcss" to convert the Sass generated CSS file into a separate file and then use that newly generated file when I switch to another language.

Here's how my index.js looks like ...

import React from "react";
import ReactDOM from "react-dom";
import * as serviceWorker from "./serviceWorker";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import App from "./App";
import { configureStore } from "./store/configureStore";

const store = configureStore();

ReactDOM.render(
    <Provider store={store}>
        <Router>
            <App />
        </Router>
    </Provider>,
    document.getElementById("root")
);

serviceWorker.unregister();

And here's how my "App.js" looks like ...

import React, { Component } from "react";
import "./App.scss";
import { Route, Switch } from "react-router-dom";
import SignIn from "./features/signin/SignIn";

class App extends Component {
    render() {
        return (
            <>
                <Switch>
                    <Route path="/" exact component={SignIn} />
                </Switch>
            </>
        );
    }
}

export default App;

As you can see I'm using "./App.scss" file that simply have a bunch of @import statements to another ".scss" files in the "./src/css/" directory ...

/* autoprefixer grid: on */
@import "css/reset";
@import "css/variables";
@import "css/global";

I need your advice on how to do that. How to convert the generated CSS from App.scss to RTL into their own .css file and switch between them and the original generated CSS based on a change in the global state.

I searched a lot for something like this but with no luck.

Or if you have a better approach I'm all ears.

like image 910
Ruby Avatar asked Feb 01 '19 15:02

Ruby


People also ask

How do you make a RTL in React app?

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.

Can we use both CSS and SCSS in React?

Introduction: We can use SASS in React using a package called node-sass. Using node-sass we can just create sass files and use them as the normal CSS files in our React application and node-sass will take care of compiling sass files. Modules: To compile sass code, node-sass can be used.

Where do I put the CSS file in Create React app?

To add a global CSS file to your Next. js app, you need to import the file inside pages/_app. js file.


2 Answers

Here is a simple solution that requires ejecting and adding a lightweight webpack-rtl-plugin.

After running

npx create-react-app react-rtl 
cd react-rtl
yarn eject
yarn add -D webpack-rtl-plugin @babel/plugin-transform-react-jsx-source

Go to config/webpack.config.js and make some tweaks:

// import the plugin
const WebpackRTLPlugin = require('webpack-rtl-plugin')

// ...

module: { ... }
plugins: [
   // ...,
   // use the plugin
   new WebpackRTLPlugin({ diffOnly: true })
].filter(Boolean),
// ...

On this stage, if you run yarn build and look up build/static/css folder, you should hopefully see additional .rtl.css file that contains your rtl styles. Then we need to tell webpack to use MiniCssExtractPlugin.loader for development as well so it will serve styles through link tags instead of inline styles:

// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
  const loaders = [
    isEnvDevelopment && { loader: MiniCssExtractPlugin.loader }, // <-- use this
    // isEnvDevelopment && require.resolve('style-loader'), <-- instead of this 

and don't forget the plugin, lol:

module: { ... }
plugins: [
   // ...,

   // isEnvProduction &&      <-- comment this out
   new MiniCssExtractPlugin({
     // Options similar to the same options in webpackOptions.output
     // both options are optional
     filename: 'static/css/[name].[contenthash:8].css',
     chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
   }),

   // ...
].filter(Boolean),

And from here you can finally grab your default stylesheet href and use to insert rtl styles. Here's how you could implement it:

class RtlCssBundleService {
  constructor() {
    this.rtlApplied = false
    this.rtlStyles = [];
    this.ltrStyles = Array.from(
      document.querySelectorAll('link[rel="stylesheet"]')
    )
  }

  insert = () => {
    if (this.rtlApplied) { return }

    this.rtlApplied = true

    if (this.rtlStyles.length) {
      return this.rtlStyles.forEach(style => {
        document.body.appendChild(style)
      })
    }

    this.rtlStyles = this.ltrStyles.map(styleSheet => {
      const link = document.createElement("link")
      link.href = styleSheet.href.replace(/\.css$/, '.rtl.css')
      link.rel = "stylesheet"
      document.body.appendChild(link)
      return link
    })
  }

  detach = () => {
    this.rtlApplied = false
    this.rtlStyles.forEach(style => {
      document.body.removeChild(style)
    })
  }

  toggle = () => {
    return this.rtlApplied
      ? this.detach()
      : this.insert()
  }
}

const rtlStyles = new RtlCssBundleService()

export default rtlStyles

Then use this from any of your components. So anyway, I'm sure I've missed something and maybe that is a terrible approach, but it seems to work and here is the demo

like image 83
streletss Avatar answered Nov 14 '22 21:11

streletss


If you use flexbox and css grid they have RTL support built in. Then use CSS Logical Properties for margin, padding, border, etc. If that is not enough, then you can use [dir="rtl"] .your-class as a fallback.

Now you don't have two separate css files to maintain.

Here is a cross browser margin-right example.

-webkit-margin-end: 25px;
margin-inline-end: 25px;
@supports (not (-webkit-margin-end: 0)) and (not (margin-inline-end: 0)) {
    margin-right: 25px;
}

You could wrap that up into a mixin for easier use across your app.

like image 43
Blake Plumb Avatar answered Nov 14 '22 21:11

Blake Plumb