Better way to use material-system with styled-components - reactjs

I am trying to use the stack above to customize Material components with the props provided by the Material system (joining with styled-components).
The component that I'm testing:
import React from 'react'
import Grid from '#material-ui/core/Grid'
import { spacing, sizing, color, flexbox, display } from '#material-ui/system'
import styled from 'styled-components'
const Row = styled(props => <Grid container {...props} />)`
${spacing}
`
export default Row
Everything on screen works very well, but an error on console appear every time that I use a material-system prop. Ex.:
<Row maxWidth='200px'>...</Row>
the following error appear on console:
Warning: React does not recognize the maxWidth prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase maxwidth instead. If you accidentally passed it from a parent component, remove it from the DOM element.
I know that the ...props is passing maxWidth to Grid component (and it's a html element), but I'm confusing about how to use it without these console log's

Let's separate exactly which props are used for styling and which props are sent to the Grid component. Using rest, we can "pull out" certain properties from the props object and send the rest to the Grid component. Then, the properties that we pull out will form the CSS.
import React from 'react'
import Grid from '#material-ui/core/Grid'
import { spacing, sizing, color, flexbox, display } from '#material-ui/system'
import styled from 'styled-components'
const Row = styled(
({ maxWidth, somethingElse, ...rest }) => <Grid container {...rest} />
)`
${spacing}
maxWidth: ${props => props.maxWidth || defaultValue1};
somethingElse: ${props => props.somethingElse || defaultValue2};
`;
export default Row

The Material-UI documentation shows examples of various approaches to styling, including an example showing how to use styled-components with Material-UI.
Here is the example they show:
import React from 'react';
import { styled } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const MyButton = styled(Button)({
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
});
export default function StyledComponents() {
return <MyButton>Styled Components</MyButton>;
}
Another approach is using the Material-UI hook that uses a CSS-in-JS approach and the makeStyles function:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const useStyles = makeStyles({
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
});
export default function Hook() {
const classes = useStyles();
return <Button className={classes.root}>Hook</Button>;
}
There's also a way to create a custom theme in a separate file and import it where it's needed. Here is an example of the default Theme object and what it contains. It's good to take a look at and get an idea of what can be customized by using createMuiTheme.
Material-UI has multiple approaches to styling and it can be a lot to take in at first. Hope this helps.

tanks a lot about the anwsors. Based in these answers, I develop a helper to separate this kind of props:
import React from 'react'
import styled from "styled-components";
import Button from "#material-ui/core/Button";
import { spacing, sizing } from "#material-ui/system";
const allowedProps = ({props, system}) => {
const notAllowedProps = filterProps({props, system})
const allowedProps = Object.entries(props).reduce((acc, [key, value]) => {
if (!notAllowedProps.includes(key)) {
return {...acc, [key]: value}
}
return acc
}, {})
return allowedProps
}
const filterProps = ({props, system}) => {
const objSystem = system.reduce((acc, element) => ({
...acc,
...element(props)
}), {})
return Object.keys(objSystem)
}
const ButtonBase = (props) => <Button {...allowedProps({props, system: [spacing, sizing]})} />
const MyButton = styled(ButtonBase)...

Related

MUI styled cannot pass component prop to Typography in typescript

Using MUI with typescript and want to use styled from MUI as well. When passing the component prop to the styled component I get an error from the typescript sandbox below, maybe anyone knows a workaround.
https://codesandbox.io/s/material-demo-forked-lxdrj?file=/demo.tsx
You have to manually add the 'component' prop.
From https://mui.com/material-ui/guides/typescript/#complications-with-the-component-prop
import React from "react";
import type { TypographyProps } from "#material-ui/core";
import { styled } from "#material-ui/core/styles";
import Typography from "#material-ui/core/Typography";
type MyT = React.ComponentType<TypographyProps<"span", { component: "span" }>>;
const MyTypography: MyT = styled(Typography)({
background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
border: 0,
borderRadius: 3,
boxShadow: "0 3px 5px 2px rgba(255, 105, 135, .3)",
color: "white",
height: 48,
padding: "0 30px"
});
export default function StyledComponents() {
return (
<MyTypography component="span">Styled with styled-components API</MyTypography>
);
}
You can use generic type parameter in HOC created by styled:
import { styled } from '#mui/material/styles';
import Typography from '#mui/material/Typography';
type ExtraProps = {
component: React.ElementType;
};
const MyTypography = styled(Typography)<ExtraProps>({
// ...
});
Live Demo
Same idea as #GitGitBoom, but without having to define a type. You can use the solution described in the MUI Typescript Guide where you just cast your styled component to the typeof Typography
import React from "react";
import { styled } from "#material-ui/core/styles";
import Typography from "#material-ui/core/Typography";
const MyTypography = styled(Typography)({
background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
border: 0,
borderRadius: 3,
boxShadow: "0 3px 5px 2px rgba(255, 105, 135, .3)",
color: "white",
height: 48,
padding: "0 30px"
}) as typeof Typography;
export default function StyledComponents() {
return (
<MyTypography component="span">
Styled with styled-components API
</MyTypography>
);
}

Missing styles when using React with Web Components

I'm creating a widget module that will then be assembled into a bundle and connected to the static sites in a single file. One of the main problems is applying the styles of the parent sites to the widget module. Because of this, an attempt was made to encapsulate the widget using the shadow dom.
import React from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
const StyledButton = styled.button`...`;
function ModuleComponent() {
return (
<div>
<StyledButton>I does not work</StyledButton>
<button style={{ height: 40, color: "red" }}>It works</button>
</div>
);
}
class IncapsulatedReactWidgetElement extends HTMLElement {
connectedCallback() {
const mountPoint = document.createElement("div");
this.attachShadow({ mode: "open" }).appendChild(mountPoint);
ReactDOM.render(<ModuleComponent />, mountPoint);
}
}
customElements.define(
"incapsulated-react-widget-element",
IncapsulatedReactWidgetElement
);
function Root() {
return <incapsulated-react-widget-element />;
}
const MOUNT_NODE = document.getElementById("root");
ReactDOM.render(<Root />, MOUNT_NODE);
Actually it works as expected except the styles. I'm trying to use styled components but nothing works expect inline styles. As you can see, I do two render actions: first for integration ModuleComponent into custom element and second - for render the app. In which way styles can get lost?
Correct way for applying styles is using Style Hook for React. Make use of makeStyles and useStyles and its an standard way of doing it.
This may help for quick start: https://material-ui.com/styles/basics/#hook-api
The inline styles that you have used are also kinda a subset of it.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const useStyles = makeStyles({
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
});
export default function Hook() {
const classes = useStyles();
return <Button className={classes.root}>Hook</Button>;
}
if you are going to have larger stylesheets, then we can separate it to different file. For site wide changes, you can use createStyle hook.

Using Material-UI System with Default Components

Is it possible to use Material-UI System with the default Material UI components? For example, this currently does not work:
<AppBar bgColor="secondary.main" p={{ xs: 2, sm: 3, md: 4 }}>...</AppBar>
I am wondering, though, is there a way to get this to work (not just for AppBar, but for any Material UI component)? I.e., is there a way to integrate System with the default Material UI components?
If so, how?
Thanks.
The Box component can be used to add the Material-UI System features to other components by using its component prop. One caveat is that if you try to change styling that is explicitly controlled by the component, you may run into specificity issues. For instance the styling of the background color on the Button in my example below doesn't work correctly if you flip the order of the Button and Box imports since this causes the order of their styles in the <head> to also be flipped.
import React from "react";
import styled, { ThemeProvider as SCThemeProvider } from "styled-components";
import { useTheme, StylesProvider } from "#material-ui/core/styles";
import MuiAppBar from "#material-ui/core/AppBar";
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
const AppBar = styled(MuiAppBar)`
background-color: red;
${props => props.theme.breakpoints.up("sm")} {
background-color: orange;
}
${props => props.theme.breakpoints.up("md")} {
background-color: yellow;
color: black;
}
${props => props.theme.breakpoints.up("lg")} {
background-color: green;
color: white;
}
`;
export default function App() {
const muiTheme = useTheme();
return (
<StylesProvider injectFirst>
<SCThemeProvider theme={muiTheme}>
<Box component={AppBar} p={{ xs: 2, sm: 3, md: 4, lg: 5 }}>
Sample AppBar 1
</Box>
<div style={{ height: "100px" }} />
<Box component={Button} bgcolor="secondary.main">
Sample Button
</Box>
</SCThemeProvider>
</StylesProvider>
);
}
Related answer: Material-UI Grid does not hide whe use Display

Media Queries in Material-UI Using Styled-Components

Material UI has a nice set of built-in media queries: https://material-ui.com/customization/breakpoints/#css-media-queries
Material UI also allows us to use Styled-Components with Material UI: https://material-ui.com/guides/interoperability/#styled-components
I want to know how to combine the two together. That is, how can I make media queries using Styled Components and Material-UI's built-in breakpoints?
Thanks.
UPDATE:
Here is an example of what I am trying to do:
import React, { useState } from 'react'
import styled from 'styled-components'
import {
AppBar as MuiAppBar,
Drawer as MuiDrawer,
Toolbar,
} from '#material-ui/core'
const drawerWidth = 240
const AdminLayout = ({ children }) => {
return (
<BaseLayout>
<AppBar position="static">
<Toolbar>
TOOLBAR
</Toolbar>
</AppBar>
<Drawer>
DRAWER
</Drawer>
{children}
</BaseLayout>
)
}
AdminLayout.propTypes = {
children: PropTypes.node.isRequired,
}
export default AdminLayout
// ------- STYLES -------
const AppBar = styled(MuiAppBar)`
/* Implement appBar styles from useStyles */
`
const Drawer = styled(MuiDrawer)`
/* Implement drawer styles from useStyles */
`
// STYLES THAT I WANT TO CONVERT TO STYLED-COMPONENTS
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
},
drawer: {
[theme.breakpoints.up('sm')]: {
width: drawerWidth,
flexShrink: 0,
},
},
appBar: {
[theme.breakpoints.up('sm')]: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
},
},
toolbar: theme.mixins.toolbar,
}))
Below is an example showing one way of leveraging the Material-UI theme breakpoints with styled-components. This is passing the Material-UI theme to the styled-components ThemeProvider in order to make it available as a prop within the styles. The example also uses StylesProvider with the injectFirst prop so that the Material-UI styles will occur at the beginning of the <head> rather than the end, so that the styled-components styles occur after the Material-UI styles and therefore win when specificity is otherwise equal.
import React from "react";
import styled, { ThemeProvider as SCThemeProvider } from "styled-components";
import { useTheme, StylesProvider } from "#material-ui/core/styles";
import MuiAppBar from "#material-ui/core/AppBar";
const AppBar = styled(MuiAppBar)`
background-color: red;
${props => props.theme.breakpoints.up("sm")} {
background-color: orange;
}
${props => props.theme.breakpoints.up("md")} {
background-color: yellow;
color: black;
}
${props => props.theme.breakpoints.up("lg")} {
background-color: green;
color: white;
}
`;
export default function App() {
const muiTheme = useTheme();
return (
<StylesProvider injectFirst>
<SCThemeProvider theme={muiTheme}>
<AppBar>Sample AppBar</AppBar>
</SCThemeProvider>
</StylesProvider>
);
}
Related documentation:
styled-components theme usage: https://styled-components.com/docs/advanced#theming
StylesProvider injectFirst: https://material-ui.com/styles/api/#stylesprovider
If you are using the "Style Objects" approach (i.e., "JavaScript") to styled-components, then this is the way to achieve that same outcome. This builds on top of what Ryan Cogswell mentioned earlier.
Some might prefer this if switching over from another CSS-in-JS system (like Material-UI's built-in JSS). Also, the "Style Objects" approach only requires you to bring in props one time as opposed to using the props variable on any line. It's good to have choices. 😇
Style Object
const AppBar = styled(MuiAppBar)((props) => ({
backgroundColor: red;
[props.theme.breakpoints.up("sm")]: {
backgroundColor: orange,
},
[props.theme.breakpoints.up("md")]: {
backgroundColor: yellow,
color: black,
},
[props.theme.breakpoints.up("lg")]: {
backgroundColor: green,
color: white,
},
}));
Style Object, but more concise
Since we only need to access the props one time using the JavaScript approach and we only use theme in this style area, we can destructure theme from the incoming props for a bit less code.
const AppBar = styled(MuiAppBar)(({ theme }) => ({
backgroundColor: red;
[theme.breakpoints.up("sm")]: {
backgroundColor: orange,
},
[theme.breakpoints.up("md")]: {
backgroundColor: yellow,
color: black,
},
[theme.breakpoints.up("lg")]: {
backgroundColor: green,
color: white,
},
}));
Note: If you are using TypeScript and have set up your styled-components theme to match the Material-UI theme, then type safety still works as expected in either the CSS or JavaScript approach.
The breakpoints are provided as part of the default theme.
They are constants and won't change, therefore you can use them across the components or styled themes:
import React from 'react';
import styled from 'styled-components';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(theme => {
console.log('md', theme.breakpoints.up('md'));
return {};
});
const BP = {
MD: `#media (min-width:960px) `,
};
const Container = styled.div`
background-color: green;
${({ bp }) => bp} {
background-color: red;
}
`;
export default function StyledComponentsButton() {
useStyles();
return <Container bp={BP.MD}>Example</Container>;
}
const StyledDrawer = styled(Drawer)(
({ theme }) => `
.MuiDrawer-paper {
${theme.breakpoints.up('sm')} {
width: 370px;
}
${theme.breakpoints.down('sm')} {
width: 100vw;
}
}
`)
The syntax may look weird, but trying this code will explain everything
const StyledDiv = styled.div`
${({theme}) => {
console.log(theme.breakpoints.up('lg'));
return "";
}}
`;
// you will see in your console
// #media (min-width:1280px)
Once you understand that theme.breakpoints.up('lg') is same as #media (min-width:1280px) everything become obvious. everytime you put theme.breakpoints.up(key) it get replaced with #media... string.

How to theme components with styled-components and Material-UI?

Is there a possibility to use the Material-UI "theme"-prop with styled-components using TypeScript?
Example Material-UI code:
const useStyles = makeStyles((theme: Theme) => ({
root: {
background: theme.palette.primary.main,
},
}));
Now I want to replace this code with styled-components.
I have tested the following implementation (and other similar implementations) without success:
const Wrapper = styled.div`
background: ${props => props.theme.palette.primary.main};
`;
You can use withTheme to inject the theme as a prop.
import React from "react";
import { withTheme } from "#material-ui/core/styles";
import styled from "styled-components";
const StyledDiv = withTheme(styled.div`
background: ${props => props.theme.palette.primary.main};
color: ${props => props.theme.palette.primary.contrastText};
`);
export default function App() {
return (
<StyledDiv>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</StyledDiv>
);
}
For a start on the TypeScript aspects, see this demo: https://codesandbox.io/s/xyh60
This is from the withTheme HOC portion of the documentation.
I wanted a similar set of requirements (MUI, styled-components, theme & typescript). I got it working, but I'm sure it could be prettier:
import React from "react";
import styled from "styled-components";
import { Theme, useTheme } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
const StyledCustomButton: React.FC<{
theme: Theme;
}> = styled(({ ...props }) => <Button {...props}>Test</Button>)`
&& {
padding-bottom: ${(props) => props.theme.spacing(2)}px;
}
`;
const CustomButton: React.FC = () => {
const theme: Theme = useTheme();
return <StyledCustomButton theme={theme} />;
};
export default CustomButton;
I think no intended way. If you prefer the css way of writing then maybe consider this https://material-ui.com/styles/advanced/#string-templates
The example is
const useStyles = makeStyles({
root: `
background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%);
border-radius: 3px;
font-size: 16px;
border: 0;
color: white;
height: 48px;
padding: 0 30px;
box-shadow: 0 3px 5px 2px rgba(255, 105, 135, 0.3);
`,
});

Resources