How to reference a styled component that is in a different dom - reactjs

I like to add styling to a styled component that is in a different dom.
Like this Material-ui Menu component example, the dashboard button is highlighted gray, and the menu drop is highlighted light blue.
They are written in the same component file but they are in different dom.
I like to add styling to the Menu component from Button component.
Is that possible?
Demo sandbox: https://codesandbox.io/s/si8tr5?file=/demo.js
Menu material ui official doc: https://mui.com/material-ui/react-menu/#basic-menu
index.jsx
import * as React from 'react';
import MenuItem from '#mui/material/MenuItem';
import {DisplayMenu, DisplayButton} from './styles'
export default function BasicMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<DisplayButton
id="basic-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
>
Dashboard
</DisplayButton>
<DisplayMenu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</DisplayMenu>
</div>
);
}
styles.js
import styled from 'styled-components';
import Button from '#mui/material/Button';
import Menu from '#mui/material/Menu';
export const DisplayMenu = styled(Menu)`
padding: 20px;
`;
DisplayMenu.displayName = 'DisplayMenu';
export const DisplayButton = styled(Button)`
margin: 10px;
${DisplayMenu}{
& .MuiPaper-root {
width: 300px;
}
}
`;
DisplayButton.displayName = 'DisplayButton';
I know just doing this below will work but this is just an example and the reality is more complicated.
I just made it simple to ask this question here.
export const DisplayMenu = styled(Menu)`
padding: 20px;
& .MuiPaper-root {
width: 300px;
}
`;

Your current code won't work because you are applying styles to descendent DisplayMenu components (DisplayButton is a sibling). I don't really think it makes sense to do this but you could select the sibling:
export const DisplayButton = styled(Button)`
margin: 10px;
& + ${DisplayMenu}{
& .MuiPaper-root {
width: 300px;
}
}
`;
I think styling DisplayMenu directly makes the most sense (your last approach).
However If this is a permutation of DisplayMenu when used with a button, I think you should consider making the wrapper div into a styled component since that allows you to apply contextual styles to the menu (change its style based on 'where' it was used):
const MenuButton = styled.div`
${DisplayMenu}{
& .MuiPaper-root {
width: 300px;
}
`
// And use this in your component
<MenuButton>
<DisplayButton {...} />
<DisplayMenu {...} />
</MenuButton>
This way we only add the width to DisplayMenu when used within the context of MenuButton

Related

Apply styling from parent component to a specific component in child component | react, material ui, styled-components

There are multiple components in child component and can I apply styling to a specific component that is in child component from parent component?
App/index.jsx
import React from "react";
import { Container, StyledMenu } from "./styles";
const App = ({ className }) => {
return (
<Container className={className}>
<h1>this is a demo</h1>
<StyledMenu />
</Container>
);
};
export default App;
App/styles.js
import styled from "styled-components";
import Demo from "../Demo";
export const Container = styled.div`
background-color: pink;
padding: 20px;
`;
Container.displayName = "Container";
export const StyledMenu = styled(Demo)`
& .MuiPaper-root {
width: 300px;
}
`;
StyledMenu.displayName = "StyledMenu";
Demo/index.jsx
import * as React from "react";
import Button from "#mui/material/Button";
// import Menu from "#mui/material/Menu";
import MenuItem from "#mui/material/MenuItem";
import { MenuDisplay } from "./styles";
export default function BasicMenu({ className }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div className={className}>
<Button
id="basic-button"
aria-controls={open ? "basic-menu" : undefined}
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
onClick={handleClick}
>
Dashboard
</Button>
<MenuDisplay
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button"
}}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</MenuDisplay>
</div>
);
}
Demo/styles.js
import styled from "styled-components";
import Menu from "#mui/material/Menu";
export const MenuDisplay = styled(Menu)`
background-color: green;
`;
MenuDisplay.displayName = "MenuDisplay";
I like to apply width: 300px for the Menu component (where it is a dropdown menu) in Demo/index.jsx from App/style.js.
Is that possible?
Here is codesandbox for demo
Here is Menu component from mui doc
Attempts
I imported MenuDisplay (that's in Demo/styles.js) inside App/styles.js
import styled from "styled-components";
import Demo from "../Demo";
import { MenuDisplay } from "../Demo/styles";
export const Container = styled.div`
background-color: pink;
padding: 20px;
`;
Container.displayName = "Container";
export const StyledMenu = styled(Demo)`
${MenuDisplay} & {
& .MuiPaper-root {
width: 300px;
}
}
`;
StyledMenu.displayName = "StyledMenu";
This didn't work
To apply css to your child component, define width in the Demo/styles.js
export const MenuDisplay = styled(Menu)width: 300px;;
For more detail see the replay https://dashboard.testwise.io/replays/bRJwLQfxEcG of your issue.

why backgroundColor applies the place where I didn't mean to | Menu component from MUI | styled-components

I set background color pink to the Menu component from mui and this is what I'm seeing when I click dashboard button.
I expected this to add background color to the menu but turned out that half of the page got colored pink.
How can I apply background color to the menu?
import * as React from "react";
import Button from "#mui/material/Button";
import MenuItem from "#mui/material/MenuItem";
import { MenuRaw } from "./styles";
export default function BasicMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<Button
id="basic-button"
aria-controls={open ? "basic-menu" : undefined}
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
onClick={handleClick}
>
Dashboard
</Button>
<MenuRaw
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button"
}}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</MenuRaw>
</div>
);
}
import styled from "styled-components";
import Menu from "#mui/material/Menu";
export const MenuRaw = styled(Menu)`
width: 412px;
background-color: pink;
`;
You need to specify the class of the menu element that you want to change. In this case you want to overwrite the .MuiPaper-root class of Paper component:
export const MenuRaw = styled(Menu)`
& .MuiPaper-root {
background-color: pink;
width: 412px;
}
`;

styled-components: Extend existing component with additional prop for styling

I'm using styled-components to override styling from an existing component, in this case ToggleButton from material ui. However I want my new component to have one additional property (hasMargin) that controls the style:
import {ToggleButton} from "#material-ui/lab";
import styled, {css} from "styled-components";
const StyledToggleButton = styled(ToggleButton)<{ hasMargin?: boolean }>`
&& {
color: red;
${props => props.hasMargin &&
css`
margin: 10px;
`}
}
`;
My intention is that StyledToggleButton behaves exactly like ToggleButton but has red color and can be called with an additional property hasMargin to turn on the margin css. I want to call it like this:
<StyledToggleButton
value={""} // ToggleButton prop
size={"small"} // ToggleButton prop
hasMargin={true} // My prop from StyledToggleButton
>
click
</StyledToggleButton>
But if I do it like this, in the browser console I get:
Warning: React does not recognize the `hasMargin` prop on a DOM element.
This is because StyledToggleButton seems to also pass hasMargin further to ToggleButton and ToggleButton of course can't and shouldn't deal with that (passing it down to the DOM element where it makes no sense). What is the best way to rewrite my code, so hasMargin is only processed by StyledToggleButton?
I've made a codesandbox to illustrate the issue. Thank you!
This is exactly what transient props are for.
All you need to do is prefix the prop with a $ and styled-components won't pass the prop to the underlying DOM element.
// Note the $ prefix in the prop name 👇
const StyledToggleButton = styled(ToggleButton)<{ $hasMargin?: boolean }>`
&& {
color: red;
// Just use the prop as normal
${(props) =>
props.$hasMargin &&
css`
margin: 10px;
`}
}
`;
<StyledToggleButton
value={""}
size={"small"}
{/* Assign the prop as normal */}
$hasMargin={true}
>
click
</StyledToggleButton>;
Here's your updated Codesandbox link with that error gone.
What about wrapping the ToggleButton with span and applying styles from there?
const StyledToggleButton = styled.span<{ hasMargin?: boolean }>`
margin: ${props => props.hasMargin && '50px'};
button {
color: red !important;
}
`
interface MyButtonProps extends ToggleButtonProps {
hasMargin?: boolean;
}
function MyButton(props: MyButtonProps) {
const { hasMargin, ...toggleButtonProps } = props;
return (
<StyledToggleButton hasMargin={hasMargin} >
<ToggleButton {...toggleButtonProps}>
{props.children}
</ToggleButton>
</StyledToggleButton>
);
}
export default function App() {
return (
<div className="App">
<MyButton value={""}>click</MyButton>
<MyButton value={""} size={"small"} hasMargin={true}>
click
</MyButton>
</div>
);
}

MaterialUI for React with Styled-Components

I want to style the Paper of MaterialUI's Dialog
const StyledDialog = styled(Dialog)`
& .MuiPaper-root {
width: 600px;
}
`;
However, this means that all elements nested inside the Dialog that have the MuiPaper-root class (for example, other Papers) will inherit it.
Is there any way of scoping this styling only to the Paper used by the first Dialog?
There are a several ways to approach this. One approach is to use child selectors (as mentioned in Kaca992's answer), but the Paper is not a direct child of the Dialog so to use this approach you need & > .MuiDialog-container > .MuiPaper-root. Another option is to use Dialog's PaperComponent prop and provide it with a styled Paper component. A third option is to leverage the MuiDialog-paper CSS class.
All three approaches are shown in the example below.
import React from "react";
import Button from "#material-ui/core/Button";
import DialogTitle from "#material-ui/core/DialogTitle";
import Dialog from "#material-ui/core/Dialog";
import Paper from "#material-ui/core/Paper";
import styled from "styled-components";
const StyledDialog = styled(Dialog)`
& > .MuiDialog-container > .MuiPaper-root {
background-color: purple;
}
`;
const StyledDialog2 = styled(Dialog)`
& .MuiDialog-paper {
background-color: blue;
}
`;
const StyledPaper = styled(Paper)`
background-color: green;
`;
export default function SimpleDialogDemo() {
const [open1, setOpen1] = React.useState(false);
const [open2, setOpen2] = React.useState(false);
const [open3, setOpen3] = React.useState(false);
return (
<div>
<Button variant="outlined" color="primary" onClick={() => setOpen1(true)}>
Open dialog using child selectors
</Button>
<Button variant="outlined" color="primary" onClick={() => setOpen3(true)}>
Open dialog using MuiDialog-paper
</Button>
<Button variant="outlined" color="primary" onClick={() => setOpen2(true)}>
Open dialog using custom Paper
</Button>
<StyledDialog
onClose={() => setOpen1(false)}
aria-labelledby="simple-dialog-title"
open={open1}
>
<DialogTitle id="simple-dialog-title">
Paper styled via child selectors
</DialogTitle>
</StyledDialog>
<StyledDialog2
onClose={() => setOpen3(false)}
aria-labelledby="simple-dialog-title"
open={open3}
>
<DialogTitle id="simple-dialog-title">
Paper styled via MuiDialog-paper
</DialogTitle>
</StyledDialog2>
<Dialog
onClose={() => setOpen2(false)}
aria-labelledby="simple-dialog-title"
open={open2}
PaperComponent={StyledPaper}
>
<DialogTitle id="simple-dialog-title">
Paper styled via custom Paper component
</DialogTitle>
</Dialog>
</div>
);
}
Try this:
const StyledDialog = styled(Dialog)`
& > .MuiPaper-root {
width: 600px;
}
`;
css > operator will take only childs that are direct childs of the dialog component. If that is not ok look at other css operators: https://www.w3schools.com/cssref/css_selectors.asp

Styled Components Props React js

I pass a property to my styled component. Basically I pass his height that starts with 400px.
And when I click a button it will go to 30px, but the way I did my div starts with 400px and when I click the button it goes to 30px and then doesn't expand the height size anymore:
export default function Menu() {
const [open, setOpen] = useState(true); // declare new state variable "open" with setter
const handleClick = e => {
e.preventDefault();
setOpen(false);
};
return (
<DivMenuButton height={open ? '400px' : '30px'}>
<button
style={{ margin:0, padding: 0, height: "30px", width: "100%", borderRadius:'0px' }}
onClick={handleClick}
>
Button
</button>
</DivMenuButton>
);
}
My styled component:
import React from 'react';
import styled from 'styled-components';
export const DivMenuButton = styled.div`
border: 0px;
background-color: #000; // was wrong syntax
width: 200px;
height: ${props => props.height}
`;
code:
https://codesandbox.io/s/beautiful-nightingale-fsm58
Your code is actually correct (almost).
The props and styled components parts are correct.
The thing you did wrong is that you always set open to false when clicking the button, instead of setting it to the opposite of what it was before.
So, instead of doing this:
const handleClick = e => {
e.preventDefault();
setOpen(false);
};
you should do this:
const handleClick = e => {
e.preventDefault();
setOpen(!open);
};

Resources