ReactJs DropDown Menu, increase and decrease height animation - css

Hello I have the following objective, I'm trying to make a height increase effect and the reverse effect with the keyframes:
like this:
and so far I only got the effect by opening, and I'm not able to do the reverse transition effect, that is, when closing, gradually decrease the height
i get this:
code:
JSX:
const MenuItem = ({ key, index, tag, setVisible, visibleMenu }) => {
const { name, dropdownItems, icon } = tag;
const handleClick = index => {
if (visibleMenu === true) return setVisible("none");
if (visibleMenu === index) return setVisible("none");
return setVisible(index);
};
return (
<ListItem active={visibleMenu}>
<div
onClick={() => {
handleClick(index);
}}
className="li-menuOpen"
>
<a>
<FontAwesomeIcon
className="icon-li"
icon={icon}
size={"lg"}
fixedWidth
color="white"
/>
<span
className="li-name"
onClick={() => {
handleClick(index);
}}
>
{name}
</span>
</a>
<span className="svg-arrow">
{dropdownItems ? (
<FontAwesomeIcon
className="down-up_svg"
icon={visibleMenu ? faAngleUp : faAngleDown}
size="1x"
fixedWidth
color="white"
/>
) : (
""
)}
</span>
</div>
{dropdownItems ? (
<DropDown list={dropdownItems} active={visibleMenu} />
) : (
""
)}
</ListItem>
);
};
export default function App() {
const MenuTags = [
{
name: "Carteira",
link: "../cartcredit",
icon: faWallet,
dropdownItems: [
{ name: "Histórico", link: "/history" },
{ name: "Adicionar Crédito", link: "/add" }
]
}
];
const [visible, setVisible] = useState(null);
return (
<MenuList>
{MenuTags.map((item, index) => (
<MenuItem
key={index}
index={index}
tag={item}
setVisible={setVisible}
visibleMenu={visible === index ? true : false}
/>
))}
</MenuList>
);
}
DropDown JSX and CSS WITH EMOTION
const increaseHeightDrop = keyframes`
from {
max-height: 0;
}
to {
max-height: 500px;
}
`;
const decreaseHeight = keyframes`
from {
max-height: 500;
}
to {
max-height: 0px;
display:none;
}
`;
const OpenedStyled = styled.ul`
${props => {
if (props.active) {
return css`
animation: ${decreaseHeight} 0.8s;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards;
animation-fill-mode: forwards;
`;
}
}}
overflow: hidden;
animation: ${increaseHeightDrop} 0.8s;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards;
animation-fill-mode: forwards;
padding: 7px 15px;
background: #307096;
li {
white-space: nowrap;
padding: 5px 5px 5px 0px;
}
a {
font-family: "Ubuntu";
font-size: 14px;
font-weight: 300;
text-decoration: none;
color: #fff;
}
& .li-open:hover > .icon-li-drop {
color: orange;
}
& .icon-li-drop {
margin-right: 10px;
}
`;
const OpenedSide = ({ open, active, list }) => {
return (
<>
{active ? (
<OpenedStyled open={open} active={active}>
{list.map(item => (
<li className="li-open" key={item.name}>
<FontAwesomeIcon
className="icon-li-drop"
icon={faCircleNotch}
size="1x"
fixedWidth
color="white"
/>
<a>{item.name}</a>
</li>
))}
</OpenedStyled>
) : (
""
)}
</>
);
};
const DropDown = ({ active, list }) => {
console.log(list);
return <OpenedSide active={active} list={list} />;
};
export default DropDown;
example:
https://codesandbox.io/s/purple-silence-ytc5h?file=/src
In my codesand box I have a working example,
basically i just have difficulties in how i can make the transition when closing the dropdown, if anyone has any better idea for the code i would be grateful

Related

React component with state triggers animation in subcomponent when the state of parent component is changed

ProjectsSection component renders a document section with header and cards. A header animation should be triggered when the header is visible on the screen for the 1st time and run only once. However when I added a state to the ProjectsSection component with every state change a header animation runs and what is surprising to me only a part of it (letters/ spans that are children to h2 move, but brackets/ pseudoelements on h2 don't). In my project I use css modules for styling.
I have tried to wrap SectionHeader component in React.memo but it does not help.
Main component:
const ProjectsSection = () => {
const [openProjectCardId, setOpenProjectCardId] = useState('');
return (
<section id="projects" className="section">
<div className="container">
<SectionHeader>Projects</SectionHeader>
<div className={styles.content_wrapper}>
{projects.map((project, ind) => (
<Project
key={project.id}
project={project}
ind={ind}
isOpen={openProjectCardId === project.id}
setOpenProjectCardId={setOpenProjectCardId}
/>
))}
</div>
</div>
</section>
);
};
SectionHeader component:
const SectionHeader = ({ children }) => {
const headerRef = useIntersection(
styles.sectionHeader__isVisible,
{ rootMargin: '0px 0px -100px 0px', threshold: 1 },
);
const textToLetters = children.split('').map((letter) => {
const style = !letter ? styles.space : styles.letter;
return (
<span className={style} key={nanoid()}>
{letter}
</span>
);
});
return (
<div className={styles.sectionHeader_wrapper} ref={headerRef}>
<h2 className={styles.sectionHeader}>{textToLetters}</h2>
</div>
);
};
Css
.sectionHeader_wrapper {
position: relative;
// other properties
&::before {
display: block;
content: ' ';
position: absolute;
opacity: 0;
// other properties
}
&::after {
display: block;
content: ' ';
position: absolute;
opacity: 0;
// other properties
}
}
.sectionHeader {
position: relative;
display: inline-block;
overflow: hidden;
// other properties
}
.letter {
position: relative;
display: inline-block;
transform: translateY(100%) skew(0deg, 20deg);
}
.sectionHeader__isVisible .letter {
animation: typeletter .25s ease-out forwards;
}
useIntersection hook
const useIntersection = (
activeClass,
{ root = null, rootMargin = '0px', threshold = 1 },
dependency = [],
unobserveAfterFirstIntersection = true
) => {
const elementRef = useRef(null);
useEffect(() => {
const options: IntersectionObserverInit = {
root,
rootMargin,
threshold,
};
const observer = new IntersectionObserver((entries, observerObj) => {
entries.forEach((entry) => {
if (unobserveAfterFirstIntersection) {
if (entry.isIntersecting) {
entry.target.classList.add(activeClass);
observerObj.unobserve(entry.target);
}
} else if (entry.isIntersecting) {
entry.target.classList.add(activeClass);
} else {
entry.target.classList.remove(activeClass);
}
});
}, options);
// if (!elementRef.current) return;
if (elementRef.current) {
observer.observe(elementRef.current);
}
}, [...dependency]);
return elementRef;
};
I have found a solution to this problem.
This was apparently about textToLetters variable created every time the state of parent component was changing.
I used useMemo to keep this value and now animation is not triggered anymore.
const textToLetters = (text) =>
text.split('').map((letter) => {
const style = !letter ? styles.space : styles.letter;
return (
<span className={style} key={nanoid()}>
{letter}
</span>
);
});
const SectionHeader= ({ children }) => {
const headerRef = useIntersection(
styles.sectionHeader__isVisible,
{ rootMargin: '0px 0px -100px 0px', threshold: 1 },
[children]
);
const letters = React.useMemo(() => textToLetters(children), [children]);
return (
<div className={styles.sectionHeader_wrapper} ref={headerRef}>
<h2 className={styles.sectionHeader}>{letters}</h2>
</div>
);
};

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

React - slide down menu reveal menu items on hover

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

How to rotate an image in an after pseudoelement with styled components

I have a dropdown menu with an arrow in the after pseudo element that i want to rotate when clicked.
here is the styled component:
export const Header = styled.div`
padding: 0.2rem 0.5rem;
&:after {
content: url(${arrowUp});
height: 100%;
width: 15px;
text-align: right;
float: right;
transform: rotate(180deg);
}
&:hover {
cursor: pointer;
}
`;
This is the React.ts component
export const AttributeValuesChecklist: React.FC<IProps> = ({
title,
name,
values,
valuesShowLimit = false,
valuesShowLimitNumber = 5,
onValueClick,
}: IProps) => {
const [viewAllOptions, setViewAllOptions] = React.useState(!valuesShowLimit);
const showAttributeList = e => {
const actualDisplay = e.target.parentNode.children[1].style.display;
if (actualDisplay === "block") {
e.target.parentNode.children[1].style.display = "none";
} else {
e.target.parentNode.children[1].style.display = "block";
}
};
return (
<S.Wrapper>
{title && (
<S.Header onClick={e => showAttributeList(e)}>{title}</S.Header>
)}
<DropMenu>
{values &&
values.map((value, index) => {
if (!viewAllOptions && index > valuesShowLimitNumber - 1) {
return <></>;
}
return (
<Checkbox
name={name}
checked={!!value.selected}
onChange={() => onValueClick(value)}
>
{value && value.name}
</Checkbox>
);
})}
</DropMenu>
<S.BottomBorder />
</S.Wrapper>
);
};
I've tried the "data-attribute" trick but it doesn't work. And beacuse i'm using styled-components i don't know how can I use premade classes.
//styled-component
export const Header = styled.div<{isClicked: boolean}>`
padding: 0.2rem 0.5rem;
&:after {
content: url(${arrowUp});
height: 100%;
width: 15px;
text-align: right;
float: right;
transform: ${props => props.isClicked ? rotate(180deg) : rotate(0deg)};
}
`;
//component
//you can make a state to store the value or pass from props
<S.Header isClicked={//boolean value}>{title}</S.Header>

React Carousel not showing all images

I'm trying to build a carousel but can't figure out the problem. So the first image loads fine, but every following image isn't shown. I'm guessing the component isn't rerendering, but I'm not 100%
const [curImage, setCurImage] = useState (0)
const images = ['/images/slideshow/slideshow1.webp', '/images/slideshow/slideshow2.webp','/images/slideshow/slideshow3.webp', '/images/slideshow/slideshow4.webp', '/images/slideshow/slideshow5.webp', '/images/slideshow/slideshow6.webp']
const goToNext = () => {
setCurImage(curImage === images.length - 1 ? 0 : curImage + 1);
}
useEffect(() => {
setTimeout(goToNext, 2000);
return function() {
clearTimeout(goToNext);
}
})
return (
<div className='Slideshow'>
{images.map((image, key) => (
<div
className={key === curImage ? 'Slideshow__Images Active':'Slideshow__Images'}
key={key}
aria-hidden={key !== curImage}
>
{key === curImage && (
<img src={`${image}`} alt='Gulfport Votes Images'/>
)}
</div>
))}
</div>
)
CSS
.Slideshow__Images {
opacity: 0;
transition: 0.3s ease;
}
.Slideshow .Active {
opacity: 1;
transition-duration: 0.2s;
}

Resources