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:
Erik Tiekstra
2024-10-03 15:34:20 +02:00
parent 451d461c7f
commit 676b763e67
8 changed files with 96 additions and 43 deletions

View File

@@ -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)

View File

@@ -97,7 +97,8 @@
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.hamburger { .hamburger,
.modal {
display: none; display: none;
} }
} }

View File

@@ -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)}
> >

View File

@@ -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) {

View File

@@ -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 ? (

View File

@@ -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
View 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
View 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