diff --git a/src/components/ShopMenu/ShopMenu.jsx b/src/components/ShopMenu/ShopMenu.jsx index 850cb54c..a69f434e 100644 --- a/src/components/ShopMenu/ShopMenu.jsx +++ b/src/components/ShopMenu/ShopMenu.jsx @@ -1,20 +1,77 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useCallback } from 'react'; import { ChevronDown, ChevronUp, ArrowRight } from 'lucide-react'; const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => { const [focusedIndex, setFocusedIndex] = useState(-1); + const [animationState, setAnimationState] = useState('closed'); // 'closed', 'opening', 'open', 'closing' + const [showScrollToTop, setShowScrollToTop] = useState(false); const menuItemsRef = useRef([]); const shopButtonRef = useRef(null); + // Handle opening animation + const handleOpenMenu = () => { + setAnimationState('opening'); + setTimeout(() => { + setAnimationState('open'); + }, 500); // Match animation duration + }; + + // Handle closing animation + const handleCloseMenu = useCallback(() => { + if (animationState === 'closing') return; // Prevent multiple close calls + setAnimationState('closing'); + setTimeout(() => { + setShopOpen(false); + setAnimationState('closed'); + setFocusedIndex(-1); + }, 500); // Match animation duration + }, [animationState, setShopOpen]); + + // Prevent background scrolling when menu is open + useEffect(() => { + if (shopOpen || animationState === 'opening' || animationState === 'open' || animationState === 'closing') { + // Add overflow-hidden to body + document.body.style.overflow = 'hidden'; + + // Make main content inert for screen readers + const mainContent = document.querySelector('main'); + if (mainContent) { + mainContent.setAttribute('inert', 'true'); + mainContent.setAttribute('aria-hidden', 'true'); + } + } else { + // Restore scrolling + document.body.style.overflow = ''; + + // Restore main content accessibility + const mainContent = document.querySelector('main'); + if (mainContent) { + mainContent.removeAttribute('inert'); + mainContent.removeAttribute('aria-hidden'); + } + } + + // Cleanup on unmount + return () => { + document.body.style.overflow = ''; + const mainContent = document.querySelector('main'); + if (mainContent) { + mainContent.removeAttribute('inert'); + mainContent.removeAttribute('aria-hidden'); + } + }; + }, [shopOpen, animationState]); + // Close shop menu when clicking outside useEffect(() => { const handleClickOutside = (event) => { if ( shopButtonRef.current && - !shopButtonRef.current.contains(event.target) + !shopButtonRef.current.contains(event.target) && + shopOpen && + animationState !== 'closing' ) { - setShopOpen(false); - setFocusedIndex(-1); + handleCloseMenu(); } }; @@ -22,7 +79,32 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => { return () => { document.removeEventListener('mousedown', handleClickOutside); }; - }, [setShopOpen]); + }, [setShopOpen, shopOpen, animationState, handleCloseMenu]); + + // Handle opening animation when shopOpen changes + useEffect(() => { + if (shopOpen && animationState === 'closed') { + handleOpenMenu(); + } + }, [shopOpen, animationState]); + + // Handle scroll to top functionality + useEffect(() => { + const handleScroll = () => { + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + setShowScrollToTop(scrollTop > 300); // Show arrow when scrolled down 300px + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const scrollToTop = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }; // Keyboard accessibility useEffect(() => { @@ -31,8 +113,7 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => { switch (event.key) { case 'Escape': - setShopOpen(false); - setFocusedIndex(-1); + handleCloseMenu(); shopButtonRef.current?.focus(); break; case 'ArrowDown': @@ -92,7 +173,7 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => { return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [shopOpen, focusedIndex, setShopOpen, shopButtonRef]); + }, [shopOpen, focusedIndex, setShopOpen, shopButtonRef, handleCloseMenu]); // Focus management useEffect(() => { @@ -103,10 +184,10 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => { // Handle navigation to collection const handleCollectionClick = (collectionName) => { - const url = `https://corexshoptest.onrender.com/api/collections/${collectionName.toLowerCase()}`; + const encodedName = encodeURIComponent(collectionName.toLowerCase()); + const url = `https://corexshoptest.onrender.com/api/collections/${encodedName}`; window.open(url, '_blank'); - setShopOpen(false); - setFocusedIndex(-1); + handleCloseMenu(); }; // Helper function to create menu item with accessibility @@ -137,7 +218,11 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - onShopClick(); + if (shopOpen || animationState === 'opening' || animationState === 'open') { + handleCloseMenu(); + } else { + onShopClick(); + } }} onKeyDown={onShopKeyDown} 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 }) => { {/* Mega Menu */} - {shopOpen && ( + {(shopOpen || animationState === 'opening' || animationState === 'open' || animationState === 'closing') && (
{ {/* SHOP ALL Section */}
-

handleCollectionClick('all-products')} + 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" > - SHOP ALL -

- +

+ ALL PRODUCTS +

+ +
@@ -427,8 +519,19 @@ const ShopMenu = ({ shopOpen, setShopOpen, onShopClick, onShopKeyDown }) => {
)} + + {/* Scroll to Top Arrow */} + {showScrollToTop && ( + + )} ); }; -export default ShopMenu; +export default ShopMenu; \ No newline at end of file diff --git a/src/index.css b/src/index.css index bc3012de..0577f2bb 100644 --- a/src/index.css +++ b/src/index.css @@ -49,6 +49,43 @@ animation: double-pop 520ms cubic-bezier(.22, 1, .36, 1) both; } +/* ========================== + SHOP MENU ANIMATIONS +========================== */ +@keyframes slide-down { + 0% { + opacity: 0; + transform: translateY(-20px) scaleY(0.95); + } + 100% { + opacity: 1; + transform: translateY(0) scaleY(1); + } +} + +@keyframes slide-up { + 0% { + opacity: 1; + transform: translateY(0) scaleY(1); + } + 100% { + opacity: 0; + transform: translateY(-20px) scaleY(0.95); + } +} + +.animate-slide-down { + animation: slide-down 0.5s ease-out forwards; + opacity: 0; + transform: translateY(-20px) scaleY(0.95); +} + +.animate-slide-up { + animation: slide-up 0.5s ease-in forwards; + opacity: 1; + transform: translateY(0) scaleY(1); +} + /* ========================== LINK UNDERLINE EFFECTS ========================== */ @@ -106,7 +143,9 @@ @media (prefers-reduced-motion: reduce) { .link-underline::after, .link-underline-inverse::after, - .animate-double-pop { + .animate-double-pop, + .animate-slide-down, + .animate-slide-up { animation: none !important; transition: none !important; }