fix(SW-272): now closing on click outside and also closing the mobile menu when changing to screensizes larger than mobile
This commit is contained in:
@@ -9,6 +9,7 @@ import useDropdownStore from "@/stores/main-menu"
|
|||||||
import { GiftIcon, SearchIcon, ServiceIcon } from "@/components/Icons"
|
import { GiftIcon, SearchIcon, ServiceIcon } from "@/components/Icons"
|
||||||
import LanguageSwitcher from "@/components/LanguageSwitcher"
|
import LanguageSwitcher from "@/components/LanguageSwitcher"
|
||||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||||
|
import useMediaQuery from "@/hooks/useMediaQuery"
|
||||||
|
|
||||||
import HeaderLink from "../../HeaderLink"
|
import HeaderLink from "../../HeaderLink"
|
||||||
|
|
||||||
@@ -37,6 +38,13 @@ export default function MobileMenu({
|
|||||||
isHeaderLanguageSwitcherMobileOpen ||
|
isHeaderLanguageSwitcherMobileOpen ||
|
||||||
isFooterLanguageSwitcherOpen
|
isFooterLanguageSwitcherOpen
|
||||||
|
|
||||||
|
const isAboveMobile = useMediaQuery("(min-width: 768px)")
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAboveMobile && isHamburgerMenuOpen) {
|
||||||
|
toggleDropdown(DropdownTypeEnum.HamburgerMenu)
|
||||||
|
}
|
||||||
|
}, [isAboveMobile, isHamburgerMenuOpen, toggleDropdown])
|
||||||
|
|
||||||
useHandleKeyUp((event: KeyboardEvent) => {
|
useHandleKeyUp((event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape" && isHamburgerMenuOpen) {
|
if (event.key === "Escape" && isHamburgerMenuOpen) {
|
||||||
toggleDropdown(DropdownTypeEnum.HamburgerMenu)
|
toggleDropdown(DropdownTypeEnum.HamburgerMenu)
|
||||||
|
|||||||
@@ -97,7 +97,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.hamburger {
|
.hamburger,
|
||||||
|
.modal {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { useRef } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import useDropdownStore from "@/stores/main-menu"
|
import useDropdownStore from "@/stores/main-menu"
|
||||||
|
|
||||||
import { ChevronDownIcon } from "@/components/Icons"
|
import { ChevronDownIcon } from "@/components/Icons"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import useClickOutside from "@/hooks/useClickOutside"
|
||||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||||
import { getInitials } from "@/utils/user"
|
import { getInitials } from "@/utils/user"
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ export default function MyPagesMenu({
|
|||||||
user,
|
user,
|
||||||
}: MyPagesMenuProps) {
|
}: MyPagesMenuProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
const myPagesMenuRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const { toggleDropdown, isMyPagesMenuOpen } = useDropdownStore()
|
const { toggleDropdown, isMyPagesMenuOpen } = useDropdownStore()
|
||||||
|
|
||||||
@@ -33,8 +36,12 @@ export default function MyPagesMenu({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useClickOutside(myPagesMenuRef, isMyPagesMenuOpen, () => {
|
||||||
|
toggleDropdown(DropdownTypeEnum.MyPagesMenu)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.myPagesMenu}>
|
<div className={styles.myPagesMenu} ref={myPagesMenuRef}>
|
||||||
<MainMenuButton
|
<MainMenuButton
|
||||||
onClick={() => toggleDropdown(DropdownTypeEnum.MyPagesMenu)}
|
onClick={() => toggleDropdown(DropdownTypeEnum.MyPagesMenu)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useIntl } from "react-intl"
|
|||||||
import useDropdownStore from "@/stores/main-menu"
|
import useDropdownStore from "@/stores/main-menu"
|
||||||
|
|
||||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||||
|
import useMediaQuery from "@/hooks/useMediaQuery"
|
||||||
import { getInitials } from "@/utils/user"
|
import { getInitials } from "@/utils/user"
|
||||||
|
|
||||||
import Avatar from "../Avatar"
|
import Avatar from "../Avatar"
|
||||||
@@ -32,6 +33,13 @@ export default function MyPagesMobileMenu({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isAboveMobile = useMediaQuery("(min-width: 768px)")
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAboveMobile && isMyPagesMobileMenuOpen) {
|
||||||
|
toggleDropdown(DropdownTypeEnum.MyPagesMobileMenu)
|
||||||
|
}
|
||||||
|
}, [isAboveMobile, isMyPagesMobileMenuOpen, toggleDropdown])
|
||||||
|
|
||||||
// Making sure the menu is always opened at the top of the page, just below the header.
|
// Making sure the menu is always opened at the top of the page, just below the header.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMyPagesMobileMenuOpen) {
|
if (isMyPagesMobileMenuOpen) {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { useRef } from "react"
|
||||||
|
|
||||||
import useDropdownStore from "@/stores/main-menu"
|
import useDropdownStore from "@/stores/main-menu"
|
||||||
|
|
||||||
import { ChevronDownIcon, ChevronRightIcon } from "@/components/Icons"
|
import { ChevronDownIcon, ChevronRightIcon } from "@/components/Icons"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import useClickOutside from "@/hooks/useClickOutside"
|
||||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||||
|
|
||||||
import MainMenuButton from "../../MainMenuButton"
|
import MainMenuButton from "../../MainMenuButton"
|
||||||
@@ -15,8 +18,10 @@ import type { NavigationMenuItemProps } from "@/types/components/header/navigati
|
|||||||
|
|
||||||
export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
|
export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
|
||||||
const { openMegaMenu, toggleMegaMenu } = useDropdownStore()
|
const { openMegaMenu, toggleMegaMenu } = useDropdownStore()
|
||||||
|
const megaMenuRef = useRef<HTMLDivElement>(null)
|
||||||
const { submenu, title, link, seeAllLink, card } = item
|
const { submenu, title, link, seeAllLink, card } = item
|
||||||
const isMegaMenuOpen = openMegaMenu === title
|
const megaMenuTitle = `${title}-${isMobile ? "mobile" : "desktop"}`
|
||||||
|
const isMegaMenuOpen = openMegaMenu === megaMenuTitle
|
||||||
|
|
||||||
useHandleKeyUp((event: KeyboardEvent) => {
|
useHandleKeyUp((event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape" && isMegaMenuOpen) {
|
if (event.key === "Escape" && isMegaMenuOpen) {
|
||||||
@@ -24,10 +29,14 @@ export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useClickOutside(megaMenuRef, isMegaMenuOpen && !isMobile, () => {
|
||||||
|
toggleMegaMenu(false)
|
||||||
|
})
|
||||||
|
|
||||||
return submenu.length ? (
|
return submenu.length ? (
|
||||||
<>
|
<>
|
||||||
<MainMenuButton
|
<MainMenuButton
|
||||||
onClick={() => toggleMegaMenu(title)}
|
onClick={() => toggleMegaMenu(megaMenuTitle)}
|
||||||
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : styles.desktop}`}
|
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : styles.desktop}`}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
@@ -41,6 +50,7 @@ export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
|
|||||||
)}
|
)}
|
||||||
</MainMenuButton>
|
</MainMenuButton>
|
||||||
<div
|
<div
|
||||||
|
ref={megaMenuRef}
|
||||||
className={`${styles.dropdown} ${isMegaMenuOpen ? styles.isExpanded : ""}`}
|
className={`${styles.dropdown} ${isMegaMenuOpen ? styles.isExpanded : ""}`}
|
||||||
>
|
>
|
||||||
{isMegaMenuOpen ? (
|
{isMegaMenuOpen ? (
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useRef } from "react"
|
import { useRef } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { languages } from "@/constants/languages"
|
import { languages } from "@/constants/languages"
|
||||||
import useDropdownStore from "@/stores/main-menu"
|
import useDropdownStore from "@/stores/main-menu"
|
||||||
|
|
||||||
import { ChevronDownIcon, GlobeIcon } from "@/components/Icons"
|
import { ChevronDownIcon, GlobeIcon } from "@/components/Icons"
|
||||||
|
import useClickOutside from "@/hooks/useClickOutside"
|
||||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
@@ -28,18 +29,13 @@ export default function LanguageSwitcher({
|
|||||||
}: LanguageSwitcherProps) {
|
}: LanguageSwitcherProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const currentLanguage = useLang()
|
const currentLanguage = useLang()
|
||||||
const toggleDropdown = useDropdownStore((state) => state.toggleDropdown)
|
const {
|
||||||
|
toggleDropdown,
|
||||||
|
isFooterLanguageSwitcherOpen,
|
||||||
|
isHeaderLanguageSwitcherMobileOpen,
|
||||||
|
isHeaderLanguageSwitcherOpen,
|
||||||
|
} = useDropdownStore()
|
||||||
const languageSwitcherRef = useRef<HTMLDivElement>(null)
|
const languageSwitcherRef = useRef<HTMLDivElement>(null)
|
||||||
const isFooterLanguageSwitcherOpen = useDropdownStore(
|
|
||||||
(state) => state.isFooterLanguageSwitcherOpen
|
|
||||||
)
|
|
||||||
const isHeaderLanguageSwitcherOpen = useDropdownStore(
|
|
||||||
(state) => state.isHeaderLanguageSwitcherOpen
|
|
||||||
)
|
|
||||||
const isHeaderLanguageSwitcherMobileOpen = useDropdownStore(
|
|
||||||
(state) => state.isHeaderLanguageSwitcherMobileOpen
|
|
||||||
)
|
|
||||||
|
|
||||||
const isFooter = type === LanguageSwitcherTypesEnum.Footer
|
const isFooter = type === LanguageSwitcherTypesEnum.Footer
|
||||||
const isHeader = !isFooter
|
const isHeader = !isFooter
|
||||||
|
|
||||||
@@ -71,33 +67,11 @@ export default function LanguageSwitcher({
|
|||||||
window.scrollTo(0, scrollPosition)
|
window.scrollTo(0, scrollPosition)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
useClickOutside(
|
||||||
useEffect(() => {
|
languageSwitcherRef,
|
||||||
function handleClickOutside(evt: Event) {
|
isLanguageSwitcherOpen && !isHeaderLanguageSwitcherMobileOpen,
|
||||||
const target = evt.target as HTMLElement
|
() => toggleDropdown(dropdownType)
|
||||||
if (
|
)
|
||||||
languageSwitcherRef.current &&
|
|
||||||
target &&
|
|
||||||
!languageSwitcherRef.current.contains(target) &&
|
|
||||||
isLanguageSwitcherOpen &&
|
|
||||||
!isHeaderLanguageSwitcherMobileOpen
|
|
||||||
) {
|
|
||||||
toggleDropdown(dropdownType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (languageSwitcherRef.current) {
|
|
||||||
document.addEventListener("click", handleClickOutside)
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("click", handleClickOutside)
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
dropdownType,
|
|
||||||
toggleDropdown,
|
|
||||||
isLanguageSwitcherOpen,
|
|
||||||
isHeaderLanguageSwitcherMobileOpen,
|
|
||||||
])
|
|
||||||
|
|
||||||
const classNames = languageSwitcherVariants({ color, position })
|
const classNames = languageSwitcherVariants({ color, position })
|
||||||
|
|
||||||
|
|||||||
24
hooks/useClickOutside.ts
Normal file
24
hooks/useClickOutside.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
export default function useClickOutside(
|
||||||
|
ref: React.RefObject<HTMLElement>,
|
||||||
|
isOpen: boolean,
|
||||||
|
callback: () => void
|
||||||
|
) {
|
||||||
|
useEffect(() => {
|
||||||
|
function handleClickOutside(evt: Event) {
|
||||||
|
const target = evt.target as HTMLElement
|
||||||
|
if (ref.current && target && !ref.current.contains(target) && isOpen) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
document.addEventListener("click", handleClickOutside)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("click", handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [ref, isOpen, callback])
|
||||||
|
}
|
||||||
21
hooks/useMediaQuery.ts
Normal file
21
hooks/useMediaQuery.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
function useMediaQuery(query: string) {
|
||||||
|
const [isMatch, setIsMatch] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const media = window.matchMedia(query)
|
||||||
|
if (media.matches !== isMatch) {
|
||||||
|
setIsMatch(media.matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener = () => setIsMatch(media.matches)
|
||||||
|
media.addEventListener("change", listener)
|
||||||
|
|
||||||
|
return () => media.removeEventListener("change", listener)
|
||||||
|
}, [isMatch, query])
|
||||||
|
|
||||||
|
return isMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useMediaQuery
|
||||||
Reference in New Issue
Block a user