feat(SW-187): simplify dropdown menu

This commit is contained in:
Pontus Dreij
2024-09-09 14:58:22 +02:00
parent ef33d082d8
commit 07eb0401bb
9 changed files with 93 additions and 158 deletions

View File

@@ -18,6 +18,7 @@ import LoginButton from "../LoginButton"
import styles from "./mainMenu.module.css" import styles from "./mainMenu.module.css"
import type { MainMenuProps } from "@/types/components/current/header/mainMenu" import type { MainMenuProps } from "@/types/components/current/header/mainMenu"
import { DropdownType } from "@/types/components/dropdown/dropdown"
export function MainMenu({ export function MainMenu({
frontpageLinkText, frontpageLinkText,
@@ -61,12 +62,11 @@ export function MainMenu({
"/sv/current-content-page", "/sv/current-content-page",
].includes(pathname) ].includes(pathname)
const { const { toggleDropdown, openDropdown } = useDropdownStore()
isHamburgerMenuOpen,
isMyPagesMobileMenuOpen, const isMyPagesMobileMenuOpen =
toggleHamburgerMenu, openDropdown === DropdownType.MyPagesMobileMenu
toggleMyPagesMobileMenu, const isHamburgerMenuOpen = openDropdown === DropdownType.HamburgerMenu
} = useDropdownStore()
function handleMyPagesMobileMenuClick() { function handleMyPagesMobileMenuClick() {
// Only track click when opening it // Only track click when opening it
@@ -74,7 +74,7 @@ export function MainMenu({
trackClick("profile picture icon") trackClick("profile picture icon")
} }
toggleMyPagesMobileMenu() toggleDropdown(DropdownType.MyPagesMobileMenu)
} }
return ( return (
@@ -89,7 +89,7 @@ export function MainMenu({
<button <button
aria-pressed="false" aria-pressed="false"
className={`${styles.expanderBtn} ${isHamburgerMenuOpen ? styles.expanded : ""}`} className={`${styles.expanderBtn} ${isHamburgerMenuOpen ? styles.expanded : ""}`}
onClick={toggleHamburgerMenu} onClick={() => toggleDropdown(DropdownType.HamburgerMenu)}
type="button" type="button"
> >
<span className={styles.iconBars}></span> <span className={styles.iconBars}></span>

View File

@@ -13,6 +13,8 @@ import useLang from "@/hooks/useLang"
import styles from "./my-pages-mobile-dropdown.module.css" import styles from "./my-pages-mobile-dropdown.module.css"
import { DropdownType } from "@/types/components/dropdown/dropdown"
type Navigation = Awaited<ReturnType<(typeof navigationQueryRouter)["get"]>> type Navigation = Awaited<ReturnType<(typeof navigationQueryRouter)["get"]>>
export default function MyPagesMobileDropdown({ export default function MyPagesMobileDropdown({
@@ -22,8 +24,10 @@ export default function MyPagesMobileDropdown({
}) { }) {
const { formatMessage } = useIntl() const { formatMessage } = useIntl()
const lang = useLang() const lang = useLang()
const { toggleMyPagesMobileMenu, isMyPagesMobileMenuOpen } = const { toggleDropdown, openDropdown } = useDropdownStore()
useDropdownStore()
const isMyPagesMobileMenuOpen =
openDropdown === DropdownType.MyPagesMobileMenu
if (!navigation) { if (!navigation) {
return null return null
@@ -51,7 +55,9 @@ export default function MyPagesMobileDropdown({
size={menuItem.display_sign_out_link ? "small" : "regular"} size={menuItem.display_sign_out_link ? "small" : "regular"}
variant="myPageMobileDropdown" variant="myPageMobileDropdown"
color="burgundy" color="burgundy"
onClick={toggleMyPagesMobileMenu} onClick={() =>
toggleDropdown(DropdownType.MyPagesMobileMenu)
}
> >
{link.linkText} {link.linkText}
</Link> </Link>

View File

@@ -14,6 +14,7 @@ import NavigationMenu from "../NavigationMenu"
import styles from "./mobileMenu.module.css" import styles from "./mobileMenu.module.css"
import { DropdownType } from "@/types/components/dropdown/dropdown"
import type { MobileMenuProps } from "@/types/components/header/mobileMenu" import type { MobileMenuProps } from "@/types/components/header/mobileMenu"
export default function MobileMenu({ export default function MobileMenu({
@@ -22,30 +23,28 @@ export default function MobileMenu({
topLink, topLink,
}: MobileMenuProps) { }: MobileMenuProps) {
const intl = useIntl() const intl = useIntl()
const { const { toggleDropdown, openDropdown } = useDropdownStore()
isHamburgerMenuOpen, const isHamburgerMenuOpen = openDropdown === DropdownType.HamburgerMenu
isMyPagesMobileMenuOpen, const isMyPagesMobileMenuOpen =
isHeaderLanguageSwitcherOpen, openDropdown === DropdownType.MyPagesMobileMenu
toggleHamburgerMenu, const isHeaderLanguageSwitcherOpen =
toggleMyPagesMobileMenu, openDropdown === DropdownType.HeaderLanguage
toggleLanguageSwitcher,
} = useDropdownStore()
useHandleKeyUp((event: KeyboardEvent) => { useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && isHamburgerMenuOpen) { if (event.key === "Escape" && isHamburgerMenuOpen) {
toggleHamburgerMenu() toggleDropdown(DropdownType.HamburgerMenu)
} }
}) })
function handleHamburgerClick() { function handleHamburgerClick() {
if (isMyPagesMobileMenuOpen) { if (isMyPagesMobileMenuOpen) {
toggleMyPagesMobileMenu() toggleDropdown(DropdownType.MyPagesMobileMenu)
} else { } else {
if (isHeaderLanguageSwitcherOpen) { if (isHeaderLanguageSwitcherOpen) {
toggleLanguageSwitcher("header") toggleDropdown(DropdownType.HeaderLanguage)
} }
toggleHamburgerMenu() toggleDropdown(DropdownType.HamburgerMenu)
} }
} }
@@ -61,7 +60,10 @@ export default function MobileMenu({
> >
<span className={styles.bar}></span> <span className={styles.bar}></span>
</button> </button>
<Modal className={styles.modal} isOpen={isHamburgerMenuOpen}> <Modal
className={styles.modal}
isOpen={isHamburgerMenuOpen || isHeaderLanguageSwitcherOpen}
>
<Dialog <Dialog
className={styles.dialog} className={styles.dialog}
aria-label={intl.formatMessage({ id: "Menu" })} aria-label={intl.formatMessage({ id: "Menu" })}

View File

@@ -7,7 +7,6 @@ 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 { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import useLang from "@/hooks/useLang"
import { getInitials } from "@/utils/user" import { getInitials } from "@/utils/user"
import Avatar from "../Avatar" import Avatar from "../Avatar"
@@ -16,6 +15,7 @@ import MyPagesMenuContent from "../MyPagesMenuContent"
import styles from "./myPagesMenu.module.css" import styles from "./myPagesMenu.module.css"
import { DropdownType } from "@/types/components/dropdown/dropdown"
import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu" import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu"
export default function MyPagesMenu({ export default function MyPagesMenu({
@@ -24,18 +24,22 @@ export default function MyPagesMenu({
user, user,
}: MyPagesMenuProps) { }: MyPagesMenuProps) {
const intl = useIntl() const intl = useIntl()
const lang = useLang()
const { toggleMyPagesMenu, isMyPagesMenuOpen } = useDropdownStore() const { toggleDropdown, openDropdown } = useDropdownStore()
const isMyPagesMenuOpen = openDropdown === DropdownType.MyPagesMenu
useHandleKeyUp((event: KeyboardEvent) => { useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && isMyPagesMenuOpen) { if (event.key === "Escape" && isMyPagesMenuOpen) {
toggleMyPagesMenu() toggleDropdown(DropdownType.MyPagesMenu)
} }
}) })
return ( return (
<div className={styles.myPagesMenu}> <div className={styles.myPagesMenu}>
<MainMenuButton className={styles.button} onClick={toggleMyPagesMenu}> <MainMenuButton
className={styles.button}
onClick={() => toggleDropdown(DropdownType.MyPagesMenu)}
>
<Avatar initials={getInitials(user.firstName, user.lastName)} /> <Avatar initials={getInitials(user.firstName, user.lastName)} />
<Subtitle type="two" className={styles.userName}> <Subtitle type="two" className={styles.userName}>
{intl.formatMessage({ id: "Hi" })} {user.firstName}! {intl.formatMessage({ id: "Hi" })} {user.firstName}!
@@ -51,7 +55,7 @@ export default function MyPagesMenu({
navigation={navigation} navigation={navigation}
user={user} user={user}
membership={membership} membership={membership}
toggleOpenStateFn={toggleMyPagesMenu} toggleOpenStateFn={() => toggleDropdown(DropdownType.MyPagesMenu)}
/> />
</div> </div>
) : null} ) : null}

View File

@@ -14,6 +14,7 @@ import MyPagesMenuContent from "../MyPagesMenuContent"
import styles from "./myPagesMobileMenu.module.css" import styles from "./myPagesMobileMenu.module.css"
import { DropdownType } from "@/types/components/dropdown/dropdown"
import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu" import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu"
export default function MyPagesMobileMenu({ export default function MyPagesMobileMenu({
@@ -22,12 +23,13 @@ export default function MyPagesMobileMenu({
user, user,
}: MyPagesMenuProps) { }: MyPagesMenuProps) {
const intl = useIntl() const intl = useIntl()
const { toggleMyPagesMobileMenu, isMyPagesMobileMenuOpen } = const { openDropdown, toggleDropdown } = useDropdownStore()
useDropdownStore() const isMyPagesMobileMenuOpen =
openDropdown === DropdownType.MyPagesMobileMenu
useHandleKeyUp((event: KeyboardEvent) => { useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && isMyPagesMobileMenuOpen) { if (event.key === "Escape" && isMyPagesMobileMenuOpen) {
toggleMyPagesMobileMenu() toggleDropdown(DropdownType.MyPagesMobileMenu)
} }
}) })
@@ -35,7 +37,7 @@ export default function MyPagesMobileMenu({
<div className={styles.myPagesMobileMenu}> <div className={styles.myPagesMobileMenu}>
<MainMenuButton <MainMenuButton
className={styles.button} className={styles.button}
onClick={toggleMyPagesMobileMenu} onClick={() => toggleDropdown(DropdownType.MyPagesMobileMenu)}
aria-label={intl.formatMessage({ id: "Open my pages menu" })} aria-label={intl.formatMessage({ id: "Open my pages menu" })}
> >
<Avatar initials={getInitials(user.firstName, user.lastName)} /> <Avatar initials={getInitials(user.firstName, user.lastName)} />
@@ -49,7 +51,9 @@ export default function MyPagesMobileMenu({
membership={membership} membership={membership}
navigation={navigation} navigation={navigation}
user={user} user={user}
toggleOpenStateFn={toggleMyPagesMobileMenu} toggleOpenStateFn={() =>
toggleDropdown(DropdownType.MyPagesMobileMenu)
}
/> />
</Dialog> </Dialog>
</Modal> </Modal>

View File

@@ -13,6 +13,7 @@ import { useTrapFocus } from "@/hooks/useTrapFocus"
import styles from "./languageSwitcherContent.module.css" import styles from "./languageSwitcherContent.module.css"
import { DropdownType } from "@/types/components/dropdown/dropdown"
import type { LanguageSwitcherProps } from "@/types/components/languageSwitcher/languageSwitcher" import type { LanguageSwitcherProps } from "@/types/components/languageSwitcher/languageSwitcher"
export default function LanguageSwitcherContent({ export default function LanguageSwitcherContent({
@@ -21,10 +22,13 @@ export default function LanguageSwitcherContent({
}: LanguageSwitcherProps) { }: LanguageSwitcherProps) {
const intl = useIntl() const intl = useIntl()
const currentLanguage = useLang() const currentLanguage = useLang()
const { toggleLanguageSwitcher } = useDropdownStore() const { toggleDropdown } = useDropdownStore()
const languageSwitcherRef = useTrapFocus() const languageSwitcherRef = useTrapFocus()
const urlKeys = Object.keys(urls) as Lang[] const urlKeys = Object.keys(urls) as Lang[]
const position = type === "footer" ? "footer" : "header" const position =
type === "footer"
? DropdownType.FooterLanguage
: DropdownType.HeaderLanguage
return ( return (
<div className={styles.languageSwitcherContent} ref={languageSwitcherRef}> <div className={styles.languageSwitcherContent} ref={languageSwitcherRef}>
@@ -33,7 +37,7 @@ export default function LanguageSwitcherContent({
<button <button
type="button" type="button"
className={styles.backButton} className={styles.backButton}
onClick={() => toggleLanguageSwitcher(position)} onClick={() => toggleDropdown(position)}
> >
<ChevronLeftIcon color="red" /> <ChevronLeftIcon color="red" />
<Subtitle type="one">Main Menu</Subtitle> <Subtitle type="one">Main Menu</Subtitle>

View File

@@ -14,6 +14,7 @@ import { languageSwitcherVariants } from "./variants"
import styles from "./languageSwitcher.module.css" import styles from "./languageSwitcher.module.css"
import { DropdownType } from "@/types/components/dropdown/dropdown"
import type { LanguageSwitcherProps } from "@/types/components/languageSwitcher/languageSwitcher" import type { LanguageSwitcherProps } from "@/types/components/languageSwitcher/languageSwitcher"
export default function LanguageSwitcher({ export default function LanguageSwitcher({
@@ -22,22 +23,23 @@ export default function LanguageSwitcher({
}: LanguageSwitcherProps) { }: LanguageSwitcherProps) {
const intl = useIntl() const intl = useIntl()
const currentLanguage = useLang() const currentLanguage = useLang()
const { const { toggleDropdown, openDropdown } = useDropdownStore()
toggleLanguageSwitcher,
isHeaderLanguageSwitcherOpen,
isFooterLanguageSwitcherOpen,
} = useDropdownStore()
const position = type === "footer" ? "footer" : "header" const position = type === "footer" ? "footer" : "header"
const dropdownType =
type === "footer"
? DropdownType.FooterLanguage
: DropdownType.HeaderLanguage
const color = type === "footer" ? "pale" : "burgundy" const color = type === "footer" ? "pale" : "burgundy"
const isLanguageSwitcherOpen = const isLanguageSwitcherOpen =
type === "footer" type === "footer"
? isFooterLanguageSwitcherOpen ? openDropdown === DropdownType.FooterLanguage
: isHeaderLanguageSwitcherOpen : openDropdown === DropdownType.HeaderLanguage
useHandleKeyUp((event: KeyboardEvent) => { useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && isLanguageSwitcherOpen) { if (event.key === "Escape" && isLanguageSwitcherOpen) {
toggleLanguageSwitcher(position) toggleDropdown(dropdownType)
} }
}) })
@@ -53,7 +55,7 @@ export default function LanguageSwitcher({
? "Close language menu" ? "Close language menu"
: "Open language menu", : "Open language menu",
})} })}
onClick={() => toggleLanguageSwitcher(position)} onClick={() => toggleDropdown(dropdownType)}
> >
<GlobeIcon width={20} height={20} color={color} /> <GlobeIcon width={20} height={20} color={color} />
<span>{languages[currentLanguage]}</span> <span>{languages[currentLanguage]}</span>

View File

@@ -1,118 +1,16 @@
import { create } from "zustand" import { create } from "zustand"
// TODO: When MyPagesMobileMenu is removed, also remove the import {
// isMyPagesMobileMenuOpen state and toggleMyPagesMobileMenu function type DropdownState,
interface DropdownState { DropdownType,
isHamburgerMenuOpen: boolean } from "@/types/components/dropdown/dropdown"
isMyPagesMobileMenuOpen: boolean
isMyPagesMenuOpen: boolean
isHeaderLanguageSwitcherOpen: boolean
isFooterLanguageSwitcherOpen: boolean
toggleHamburgerMenu: () => void
toggleMyPagesMobileMenu: () => void
toggleMyPagesMenu: () => void
toggleLanguageSwitcher: (location: "header" | "footer") => void
}
const useDropdownStore = create<DropdownState>((set) => ({ const useDropdownStore = create<DropdownState>((set) => ({
isHamburgerMenuOpen: false, openDropdown: null,
isMyPagesMobileMenuOpen: false, toggleDropdown: (dropdown: DropdownType) =>
isMyPagesMenuOpen: false, set((state) => ({
isHeaderLanguageSwitcherOpen: false, openDropdown: state.openDropdown === dropdown ? null : dropdown,
isFooterLanguageSwitcherOpen: false, })),
toggleHamburgerMenu: () =>
set(
({ isHamburgerMenuOpen, isMyPagesMenuOpen, isMyPagesMobileMenuOpen }) => {
// Close the other dropdowns if they're open
if (!isHamburgerMenuOpen) {
if (isMyPagesMenuOpen) {
set({ isMyPagesMenuOpen: false })
}
if (isMyPagesMobileMenuOpen) {
set({ isMyPagesMobileMenuOpen: false })
}
}
return { isHamburgerMenuOpen: !isHamburgerMenuOpen }
}
),
toggleMyPagesMobileMenu: () =>
set(
({
isMyPagesMenuOpen,
isMyPagesMobileMenuOpen,
isHamburgerMenuOpen,
isHeaderLanguageSwitcherOpen,
isFooterLanguageSwitcherOpen,
}) => {
// Close the other dropdowns if they're open
if (!isMyPagesMobileMenuOpen) {
if (isMyPagesMenuOpen) {
set({ isMyPagesMenuOpen: false })
}
if (isHamburgerMenuOpen) {
set({ isHamburgerMenuOpen: false })
}
if (isHeaderLanguageSwitcherOpen) {
set({ isHeaderLanguageSwitcherOpen: false })
}
if (isFooterLanguageSwitcherOpen) {
set({ isFooterLanguageSwitcherOpen: false })
}
}
return { isMyPagesMobileMenuOpen: !isMyPagesMobileMenuOpen }
}
),
toggleMyPagesMenu: () =>
set(
({
isHamburgerMenuOpen,
isHeaderLanguageSwitcherOpen,
isFooterLanguageSwitcherOpen,
isMyPagesMenuOpen,
isMyPagesMobileMenuOpen,
}) => {
// Close the other dropdowns if they're open
if (!isMyPagesMenuOpen) {
if (isHamburgerMenuOpen) {
set({ isHamburgerMenuOpen: false })
}
if (isMyPagesMobileMenuOpen) {
set({ isMyPagesMobileMenuOpen: false })
}
if (isHeaderLanguageSwitcherOpen) {
set({ isHeaderLanguageSwitcherOpen: false })
}
if (isFooterLanguageSwitcherOpen) {
set({ isFooterLanguageSwitcherOpen: false })
}
}
return { isMyPagesMenuOpen: !isMyPagesMenuOpen }
}
),
toggleLanguageSwitcher: (location: "header" | "footer") =>
set((state) => {
const isCurrentlyOpen =
location === "header"
? state.isHeaderLanguageSwitcherOpen
: state.isFooterLanguageSwitcherOpen
if (!isCurrentlyOpen) {
return {
isHeaderLanguageSwitcherOpen: location === "header",
isFooterLanguageSwitcherOpen: location === "footer",
isMyPagesMenuOpen: false,
isMyPagesMobileMenuOpen: false,
isHamburgerMenuOpen: false,
}
}
return {
isHeaderLanguageSwitcherOpen:
location === "header" ? false : state.isHeaderLanguageSwitcherOpen,
isFooterLanguageSwitcherOpen:
location === "footer" ? false : state.isFooterLanguageSwitcherOpen,
}
}),
})) }))
export default useDropdownStore export default useDropdownStore

View File

@@ -0,0 +1,15 @@
// TODO: When MyPagesMobileMenu is removed, also remove the
// isMyPagesMobileMenuOpen state
export enum DropdownType {
HeaderLanguage,
FooterLanguage,
HamburgerMenu,
MyPagesMenu,
MyPagesMobileMenu,
}
export interface DropdownState {
openDropdown: DropdownType | null
toggleDropdown: (dropdown: DropdownType) => void
}