Dark theme does not work with React Stitches - reactjs

I'm using Stitches in React to handle my CSS and theming. I have the following code:
import React from 'react'
import { createStitches, globalCss } from '#stitches/react'
const { theme, createTheme } = createStitches({
theme: {
colors: {
text: 'blue',
bodyBg: 'lightgray',
},
},
})
const darkTheme = createTheme('dark-theme', {
colors: {
bodyBg: 'black',
},
})
const globalStyles = globalCss({
'body': {
color: '$text',
backgroundColor: '$bodyBg'
},
});
function App() {
globalStyles()
return (
<div className='App'>
<h1>Hello World!</h1>
</div>
)
}
export default App
As you can see, I have a default theme, and then a dark theme that extends the default theme while overriding some properties (in this case, the bodyBg). I'm applying these styles directly in my <body>. The default theme works fine, but the dark theme does not. When I add the .dark-theme class to my <html>, nothing changes (the background should turn black). What exactly am I doing wrong here?

You are probably trying to add the class directly to the body in the developer tools which doesn't work.
I managed to make it work with a button onClick event:
const darkTheme = createTheme('dark-theme', {
colors: {
bodyBg: 'black',
},
})
function App() {
const setBlackTheme = () => {
document.body.classList.remove(...document.body.classList);
// Here we set the darkTheme as a class to the body tag
document.body.classList.add(darkTheme);
}
globalStyles()
return (
<div className='App'>
<button onClick={setBlackTheme}>Dark Theme</button>
<h1>Hello World!</h1>
</div>
)
}
export default App
Try it and let's see if it works for you as well.

Related

Dynamic theme using Remote (async) styles in MUI React?

I'm working on a SaaS in which a particular entity (my customer) and customize their styles like primary color, secondary color, font-family etc.
How can I load remote data inside the theme?
I've tried several conditional theming so far, I think theme is not accepting async data
My data should come like this from the API response.
{ "styles": { "primary": "#087f5b", "bodyBackground": "#f8f9fa", "bodyText": "#343a40" } }
I need to fetch it on the fly as soon as user is loading the page, I'm ready to show a loading screen as well while the theme data comes from the server.
Here's what I've tried so far. It works when I'm trying || operator to add conditional primary color, but is there any better way of doing it ?
import { Box, Button, CircularProgress } from "#mui/material";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import { useQuery } from "#tanstack/react-query";
import { useState } from "react";
import { api } from "./axios.instance";
function App() {
const [siteData, setSiteData] = useState([]);
const { loading } = useQuery(
["orgData"],
async () => await api.get("/styles"),
{
onSuccess: (res) => setSiteData(res.data),
}
);
const theme = createTheme({
palette: {
primary: {
main: siteData.primary || "#000",
},
},
});
return (
<>
{loading ? (
<CircularProgress />
) : (
<ThemeProvider theme={theme}>
<Box>
<Button variant="contained">Button</Button>
</Box>
</ThemeProvider>
)}
</>
);
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

How to customize background color of button using Material UI themes

I can't figure out from the documentation how to style a button so the background color of that component is purple using Material UI themes. I can get the text color to change to purple with the below. Please let me know what I am doing wrong!
import React, { Component } from 'react';
import { createTheme, ThemeProvider } from '#material-ui/core/styles';
import purple from '#material-ui/core/colors/purple';
import Button from '#material-ui/core/Button';
const theme = createTheme({
palette: {
primary: {
main:purple[500],
}
},
});
export default class Practice extends Component {
render(){
return (
<div>
<ThemeProvider theme={theme}>
<Button color="primary">Add theme</Button>
</ThemeProvider>
</div>
)}}
const theme = createTheme({
overrides : {
MuiButton : {
root : {
// apply your style here
}
},
palette: {
primary: {
main:purple[500],
}
},
});
For the currently latest MUI 5 it should be (from the docs):
const theme = createTheme({
components: {
// Name of the component
MuiButtonBase: {
defaultProps: {
// The props to change the default for.
disableRipple: true, // No more ripple, on the whole application 💣!
},
},
},
});
And then provide your theme as usual.
More info: https://mui.com/material-ui/customization/theme-components/#default-props

When migrating to Material-UI v5, how to deal with conditional classes?

In the official migration guide, they give the following example of changing the code from JSS (makeStyles) to the new styled mode.
Before:
const useStyles = makeStyles((theme) => ({
background: theme.palette.primary.main,
}));
function Component() {
const classes = useStyles();
return <div className={classes.root} />
}
After:
const MyComponent = styled('div')(({ theme }) =>
({ background: theme.palette.primary.main }));
function App(props) {
return (
<ThemeProvider theme={theme}>
<MyComponent {...props} />
</ThemeProvider>
);
}
This is fine for a single class, but what to do when the code has conditional classes?
e.g.
<main className={classnames(content, open ? contentOpen : contentClosed)}>
{/* content goes here */}
</main>
Here, content, contentOpen, and contentClosed are classes returned from useStyles, but contentOpen and contentClosed are rendered conditionally based on the value of open.
With the new styled method, instead of generating class names we're generating components. Is there a way to elegantly replicate the rendering without resorting to content duplication in the ternary expression?
e.g. we don't want to do something like:
function App(props) {
return (
<ThemeProvider theme={theme}>
{open
? <MyOpenComponent {...props}>{/* content */}</MyOpenComponent>
: <MyClosedComponent {...props}>{/* content */}</MyClosedComponent>
</ThemeProvider>
);
}
There are quite a few possible ways to deal with this. One approach using styled is to leverage props to do dynamic styles rather than trying to use multiple classes.
Here's an example:
import React from "react";
import Button from "#mui/material/Button";
import { styled } from "#mui/material/styles";
const StyledDiv = styled("div")(({ open, theme }) => {
const color = open
? theme.palette.primary.contrastText
: theme.palette.secondary.contrastText;
return {
backgroundColor: open
? theme.palette.primary.main
: theme.palette.secondary.main,
color,
padding: theme.spacing(0, 1),
"& button": {
color
}
};
});
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<StyledDiv open={open}>
<h1>{open ? "Open" : "Closed"}</h1>
<Button onClick={() => setOpen(!open)}>Toggle</Button>
</StyledDiv>
);
}
Here's an equivalent example using TypeScript:
import * as React from "react";
import Button from "#mui/material/Button";
import { styled } from "#mui/material/styles";
const StyledDiv: React.ComponentType<{ open: boolean }> = styled("div")(
({ open, theme }) => {
const color = open
? theme.palette.primary.contrastText
: theme.palette.secondary.contrastText;
return {
backgroundColor: open
? theme.palette.primary.main
: theme.palette.secondary.main,
color,
padding: theme.spacing(0, 1),
"& button": {
color
}
};
}
);
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<StyledDiv open={open}>
<h1>{open ? "Open" : "Closed"}</h1>
<Button onClick={() => setOpen(!open)}>Toggle</Button>
</StyledDiv>
);
}
Some other possible approaches:
Use Emotion's css prop and Emotion's capabilities for composing styles
Use tss-react to retain similar syntax to makeStyles but backed by Emotion (so you wouldn't be including both Emotion and JSS in your bundle as would be the case if you leverage makeStyles from #material-ui/styles). This is the route I took when migrating to v5 and as part of my migration I created a codemod for migrating JSS makeStyles to tss-react's makeStyles.

Change the link colors in Material UI Sidebar (Drawer) in a Reactjs app

I tried using different properties in the sidebar style to override the link color but nothing is working.
Default style/color of Sidebar drawer (before overriding the style)
MySidebar.js
// MySidebar.js
import { Sidebar } from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
import React from 'react';
const useSidebarStyles = makeStyles({
drawerPaper: {
backgroundColor: '#0c2d48',
color: '#fff',
},
});
const MySidebar = props => {
const classes = useSidebarStyles();
return (
<Sidebar classes={classes} {...props} />
);
};
export default MySidebar;
MyLayout.js
// MyLayout.js
import React from 'react';
import { Layout } from 'react-admin';
import MySidebar from './MySidebar';
const MyLayout = props => (
<Layout
{...props}
sidebar={MySidebar}
/>
);
export default MyLayout;
Result (after overriding the default style in MySidebar.js)
As you can see, I'm able to change bg color of the sidebar but not the link colors.
Please help. It's driving me crazy!
You can create your own theme as described here:
https://marmelab.com/react-admin/Theming.html#writing-a-custom-theme
and in your theme redefine the colors of all MenuItemLink components:
export const lightTheme = {
...
overrides: {
RaMenuItemLink: {
root: {
color: "#c51162",
},
active: {
color: "#ff4081",
},
},
},
}
I can see that you are using React-Admin for the sidebar and not the Material UI sidebar directly. So possibly the React-Admin template modified the props of original material Ui and you should take a look inside the react admin sidebar and modify it directly there to get the results. If you apply material UI customization on this one, it might be the reason its not working.
Alternative is to try Material UI Themeing with ThemeProvider and apply the theme, which can overwrite the current styling.
Try changing link colors.
drawerPaper: {
backgroundColor: '#0c2d48',
color: '#fff',
},
link: {
color: '#fff',
},
Update: Instead of link you can give it the css class name, by looking at inspect element on the ouput sidebar.

Switch between dark and light theme in non-material ui component

I am trying to introduce a theme switcher in my app. I have a lot of non-material-ui elements that I need the theme to reflect the changes on them.
The code below shows that I have a state that is called darkState that is set to true. The material ui components in my app reflect those changes but for example the div below does not get the dark color of the dark theme. What is that I am doing wrong in here?
import React, { useState } from "react";
import Header from "./components/Header.js";
import TopBar from "./components/TopBar.js";
import Sequence from "./components/Sequence.js";
import SecondaryWindow from "./components/SecondaryWindow.js";
import { MuiThemeProvider, createMuiTheme, makeStyles } from "#material-ui/core/styles";
import "./App.css";
import { MainContextProvider } from "./contexts/mainContext.js";
function App() {
const [darkState, setDarkState] = useState(true);
const palletType = darkState ? "dark" : "light";
const theme = createMuiTheme({
palette: {
secondary: {
main: "#0069ff",
},
type: palletType,
},
});
const useStyles = makeStyles((theme) => ({
root: {
paddingLeft: 80,
height: "100%",
backgroundColor: theme.palette.background.default,
},
}));
const classes = useStyles();
return (
<MuiThemeProvider theme={theme}>
<MainContextProvider>
<div className={classes.root}>
<Header />
<TopBar />
<Sequence />
<SecondaryWindow />
</div>
</MainContextProvider>
</MuiThemeProvider>
);
}
export default App;
Now I know the answer, in my example, the class root is not able to benefit from the custom-created theme that is provided by MuiThemeProvider. Instead, it uses the original theme that comes in Mui. To solve this, I separated that div into a component. This way, the theme context (custom-theme from MuiThemeProvider) can be accessed by the div. This way when I switch DarkState, colors update on Mui components and HTML elements based on the custom theme palette.
import React, { useContext, useState } from "react";
import Header from "./components/Header.js";
import TopBar from "./components/TopBar.js";
import Sequence from "./components/Sequence.js";
import SecondaryWindow from "./components/SecondaryWindow.js";
import { MuiThemeProvider, createMuiTheme, makeStyles } from "#material-ui/core/styles";
import "./App.css";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { MainContextProvider } from "./contexts/mainContext.js";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
function AppContent() {
const useStyles = makeStyles((theme) => ({
root: {
paddingLeft: 80,
height: "100%",
backgroundColor: theme.palette.background.default,
},
}));
const classes = useStyles();
return (
<div className={classes.root}>
<Header />
<TopBar />
<Sequence />
<SecondaryWindow />
</div>
);
}
function App() {
const [darkState, setDarkState] = useState(true);
const palletType = darkState ? "dark" : "light";
const theme = createMuiTheme({
palette: {
secondary: {
main: "#0069ff",
},
type: palletType,
},
});
return (
<MuiThemeProvider theme={theme}>
<MainContextProvider>
<AppContent />
</MainContextProvider>
</MuiThemeProvider>
);
}
export default App;
It's because you only change the #material component not the CSS, to change the CSS Theme, you need to make variable for CSS for Dark Theme.
on :root declare all the light theme color and div.darkmode all the darkmode:
:root {
--color-bg: #fff;
--color-text: #000;
}
.div.darkmode {
--color-bg: #363636;
--color-text: #d1d1d1;
}
/** Usage */
.div {
color: var(--color-text);
background: var(--color-bg)
}
and make a condition on the div when the dark theme is true a new classname darkmode will be added to dive as you wrote above
<div className={`${classes.root} ${darkState && `darkmode`}`}>
<Header />
<TopBar />
<Sequence />
<SecondaryWindow />
</div>
I created an example for you here.
let us know if anything goes wrong!
workaround 2
if you're not doing any customer style by CSS file then this will work
import React from 'react';
import CssBaseline from '#material-ui/core/CssBaseline';
export default function MyApp() {
return (
<MuiThemeProvider theme={theme}>
<CssBaseline />
{/* The rest of your application */}
</MuiThemeProvider>
);
}
I would declare both variations of your theme as constants above the mounting or rendering. This way you are not literally creating a new theme ever time your theme swaps. I would have a state holding the reference to the MUI-Theme constant.
You can manipulate data-theme attribute to toggle the dark/light theme. Try it on StackBlitz.
Setting up themes
Use data-theme attribute to set the selected theme.
/* default theme (light) */
:root {
--primary-color: #302ae6;
--secondary-color: #536390;
--font-color: #424242;
--bg-color: #fff;
}
/* dark theme */
[data-theme='dark'] {
--primary-color: #9a97f3;
--secondary-color: #818cab;
--font-color: #e1e1ff;
--bg-color: #161625;
}
Switching theme in React Hooks
// App.js
const [theme, setTheme] = useState({
light: true,
});
const handleChangeTheme = (event) => {
setTheme({ ...theme, [event.target.name]: event.target.checked });
};
Set our data-theme attribute accordingly
const currentTheme = theme.light === true ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', currentTheme);

Resources