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

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);
`,
});

Related

TypeScript React, unable to send props to component

I have a simple button component, I am styling it with styled components, but I can not send a simple prop to the component as TypeScript complains with No overload matches this call
App.tsx
import React from 'react';
import Button from '../form/button';
export const App = (): React.ReactElement => <Button secondary>Click me</Button>;
Button.tsx
import React from 'react';
import styled from 'styled-components';
const StyledButton = styled.button`
background-color: #333;
padding: 10px 20px;
color: ${({ secondary }) => (secondary ? 'white' : 'red')};
`;
interface Props {
children: string;
secondary?: any;
}
const Button: React.FC<Props> = ({ children, secondary }) => (
<StyledButton secondary={secondary}>{children}</StyledButton>
);
export default Button;
This should work, what does TypeScript needs me to specify? The error is not very helpful
It's because you haven't passed the props to the styled component, so it does not know what secondary is.
You can pass your props like so (and I removed children since you don't need to specify this manually)
import React from "react";
import styled from "styled-components";
interface Props {
secondary?: any;
}
const StyledButton = styled.button<Props>`
background-color: #333;
padding: 10px 20px;
color: ${({ secondary }) => (secondary ? "white" : "red")};
`;
const Button: React.FC<Props> = ({ children, secondary }) => (
<StyledButton secondary={secondary}>{children}</StyledButton>
);
export default Button;
CodeSandbox: https://codesandbox.io/s/determined-worker-62xtm?file=/src/form/Button.tsx
You can just specify that the props passed and then destructured are from the interface Props and access the parameter secondary.
const StyledButton = styled.button`
background-color: #333;
padding: 10px 20px;
color: ${({secondary}: Props) => (secondary ? 'white' : 'red')}
`;

How to EXTEND imported component styled components

I cannot extend my imported component. I was looking into styled components docs and find that in v4+ should works prop "as", but it doesnt.
COMPONENT:
type Props = {
padding: string,
justify: string
}
const FlexContainer = styled.div<Props>`
padding: ${props => props.padding};
display: flex;
flex-wrap: wrap;
justify-content: ${props => props.justify};
`
export const Flexcontainer: React.FC<Props> = props =>{
return (
<FlexContainer padding={props.padding} justify={props.justify}>
{props.children}
</FlexContainer>
)
}
EXTENDED STYLE:
import { Flexcontainer } from '../../reusable/FlexContainer';
const FlexContainerExtended = styled.div`
color: red;
`
USE:
<FlexContainerExtended
padding={null}
justify={"flex-start"}
as={Flexcontainer}>
You just have to add a prop className to your Flexcontainer component like this:
export const Flexcontainer: React.FC<Props> = props =>{
return (
<FlexContainer className={props.className} padding={props.padding} justify={props.justify} >
{props.children}
</FlexContainer>
)}
To override styles, styled-components passes a className as props to the overrided component, it's why
You can just pass the base component to the styled function to override it.
type Props = {
padding: string,
justify: string
}
const FlexContainer = styled.div<Props>`
padding: ${props => props.padding};
display: flex;
flex-wrap: wrap;
justify-content: ${props => props.justify};
`
const FlexContainerExtended = styled(FlexContainer)`
color: red;
`
export const Flexcontainer: React.FC<Props> = props =>{
return (
<FlexContainer padding={props.padding} justify={props.justify}>
{props.children}
</FlexContainer>
)
}
// And use it like this
<FlexContainerExtended
padding={null}
justify={"flex-start"}/>
I know this question was asked a long time ago, but I leave here the solution I found for future visitors.
Base component definition
import React from 'react'
import styled from 'styled-components'
const ContainerWrapper = styled.div`
width: 100%;
max-width: 1200px;
padding-left: 5%;
padding-right: 5%;
margin-left: auto;
margin-right: auto;
`
export default function Container({ children, ...props }) {
return (
<ContainerWrapper {...props}>
{children}
</ContainerWrapper>
)
}
Extended component definition
Obs.: note that the extended component is an article, while the base component is a div, and so far they have no relationship.
Also note that the base component (Container) has been imported but not yet used.
import React from 'react'
import styled from 'styled-components'
import Container from '../../common/Container'
const HeroWrapper = styled.article`
height: 100vh;
padding-top: 74px;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
background-attachment: fixed;
`
Invoking componentes
Now, in the same file where I declared the extended component, I just call the base component, informing the name of the extended component in the as attribute.
export default function PostsWrapper() {
return (
<Container as={HeroWrapper}>
{/* ... */}
</Container>
)
}

React - styled-components, props, and TypeScript

I looking into styled-components and trying the examples from the website but I also wanted to use TypeScript.
I have this simple example here
import React from 'react';
import './App.css';
import styled from 'styled-components';
interface IProps{
primary: boolean
}
const App:React.FC<IProps> = ({primary}) => {
return (
<div className="App">
<h1>Styled Components</h1>
<Button>Normal</Button>
<Button primary>Primary</Button>
</div>
);
}
const Button = styled.button`
background: ${props => props.primary ? 'red' : 'white'};
color: ${props => props.primary ? 'white' : 'red'};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 1px solid green;
border-radius: 3px;
`
export default App;
but I'm getting errors on the primary prop
I getting the error
Property 'primary' does not exist on type 'ThemedStyledProps<Pick<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "form" | ... 264 more ... | "onTransitionEndCapture"> & { ...; }, any>'
Ho can I use styled-components with TypeScript?
Use styled-components with typescript:
const Button = styled.button<{ primary?: boolean }>`
Full code:
import * as React from 'react';
import styled from 'styled-components';
interface IProps{
primary?: boolean
}
const App:React.FC<IProps> = () => {
return (
<div className="App">
<h1>Styled Components</h1>
<Button>Normal</Button>
<Button primary>Primary</Button>
</div>
);
}
const Button = styled.button<{ primary?: boolean }>`
background: ${props => props.primary ? 'red' : 'white'};
color: ${props => props.primary ? 'white' : 'red'};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 1px solid green;
border-radius: 3px;
`
export default App;
I prepare a example from my project
Wrapper.ts(styled-component)
import styled from 'styled-components';
interface Props {
height: number;
}
export const Wrapper = styled.div<Props>`
padding: 5%;
height: ${(props) => props.height}%;
`;
index.tsx
import React, { FunctionComponent } from 'react';
import { Wrapper } from './Wrapper';
interface Props {
className?: string;
title: string;
height: number;
}
export const MainBoardList: FunctionComponent<Props> = ({ className, title, height }) => (
<Wrapper height={height} className={className}>
{title}
</Wrapper>
);
you can import interface/type from 'index.tsx' to 'Wrapper.ts' to reuse.
other wise you can specify type like this
export const Wrapper = styled.div<{height:number}>`

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 do I over write nested classes of material-ui in styled-components?

Method 1:
const StyledTextField = styled(({ ...rest }) => (
<OutlinedInput {...rest} classes={{ root: 'outlinedRoot' }} />
))`
.outlinedRoot {
.$notchedOutline {
border-color: red;
}
}
background: #fff;
border-radius: 4px;
color: #808080;
height: 53px;
`;
Method 2:
const StyledTextField = styled(OutlinedInput).attrs({
classes: { root: 'outlinedRoot', notchedOutline: 'notchedOutline' }
})`
.outlinedRoot {
.notchedOutline {
border-color: red;
}
}
background: #fff;
border-radius: 4px;
color: #808080;
height: 53px;
`;
I have seen also what classes needs to be added and read articles about that, but I am not able to overwrite
I tried above two ways to change the outline color but i am not able to do that
Below is an example of one way to do this. To do this with TextField rather than OutlinedInput, just add a space before .MuiOutlinedInput-root so that you match that as a descendant of TextField rather than matching that as one of the other classes on the root element. .MuiOutlinedInput-root is actually unnecessary for the styling except for helping with CSS specificity.
import React from "react";
import ReactDOM from "react-dom";
import OutlinedInput from "#material-ui/core/OutlinedInput";
import styled from "styled-components";
const StyledOutlinedInput = styled(OutlinedInput)`
&.MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline {
border-color: green;
}
&:hover.MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline {
border-color: red;
}
&.MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline {
border-color: purple;
}
`;
function App() {
return (
<div className="App">
<StyledOutlinedInput defaultValue="My Default Value" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Related answers:
Change border color on Material-UI TextField
Change outline for OutlinedInput with React material-ui
How do I override hover notchedOutline for OutlinedInput
Global outlined override

Resources