Skip to content

Commit bef40cb

Browse files
committed
transition addded to sidebar menu, had to use dual state approach to fix sliding animation
1 parent ab1506d commit bef40cb

File tree

1 file changed

+51
-35
lines changed

1 file changed

+51
-35
lines changed

frontend/src/components/Hamburger.tsx

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,28 @@ import { Menu, X } from 'lucide-react';
44
import Link from 'next/link';
55

66
const Hamburger = () => {
7+
// Needs dom element to be same bfore and after
8+
// setIsOpen(true) occurs first while dom portal doesnt exist
9+
// Then creates dom portal
10+
// So it tries to go from isOpen=true to isOpen=true
11+
// So no animation occurs as it starts from its finishing val
12+
// So we need to create dom portal first, then menu
13+
// animation should only start after dom portal is created
714
const [isOpen, setIsOpen] = useState(false);
15+
const [showMenu, setShowMenu] = useState(false);
16+
17+
const openMenu = () => {
18+
setShowMenu(true);
19+
setTimeout(() => setIsOpen(true), 10);
20+
};
21+
22+
const closeMenu = () => {
23+
setIsOpen(false);
24+
setTimeout(() => setShowMenu(false), 300);
25+
};
826

927
useEffect(() => {
10-
if (isOpen) {
28+
if (showMenu) {
1129
document.body.style.overflow = 'hidden';
1230
} else {
1331
document.body.style.overflow = 'scroll';
@@ -16,87 +34,85 @@ const Hamburger = () => {
1634
return () => {
1735
document.body.style.overflow = 'scroll';
1836
};
19-
}, [isOpen]);
37+
}, [showMenu]);
2038

2139
useEffect(() => {
2240
const handleEscape = (e: KeyboardEvent) => {
23-
if (e.key === 'Escape') setIsOpen(false);
41+
if (e.key === 'Escape') closeMenu();
2442
};
2543

26-
if (isOpen) document.addEventListener('keydown', handleEscape);
44+
if (showMenu) document.addEventListener('keydown', handleEscape);
2745

2846
return () => {
2947
document.removeEventListener('keydown', handleEscape);
3048
};
31-
}, [isOpen, setIsOpen]);
32-
33-
useEffect(() => {
34-
if (isOpen) {
35-
document.body.style.overflow = 'hidden';
36-
} else {
37-
document.body.style.overflow = 'scroll';
38-
}
39-
40-
return () => {
41-
document.body.style.overflow = 'scroll';
42-
};
43-
}, [isOpen]);
49+
}, [showMenu]);
4450

4551
// Portal renders the backdrop at document.body level
46-
// Hence, instead of showing just at nav, it shows over all components
52+
// Hence, instead of showing just at nav, shows over all components
4753
const backdrop =
48-
isOpen && typeof document !== 'undefined'
54+
showMenu && typeof document !== 'undefined'
4955
? ReactDOM.createPortal(
50-
<div className="inset-0 fixed bg-black/50 z-50 backdrop-blur-sm">
51-
{/* Backdrop */}
52-
<div className="absolute inset-0" onClick={() => setIsOpen(false)} />
53-
56+
// Backdrop
57+
<div
58+
className={`fixed inset-0 z-50 ease-out transition-all duration-300 ${
59+
isOpen
60+
? 'bg-black/50 backdrop-blur-sm'
61+
: 'bg-black/0 backdrop-blur-none'
62+
}`}
63+
onClick={closeMenu}
64+
>
5465
{/* Sidebar */}
55-
<div className="absolute right-0 top-0 bg-[#3977F9] h-screen w-80 z-50">
66+
<div
67+
className={`absolute top-0 right-0 bg-[#3977F9] h-screen w-80 z-50 ease-in-out transition-transform duration-300 ${
68+
isOpen ? 'translate-x-0' : 'translate-x-full'
69+
}`}
70+
onClick={(e) => e.stopPropagation()}
71+
>
5672
<button
57-
onClick={() => setIsOpen(false)}
58-
className="text-white absolute top-[45px] right-[30px] p-2"
73+
onClick={closeMenu}
74+
className="text-white top-[45px] right-[35px] absolute p-2"
5975
>
6076
<X className="h-6 w-6" />
6177
</button>
6278

63-
<div className="p-6 pt-20 flex flex-col gap-4 items-start">
79+
<div className="p-6 pt-20 items-start flex flex-col gap-4">
6480
<Link
6581
href="/about"
6682
className="text-white text-lg p-3 hover:underline"
67-
onClick={() => setIsOpen(false)}
83+
onClick={closeMenu}
6884
>
6985
About Us
7086
</Link>
7187

7288
<Link
7389
href="/events"
7490
className="text-white text-lg p-3 hover:underline"
75-
onClick={() => setIsOpen(false)}
91+
onClick={closeMenu}
7692
>
7793
Events
7894
</Link>
7995

8096
<Link
8197
href="/resources"
8298
className="text-white text-lg p-3 hover:underline"
83-
onClick={() => setIsOpen(false)}
99+
onClick={closeMenu}
84100
>
85101
Resources
86102
</Link>
87103

88104
<Link
89105
href="/sponsors"
90106
className="text-white text-lg p-3 hover:underline"
91-
onClick={() => setIsOpen(false)}
107+
onClick={closeMenu}
92108
>
93109
Sponsors
94110
</Link>
95111

96112
<Link
97113
href="/contact-us"
98114
className="text-white text-lg p-3 hover:underline"
99-
onClick={() => setIsOpen(false)}
115+
onClick={closeMenu}
100116
>
101117
Contact Us
102118
</Link>
@@ -105,7 +121,7 @@ const Hamburger = () => {
105121
href="https://csesoc-merch.square.site/"
106122
target="_blank"
107123
className="text-white text-lg p-3 hover:underline"
108-
onClick={() => setIsOpen(false)}
124+
onClick={closeMenu}
109125
>
110126
Merch
111127
</a>
@@ -118,12 +134,12 @@ const Hamburger = () => {
118134

119135
return (
120136
<>
121-
<button className="p-2 z-50 relative" onClick={() => setIsOpen(!isOpen)}>
137+
<button className="p-2 relative z-50" onClick={openMenu}>
122138
<Menu className="text-white w-6 h-6" />
123139
</button>
124140
{backdrop}
125141
</>
126142
);
127143
};
128144

129-
export default Hamburger;
145+
export default Hamburger;

0 commit comments

Comments
 (0)