React component Material UI withStyles: Variables in styles? - reactjs

Is there a way to use variables in my React components using Material UI and withStyles? How can replace the repeated '20px' in the styles const below with a variable? Is this possible?
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import Topnav from '../component/Topnav';
import Footer from '../component/Footer';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
margin: {
marginLeft: '20px',
marginRight: '20px',
marginTop: '20px',
}
};
class MainLayoutComp extends Component {
render = props => {
const { children, classes } = this.props;
return (
<>
<Topnav />
<div className={classes.margin}>
{children}
<Footer />
</div>
</>
);
}
}
const MainLayout = withStyles(styles)(MainLayoutComp);
export default MainLayout;

The answer below is specific to this question of replacing repeated values in the styles, but may not be what most people are looking for based on the title of the question. If you are wanting dynamic styles based on props, see the following questions:
How to allow customization of a React component's style via props, when withStyles api is used?
Send Variable to withStyles in Material UI?
It's just JavaScript, so you can do the following:
const myMargin = '20px';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
margin: {
marginLeft: myMargin,
marginRight: myMargin,
marginTop: myMargin,
}
};
Also, you can easily leverage your theme by using a function for the styles. withStyles will pass the theme as an argument:
const styles = (theme) => ({
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
margin: {
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
marginTop: theme.spacing.unit * 3,
}
});
Here's a working example showing both:

Related

How to apply multiple classes to components on React 5?

Before v5, I was able to apply multiple class to the component like the following:
import React from "react";
import { makeStyles } from '#mui/styles';
const useStyles = makeStyles({
image: {
borderRadius: "50%",
width: 256,
height: 256,
},
shadow: {
boxShadow: "-4px -3px 45px 21px rgba(0,0,0,0.35)",
},
}));
const MyComponent = (props) => {
const { data } = props;
const classes = useStyles();
return (
<img src={data.image} className={`${classes.image} ${classes.shadow}`} alt={data.title} />
);
};
export default MyComponent;
However, since the change of styling engine(?), it seems like I can only apply a single styling like:
import React from "react";
import { styled } from "#mui/system";
const ProfileImage = styled("img")({
borderRadius: "50%",
width: 256,
height: 256,
});
const MyComponent= (props) => {
const { data } = props;
return (
<ProfileImage src={data.image} alt={data.title} />
);
};
export default MyComponent;
Would there be any possible solution to achieve the same behavior as the one above?
Thank you!
You could use an object and just spread the properties you want to use
const classes = {
image: {
borderRadius: "50%",
width: 256,
height: 256
},
shadow: {
boxShadow: "-4px -3px 45px 21px rgba(0,0,0,0.35)"
}
};
const ProfileImage1 = styled("img")({
...classes.image,
...classes.shadow
});
If you are using JS and want to benefit from type intellisense, you could use type annotation to get the hint of CSS properties
import { styled, SxProps, Theme } from "#mui/system";
/** #type{Record<string, SxProps<Theme>>} */
const classes = {
image: {
borderRadius: "50%",
width: 256,
height: 256
},
shadow: {
boxShadow: "-4px -3px 45px 21px rgba(0,0,0,0.35)"
}
};
Also, if you want to make the class conditional, styled component allows you to use prop from the component
const ProfileImage1 = styled("img")(({ shadow }) => ({
...classes.image,
...(shadow ? classes.shadow : {})
}));
export default function App() {
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
gap: 5
}}
>
<ProfileImage1 src={"https://via.placeholder.com/256"} shadow />
<ProfileImage1 src={"https://via.placeholder.com/256"} />
</Box>
);
}

How to 'justify-content: center' for MUI Pagination?

I am trying to center the contents of the Pagination. However, this does not work. On console, I need to justify the ul wrapper and I can not find any information on MUI site related to the pagination props or a guide on how to center the item.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Pagination from '#material-ui/lab/Pagination';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
marginTop: theme.spacing(2),
},
},
pagination: {
alignItems: 'center',
justify: 'center',
}
}));
const Paginated = (props) => {
const classes = useStyles();
return (
<div className={classes.root}>
<Pagination className={classes.pagination} count={props.totalPage} color='primary' />
</div>
);
};
export default Paginated;
and I have been trying on codesandbox as well. https://codesandbox.io/s/material-demo-zv1ps?file=/demo.js
Is there any way I can do this without having an additional box or Grid wrapper to wrap it out?
root: {
"& > *": {
marginTop: theme.spacing(2),
justifyContent:"center",
display:'flex'
}
}
We can also use Stack component:
import Stack from '#mui/material/Stack';
<Stack alignItems="center">
<Pagination className={classes.pagination} count={props.totalPage} color='primary' />
</Stack>
margin: "auto" worked for me
root: {
margin: "auto",
},
I got the idea from here: 4 ways to center a component in Material-UI

Material-UI media query does't work with native styled

Wouldn't be possible to use [props => props.theme.breakpoints.up('sm')] ?
import React from 'react';
import { styled, withTheme } from '#material-ui/core';
export const DefaultContent = withTheme(
styled(({ theme, ...other }) => <main {...other} />)({
flexGrow: 1,
padding: props => props.theme.spacing(1),
backgroundColor: 'red',
[props => props.theme.breakpoints.up('sm')]: {
backgroundColor: 'blue',
},
})
);
You don't need functions when using theme properties - it kind of looks like you're mixing backtick syntax (where you would see props passed in a function on each line) rather than object syntax. This works:
const DefaultContent = styled('main')(({ theme }) => ({
flexGrow: 1,
padding: theme.spacing(1),
backgroundColor: 'red',
[theme.breakpoints.up('sm')]: {
backgroundColor: 'blue'
}
}));

How do you change a style of a child when hovering over a parent using MUI styles?

I'm using MUI in react. Let's say I have this component with these styles:
const useStyles = makeStyles(theme => ({
outerDiv: {
backgroundColor: theme.palette.grey[200],
padding: theme.spacing(4),
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.grey[100]
}
},
addIcon: (props: { dragActive: boolean }) => ({
height: 50,
width: 50,
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})
}));
function App() {
const classes = useStyles();
return (
<Grid container>
<Grid item className={classes.outerDiv}>
<AddIcon className={classes.addIcon} />
</Grid>
</Grid>
);
}
I want to change the style of addIcon when hovering over outerDiv using the styles above.
Here's my example.
Below is an example of the correct syntax for v4 ("& $addIcon" nested within &:hover). Further down are some v5 examples.
import * as React from "react";
import { render } from "react-dom";
import { Grid, makeStyles } from "#material-ui/core";
import AddIcon from "#material-ui/icons/Add";
const useStyles = makeStyles(theme => ({
outerDiv: {
backgroundColor: theme.palette.grey[200],
padding: theme.spacing(4),
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.grey[100],
"& $addIcon": {
color: "purple"
}
}
},
addIcon: (props: { dragActive: boolean }) => ({
height: 50,
width: 50,
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})
}));
function App() {
const classes = useStyles();
return (
<Grid container>
<Grid item className={classes.outerDiv}>
<AddIcon className={classes.addIcon} />
</Grid>
</Grid>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Related documentation and answers:
https://cssinjs.org/jss-plugin-nested?v=v10.0.0#use-rulename-to-reference-a-local-rule-within-the-same-style-sheet
how to use css in JS for nested hover styles, Material UI
Material UI: affect children based on class
Advanced styling in material-ui
For those who have started using Material-UI v5, the example below implements the same styles but leveraging the new sx prop.
import Grid from "#mui/material/Grid";
import { useTheme } from "#mui/material/styles";
import AddIcon from "#mui/icons-material/Add";
export default function App() {
const theme = useTheme();
return (
<Grid container>
<Grid
item
sx={{
p: 4,
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
"& .addIcon": {
color: "purple"
}
}
}}
>
<AddIcon
className="addIcon"
sx={{
height: "50px",
width: "50px",
color: theme.palette.grey[400],
mb: 2
}}
/>
</Grid>
</Grid>
);
}
Here's another v5 example, but using Emotion's styled function rather than Material-UI's sx prop:
import Grid from "#mui/material/Grid";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import AddIcon from "#mui/icons-material/Add";
import styled from "#emotion/styled/macro";
const StyledAddIcon = styled(AddIcon)(({ theme }) => ({
height: "50px",
width: "50px",
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
}));
const StyledGrid = styled(Grid)(({ theme }) => ({
padding: theme.spacing(4),
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
[`${StyledAddIcon}`]: {
color: "purple"
}
}
}));
const theme = createTheme();
export default function App() {
return (
<ThemeProvider theme={theme}>
<Grid container>
<StyledGrid item>
<StyledAddIcon />
</StyledGrid>
</Grid>
</ThemeProvider>
);
}
And one more v5 example using Emotion's css prop:
/** #jsxImportSource #emotion/react */
import Grid from "#mui/material/Grid";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import AddIcon from "#mui/icons-material/Add";
const theme = createTheme();
export default function App() {
return (
<ThemeProvider theme={theme}>
<Grid container>
<Grid
item
css={(theme) => ({
padding: theme.spacing(4),
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
"& .addIcon": {
color: "purple"
}
}
})}
>
<AddIcon
className="addIcon"
css={(theme) => ({
height: "50px",
width: "50px",
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})}
/>
</Grid>
</Grid>
</ThemeProvider>
);
}
This denotes the current selector which is the parent component:
'&': { /* styles */ }
This means the parent component in hover state:
'&:hover': { /* styles */ }
This means the child component inside the parent that is in hover state:
'&:hover .child': { /* styles */ }
You can also omit the ampersand & if you're using a pseudo-class:
':hover .child': { /* styles */ }
Complete code using sx prop (The same style object can also be used in styled()):
<Box
sx={{
width: 300,
height: 300,
backgroundColor: "darkblue",
":hover .child": {
backgroundColor: "orange"
}
}}
>
<Box className="child" sx={{ width: 200, height: 200 }} />
</Box>
Possibly an obvious point, but just to add to the answer above: if you are referencing a separate className, don't forget that you also need to create it in the makeStyles hook or else it won't work. For instance:
const useStyles = makeStyles({
parent: {
color: "red",
"&:hover": {
"& $child": {
color: "blue" // will only apply if the class below is declared (can be declared empty)
}
}
},
// child: {} // THIS must be created / uncommented in order for the code above to work; assigning the className to the component alone won't work.
})
const Example = () => {
const classes = useStyles()
return (
<Box className={classes.parent}>
<Box className={classes.child}>
I am red unless you create the child class in the hook
</Box>
</Box>
)
}
If you were using makeStyles in MUI v4 and have migrated to MUI v5 then you are likely now importing makeStyles from tss-react. If that is the case, then you achieve the same by the following:
import { makeStyles } from 'tss-react';
const useStyles = makeStyles((theme, props, classes) => ({
outerDiv: {
backgroundColor: theme.palette.grey[200],
padding: theme.spacing(4),
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.grey[100],
[`& .${classes.addIcon}`]: {
color: "purple"
}
}
},
addIcon: (props: { dragActive: boolean }) => ({
height: 50,
width: 50,
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})
}));
The third argument passed to the makeStyles callback is the classes object.

Style the dropdown element of MUI Select

I'm trying to customize the design (borders, radius border) of the drop-down element of the MUI Select component.
The MUI documentation mentions various properties to override and style the various sub-components, but none for the drop-down itself. The reason for it might be that the drop down renders out of the root component, with position absolute relative to the page.
Any idea how I can style the dropdown?
Here is a screenshot of the current state of the component:
I was able to customize the design of the input element of the MUI Select component
Material-UI v4
You can do that in two different ways:
1. At global level
This way all the menus in the application will get the style.
First you need to create a theme.js file:
'use strict';
import { createMuiTheme } from '#material-ui/core/styles';
const theme = createMuiTheme({
overrides: {
// Applied to the <ul> element
MuiMenu: {
list: {
backgroundColor: "#cccccc",
},
},
// Applied to the <li> elements
MuiMenuItem: {
root: {
fontSize: 12,
},
},
},
});
export default theme;
Then import it in your main application component, so it will be applied to all the application components:
'use strict';
import React from "react";
import { ThemeProvider } from '#material-ui/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import theme from 'theme.js';
export default class App extends React.Component {
render() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* Your app content */}
</ThemeProvider>
);
}
}
2. At component level
With this approach you can define a different menu style for each component.
'use strict';
import React from "react";
import { makeStyles } from '#material-ui/core/styles';
import Select from "#material-ui/core/Select";
const useStyles = makeStyles({
select: {
"& ul": {
backgroundColor: "#cccccc",
},
"& li": {
fontSize: 12,
},
},
});
export default class MyComponent extends React.Component {
const classes = useStyles();
render() {
return (
<Select MenuProps={{ classes: { paper: classes.select } }} />
);
}
}
You can use the sx prop in MUI v5 to style the Paper which contains a list of MenuItem inside like this:
<Select
fullWidth
value={age}
onChange={handleChange}
MenuProps={{
PaperProps: {
sx: {
bgcolor: 'pink',
'& .MuiMenuItem-root': {
padding: 2,
},
},
},
}}
>
Live Demo
For Material-ui version 0
Apply styles to dropdownMenuprops as stated here Select Properties
const dropdownMenuProps={
menuStyle:{
border: "1px solid black",
borderRadius: "5%",
backgroundColor: 'lightgrey',
},
}
Apply the style to select using dropdownmenuprops
<SelectField
multiple={true}
hintText="Overriden"
value={values}
onChange={this.handleChange}
dropDownMenuProps={dropdownMenuProps}
>
SandBox Demo using version 0.18
For Material-ui Version 1
Dropdown or menu styles are overriden using MenuProps property.
Select-API
Try this
const styles = theme => ({
dropdownStyle:
{
border: "1px solid black",
borderRadius: "5%",
backgroundColor:'lightgrey',
},
});
Apply the style to MenuProps
<Select
value={this.state.age}
onChange={this.handleChange}
inputProps={{
name: "age",
id: "age-simple"
}}
MenuProps={{ classes: { paper: classes.dropdownStyle } }}
>
I tried this in codesandbox and it works for me
Code Sandbox Demo
Read the API of Menu and Select for more details.
For anyone still looking for this in 2022:
MenuProps={{
sx: {
'& .MuiMenu-paper': {
backgroundColor: 'dark.primary',
color: 'text.light'
},
'& .MuiMenuItem-root:hover': {
backgroundColor: 'dark.secondary',
color: 'text.white'
},
'& .Mui-selected': {
backgroundColor: 'primary.main',
color: 'text.white'
}
}
}}
sx={{
color: '#fff',
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: 'red',
},
'.MuiSvgIcon-root': {
color: '#fff'
},
'&:before': {
borderBottom: `1px solid ${DarkTheme.palette.primary.light}`
},
'&:hover': {
':before': {
borderBottom: `1px solid ${DarkTheme.palette.primary.dark}`
}
},
'& .MuiMenuItem-root': {
backgroundColor: 'dark.primary'
},
'& .MuiMenu-paper': {
backgroundColor: 'dark.primary'
}
}}

Resources