Styled Components Props React js - reactjs

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

Related

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

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

I want to close the modal when react esc is pressed

We are using react and styled-components.
When the button is pressed, the modal(drawer) is displayed from the right.
I want the modal to be closed when the esc button is pressed.
How do I detect that the esc button has been pressed?
codesandbox
import Drawer from "./components/Drawer";
import { FunctionComponent, useState } from "react";
const App: FunctionComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const onClose = () => {
setIsOpen(false);
};
return (
<div className="App">
<button onClick={() => setIsOpen(!isOpen)}>button</button>
<Drawer isOpen={isOpen} onClose={onClose}></Drawer>
</div>
);
};
export default App;
import React from "react";
import styled from "styled-components";
type Props = {
isOpen: boolean;
padding?: number;
children: React.ReactNode;
onClose: () => void;
handleKeyDown: () => void;
};
export default (props: Props) => {
return (
<>
<Overlay isOpen={props.isOpen} onClick={props.onClose} />
<DrawerModal {...props}>{props.children}</DrawerModal>
</>
);
};
const DrawerModal = styled.div<Props>`
position: absolute;
overflow: scroll;
top: 0;
right: 0;
height: 100vh;
width: ${({ isOpen }: Props) => (isOpen ? 400 : 0)}px;
background: blue;
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.25);
transition: 200ms cubic-bezier(0.25, 0.1, 0.24, 1);
`;
const Overlay = styled.div<{ isOpen: boolean }>`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: ${(props: { isOpen: boolean }) => (props.isOpen ? 0 : -1)};
`;
Use an effect like this to handle keyboard events like this:
useEffect(() => {
function handleEscapeKey(event: KeyboardEvent) {
if (event.code === 'Escape') {
setIsOpen(false)
}
}
document.addEventListener('keydown', handleEscapeKey)
return () => document.removeEventListener('keydown', handleEscapeKey)
}, [])
The effect declares a function that handle a keydown event and checks if the escape key was pressed. Then it binds that event handler to the document. When the component is removed, it cleans up by removing the event handler from the document.

How to change button background color when I click the button in React using Hooks

I am working on a React project, In my project I have two buttons, for First button I assigned a state for second button I written a function and I assigned a state as well. but my onClick function is not working. please help me to resolve this isssue.
This is App.js
import React, { useState } from "react";
import { Button } from "antd"
import 'antd/dist/antd.css';
import "./App.css";
const App = () => {
const [buttonOne, setButtonOne] = useState("red")
const [buttonTwo, setButtonTwo] = useState("blue")
const buttonTwoBackgroundColor = () => {
setButtonTwo({
backgroundColor: "red",
border: "red"
})
}
return (
<div>
<Button style={{backgroundColor: buttonOne, border: buttonOne}} className="one" type="primary">First</Button>
<Button style={{backgroundColor: buttonTwo, border: buttonTwo}} onClick={buttonTwoBackgroundColor} className="two" type="primary">Second</Button>
</div>
)
}
export default App
This is App.css
.one {
margin-right: 5px;
margin-left: 250px;
margin-top: 50px;
}
.two, .three, .four, .five {
margin-right: 5px;
}
In the function buttonTwoBackgroundColor you set setButtonTwo to a object. it should be a string.
const buttonTwoBackgroundColor = () => {
setButtonTwo("red")
}
change
onClick={buttonTwoBackgroundColor}
to
onClick={setButtonTwo("red")}
you are using useState as string at initialization and assigning an object in function below, which is not proper way!
You are trying to set state to be an object when you have defined the defualt state as a string.
Update the default state to an object and then access properties like this:
import React, { useState } from "react";
import { Button } from "antd";
import "./styles.css";
export default function App() {
const [buttonOne, setButtonOne] = useState("red");
const [buttonTwo, setButtonTwo] = useState({backgroundColor: "blue", border: "blue"});
const buttonTwoBackgroundColor = () => {
setButtonTwo(prevState => ({
...prevState,
backgroundColor: 'blue',
border: 'red'
}));
};
return (
<div>
<Button
style={{ backgroundColor: buttonOne, border: buttonOne }}
className="one"
type="primary"
>
First
</Button>
<Button
style={{ backgroundColor: buttonTwo.backgroundColor, border: buttonTwo.border }}
onClick={buttonTwoBackgroundColor}
className="two"
type="primary"
>
Second
</Button>
</div>
);
}

React GridList w/ Modal Images

Well, how to start, hi!
I'm creating some Slider with images using Material-UI GridList, and I want those images to be opened in a modal way, just to see them clearly.
I will put the code, and then explain.
import React, {useState} from "react";
import { makeStyles } from "#material-ui/core/styles";
import Modal from "#material-ui/core/Modal";
import tileData from './../../utils/tileData'
import useStylesForSlider from './../../hooks/useStylesForSlider'
import GridList from '#material-ui/core/GridList'
function getModalStyle() {
const top = 50
const left = 50
return {
top: `${top}%`,
left: `${left}%`,
transform: `translate(-${top}%, -${left}%)`
};
}
const useStyles = makeStyles((theme) => ({
paper: {
position: "absolute",
width: 400,
backgroundColor: theme.palette.background.paper,
border: "2px solid #000",
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3)
}
}));
export default function SimpleModal() {
// I have this in another folder, i will put the other ones too, i'm just starting
const classesRoot = useStylesForSlider()
const classes = useStyles();
// getModalStyle is not a pure function, we roll the style only on the first render
const [modalStyle] = useState(getModalStyle);
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const body = (
<div style={modalStyle} className={classes.paper}>
</div>
);
return (
<div className={classesRoot.root}>
<GridList className={classesRoot.gridList} cols={2.5}>
{tileData.map((tile) => (<img src={tile.img} alt={tile.img} onClick={handleOpen} key={tile.img}/>))}
</GridList>
<Modal
open={open}
onClose={handleClose}>
{body}
</Modal>
</div>
);
}
I have in "tileData" an array with the images, and I map them into a tag just to put them in the slider (GridList). It works well. Now, i want to click some img, and then open it in modal window. I click it, and the modal opens, but now comes my question, how do I put the image I clicked somewhere in the "body" constant, or how do I do to do it well. I don't know if i'm explaining well, but I expect to have some good advices, i'm pretty new in React world
You could create a state for the current chosen image index (or a unique id) of your tileData array and then load the image in the body by its index (or id). Here is an example:
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Modal from "#material-ui/core/Modal";
import tileData from './../../utils/tileData'
import GridList from "#material-ui/core/GridList";
function getModalStyle() {
const top = 50;
const left = 50;
return {
top: `${top}%`,
left: `${left}%`,
transform: `translate(-${top}%, -${left}%)`
};
}
const useStyles = makeStyles((theme) => ({
paper: {
position: "absolute",
width: 400,
backgroundColor: theme.palette.background.paper,
border: "2px solid #000",
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3)
}
}));
export default function SimpleModal() {
// I have this in another folder, i will put the other ones too, i'm just starting
//const classesRoot = useStylesForSlider();
const classes = useStyles();
// getModalStyle is not a pure function, we roll the style only on the first render
const [modalStyle] = useState(getModalStyle);
const [open, setOpen] = useState(false);
const [currentIdx, setCurrentIdx] = useState(null); // add a state for the current index
const handleOpen = (idx) => {
setCurrentIdx(idx); // set new current index
setOpen(true);
};
const handleClose = () => {
setCurrentIdx(null); // reset current index
setOpen(false);
};
const body = (
<div style={modalStyle} className={classes.paper}>
{tileData[currentIdx] && (
<img src={tileData[currentIdx].img} alt={tileData[currentIdx].img} />
)}
</div>
);
return (
<div>
<GridList cols={2.5}>
{tileData.map((tile, idx) => (
<img
src={tile.img}
alt={tile.img}
onClick={() => handleOpen(idx)}
key={tile.img}
/>
))}
</GridList>
<Modal open={open} onClose={handleClose}>
{body}
</Modal>
</div>
);
}
Live Demo

Material UI: Display sub-element on hover of parent

When the user hovers over a Card component, I'd like to show a button on that component that is otherwise invisible. In CSS, I'd do something like this:
.card:hover my-button {
display: block;
}
How do I replicate this in the "Material-UI" way?
All the Material-UI tips I found so far suggest something like this to add hover styling, but this applies the styles to the component that is being hovered over and not a different one.
'&:hover': {
background: 'blue'
}
You can do the exact same thing with CSS using the createMuiTheme:
export const theme = createMuiTheme({
overrides: {
// For label
MuiCard: {
root: {
"& .hidden-button": {
display: "none"
},
"&:hover .hidden-button": {
display: "flex"
}
}
}
}
});
Give the Button inside your Card the className hidden-button and you will get the same thing that you want.
Check it here: https://codesandbox.io/s/mui-theme-css-hover-example-n8ou5
It is not specific to Material UI but a react specific thing. you need a state variable to show/hide your button.
const App = () => {
const [show, setShow] = useState(false);
return (
<Card
onMouseOver={() => setShow(true)}
onMouseOut={() => setShow(false)}>
<CardBody>
// some content
{show && <Button>Click</Button>}
</CardBody>
</Card>
);
}
If you want to define this purely inside of a styled component instead of using either of createMuiTheme or makeStyles, then try the following.
We will give an id to each child component and reference them when defining the behaviour to implement when we hover over the parent component:
const NameCellBox = styled(Box)(({ theme }) => ({
...other styles,
"&:hover #cellBoxLengthTypo": {
display: "none",
},
"&:hover #cellBoxContentTypo": {
display: "inline-block",
},
}));
const CellBoxLengthTypo = styled(Typography)(({ theme }) => ({
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.h5.fontSize,
}));
const CellBoxContentTypo = styled(Typography)(({ theme }) => ({
display: "none",
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.h5.fontSize,
}));
const items: string[] = ["andrew", "barry", "chris", "debbie"];
return (
<>
<NameCellBox>
<CellBoxLengthTypo id="cellBoxLengthTypo">+{items.length}</CellBoxLengthTypo>
<CellBoxContentTypo id="cellBoxContentTypo">{items.join(", ")}</CellBoxContentTypo>
</NameCellBox>
</>
);
Found it useful for updating a DataGrid cell in Material UI when hover or other event fired.
I faced this problem today and after I discussed it with my mentor I made this result and it worked well for me. but first, let me tell you the disadvantage of using eventListener like onMouseEnter & on MouseLeave that it will cause so many renders.
I gave the (parent component) a movie-card class and the (child component) a movie-card-content class like the following
// movie-card.css
.movie-card-content {
opacity: 0;
}
.movie-card:hover .movie-card-content {
opacity: 1;
}
MUI allows you to add className prop so I gave the proper classNames
//MovieCard.jsx (component)
import "./movie-card.css";
function MovieCard () {
return (
<Card className="movie-card">
<CardContent className="movie-card-content">...<CardContent>
</Card>
);
}
and that's it 😉
import {
makeStyles
} from '#material-ui/core'
const useStyles = makeStyles(() => ({
root: {
"& .appear-item": {
display: "none"
},
"&:hover .appear-item": {
display: "block"
}
}
}))
export default function MakeTextAppearOnHover() {
const classes = useStyles()
return (
<div className = { classes.root }>
Hello world
<span className = 'appear-item' >
Appearing text Go
</span>
</div>
)
}
This is a material UI example that displays the sub-element on hover of the parent.
I also noticed that using some of the examples above, when using Material UI's makeStyles, the overlay item was flickering a lot when clicked. This solution below does not flicker when sub-element is clicked.
import React from "react"
import { Card, CardActionArea, CardContent, CardMedia } from "#material-
ui/core";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles(theme => ({
card: {
// some styles
},
cardAction: {
position: "relative"
},
media: {
// some styles
},
overlay: {
position: "absolute",
top: "85px"
}
}));
const App = () => {
const classes = useStyles();
const [show, setShow] = React.useState(false);
const handleMouseOver = () => {
setShow(true);
};
const handleMouseOut = () => {
setShow(false);
};
return (
<Card>
<CardActionArea
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut} className={classes.card} >
<CardMedia className={classes.media} component="img" >
// some content
</CardMedia>
<CardContent className={classes.overlay} style={{ display: show ?
'block' : 'none' }>
// some content
</CardContent>
</CardActionArea>
</Card>
);
}

Resources