1- import React , { useState , useRef , useEffect } from 'react' ;
1+ import React , { useState , useRef , useEffect , useCallback } from 'react' ;
22import { ChevronDown , ChevronUp , ArrowRight } from 'lucide-react' ;
33
44const 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 ;
0 commit comments