Access props inside the styled component using MUI - reactjs

I'm trying to create a custom button component using MUI on a React JS project. I've checked the docs and I discovered that I can do this using styled components. All worked well with the code presented below. The problem is that I want to create a more "customizable" component. I have inside my theme file 2 sets of colors (primary and secondary). The fact is that I want to create a button that is able to take a prop for this set of colors (primary / secondary).
import * as React from 'react';
import ButtonUnstyled, {
buttonUnstyledClasses,
ButtonUnstyledProps,
} from '#mui/core/ButtonUnstyled';
import { styled } from '#mui/system';
import { theme } from '../../theme';
const CustomButtonRoot = styled('button')(`
background-color: ${theme.palette[props.type].main};
padding: 15px 20px;
border-radius: 10px;
color: #fff;
`);
interface TodoButtonProps {
unstyledProps: ButtonUnstyledProps,
type?: 'primary' | 'secondary'
}
function CustomButton(props: TodoButtonProps) {
return <ButtonUnstyled {...props} component={CustomButtonRoot} />;
}
export default CustomButton
The question is: How I can include this prop inside the styled component code?

Pass a callback. In this callback the first argument is the props of the styled component. You can also use the style object instead of template string. More detail in this answer.
const CustomButtonRoot = styled("button")(
({ theme, myColor }) => `
padding: 15px 20px;
border-radius: 10px;
color: ${myColor};
`
);
<CustomButton myColor="red">abc</CustomButton>

Related

Is there any way I can style the nested component?

Here's my code.
/*Default Styled Component*/
import styled from "styled-components";
export const MenuWrapper = styled.ul`
border: 1px solid blue;
`
export const MenuList = styled.li`
color: blue;
`
export const Menu = (
<MenuWrapper>
<MenuList>1</MenuList>
<MenuList>2</MenuList>
<MenuList>3</MenuList>
</MenuWrapper>
);
/* This is where I tried to restyle */
import styled from 'styled-components';
import {
Menu,
MenuWrapper,
MenuList,
} from '../src/components/Menu.style';
const StyledMenu = styled(Menu)`
${MenuWrapper} {
border: 1px solid green;
}
${MenuList} {
color: green;
}
What I want to do is to create a default styled component with multiple elements in it and restyle according to my needs by using 'styled(Menu)' and adding ${MenuWrapper} ${MenuList} as selectors for nested elements but they all don't seem to work... Anyone can help..? Thank you in advance...
I see several issues here, but in the first place it seems like the problem is caused by the fact that you're not attaching the className prop to what's inside Menu.
See https://styled-components.com/docs/basics#styling-any-component and this Codesandbox: https://codesandbox.io/s/sweet-carlos-vt165s

Conditional styled components effect all instances when nested

If i use a prop condition on the main component it will work per instance.
So for example if i have:
const Div = styled.div<Props>`
${(props) => {
return css`
${props.test && "border: 5px solid red;"};
`;
}}
`;
only components which have the test prop will have this ugly border :)
But if i use this same condition on a nested css rule like that:
const Div = styled.div<Props>`
${(props) => {
return css`
.tabletScreen & {
${props.test && "border: 5px solid red;"};
}
`;
}}
`;
All instances of this component will have this ugly border if one of the components has this test prop.
When inspect it i see that all instances of the component gets the same class generated, so the this:
.tabletScreen .sc-jcFjpl {
border: 5px solid red;
}
Is implemented to all instances.
But on the first case (when the condition is not nested) the component with the test prop will get another class so it won't override the others.
How can i fix this?
Use && instead of & and it'll be scoped to that stylistic instance of the component. Single ampersand refers to the "static component class".
More info here: https://styled-components.com/docs/basics#pseudoelements-pseudoselectors-and-nesting
import React from "react";
import { render } from "react-dom";
import styled, { css } from "styled-components";
import Wrapper from "./Wrapper";
import Title from "./Title";
// Render these styled components like normal react components.
// They will pass on all props and work
// like normal react components – except they're styled!
const Div = styled.div`
${(props) => {
return css`
.tabletScreen {
border: ${props.test && '5px solid red'};
}
`;
}}
`;
const App = () => (
<Wrapper>
<Div test>
Without Test
<div className="tabletScreen">TabletScreen</div>
</Div>
</Wrapper>
);
render(<App />, document.getElementById("root"));

Using Storybook to show React components in both React & Plain HTML/CSS

so if I have a React Button component that uses styled component like so:
import styled, { css } from "styled-components";
import React from "react";
const Button = ({ theme, children }) => {
const StyledButton = styled.a`
padding: 10px 20px;
border-radius: 20px;
${theme === "primary" &&
css`
color: black;
background: white;
`}
${theme === "secondary" &&
css`
color: white;
background: black;
`}
`;
return <StyledButton>{children}</StyledButton>;
};
export default Button;
Is it possible to display this component, alongside its React code & HTML/CSS code on Storybook? (Without having to hard code the HTML/CSS code in)
I was thinking of using React's renderToString() to convert it into HTML and using the styled component's string for the CSS. Not sure if this is possible?
Yes, it is a storybook addon, not a React API.
See storysource addon.

Receiving prop in Material UI and conditional rendering

This is a styled-component code block, it gets an img prop and based on the prop, it decides to load a specific image.
export const DialogBanner = styled.div`
min-height: 250px;
margin-bottom: 20px;
${({ img }) =>
img
? `background-image: url(${img});`
: `background-image: url("/img/banner.jpg");`}
background-position: center;
background-size: cover;
filter: contrast(85%);
`;
<DialogBanner img={food.img} />
How can I write the same thing using Material UI's useStyles?
EDIT: This is now legacy and was written for version 4
Using the latest version of Material-UI you would use makeStyles which creates a hook for use within your component. https://material-ui.com/styles/basics/#adapting-based-on-props
For Example.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
// Create our styles using passed props
// These are `functors` it seems
const useStyles = makeStyles({
root: {
backgroundImage: ({ img }) =>
img
? `url(${img});`
: `url("/img/banner.jpg");`}
},
});
function DialogBanner(props) {
// Separating out our children
const { children, ...rest } = props
// Prop containing our `img`
const classes = useStyles(rest);
retun <div className={classes.root>{children}</div>
}

Extend a Styled Component from a different file

What I'm trying to achieve is to be able to add extra styling to my Button.tsx(essentially extending the styles) when using this component in different files. As you can see in my Button.tsx I have defined some default styles I would like the button to have, but as I use more buttons across my app, I might want to change the background or color, etc.
One thing I could do is this:
Example of not what I want to do:
import React from 'react'
import styled from 'styled-components'
interface IButton = {
children: string
}
export default function Button({ children }: IButton) {
const Button = styled.button`
padding: 1em;
background: #ccc;
border-radius: 5px;
`
const RedButton = styled(Button)`
// Inherits all of the styles from Button.
background: red;
`
return (
<Button>{children}</Button
)
}
This example will inherit my Button styles and then allows me to extend. The problem with this solution is, if I decide to add more buttons, I will always have to come back to this file and then add the different variants, which could start to make this file get pretty chunky and messy.
Ideally I would like to extend my <Button> from the App.tsx file, or which ever file I'm using my <Button> in.
How could I adjust the code below to achieve this?
Button.tsx
import React from 'react'
import styled from 'styled-components'
interface IButton = {
children: string
}
export default function Button({ children }: IButton) {
const Button = styled.button`
padding: 1em;
background: #ccc;
border-radius: 5px;
`
return (
<Button>{children}</Button
)
}
App.tsx
import React from 'react'
import styled from 'styled-components'
export default function App() {
return (
{/* This button would render with the default styles from Button.tsx */}
<Button>Button One</Button>
{/* This button would render with extended styles, a blue background for example */}
<Button>Button Two</Button>
)
}
In your App.tsx you can do the same:
const BlueButton = styled(Button)`
background: blue;
`
what styled-components does is it creates a class with background blue and pass it to your Button. so in your Button.tsx you need to accept the css class
export default function Button({ className, children }: IButton) {
const Button = styled.button`
padding: 1em;
background: #ccc;
border-radius: 5px;
`
return (
<Button className={className}>{children}</Button
)
}
Edit
another way to do is to export the style like this
const BaseStyles = css`
padding: 1em;
background: #ccc;
border-radius: 5px;
`
const BaseButton = styled.button`
${BaseStyles}
`
Then later override the styles
const BlueButton = styled.button`
${BaseStyles}
background: blue;
`

Resources