Reusable component using theme-based Box features - reactjs

I'd like to create a reusable component using Material-UI's api (not using styled-components.) I got this far - and it almost works - but the settings that use theme variable don't work (e.g, bgcolor and padding). Am I doing something wrong - or is this not possible?
const BigPanel = styled(Box)({
display: 'flex',
width: '100%',
flexgrow: 1,
bgcolor: 'background.paper',
borderRadius: 10,
boxShadow:'1',
p:{ xs: 4, md: 8 }
});

The object passed to styled is intended to be CSS properties, but you have a mixture of CSS properties and Box props (bgcolor, p). Even the ones that are valid CSS properties (display, width) are also valid Box props, so the most straightforward solution is to specify all of them as props.
One way to handle this is to use defaultProps. This makes it very easy to override some of the props when using the component by specifying them explicitly as shown in the example below.
import React from "react";
import Box from "#material-ui/core/Box";
import CssBaseline from "#material-ui/core/CssBaseline";
import { styled } from "#material-ui/core/styles";
const BigPanel = styled(Box)({});
BigPanel.defaultProps = {
display: "flex",
width: "100%",
borderRadius: 10,
flexGrow: 1,
bgcolor: "background.paper",
p: { xs: 4, md: 8 },
boxShadow: "1"
};
export default function App() {
return (
<>
<CssBaseline />
<BigPanel>Default BigPanel</BigPanel>
<BigPanel bgcolor="primary.main" color="primary.contrastText">
BigPanel with explicit props
</BigPanel>
</>
);
}
In the example above, styled isn't really serving any purpose anymore except to create a new component type. Though it isn't less code, below is an alternative way to get the same effect without using styled:
const BigPanel = React.forwardRef(function BigPanel(props, ref) {
return <Box ref={ref} {...props} />;
});
BigPanel.defaultProps = {
display: "flex",
width: "100%",
borderRadius: 10,
flexGrow: 1,
bgcolor: "background.paper",
p: { xs: 4, md: 8 },
boxShadow: "1"
};

Related

How to style Input in material ui

I am new to material ui.
I use fifth version.
<InputBase
ref={params.InputProps.ref}
inputProps={params.inputProps}
autoFocus
sx={{
...InputCSS,
}}
/>
const InputCSS = {
width: '100%',
textIndent: '17px',
py: '12px',
fontSize: '20px',
borderRadius: '3px',
border: '1.5px solid rgba(0, 0, 0, 0.4)',
'MuiInputBase-root': { // does not work
borderColor: 'red !important',
color: 'red !important',
},
'&:focus' : { // does not work
...
}
}
I could have used styled('input') and it works, I can set &:focus and it works but I can't type anything in the input.
I want to get rid of the initial border and set focus property.
How can I change the border for this class?
I know that in v5 we can style our components using variants or sx or styled.
What is the best advice for styling mui components? Because almost all the info in the internet uses outdated useStyle or makeStyles which doesn't work with react 18v.
sometimes I just struggle with components styling in mui
You have several ways to customize a Mui component, but my three favorite approaches are:
Styled utility
Sx prop
Custom global theme
So when should I use each of these approaches?
Styled utility
If you want to make a component reusable across your app, go with styled, if you had ever used styled components then styled will be very familiar to you, here is an example of how to use it:
import * as React from 'react';
import Slider, { SliderProps } from '#mui/material/Slider';
import { alpha, styled } from '#mui/material/styles';
// if you are using typescript, don't forget to pass the component props on styled
const SuccessSlider = styled(Slider)<SliderProps>(({ theme }) => ({
width: 300,
color: theme.palette.success.main,
// to override the styles of inner elements,
// you have to use the & selector followed by the class name.
'&.MuiSlider-thumb': {
'&:hover, &.Mui-focusVisible': {
boxShadow: `0px 0px 0px 8px ${alpha(theme.palette.success.main, 0.16)}`,
},
'&.Mui-active': {
boxShadow: `0px 0px 0px 14px ${alpha(theme.palette.success.main, 0.16)}`,
},
},
}));
export default function StyledCustomization() {
return <SuccessSlider defaultValue={30} />;
}
To make the component even more dynamically, you can define custom props as well, like width, color, border and so on, read the styled docs to know more about it.
Sx prop
If you want to make an one time off style in a single component, the easiest way is to use the sx prop, but be careful with this approach because it can lead to a lot of repetition in your code. Following the code you gave:
<InputBase
ref={params.InputProps.ref}
inputProps={params.inputProps}
autoFocus
sx={{
...InputCSS,
}}
/>
const InputCSS = {
width: '100%',
textIndent: '17px',
py: '12px',
fontSize: '20px',
borderRadius: '3px',
border: '1.5px solid rgba(0, 0, 0, 0.4)',
// you should pass the & selector followed by the class that you want to override
'&.MuiInputBase-root': {
// avoid the use of !important
borderColor: 'red',
color: 'red',
},
'&.MuiInputBase-root:focus': {
...
}
}
check it out on codeSandbox. Just a suggestion, depending on your case the component TextField could fit better as it comes with some good styles and you don't need to style it from scratch.
Side note:
Every mui component has an api doc with a css section where you can find all available classes to override, for instance, see the InputBase api docs, also read the docs about sx prop.
Custom theme
At last but not least, if you want to customize your whole app with custom palette, components, typography, breakpoints and so on, no doubt you should use a custom theme, as mui provides a powerful tool called createTheme to do so, what I like the most in custom theme is the possibility to make custom variants of the component, like so:
import * as React from "react";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import Button from "#mui/material/Button";
import { red } from "#mui/material/colors";
// if you are using typescript, you must declare the module with the
// custom properties in order to get access of this property when using the component
declare module "#mui/material/Button" {
// define custom variants
interface ButtonPropsVariantOverrides {
dashed: true;
redVariant: true;
}
}
const defaultTheme = createTheme();
const theme = createTheme({
components: {
MuiButton: {
variants: [
{
// use the variant name defined earlier
props: { variant: "dashed" },
// set the styles for this variant
style: {
textTransform: "none",
border: `2px dashed ${defaultTheme.palette.primary.main}`,
color: defaultTheme.palette.primary.main
}
},
{
props: { variant: "redVariant" },
style: {
border: `2px solid ${red[300]}`,
color: red[600]
}
}
]
}
}
});
export default function GlobalThemeVariants() {
return (
// use the theme provider to get access of custom theme
// and variants within any child component
<ThemeProvider theme={theme}>
<Button variant="dashed" sx={{ m: 1 }}>
Dashed
</Button>
<Button variant="redVariant" color="secondary">
custom red variant
</Button>
</ThemeProvider>
);
}
See the example, also take a look at the custom theme docs. Hope I clarified the things a bit!

Mui Typography alignment based on breakpoints

Is there a way for me to change my Typography align property based on the pre-defined breakpoints?
For example:
<Typography
align={{ xs: 'left', sm: 'left', md: 'left', lg: 'right', xl: 'right' }}>
The following will cause my page to display nothing but white. I usually use that syntax to work with margins in the Box class and it works fine.
You can't set the Typography align attribute with an object.
As specified in the documentation for the Typography component, the align attribute can only be 'inherit' | 'left' | 'center' | 'right' | 'justify'.
Instead, you could use the withWidth HOC:
Sometimes you might want to change the React rendering tree based on the breakpoint value. We provide a withWidth() higher-order component for this use case.
withWidth injects a width property into your component that gives you access to the current breakpoint value. This allows you to render different props or content based on screen size.
function ResponsiveTypography({ width }) {
// This is equivalent to theme.breakpoints.down("md")
const isSmallScreen = /xs|sm|md/.test(width);
const typographyProps = {
align: isSmallScreen ? "left" : "right"
};
return (
<Typography {...typographyProps}>
Some text
</Typography>
);
}
export default withWidth()(ResponsiveTypography);
This seem to work for me, MUI v5:
import { useTheme } from '#mui/material/styles';
...
const theme = useTheme();
...
<Typography
sx={{
textAlign: 'center',
[theme.breakpoints.up('md')]: {
textAlign: 'left',
},
[theme.breakpoints.up('xl')]: {
textAlign: 'right',
},
[theme.breakpoints.between('sm', 'md')]: {
}
[theme.breakpoints.not('md')]: {
}
}}
>
Breakpoints API
https://mui.com/material-ui/customization/breakpoints/
With MUI v5, I found that sx prop has the ability to do breakpoints without importing the theme object (like in #atazmin's answer).
The breakpoints are mobile-first and get translated to min-width Media Queries. You can specify the starting value with xs and change it by breakpoint using lg and up.
<Typography
sx={{
textAlign: {
xs: 'left',
lg: 'right',
},
}}
>

React component Material UI withStyles: Variables in styles?

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:

React Admin Layout Component From Scratch

I want to implement a customized Layout for react-admin. I've tried to use their documentation 'https://marmelab.com/react-admin/Theming.html#using-a-custom-layout'
But it didn't work well ( for example we didn't have theme in our component). There is a broken link in the documentation that says you can use it for customizing :(.
I've implemented something right now that works fine right now but I don't know would work completely without problem/side-effects till the end of the project.
import * as React from 'react'
import { MuiThemeProvider, withStyles, createStyles} from '#material-ui/core/styles'
import appTheme from '../config/Theme'
import AppBar from './AppBar'
import Navbar from './Navbar'
const styles = (theme: any) => createStyles({
appFrame: {
display: 'flex',
flexDirection: 'column',
overflowX: 'auto',
},
content: {
display: 'flex',
flexDirection: 'column',
flexGrow: 2,
marginTop: '4em',
padding: theme.spacing.unit * 3,
paddingLeft: 5,
},
contentNoSidebar: {
display: 'flex',
flexGrow: 1,
},
root: {
backgroundColor: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
position: 'relative',
zIndex: 1,
},
})
class CustomLayout extends React.Component<any, any> {
public render () {
const {
children,
classes,
logout,
open,
title,
} = this.props
return (
<MuiThemeProvider theme={appTheme}>
<div className={classes.root}>
<div className={classes.appFrame}>
<AppBar title={title} open={open} logout={logout} />
<Navbar/>
<main className={classes.contentNoSidebar}>
<div className={classes.content}>
{children}
</div>
</main>
</div>
</div>
</MuiThemeProvider>
)
}
}
export default withStyles(styles)(CustomLayout)
I appreciate if you could help me in this case.
I see nothing wrong with this code sample. This is indeed the way to go to customize the Layout. Do you have any issue with it ?
Can you open an issue on the react-admin repository about the documentation broken link ?

Media queries in MUI components

I am using MUI components in ReactJs project, for some reason I need customization in some components to make it responsive according to screen width.
I have added media query and pass it as style attribute in the components but not working, any idea?
I am using code like this:
const drawerWidth = {
width: '50%',
'#media(minWidth: 780px)' : {
width: '80%'
}
}
<Drawer
.....
containerStyle = {drawerStyle}
>
</Drawer>
Code is working for web only, on mobile device no effect. Even CSS code is not applying I've checked in developer console. I am using MUI version 0.18.7.
Any help would be appreciated.
PS: As per requirement I need to make some changes according to screen size using CSS.
By using the breakpoints attribute of the theme, you can utilize the same breakpoints used for the Grid and Hidden components directly in your component.
API
theme.breakpoints.up(key) => media query
Arguments
key (String | Number): A breakpoint key (xs, sm, etc.) or a screen width number in pixels.
Returns
media query: A media query string ready to be used with JSS.
Examples
const styles = theme => ({
root: {
backgroundColor: 'blue',
[theme.breakpoints.up('md')]: {
backgroundColor: 'red',
},
},
});
for more information check this out
You were almost right, but you need to use min-width instead of minWidth:
const styles = {
drawerWidth: {
width: '50%',
'#media (min-width: 780px)': {
width: '80%'
}
}
}
You have a typo in the media query. You should use the following syntax and it will work as expected:
const drawerWidth = {
width: '50%',
'#media (min-width: 780px)' : {
width: '80%'
}
}
instead of
const drawerWidth = {
width: '50%',
'#media(minWidth: 780px)' : {
width: '80%'
}
}
In MUI v5, breakpoints can be declared in sx props by specifying an object where the keys are the breakpoint names and the values are the CSS values.
You can see MUI default breakpoints here. The breakpoint names and values can be overrided using createTheme():
const theme = createTheme({
breakpoints: {
values: {
xxs: 0, // small phone
xs: 300, // phone
sm: 600, // tablets
md: 900, // small laptop
lg: 1200, // desktop
xl: 1536 // large screens
}
}
});
return (
<ThemeProvider theme={theme}>
<Box
sx={{
// specify one value that is applied in all breakpoints
color: 'white',
// specify multiple values applied in specific breakpoints
backgroundColor: {
xxs: "red",
xs: "orange",
sm: "yellow",
md: "green",
lg: "blue",
xl: "purple"
}
}}
>
Box 1
</Box>
</ThemeProvider>
);
In the example above, xs: "orange" means set the Box color to orange if the screen width is inside xs range [300, 600).
You can also set the breakpoints using an array consists of the values from the smallest to largest breakpoint:
return (
<ThemeProvider theme={theme}>
<Box
sx={{
backgroundColor: [
"red",
"orange",
// unset, screen width inside this breakpoint uses the last non-null value
null,
"green",
"blue",
"purple"
]
}}
>
Box 2
</Box>
</ThemeProvider>
);
Similiar answer to #Lipunov's, based on #nbkhope's comment
const styles = {
drawerWidth: {
width: '50%',
[theme.breakpoints.up(780)]: {
width: '80%'
}
}
}
I've solved this problem by doing something like this:
const dropzoneStyles =
window.screen.availWidth < 780 ?
{ 'width': '150px', 'height': '150px', 'border': 'none', 'borderRadius': '50%' }
: { 'width': '200px', 'height': '200px', 'border': 'none', 'borderRadius': '50%' };
and then appending it as an attribute in the Material UI element:
<Dropzone style={dropzoneStyles} onDrop={this.handleDrop.bind(this)}>
So the key is to find out the window screen using window.screen.availWidth. And you would be doing this in the render() function. Hope that helps!
In the style property on React you can only define properties that you can define in a normal DOM element (You can't include media queries for example)
The way you can include media queries for that component would be passing a class name to the Drawer Component
<Drawer containerClassName="someClass" />
And then in a CSS file you do something like this
#media(min-width: 780px){
.someClass {
width: 50%!important;
}
}
In my case I just needed the breakpoint on one component and I found the createTheme approach a little bit too much. I ended using useMediaQuery and useTheme.
I see that with useMEdiaQuery you can be quite granular
import { useTheme } from '#mui/material/styles';
import useMediaQuery from '#mui/material/useMediaQuery';
const Component = () => {
const theme = useTheme();
const matchesSM = useMediaQuery(theme.breakpoints.down('sm'));
const matchesMD = useMediaQuery(theme.breakpoints.only('md'));
const dynamicStyles = {
...matchesSM && {margin: '10px 0'},
...matchesMD && {margin: '20px 0'}
}
return (
<Grid item xs={12} md={4} sx={{...dynamicStyles}}>
<div>Children</div>
</Grid>
)
}
CSS media queries are the idiomatic approach to make your UI responsive. The theme provides five styles helpers to do so:
theme.breakpoints.up(key)
theme.breakpoints.down(key)
theme.breakpoints.only(key)
theme.breakpoints.not(key)
theme.breakpoints.between(start, end)
In the following stress test, you can update the theme color and the background-color property live:
const styles = (theme) => ({
root: {
padding: theme.spacing(1),
[theme.breakpoints.down('md')]: {
backgroundColor: theme.palette.secondary.main,
},
[theme.breakpoints.up('md')]: {
backgroundColor: theme.palette.primary.main,
},
[theme.breakpoints.up('lg')]: {
backgroundColor: green[500],
},
},
});
<Root>
<Typography>down(md): red</Typography>
<Typography>up(md): blue</Typography>
<Typography>up(lg): green</Typography>
</Root>
Know more
Create a variable and then use that variable anywhere in the function
import React from 'react';
import { createMuiTheme, ThemeProvider, useTheme } from '#materialui/core/styles';
import useMediaQuery from '#material-ui/core/useMediaQuery';
function MyComponent() {
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.up('sm')); // Variable for media query
return <span hidden={matches}>Hidden on screen size greater then sm </span>;
}
const theme = createMuiTheme();
export default function ThemeHelper() {
return (
<ThemeProvider theme={theme}>
<MyComponent />
</ThemeProvider>
);
}

Resources