React - slide down menu reveal menu items on hover - css

I am trying to add a dropdown menu slide down that slowly reveals the menu items. I am using CSSTransition from react-transition-group. I have the blue background doing the nice slow slidedown that I want. But the text items just instantly shows up. Here is my codesandbox showing the issue: https://codesandbox.io/s/compassionate-dawn-zbmt5
My Navbar component:
import { Link } from "react-router-dom";
import React, { useState } from "react";
import { CSSTransition } from "react-transition-group";
const Navbar = () => {
const [showSchedDropdown, setShowSchedDropdown] = useState(false);
return (
<ul className={"nav"}>
<li
className={"nav-item dropdown"}
onMouseEnter={() => setShowSchedDropdown(true)}
onMouseLeave={() => setShowSchedDropdown(false)}
>
<button
className={"nav-link dropdown-toggle"}
aria-haspopup={"true"}
aria-expanded={"false"}
onClick={() => setShowSchedDropdown(true)}
>
Dropdown Menu
</button>
<CSSTransition
in={showSchedDropdown}
timeout={1300}
classNames={"hidden-menu"}
>
<div className={"dropdown-menu"}>
{showSchedDropdown && (
<>
<Link
className={"dropdown-item"}
to={"/baglines"}
onClick={() => setShowSchedDropdown(false)}
>
Menu Item 1
</Link>
<Link
className={"dropdown-item"}
to={"/totelines"}
onClick={() => setShowSchedDropdown(false)}
>
Menu Item 2
</Link>
<Link
className={"dropdown-item"}
to={"/otherlines"}
onClick={() => setShowSchedDropdown(false)}
>
Menu Item 3
</Link>
</>
)}
</div>
</CSSTransition>
</li>
</ul>
);
};
export default Navbar;
And the CSS:
.dropdown-menu {
display: block;
margin-top: 0;
padding-top: 0;
color: #ffffff;
background-color: rgba(#265077, 1);
font-size: 0.9rem;
border: none;
transition: height 1.3s ease;
&.hidden-menu-enter,
&.hidden-menu-exit-done {
height: 0;
transition: height 1.3s ease;
}
&.hidden-menu-enter-active {
height: 100px;
transition: height 1.3s ease;
}
&.hidden-menu-enter-done {
height: 100px;
}
&.hidden-menu-leave {
height: 0;
transition: height 1.3s ease;
}
&.hidden-menu-leave-active {
height: 100px;
transition: height 1.3s ease;
}
}

Related

React - how to stop div onClick from changing sibling divs

I have 6 div elements that I want to be able to click one of and have the className change for just the div I've clicked on. Currently, when I click on one div, ALL the div classNames change. This idea came from a vanilla JS concept that I'm trying to convert into a React component. I'm not sure where/what is going wrong, if anyone can tell me how to prevent the sibling divs' onClicks from being fired or if what I have is wrong fundamentally, I would be eternally grateful. This is what I have so far:
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => {
setIsOpen(!isOpen);
};
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen ? "open open-active" : ""}`}
onClick={toggleOpen}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;
This is my Panels Component code:
import React from "react";
const Panels = ({ name, description, website }) => {
return (
<div className="panel_text">
<p>{description}</p>
<p>{name}</p>
<a href={website}>
<p>Visit {name}</p>
</a>
</div>
)};
export default Panels;
Aaaand this is my CSS code:
*, *:before, *:after {
box-sizing: inherit;
}
.panels {
min-height: 100vh;
overflow: hidden;
display: flex;
}
.panel, .panel_text {
background: '#fff';
box-shadow: inse 0 0 0 5px rgba(255, 255, 255, 0.1);
color: var(--color-golden);
text-align: center;
align-items: center;
transition:
font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),
flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11);
font-size: 20px;
background-size: cover;
background-position: center;
flex: 1;
justify-content: center;
display: flex;
flex-direction: column;
}
.panel0 {background-image: url(../../assets/defuegocarousel.jpg);}
.panel1 {background-image: url(../../assets/HaydenslakeSz.jpg);}
.panel2 {background-image: url(../../assets/stonecliffSz.jpg);}
.panel3 {background-image: url(../../assets/shigezooutside.png);}
.panel4 {background-image: url(../../assets/southparkSz.jpeg);}
.panel5 {background-image: url(../../assets/lechonoutside.jpg);}
.panel > * {
margin: 0;
width: 100%;
transition: transform 0.5s;
flex: 1 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.panel > *:first-child {transform: translateY(-100%);}
.panel.open-active > *:first-child {transform: translateY(0); }
.panel > *:last-child { transform: translateY(100%); }
.panel.open-active > *:last-child {transform: translateY(0); }
.panel_text p, a {
text-transform: uppercase;
}
.panel p:nth-child(2) {
font-size: 4rem;
}
.panel.open {
flex: 5;
font-size: 40px;
}
#media only screen and (max-width: 600px) {
.panel p {
font-size: 1rem;
}
}
You're saving a boolean value as a div element is open or not. So this value is considered for all div element's because there is no identifier which div element is open. You need to save a div element value to identify the open div element.
So you can use a div element's index instead of a boolean value. For example, try the below code.
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState(null);
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen === index ? "open open-active" : ""}`}
onClick={() => setIsOpen(index)}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;
your isOpen state is common between all your div's
you should specify a unique value for isOpen of each div
you can change your isOpen state to an object like this :
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState({});
const toggleOpen = (index) => {
setIsOpen(prevState => {...prevState ,[index]:!(!!prevState[index]) });
};
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen[index] ? "open open-active" : ""}`}
onClick={()=>toggleOpen(index)}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;

CSSTransition to make a slide out drawer in css grid layout

I am trying to make a slide out drawer utilizing the npm package react-transition-group. For whatever reason, I cannot seem to get the drawer to slide out from left to right on clicking the additional criteria button. If you can solve this issue without using the package, that is ok too!
Here is the code I am trying to get to work as a React component:
{/* DeveloperSearch.js */}
<CSSTransition
in={sidebarClicked}
appear
timeout={1000}
classNames="drawer"
mountOnEnter
unmountOnExit
>
<div className="DevSearch__additional-search-criteria">
Additional Search Criteria
<div className="DevSearch__additional-search-criteria-individual">
<div
style={{
fontSize: '0.8rem',
marginBottom: '5px',
fontWeight: 'bold',
}}
>
Only show people who match more than {criteriaMatch}% of all
search criteria
</div>
<input
className="form-control"
type="number"
value={criteriaMatch}
onChange={(e) => setCriteriaMatch(e.target.value)}
min={0}
max={100}
step={5}
/>
</div>
</div>
</CSSTransition>
I also have a css file that is specifically for the CSS Transition component called DeveloperSearch.css:
.drawer-exit {
width: 250px;
}
.drawer-exit.drawer-exit-active {
width: 250px;
transition: width 1000ms ease-in;
}
.drawer-exit-done {
width: 0px;
}
.drawer-enter {
width: 250px;
}
.drawer-enter.drawer-enter-active {
width: 250px;
transition: all 1000ms ease-in;
}
Unfortunately, my results are no where near what I was wanting, as the drawer does not seem to slide out at all...
I also have replicated this issue in a codesandbox that can be found by clicking here. Thanks for your help!
Here is a pure css based solution but this is a bit hacky
Markup
const Drawer = ({ transitionExit, handleExit }) => (
<div
onClick={handleExit}
className={`drawer ${transitionExit ? "exit" : ""}`}
>
<p>Home</p>
<p>About</p>
<p>Contact</p>
<p>Close Drawer</p>
</div>
);
export default function App() {
const [isOpen, setIsOpen] = useState(false);
const [transitionExit, setTransitionExit] = useState(false);
const handleExit = () => {
setTransitionExit(true);
setTimeout(() => {
setIsOpen(false);
setTransitionExit(false);
// timeout should be less than animation time otherwise state might still be true
// after animation ends and drawer appears for few milliseconds
}, 450);
};
return (
<div className="App">
<div className="wrapper">
<div className="sidebar_container">
<button onClick={() => setIsOpen(true)}>open</button>
</div>
{isOpen && (
<div className={`container ${transitionExit ? "exit" : ""}`}>
<Drawer handleExit={handleExit} transitionExit={transitionExit} />
</div>
)}
</div>
</div>
);
}
CSS
.wrapper {
height: 90vh;
max-width: 60vw;
display: grid;
grid-template-columns: 30% 70%;
overflow: hidden;
margin: 40px;
}
.sidebar_container {
width: 100px;
height: 100%;
background-color: rgb(250, 207, 213);
padding: 30px;
position: relative;
z-index: 30;
}
#keyframes containerTransitionEnter {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#keyframes drawerTransitionEnter {
0% {
opacity: 0;
left: -10vw;
}
100% {
opacity: 1;
left: 0vw;
}
}
#keyframes containerTransitionExit {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#keyframes drawerTransitionExit {
0% {
opacity: 1;
left: 0vw;
}
100% {
opacity: 0;
left: -10vw;
}
}
.container {
position: relative;
z-index: 10;
height: 90vh;
animation: containerTransitionEnter 0.5s;
}
.drawer {
box-sizing: border-box;
position: relative;
height: 90vh;
width: 25vw;
padding: 20px;
background-color: rgb(4, 118, 156);
border-right: 1px solid rgba(0, 0, 0, 0.3);
animation: drawerTransitionEnter 0.5s;
}
p {
margin-bottom: 10px;
color: white;
}
.container.exit {
animation: containerTransitionExit 0.5s;
}
.drawer.exit {
animation: drawerTransitionExit 0.5s;
}
Here is the link to codesandbox
Since you are using react you can use Material UI for this Here
and you can try this in your case
<Drawer
className={classes.drawer}
variant=''
anchor='left'
open={open}
classes={{
paper: classes.drawerPaper,
}}>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'ltr' ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
{arr.map((text, index) => (
<ListItem
button
key={text}
onClick={
text === 'Home'
? goToHome
: text === 'About'
? handleOpenAbout
: text === 'Contact'
? goToContact
: text == 'Team'
? goToMyTea,
: goToDashboard
}>
<ListItemIcon>
{text === 'Home' ? (
<HomeIcon />
) : text === 'About' ? (
<NoteAddIcon />
) : text === 'About' || text === 'Contact' ? (
<ListAltIcon />
) : text === 'Dashboard' ? (
<DashboardIcon />
) : (
<></>
)}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
You should not delete div that has class="DevSearch__additional-search-criteria drawer-enter-done" from the DOM. In this case, Transition will not work. If you want to delete it, you must use css animation.
In this way, after adding div to the DOM, put animation on it to enter as a slider

Trying to display burger menu dropdown behind navbar

I have spent countless hours trying to figure out why my dropdown that is opened/closed by a burger menu icon click is sitting in front of the navbar even though I have specified z-indexes, overflows and positions. This issue is only happening on the MobileNav component below. MobileNav consists of a burger icon and the actual dropdown. Once the burger icon is clicked, the dropdown will either close or open. Currently It is displaying above the nav component and I am having a very hard time figuring out why. Any help will be much appreciated.
Vid to see the dropdown's behavior: https://www.youtube.com/watch?v=zOBnb6r_RN4&ab_channel=TylerOreskey
The dropdown is supposed to come out from the bottom of the navbar and close up into the bottom of the navbar.
Navbar Component: Renders MobileNav component
const Navbar = (props) => {
const [showDropdown, setShowDropdown] = useState(false);
const dropdownToggleHandler = () => setShowDropdown(!showDropdown);
const dropdownClosedHandler = () => setShowDropdown(false);
return (
<header
className={classes.Navbar}
style={{
position: props.passedNavbar ? "fixed" : "relative",
}}
>
<nav className={classes.MobileNav}>
<MobileNav
allNavigationRefs={props.allNavigationRefs}
scrollToDiv={props.scrollToDiv}
open={showDropdown}
closed={dropdownClosedHandler}
dropdownToggleHandler={dropdownToggleHandler}
/>
</nav>
</header>
);
};
export default memo(Navbar);
CSS file for Navbar component: z-index is not working in here.
.Navbar {
top: 0;
height: 50px;
background-color: hsl(213, 27%, 15%);
border-bottom: #00bfff 3px solid;
width: 100%;
z-index: 500;
}
#media (max-width: 500px) {
.DesktopNav {
display: none;
}
}
#media (min-width: 500px) {
.MobileNav {
display: none;
}
}
MobileNav component
const MobileNav = (props) => {
return (
<div className={classes.MobileNav}>
<DropdownToggle clicked={props.dropdownToggleHandler} />
<Dropdown open={props.open} allNavigationRefs={props.allNavigationRefs} />
</div>
);
};
export default MobileNav;
CSS file for MobileNav component
.MobileNav {
overflow: hidden;
}
Dropdown component: (This is displayed above the Navbar component and I cannot get it to be behind the navbar component).
const Dropdown = (props) => {
let attachedClasses = [classes.Dropdown, classes.Close];
if (props.open) {
attachedClasses = [classes.Dropdown, classes.Open];
}
return (
<div className={attachedClasses.join(" ")}>
<NavigationItems allNavigationRefs={props.allNavigationRefs} />
</div>
);
};
export default Dropdown;
CSS file for Dropdown component: z-index is not working in here.
.Dropdown {
background: hsl(212, 87%, 3%);
height: 200px;
transition: transform 0.3s ease-out;
z-index: 400;
display: block;
}
.Open {
transform: translate(0, 25%);
}
.Close {
transform: translate(0, -75%);
}
You are confused on how z-index works.
Consider each level in your tree as a layer.
lets say that Navbar is layer 0, MobileNav is then layer 1, and its children are on layer 2.
By default z-index is calculated among children of the same layer. This is true when the position attribute is on default static. When you alter this to relative you can instruct which layers are going to interuct with each other in a more immediate way.
Having 500 z-index on Navbar will make no sense to MobileNav. It is not his sibling, it's his child.
Here is a possible solution if you can alter the DOM tree
<header
className={classes.Navbar}
style={{
position: props.passedNavbar ? "fixed" : "relative"
}}
>
<nav className={classes.Navbar}>
<DropdownToggle clicked={props.dropdownToggleHandler} />
</nav>
<MobileNav
className={classes.MobileNav}
allNavigationRefs={props.allNavigationRefs}
scrollToDiv={props.scrollToDiv}
open={showDropdown}
closed={dropdownClosedHandler}
/>
</header>
and here is an answer if you can alter the CSS
.Navbar {
top: 0;
height: 50px;
background-color: hsl(213, 27%, 15%);
border-bottom: #00bfff 3px solid;
width: 100%;
position: relative;
}
...
.Dropdown {
background: hsl(212, 87%, 3%);
height: 200px;
transition: transform 0.3s ease-out;
display: block;
position: relative;
z-index: -1;
}

How can I better animate a modal using CSS?

I am trying to use transitions to change the way a modal I created is shown on the screen. I need the modal to slide in from the left but the code doesn't seem to work. The app is in React.
The JSX code
const Modal = (props)=>{
let ModalClasses = [Styles.Modal];
if(props.show){
ModalClasses = [Styles.Modal, Styles.Open]
}
return(
<div className={ModalClasses.join(' ')}>
<div className={Styles.ModalNav} onClick={props.clicked}>
<div></div>
<div></div>
</div>
</div>
)
}
CSS
.Modal{
background-color: white;
position: absolute;
top: 0;
left: 25%;
width: 50vw;
height: 100vh;
z-index: 2;
transform: translateX(-100%);
transition: transform 500ms ease-out;
}
.Modal.Open{
transform: translateX(0);
}
.ModalNav div{
height: 3px;
width: 20px;
background-color: black;
margin: 5px;
position: absolute;
top: 10px;
right: 0;
cursor: pointer;
}
.ModalNav div:first-of-type{
transform: rotate(45deg);
}
.ModalNav div:last-of-type{
transform: rotate(-45deg);
}
JSX code for the component I am receiving props from
class App extends Component{
state={
showModal: false,
}
showModalHandler = ()=>{
this.setState({showModal:!this.state.showModal})
}
render(){
return (
<div className="App">
{ this.state.showModal ?
<Modal
show={this.state.showModal}
clicked={this.showModalHandler}/> : null}
{ this.state.showModal ?
<Backdrop
clicked={this.showModalHandler}/> : null}
<Table clicked={this.showModalHandler}/>
</div>
);
}
}
export default App;
My goal is to get the modal to slide in but it just pops right in.
https://salesruby.netlify.app/ can be viewed here.
Here's an alternative answer that is more in line with OP's original approach of using CSS transition instead of animating. This also let's you slide the menu back in so it slides both ways.
OP's problem is returning null in the App component if the modal is in a hidden state (which it starts with). So react will not render the modal, until you change the isOpen flag to true - at that point it renders directly in the open state (popping in), and the CSS transition has no effect. Subsequently when you close the modal, it is removed from the DOM, again without time for a transition so it "pops out".
The solution below is to render the modal in its starting state, and use the IsOpen flag to toggle the state of the element, rather than pulling it in and out of the DOM.
Here is a modified sandbox from Cyrus' answer that shows this - isModalOpen flag is used by the Modal to toggle the "open" class. The rest is basically the same as what OP started with.
https://codesandbox.io/s/bold-architecture-9t50w?file=/src/Components/Modal/Modal.js
App (Notice Modal is always returned, no : null - and isModalOpen is bound)
import React, { useState } from "react";
import "./styles.css";
import Modal from "./Components/Modal/Modal";
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setIsModalOpen(true)}>Open modal </button>
<Modal isModalOpen={isModalOpen} closeModal={setIsModalOpen} />
</div>
);
};
export default App;
Modal (Notice className={isModalOpen ? "open" : ""} to toggle class)
import React from "react";
import "./modal.css";
const Modal = ({ closeModal, isModalOpen }) => {
return (
<div id="modal" className={isModalOpen ? "open" : ""}>
Modal here <button onClick={() => closeModal(false)}> open modal</button>
</div>
);
};
export default Modal;
CSS (simple transform and transition effect)
#modal {
background-color: #000000cc;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
transform: translateX(-100%);
transition: transform 0.5s ease-in-out;
width: 30%;
height: 100%;
}
#modal.open {
transform: translateX(0);
}
so I made this really fast. This is just a demo on how to make the animation slide in from left to right. If you look at the css you can see that I have left : -600px and that is the width of the modal. You can make it dynamic so that it will only take some % of the screen, but don't forget to add a max-width.
here is a link to codesandbox
//app.js
import React, { useState } from "react";
import "./styles.css";
import Modal from "./Components/Modal/Modal";
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setIsModalOpen(true)}>Open modal </button>
{isModalOpen ? <Modal closeModal={setIsModalOpen} /> : null}
</div>
);
};
export default App;
//Modal.js
import React, { useEffect } from "react";
import "./modal.css";
const Modal = ({ closeModal }) => {
return (
<div id="modal">
Modal here <button onClick={() => closeModal(false)}> open modal</button>
</div>
);
};
export default Modal;
//modal.css
#modal {
background-color: #000000cc;
position: absolute;
top: 0;
left: 100px;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
transition: top 2s;
animation: slideIn 1s;
width: 600px;
height: 600px;
}
#keyframes slideIn {
0% {
left: -600px;
}
100% {
left: 100px;
}
}

Issue when adding effects on Div

In the case I have on toggle button that will bring this "mini drawer" that has some chips,
After pressing the Button
The issue in this case is that this chips just POP on the screen and I need to add some animation like this Drawer http://www.material-ui.com/#/components/drawer, I tried to add the transition and transform on CSS but It wont work
JS Part:
renderChip() {
console.log("1.30");
return this.state.listChip.map( (i, index) => (
<Chip className="chips">
{i.nome}
</Chip>
));
}
toggleList() {
this.setState({
visivel: !this.state.visivel,
});
// (this.state.visivel) ? document.getElementById("push").style.width = "0" : document.getElementById("push").style.width = "10px"};
}
render()
{
return (
<MuiThemeProvider>
<div >
<div className="anchor">
<RaisedButton
label="Label before"
labelPosition="before"
primary={true}
onClick = {this.toggleList}
/>
</div>
<div id="push" className="anchorz" style={(this.state.visivel) ? styles.wrapper : styles.closed} >
{this.renderChip()}
</div>
</div>
</MuiThemeProvider>
);
}
CSS part:
position: fixed;
display: flex;
right: 0;
bottom: 110px;
margin-right: 0;
margin-bottom: 0;
margin-left: 10px;
z-index: 900;
transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;*/
overflow-x: hidden;
transition: 0.5s;

Resources