Switching between LTR and RTL using styled-components? - next.js

I'm building an English-Arabic app with next.js, and want to enable users to switch between the two languages.
I found this example from next.js: with-styled-components-rtl. It uses StylisRTLPlugin, which flips all CSS styles to fit RTL layout:
// _document.js
<StyleSheetManager stylisPlugins={[stylisRTLPlugin]}>
<App {...props} />
</StyleSheetManager>
It works fine, but the problem is that I can't remove stylisRTLPlugin conditionally based on the current locale bacause I have no access to useRouter() in _document.js.
What I want to be able to do:
<StyleSheetManager stylisPlugins={locale === 'ar' ? [stylisRTLPlugin] : null}>
<App {...props} />
</StyleSheetManager>

Add this config to your .babelrc to force your styled to be rendered on the server.
"plugins": [
[
"styled-components",
{
"ssr": true,
}
]
]

Related

Tailwind style doesn't seems to be apply inside Material-ui Drawer component in NextJs

I am trying to add styling inside a #mui/material/drawer component using tailwind.
import { Close } from "#mui/icons-material";
import { Box, Drawer, IconButton, TextField } from "#mui/material";
import { useContext} from "react";
import { SearchContext } from "store/context";
export default function SearchDrawer() {
const { search, setSearchMode } = useContext(SearchContext);
return (
<Drawer open={search.searchMode} anchor="bottom">
<Box sx={{ height: "100vh" }} className="bg-red-500">
<IconButton onClick={() => { setSearchMode(!search.searchMode); }}><Close/></IconButton>
<div>
<TextField variant="outlined" sx={{display: 'block'}}/>
<TextField variant="outlined" sx={{display: 'block'}}/>
</div>
</Box>
</Drawer>
);
}
Expected behavior is
<Box sx={{ height: "100vh" }} className="bg-red-500">
This line of code will make whole screen red, But Nothing happen.
Output after render
Tailwind styles are not applying on some of Material-ui components like "Drawer", "Dialog", "ToolTip" This all components are hover over other components.
Tailwindcss classes are not working in Tooltip & Dialog components #33424 - GitHub
This page says to modify material-ui theme,
defaultProps: {
container: rootElement,
},
},
But rootElement is available in Reactjs, How to do it on Nextjs.
this is what worked for me from the official docs :
Remove Tailwind CSS's preflight style so it can use the MUI's preflight instead (CssBaseline).
In your file tailwind.config.js add the following :
//Remove Tailwind CSS's preflight style so it can use the MUI's preflight instead
corePlugins: {
preflight: false,
},
Add the important option, using the id of your app wrapper. For example, #__next for Next.js and "#root" for CRA:
In your file tailwind.config.js add the following (for nextjs add #__next):
//Add the important option, using the id of your app wrapper. For example, #__next for Next.js and "#root" for CRA
important: '#__next',
Your file tailwind.config.js should like like :
/** #type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
//Add the important option, using the id of your app wrapper. For example, #__next for Next.js and "#root" for CRA
important: '#__next',
theme: {
extend: {},
},
plugins: [],
//Remove Tailwind CSS's preflight style so it can use the MUI's preflight instead
corePlugins: {
preflight: false,
},
}
Fix the CSS injection order. Most CSS-in-JS solutions inject their styles at the bottom of the HTML , which gives MUI precedence over Tailwind CSS. To reduce the need for the important property, you need to change the CSS injection order. Here's a demo of how it can be done in MUI:
import * as React from 'react';
import { StyledEngineProvider } from '#mui/material/styles';
export default function GlobalCssPriority() {
return (
<StyledEngineProvider injectFirst>
{/* Your component tree. Now you can override MUI's styles. */}
</StyledEngineProvider>
);
}
You can check the official documentation : https://mui.com/material-ui/guides/interoperability/#tailwind-css

Using Tailwind Forms Plugin with React Select

I am trying to integrate Select from react-forms with tailwind css and the tailwind forms plugin (#tailwindcss/forms).
With only tailwind and react-select, the form renders correctly. However, with the plugin, an outline appears. I would like for tailwindcss forms not to interfere with react-select styling. Is there an effective solution to allow react-select styles to override tailwind plugins?
Additionally, please let me know if there are any effective solutions for styling react-select forms using tailwind without resorting to other libraries, like emotion or styled-components.
You can set the box shadow to none for the input when focused
<Select
....
styles={{
input: (base) => ({
...base,
'input:focus': {
boxShadow: 'none',
},
}),
}}
/>
Additionally, please let me know if there are any effective solutions
for styling react-select forms using tailwind without resorting to
other libraries, like emotion or styled-components.
The answer to this is to use this library
https://github.com/ben-rogerson/twin.macro
Example, you can do something like this:
import tw from 'twin.macro';
import ReactSelect, { Props } from 'react-select';
const Styled = {
Select: tw(ReactSelect)`rounded-lg text-center border-2`
};
const Select: React.FC<Partial<Props>> = (props) => {
return (
<Styled.Select {...props} />
);
};
You could use the class strategy for the plugin, it will disable the extra styling, but you will need to implement the form-* class to every other input.
Here is the documentation for it.

JIT tailwindcss using variable in bg-[] not rendering color

when passing my color as props like this <List text="something" color="#84AB86" /> and using in the code className={'bg-[${color}] '} it does not render properly.
when looking at chrome dev tools color are added correctly like this bg-[#84AB86]
while putting the color manually without taking it from props, it does work correctly
after more testing it seems not possible either to do it like this
const color = "#84CC79"
className={`bg-[${color}]`}
any idea why
To use dynamic classes with JIT tailwind you either need to use safelist config key or create stub file where you list all your dynamic classes that you will use.
Config example:
module.exports = {
content: [
'./pages/**/*.{html,js}',
'./components/**/*.{html,js}',
],
safelist: [
'bg-red-500',
'text-3xl',
'lg:text-4xl',
]
// ...
}
Or make safelist.txt in your src folder, then add classes there just like so:
bg-[#84AB86]
bg-[#fffeee]
// etc..
And don't forget to include this safelist.txt file to your config content so tailwind could watch it.
Explanation from tailwind docs
If you are not using JIT, then you can use safelist option for PurgeCSS:
// tailwind.config.js
module.exports = {
purge: {
// Configure as you need
content: ['./src/**/*.html'],
// These options are passed through directly to PurgeCSS
options: {
// List your classes here, or you can even use RegExp
safelist: ['bg-red-500', 'px-4', /^text-/],
blocklist: [/^debug-/],
keyframes: true,
fontFace: true,
},
},
// ...
}
From the Tailwindcss documentation
Dynamic values Note that you still need to write purgeable HTML when
using arbitrary values, and your classes need to exist as complete
strings for Tailwind to detect them correctly.
Don't use string concatenation to create class names --> <div className={mt-[${size === 'lg' ? '22px' : '17px' }]}></div>
Do dynamically select a complete class name --> <div className={ size === 'lg' ? 'mt-[22px]' : 'mt-[17px]' }></div>
Tailwind doesn’t include any sort of client-side runtime, so class
names need to be statically extractable at build-time, and can’t
depend on any sort of arbitrary dynamic values that change on the
client. Use inline styles for these situations, or combine Tailwind
with a CSS-in-JS library like Emotion if it makes sense for your
project.
As mentioned above tailwind engine In order to render a custom class dynamicaly:
Does not like:
className={`bg-[${custom-color}]-100`}
It expects:
const customBgColorLight = 'bg-custom-color-100';
className={`${customBgColorLight} .....`}
For this to work properly you have to include the name of the class in the safelist:[] in your tailwind.config.js.
For tailwind v.3
/** #type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
safelist: [
'bg-custom-color-500', // your-custom-css-class
'text-custom-color-500',
'border-custom-color-500',
..... // other classes
'hover:bg-custom-color-500', // *** also include it with the selector if needed ***
.... // other classes
],
theme: {
extend: {
colors: {
'custom-color': { // you have to use quotes if key is not in camelCase format
100: '#d6d6d6',
500: '#5E8EA2',
..... //other variants
},
...... // other colors
So you can use it:
// if you want store the values to an object
const yourClassObj = {
customBgColor: 'bg-custom-color-500',
customBrdColor: 'border-custom-color-500',
customTxtColor: 'text-custom-color-500',
};
const { customBgColor, customBrdColor, customTxtColor } = yourClassObj;
<YourComponent
className={`mb-2 font-semibold py-2 px-4 rounded-lg
${ conditionGoesHere ? `${customBgColor} text-white cursor-default`
: `${customTxtColor} border ${customBrdColor}
bg-transparent hover:border-transparent
hover:${customBgColor} hover:text-white`
}`}
/>
An easy solution is to use the built in style property.
For example in React:
Dont Use:
className={`bg-[${color}]`}
Use Instead:
style={{
backgroundColor: color,
}}

How to render activeClassName style for a react-router-dom NavLink rendered within a material-ui button in a typescript environment?

I am learning react and trying to set the style of a react-router-dom NavLink encapsulated inside a material-ui Button. I am using React with Typescript and a custom Webpack build.
The documentation states that this can be achieved by setting the activeClassName property.
In the code listing below I try setting the activeClassName on the button containing the NavLink component.
import * as navbar from './NavBar.css';
const NavBarLinks = () => (
<>
<Button
color="inherit"
component={NavLink}
activeClassName={navbar.highlighted}
to="/about"
>
About
</Button>
</>
);
The highlighted css class is as follows:
.highlighted {
border-bottom: 3px solid rgb(174, 185, 28);
}
However, no bottom border is rendered. How do I get the activeClassName style to render for a react-router-dom NavLink rendered within a material-ui Button within a Typescript environment?
My build environment is configured as follows.
I have created a type declaration file for the stylesheet
and saved this in a folder referenced in tsconfig.json.
export const active: string;
export const horizmenu: string;
export const highlighted: string;
My tsconfig.json includes the typesRoot configuration. This references the types folder where my css type declaration file is stored:
"typeRoots": ["node_modules/#types", "src/types"]
My webpack development configuration is using css-loader to bundle css files under src/app/*/
{
// link css modules
test: /\.css$/,
include: path.resolve(constants.dir.SRC, 'app'),
use: [
{ loader: require.resolve('style-loader') },
{
loader: require.resolve('css-loader'),
options: {
modules: {
localIdentName: '[path][name]__[local]___[hash:base64:5]',
},
importLoaders: 1,
sourceMap: true,
},
},
],
},
...
When I perform a Webpack development build I can see that the NavBar.css contents are picked up by the css-loader and embedded as a string in the bundle file.
What you have should work, but you are passing a class name, not a style, so I think you just need to make your activeClassName prop equal to "highlighted".
<Button
color="inherit"
component={NavLink}
activeClassName="highlighted"
to="/about"
>
About
</Button>
You'll also need to add !important to you style so it overrides the CSS of the Material UI button.
Working codesandbox link

conditional css in create-react-app

I have default css file and separate css file that should be applied (to owerride default) only when certain conditions are met.
I am using create-react-app wit default import 'file.css' syntax.
What is the best way forward to decide whether to load or not load particular css file dynamically?
The require method only worked in development (as all the CSS is bundled upon build), and the import method did not work at all (using CRA version 3.3).
In our case, we have multiple themes, which cannot be bundled - so we solved this using React.lazy and React.Suspense.
We have the ThemeSelector, which loads the correct css conditionally.
import React from 'react';
/**
* The theme components only imports it's theme CSS-file. These components are lazy
* loaded, to enable "code splitting" (in order to avoid the themes being bundled together)
*/
const Theme1 = React.lazy(() => import('./Theme1'));
const Theme2 = React.lazy(() => import('./Theme2'));
const ThemeSelector: React.FC = ({ children }) => (
<>
{/* Conditionally render theme, based on the current client context */}
<React.Suspense fallback={() => null}>
{shouldRenderTheme1 && <Theme1 />}
{shouldRenderTheme2 && <Theme2 />}
</React.Suspense>
{/* Render children immediately! */}
{children}
</>
);
export default ThemeSelector;
The Theme component's only job, is to import the correct css file:
import * as React from 'react';
// 👇 Only important line - as this component should be lazy-loaded,
// to enable code - splitting for this CSS.
import 'theme1.css';
const Theme1: React.FC = () => <></>;
export default Theme1;
The ThemeSelector should wrap the App component, in the src/index.tsx:
import React from 'react';
import ReactDOM from 'react-dom';
import ThemeSelector from 'themes/ThemeSelector';
ReactDOM.render(
<ThemeSelector>
<App />
</ThemeSelector>,
document.getElementById('root')
);
As I understand, this forces each Theme to be split into separate bundles (effectively also splitting CSS).
As mentioned in the comments, this solution does not present an easy way of switching themes runtime. This solution focuses on splitting themes into separate bundles.
If you already got themes split into separate CSS files, and you want to swap themes runtime, you might want to look at a solution using ReactHelmet (illustrated by #Alexander Ladonin's answer below)
You can use require('file.css') syntax instead. This will allow you to put it inside of a conditional.
e.g.
if(someCondition) {
require('file.css');
}
Use React Helmet. It adds links, meta tags etc into document header dynamically.
Add it into any render method.
import {Component} from 'react';
import ReactHelmet from 'react-helmet';
class Example extends Component{
render(
<ReactHelmet link={
[{"rel": "stylesheet", type:"text/css", "href": "/style.css"}]
}/>);
}
}
You can rewrite it on next <ReactHelmet/> rendering.
One simple solution that I found that works in production is to use vercel's styled-jsx. First, install styled-jsx:
npm install --save styled-jsx
Or if you use Yarn:
yarn add styled-jsx
Now create strings from your css file, so for instance:
const style1 = `
div {
display: flex;
flex-direction: column;
align-items: center;
}
`
const style2 = `
div {
display: flex;
flex-direction: column;
align-items: center;
}
`
And then in your React Component, you can do something like this:
const MyComponent = () => {
return (
<div className='my-component'>
<style jsx>
{
conditionA ? style1: style2
}
</style>
</div>
)
}
Simply add <style jsx>{your_css_string}</style> to the component which you wish to add styling to and you can then to implement conditions just use different strings to import different css styling.
If you are here you most likely are trying to condition a CSS or SCSS import, probably to make some light/dark mode theme or something. The accepted answer works just on mount, after the second css is loaded they are both loaded and you dont have a way to unload them, or actually you have, keep reading...
The use of React lazy and suspense is awesome but in this case we need to help our selves from webpack, because is actually the guy that bundles stuff and can also unbundle stuff, which is what you need, a toggle of css imports basically
Adding webpack lazyStyleTag
Go to your webpack config file and add the following rules
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
// Probly you already have this rule, add this line
exclude: /\.lazy\.css$/i,
use: ["style-loader", "css-loader"],
},
// And add this rule
{
test: /\.lazy\.css$/i,
use: [
{ loader: "style-loader", options: { injectType: "lazyStyleTag" } },
"css-loader",
],
},
],
},
};
Now take your CSS files and change their name to the lazy named convention
You probably have this
styles.css
// or
styles.min.css
Now will be this:
styles.lazy.css
Then create your React theme Provider in a simple React context, this context will wrap your App so it will load the conditioned CSS everytime the context state changes. This context state is going to be availabe anywhere inside your app as well as the setter via a custom hook we will export from the same file, check this out:
import React, {
useEffect, createContext, useState, useContext,
} from 'react';
import { Nullable } from 'types';
// Import both files here like this:
// Import of CSS file number 1
import LightMode from './theme/styles.lazy.css';
// Import of CSS file number 2
import DarkMode from './theme/styles.lazy.css';
interface IContext {
theme: Nullable<string>
toggleTheme: () => void
}
const Context = createContext<IContext>({
theme: null,
toggleTheme: () => { },
});
// Your Provider component that returns the Context.Provider
// Let's also play with the sessionStorage, so this state doesn't
// brake with browser refresh or logouts
const ThemeProvider: React.FC = ({ children }) => {
// Im initialazing here the state with any existing value in the
//sessionStorage, or not...
const [theme, setTheme] = useState<Nullable<string>>(sessionStorage.getItem('themeMode') || 'dark');
// this setter Fn we can pass down to anywhere
const toggleTheme = () => {
const newThemeValue = theme === 'dark' ? 'light' : 'dark';
setTheme(newThemeValue);
sessionStorage.setItem('themeMode', newThemeValue);
};
// Now the magic, this lazy css files you can use or unuse
// This is exactly what you need, import the CSS but also unimport
// the one you had imported before. An actual toggle of import in a
// dynamic way.. brought to you by webpack
useEffect(() => {
if (theme === 'light') {
DarkMode.unuse();
LightMode.use();
} else if (theme == 'dark') {
LightMode.unuse();
DarkMode.use();
}
}, [theme]);
return (
<Context.Provider value={{ theme, toggleTheme }}>
{children}
</Context.Provider>
);
};
export default ThemeProvider;
// This useTheme hook will give you the context anywhere to set the state of // theme and this will toggle the styles imported
export const useTheme = () => useContext(Context);
Remember to put this state on the sessionStorage like in this example so your user has the state available every time it comes back or refreshes the page
Don't forget to wrap the friking App with the Provider:
import ThemeProvider from './ThemeProvider'
const App = () => {
return (
<ThemeProvider>
<App />
</ThemeProvider>
)
}
Now just toggle the CSS imports of your application using your cool useTheme hook
import { useTheme } from './yourContextFile';
// inside your component
const AnyComponentDownTheTree = () => {
const { theme, toggleTheme } = useTheme()
// use the toggleTheme function to toggle and the theme actual value
// for your components, you might need disable something or set active a
// switch, etc, etc
}
Other solution does not work for me. After one day of the search, I obtain bellow solution. In my issue, I have two CSS files for RTL or LTR like app.rtl.css or app.ltr.css
Create a functional component Style like this:
import React, { useState } from "react";
export default function Style(props) {
const [stylePath, setStylePath] = useState(props.path);
return (
<div>
<link rel="stylesheet" type="text/css" href={stylePath} />
</div>
);
}
And then you can call it, for example in App.js:
function App() {
...
return (
<Style path={`/css/app.${direction}.css`} />
)}
direction param contains rtl or ltr and determine which file should be loaded.
I tested some alternatives available in some tutorials and the best for me was to use only classes in css.
One of the problems I encountered when using
require: did not override on some occasions
import: delay generated to load css
The best way for me was to actually put a class switch
.default-sidebar {
--side-text-icon:rgba(255,255,255,.9) !important;
--side-text-section: rgb(255,255,255,.8) !important;
--side-separator-section:#ff944d !important;
}
.dark-sidebar {
--side-text-icon:rgba(255,255,255,.9) !important;
--side-text-section: rgb(255,255,255,.8) !important;
--side-separator-section:#262626 !important;
}
'
<div className={`root-sidebar ${condition?'default-sidebar':'dark-sidebar'}`}></div>

Resources