Skip to content

Commit 72de63f

Browse files
authored
Merge pull request #171 from Sushil010/feature/shop-menu-fix
Added animation in shop menu along with some minor changes
2 parents cbf55af + 5257c9f commit 72de63f

2 files changed

Lines changed: 169 additions & 27 deletions

File tree

src/components/ShopMenu/ShopMenu.jsx

Lines changed: 129 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,110 @@
1-
import React, { useState, useRef, useEffect } from 'react';
1+
import React, { useState, useRef, useEffect, useCallback } from 'react';
22
import { ChevronDown, ChevronUp, ArrowRight } from 'lucide-react';
33

44
const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
55
const [focusedIndex, setFocusedIndex] = useState(-1);
6+
const [animationState, setAnimationState] = useState('closed'); // 'closed', 'opening', 'open', 'closing'
7+
const [showScrollToTop, setShowScrollToTop] = useState(false);
68
const menuItemsRef = useRef([]);
79
const shopButtonRef = useRef(null);
810

11+
// Handle opening animation
12+
const handleOpenMenu = () => {
13+
setAnimationState('opening');
14+
setTimeout(() => {
15+
setAnimationState('open');
16+
}, 500); // Match animation duration
17+
};
18+
19+
// Handle closing animation
20+
const handleCloseMenu = useCallback(() => {
21+
if (animationState === 'closing') return; // Prevent multiple close calls
22+
setAnimationState('closing');
23+
setTimeout(() => {
24+
setShopOpen(false);
25+
setAnimationState('closed');
26+
setFocusedIndex(-1);
27+
}, 500); // Match animation duration
28+
}, [animationState, setShopOpen]);
29+
30+
// Prevent background scrolling when menu is open
31+
useEffect(() => {
32+
if (shopOpen || animationState === 'opening' || animationState === 'open' || animationState === 'closing') {
33+
// Add overflow-hidden to body
34+
document.body.style.overflow = 'hidden';
35+
36+
// Make main content inert for screen readers
37+
const mainContent = document.querySelector('main');
38+
if (mainContent) {
39+
mainContent.setAttribute('inert', 'true');
40+
mainContent.setAttribute('aria-hidden', 'true');
41+
}
42+
} else {
43+
// Restore scrolling
44+
document.body.style.overflow = '';
45+
46+
// Restore main content accessibility
47+
const mainContent = document.querySelector('main');
48+
if (mainContent) {
49+
mainContent.removeAttribute('inert');
50+
mainContent.removeAttribute('aria-hidden');
51+
}
52+
}
53+
54+
// Cleanup on unmount
55+
return () => {
56+
document.body.style.overflow = '';
57+
const mainContent = document.querySelector('main');
58+
if (mainContent) {
59+
mainContent.removeAttribute('inert');
60+
mainContent.removeAttribute('aria-hidden');
61+
}
62+
};
63+
}, [shopOpen, animationState]);
64+
965
// Close shop menu when clicking outside
1066
useEffect(() => {
1167
const handleClickOutside = (event) => {
1268
if (
1369
shopButtonRef.current &&
14-
!shopButtonRef.current.contains(event.target)
70+
!shopButtonRef.current.contains(event.target) &&
71+
shopOpen &&
72+
animationState !== 'closing'
1573
) {
16-
setShopOpen(false);
17-
setFocusedIndex(-1);
74+
handleCloseMenu();
1875
}
1976
};
2077

2178
document.addEventListener('mousedown', handleClickOutside);
2279
return () => {
2380
document.removeEventListener('mousedown', handleClickOutside);
2481
};
25-
}, [setShopOpen]);
82+
}, [setShopOpen, shopOpen, animationState, handleCloseMenu]);
83+
84+
// Handle opening animation when shopOpen changes
85+
useEffect(() => {
86+
if (shopOpen && animationState === 'closed') {
87+
handleOpenMenu();
88+
}
89+
}, [shopOpen, animationState]);
90+
91+
// Handle scroll to top functionality
92+
useEffect(() => {
93+
const handleScroll = () => {
94+
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
95+
setShowScrollToTop(scrollTop > 300); // Show arrow when scrolled down 300px
96+
};
97+
98+
window.addEventListener('scroll', handleScroll);
99+
return () => window.removeEventListener('scroll', handleScroll);
100+
}, []);
101+
102+
const scrollToTop = () => {
103+
window.scrollTo({
104+
top: 0,
105+
behavior: 'smooth'
106+
});
107+
};
26108

27109
// Keyboard accessibility
28110
useEffect(() => {
@@ -31,8 +113,7 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
31113

32114
switch (event.key) {
33115
case 'Escape':
34-
setShopOpen(false);
35-
setFocusedIndex(-1);
116+
handleCloseMenu();
36117
shopButtonRef.current?.focus();
37118
break;
38119
case 'ArrowDown':
@@ -92,7 +173,7 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
92173
return () => {
93174
document.removeEventListener('keydown', handleKeyDown);
94175
};
95-
}, [shopOpen, focusedIndex, setShopOpen, shopButtonRef]);
176+
}, [shopOpen, focusedIndex, setShopOpen, shopButtonRef, handleCloseMenu]);
96177

97178
// Focus management
98179
useEffect(() => {
@@ -103,10 +184,10 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
103184

104185
// Handle navigation to collection
105186
const handleCollectionClick = (collectionName) => {
106-
const url = `https://corexshoptest.onrender.com/api/collections/${collectionName.toLowerCase()}`;
187+
const encodedName = encodeURIComponent(collectionName.toLowerCase());
188+
const url = `https://corexshoptest.onrender.com/api/collections/${encodedName}`;
107189
window.open(url, '_blank');
108-
setShopOpen(false);
109-
setFocusedIndex(-1);
190+
handleCloseMenu();
110191
};
111192

112193
// Helper function to create menu item with accessibility
@@ -137,7 +218,11 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
137218
onClick={(e) => {
138219
e.preventDefault();
139220
e.stopPropagation();
140-
onShopClick();
221+
if (shopOpen || animationState === 'opening' || animationState === 'open') {
222+
handleCloseMenu();
223+
} else {
224+
onShopClick();
225+
}
141226
}}
142227
onKeyDown={onShopKeyDown}
143228
className={`px-5 py-2 rounded-full font-medium transition-all duration-300 cursor-pointer flex items-center ${
@@ -157,9 +242,11 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
157242
</button>
158243

159244
{/* Mega Menu */}
160-
{shopOpen && (
245+
{(shopOpen || animationState === 'opening' || animationState === 'open' || animationState === 'closing') && (
161246
<div
162-
className="fixed left-0 w-screen shadow-lg transition-all duration-300 ease-in-out transform origin-top opacity-100 translate-y-0 scale-y-100 z-40 overflow-y-auto"
247+
className={`fixed left-0 w-screen shadow-lg transform origin-top z-40 overflow-y-auto ${
248+
animationState === 'closing' ? 'animate-slide-up' : 'animate-slide-down'
249+
}`}
163250
style={{
164251
top: '104px',
165252
backgroundColor: '#F7FAFF',
@@ -174,18 +261,23 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
174261
{/* SHOP ALL Section */}
175262
<div className="mb-8">
176263
<div className="flex items-center justify-between mb-4">
177-
<h2
178-
className="font-bold text-black uppercase"
179-
style={{
180-
fontSize: '28px',
181-
lineHeight: '28px',
182-
letterSpacing: '-1.5px',
183-
fontWeight: '700',
184-
}}
264+
<button
265+
onClick={() => handleCollectionClick('all-products')}
266+
className="group flex items-center gap-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-lg cursor-pointer"
185267
>
186-
SHOP ALL
187-
</h2>
188-
<ArrowRight className="h-5 w-5 text-black" />
268+
<h2
269+
className="font-bold text-black uppercase group-hover:text-gray-600 transition-colors duration-300"
270+
style={{
271+
fontSize: '28px',
272+
lineHeight: '28px',
273+
letterSpacing: '-1.5px',
274+
fontWeight: '700',
275+
}}
276+
>
277+
ALL PRODUCTS
278+
</h2>
279+
<ArrowRight className="h-5 w-5 text-black group-hover:translate-x-1 transition-transform duration-300" />
280+
</button>
189281
</div>
190282
</div>
191283

@@ -427,8 +519,19 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
427519
</div>
428520
</div>
429521
)}
522+
523+
{/* Scroll to Top Arrow */}
524+
{showScrollToTop && (
525+
<button
526+
onClick={scrollToTop}
527+
className="fixed bottom-8 right-8 bg-[#0D1B2A] text-white p-3 rounded-full shadow-lg hover:bg-gray-800 transition-all duration-300 z-50 group"
528+
aria-label="Scroll to top"
529+
>
530+
<ChevronUp className="h-6 w-6 group-hover:scale-110 transition-transform duration-300" />
531+
</button>
532+
)}
430533
</>
431534
);
432535
};
433536

434-
export default ShopMenu;
537+
export default ShopMenu;

src/index.css

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,43 @@
4949
animation: double-pop 520ms cubic-bezier(.22, 1, .36, 1) both;
5050
}
5151

52+
/* ==========================
53+
SHOP MENU ANIMATIONS
54+
========================== */
55+
@keyframes slide-down {
56+
0% {
57+
opacity: 0;
58+
transform: translateY(-20px) scaleY(0.95);
59+
}
60+
100% {
61+
opacity: 1;
62+
transform: translateY(0) scaleY(1);
63+
}
64+
}
65+
66+
@keyframes slide-up {
67+
0% {
68+
opacity: 1;
69+
transform: translateY(0) scaleY(1);
70+
}
71+
100% {
72+
opacity: 0;
73+
transform: translateY(-20px) scaleY(0.95);
74+
}
75+
}
76+
77+
.animate-slide-down {
78+
animation: slide-down 0.5s ease-out forwards;
79+
opacity: 0;
80+
transform: translateY(-20px) scaleY(0.95);
81+
}
82+
83+
.animate-slide-up {
84+
animation: slide-up 0.5s ease-in forwards;
85+
opacity: 1;
86+
transform: translateY(0) scaleY(1);
87+
}
88+
5289
/* ==========================
5390
LINK UNDERLINE EFFECTS
5491
========================== */
@@ -106,7 +143,9 @@
106143
@media (prefers-reduced-motion: reduce) {
107144
.link-underline::after,
108145
.link-underline-inverse::after,
109-
.animate-double-pop {
146+
.animate-double-pop,
147+
.animate-slide-down,
148+
.animate-slide-up {
110149
animation: none !important;
111150
transition: none !important;
112151
}

0 commit comments

Comments
 (0)