feat(SW-187): simplify dropdown menu
This commit is contained in:
@@ -18,6 +18,7 @@ import LoginButton from "../LoginButton"
|
||||
import styles from "./mainMenu.module.css"
|
||||
|
||||
import type { MainMenuProps } from "@/types/components/current/header/mainMenu"
|
||||
import { DropdownType } from "@/types/components/dropdown/dropdown"
|
||||
|
||||
export function MainMenu({
|
||||
frontpageLinkText,
|
||||
@@ -61,12 +62,11 @@ export function MainMenu({
|
||||
"/sv/current-content-page",
|
||||
].includes(pathname)
|
||||
|
||||
const {
|
||||
isHamburgerMenuOpen,
|
||||
isMyPagesMobileMenuOpen,
|
||||
toggleHamburgerMenu,
|
||||
toggleMyPagesMobileMenu,
|
||||
} = useDropdownStore()
|
||||
const { toggleDropdown, openDropdown } = useDropdownStore()
|
||||
|
||||
const isMyPagesMobileMenuOpen =
|
||||
openDropdown === DropdownType.MyPagesMobileMenu
|
||||
const isHamburgerMenuOpen = openDropdown === DropdownType.HamburgerMenu
|
||||
|
||||
function handleMyPagesMobileMenuClick() {
|
||||
// Only track click when opening it
|
||||
@@ -74,7 +74,7 @@ export function MainMenu({
|
||||
trackClick("profile picture icon")
|
||||
}
|
||||
|
||||
toggleMyPagesMobileMenu()
|
||||
toggleDropdown(DropdownType.MyPagesMobileMenu)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -89,7 +89,7 @@ export function MainMenu({
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className={`${styles.expanderBtn} ${isHamburgerMenuOpen ? styles.expanded : ""}`}
|
||||
onClick={toggleHamburgerMenu}
|
||||
onClick={() => toggleDropdown(DropdownType.HamburgerMenu)}
|
||||
type="button"
|
||||
>
|
||||
<span className={styles.iconBars}></span>
|
||||
|
||||
@@ -13,6 +13,8 @@ import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./my-pages-mobile-dropdown.module.css"
|
||||
|
||||
import { DropdownType } from "@/types/components/dropdown/dropdown"
|
||||
|
||||
type Navigation = Awaited<ReturnType<(typeof navigationQueryRouter)["get"]>>
|
||||
|
||||
export default function MyPagesMobileDropdown({
|
||||
@@ -22,8 +24,10 @@ export default function MyPagesMobileDropdown({
|
||||
}) {
|
||||
const { formatMessage } = useIntl()
|
||||
const lang = useLang()
|
||||
const { toggleMyPagesMobileMenu, isMyPagesMobileMenuOpen } =
|
||||
useDropdownStore()
|
||||
const { toggleDropdown, openDropdown } = useDropdownStore()
|
||||
|
||||
const isMyPagesMobileMenuOpen =
|
||||
openDropdown === DropdownType.MyPagesMobileMenu
|
||||
|
||||
if (!navigation) {
|
||||
return null
|
||||
@@ -51,7 +55,9 @@ export default function MyPagesMobileDropdown({
|
||||
size={menuItem.display_sign_out_link ? "small" : "regular"}
|
||||
variant="myPageMobileDropdown"
|
||||
color="burgundy"
|
||||
onClick={toggleMyPagesMobileMenu}
|
||||
onClick={() =>
|
||||
toggleDropdown(DropdownType.MyPagesMobileMenu)
|
||||
}
|
||||
>
|
||||
{link.linkText}
|
||||
</Link>
|
||||
|
||||
@@ -14,6 +14,7 @@ import NavigationMenu from "../NavigationMenu"
|
||||
|
||||
import styles from "./mobileMenu.module.css"
|
||||
|
||||
import { DropdownType } from "@/types/components/dropdown/dropdown"
|
||||
import type { MobileMenuProps } from "@/types/components/header/mobileMenu"
|
||||
|
||||
export default function MobileMenu({
|
||||
@@ -22,30 +23,28 @@ export default function MobileMenu({
|
||||
topLink,
|
||||
}: MobileMenuProps) {
|
||||
const intl = useIntl()
|
||||
const {
|
||||
isHamburgerMenuOpen,
|
||||
isMyPagesMobileMenuOpen,
|
||||
isHeaderLanguageSwitcherOpen,
|
||||
toggleHamburgerMenu,
|
||||
toggleMyPagesMobileMenu,
|
||||
toggleLanguageSwitcher,
|
||||
} = useDropdownStore()
|
||||
const { toggleDropdown, openDropdown } = useDropdownStore()
|
||||
const isHamburgerMenuOpen = openDropdown === DropdownType.HamburgerMenu
|
||||
const isMyPagesMobileMenuOpen =
|
||||
openDropdown === DropdownType.MyPagesMobileMenu
|
||||
const isHeaderLanguageSwitcherOpen =
|
||||
openDropdown === DropdownType.HeaderLanguage
|
||||
|
||||
useHandleKeyUp((event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && isHamburgerMenuOpen) {
|
||||
toggleHamburgerMenu()
|
||||
toggleDropdown(DropdownType.HamburgerMenu)
|
||||
}
|
||||
})
|
||||
|
||||
function handleHamburgerClick() {
|
||||
if (isMyPagesMobileMenuOpen) {
|
||||
toggleMyPagesMobileMenu()
|
||||
toggleDropdown(DropdownType.MyPagesMobileMenu)
|
||||
} else {
|
||||
if (isHeaderLanguageSwitcherOpen) {
|
||||
toggleLanguageSwitcher("header")
|
||||
toggleDropdown(DropdownType.HeaderLanguage)
|
||||
}
|
||||
|
||||
toggleHamburgerMenu()
|
||||
toggleDropdown(DropdownType.HamburgerMenu)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +60,10 @@ export default function MobileMenu({
|
||||
>
|
||||
<span className={styles.bar}></span>
|
||||
</button>
|
||||
<Modal className={styles.modal} isOpen={isHamburgerMenuOpen}>
|
||||
<Modal
|
||||
className={styles.modal}
|
||||
isOpen={isHamburgerMenuOpen || isHeaderLanguageSwitcherOpen}
|
||||
>
|
||||
<Dialog
|
||||
className={styles.dialog}
|
||||
aria-label={intl.formatMessage({ id: "Menu" })}
|
||||
|
||||
@@ -7,7 +7,6 @@ import useDropdownStore from "@/stores/main-menu"
|
||||
import { ChevronDownIcon } from "@/components/Icons"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { getInitials } from "@/utils/user"
|
||||
|
||||
import Avatar from "../Avatar"
|
||||
@@ -16,6 +15,7 @@ import MyPagesMenuContent from "../MyPagesMenuContent"
|
||||
|
||||
import styles from "./myPagesMenu.module.css"
|
||||
|
||||
import { DropdownType } from "@/types/components/dropdown/dropdown"
|
||||
import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu"
|
||||
|
||||
export default function MyPagesMenu({
|
||||
@@ -24,18 +24,22 @@ export default function MyPagesMenu({
|
||||
user,
|
||||
}: MyPagesMenuProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const { toggleMyPagesMenu, isMyPagesMenuOpen } = useDropdownStore()
|
||||
|
||||
const { toggleDropdown, openDropdown } = useDropdownStore()
|
||||
const isMyPagesMenuOpen = openDropdown === DropdownType.MyPagesMenu
|
||||
|
||||
useHandleKeyUp((event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && isMyPagesMenuOpen) {
|
||||
toggleMyPagesMenu()
|
||||
toggleDropdown(DropdownType.MyPagesMenu)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<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)} />
|
||||
<Subtitle type="two" className={styles.userName}>
|
||||
{intl.formatMessage({ id: "Hi" })} {user.firstName}!
|
||||
@@ -51,7 +55,7 @@ export default function MyPagesMenu({
|
||||
navigation={navigation}
|
||||
user={user}
|
||||
membership={membership}
|
||||
toggleOpenStateFn={toggleMyPagesMenu}
|
||||
toggleOpenStateFn={() => toggleDropdown(DropdownType.MyPagesMenu)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -14,6 +14,7 @@ import MyPagesMenuContent from "../MyPagesMenuContent"
|
||||
|
||||
import styles from "./myPagesMobileMenu.module.css"
|
||||
|
||||
import { DropdownType } from "@/types/components/dropdown/dropdown"
|
||||
import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu"
|
||||
|
||||
export default function MyPagesMobileMenu({
|
||||
@@ -22,12 +23,13 @@ export default function MyPagesMobileMenu({
|
||||
user,
|
||||
}: MyPagesMenuProps) {
|
||||
const intl = useIntl()
|
||||
const { toggleMyPagesMobileMenu, isMyPagesMobileMenuOpen } =
|
||||
useDropdownStore()
|
||||
const { openDropdown, toggleDropdown } = useDropdownStore()
|
||||
const isMyPagesMobileMenuOpen =
|
||||
openDropdown === DropdownType.MyPagesMobileMenu
|
||||
|
||||
useHandleKeyUp((event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && isMyPagesMobileMenuOpen) {
|
||||
toggleMyPagesMobileMenu()
|
||||
toggleDropdown(DropdownType.MyPagesMobileMenu)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,7 +37,7 @@ export default function MyPagesMobileMenu({
|
||||
<div className={styles.myPagesMobileMenu}>
|
||||
<MainMenuButton
|
||||
className={styles.button}
|
||||
onClick={toggleMyPagesMobileMenu}
|
||||
onClick={() => toggleDropdown(DropdownType.MyPagesMobileMenu)}
|
||||
aria-label={intl.formatMessage({ id: "Open my pages menu" })}
|
||||
>
|
||||
<Avatar initials={getInitials(user.firstName, user.lastName)} />
|
||||
@@ -49,7 +51,9 @@ export default function MyPagesMobileMenu({
|
||||
membership={membership}
|
||||
navigation={navigation}
|
||||
user={user}
|
||||
toggleOpenStateFn={toggleMyPagesMobileMenu}
|
||||
toggleOpenStateFn={() =>
|
||||
toggleDropdown(DropdownType.MyPagesMobileMenu)
|
||||
}
|
||||
/>
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useTrapFocus } from "@/hooks/useTrapFocus"
|
||||
|
||||
import styles from "./languageSwitcherContent.module.css"
|
||||
|
||||
import { DropdownType } from "@/types/components/dropdown/dropdown"
|
||||
import type { LanguageSwitcherProps } from "@/types/components/languageSwitcher/languageSwitcher"
|
||||
|
||||
export default function LanguageSwitcherContent({
|
||||
@@ -21,10 +22,13 @@ export default function LanguageSwitcherContent({
|
||||
}: LanguageSwitcherProps) {
|
||||
const intl = useIntl()
|
||||
const currentLanguage = useLang()
|
||||
const { toggleLanguageSwitcher } = useDropdownStore()
|
||||
const { toggleDropdown } = useDropdownStore()
|
||||
const languageSwitcherRef = useTrapFocus()
|
||||
const urlKeys = Object.keys(urls) as Lang[]
|
||||
const position = type === "footer" ? "footer" : "header"
|
||||
const position =
|
||||
type === "footer"
|
||||
? DropdownType.FooterLanguage
|
||||
: DropdownType.HeaderLanguage
|
||||
|
||||
return (
|
||||
<div className={styles.languageSwitcherContent} ref={languageSwitcherRef}>
|
||||
@@ -33,7 +37,7 @@ export default function LanguageSwitcherContent({
|
||||
<button
|
||||
type="button"
|
||||
className={styles.backButton}
|
||||
onClick={() => toggleLanguageSwitcher(position)}
|
||||
onClick={() => toggleDropdown(position)}
|
||||
>
|
||||
<ChevronLeftIcon color="red" />
|
||||
<Subtitle type="one">Main Menu</Subtitle>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { languageSwitcherVariants } from "./variants"
|
||||
|
||||
import styles from "./languageSwitcher.module.css"
|
||||
|
||||
import { DropdownType } from "@/types/components/dropdown/dropdown"
|
||||
import type { LanguageSwitcherProps } from "@/types/components/languageSwitcher/languageSwitcher"
|
||||
|
||||
export default function LanguageSwitcher({
|
||||
@@ -22,22 +23,23 @@ export default function LanguageSwitcher({
|
||||
}: LanguageSwitcherProps) {
|
||||
const intl = useIntl()
|
||||
const currentLanguage = useLang()
|
||||
const {
|
||||
toggleLanguageSwitcher,
|
||||
isHeaderLanguageSwitcherOpen,
|
||||
isFooterLanguageSwitcherOpen,
|
||||
} = useDropdownStore()
|
||||
const { toggleDropdown, openDropdown } = useDropdownStore()
|
||||
|
||||
const position = type === "footer" ? "footer" : "header"
|
||||
const dropdownType =
|
||||
type === "footer"
|
||||
? DropdownType.FooterLanguage
|
||||
: DropdownType.HeaderLanguage
|
||||
const color = type === "footer" ? "pale" : "burgundy"
|
||||
|
||||
const isLanguageSwitcherOpen =
|
||||
type === "footer"
|
||||
? isFooterLanguageSwitcherOpen
|
||||
: isHeaderLanguageSwitcherOpen
|
||||
? openDropdown === DropdownType.FooterLanguage
|
||||
: openDropdown === DropdownType.HeaderLanguage
|
||||
|
||||
useHandleKeyUp((event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && isLanguageSwitcherOpen) {
|
||||
toggleLanguageSwitcher(position)
|
||||
toggleDropdown(dropdownType)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -53,7 +55,7 @@ export default function LanguageSwitcher({
|
||||
? "Close language menu"
|
||||
: "Open language menu",
|
||||
})}
|
||||
onClick={() => toggleLanguageSwitcher(position)}
|
||||
onClick={() => toggleDropdown(dropdownType)}
|
||||
>
|
||||
<GlobeIcon width={20} height={20} color={color} />
|
||||
<span>{languages[currentLanguage]}</span>
|
||||
|
||||
@@ -1,118 +1,16 @@
|
||||
import { create } from "zustand"
|
||||
|
||||
// TODO: When MyPagesMobileMenu is removed, also remove the
|
||||
// isMyPagesMobileMenuOpen state and toggleMyPagesMobileMenu function
|
||||
interface DropdownState {
|
||||
isHamburgerMenuOpen: boolean
|
||||
isMyPagesMobileMenuOpen: boolean
|
||||
isMyPagesMenuOpen: boolean
|
||||
isHeaderLanguageSwitcherOpen: boolean
|
||||
isFooterLanguageSwitcherOpen: boolean
|
||||
toggleHamburgerMenu: () => void
|
||||
toggleMyPagesMobileMenu: () => void
|
||||
toggleMyPagesMenu: () => void
|
||||
toggleLanguageSwitcher: (location: "header" | "footer") => void
|
||||
}
|
||||
import {
|
||||
type DropdownState,
|
||||
DropdownType,
|
||||
} from "@/types/components/dropdown/dropdown"
|
||||
|
||||
const useDropdownStore = create<DropdownState>((set) => ({
|
||||
isHamburgerMenuOpen: false,
|
||||
isMyPagesMobileMenuOpen: false,
|
||||
isMyPagesMenuOpen: false,
|
||||
isHeaderLanguageSwitcherOpen: false,
|
||||
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,
|
||||
}
|
||||
}),
|
||||
openDropdown: null,
|
||||
toggleDropdown: (dropdown: DropdownType) =>
|
||||
set((state) => ({
|
||||
openDropdown: state.openDropdown === dropdown ? null : dropdown,
|
||||
})),
|
||||
}))
|
||||
|
||||
export default useDropdownStore
|
||||
|
||||
15
types/components/dropdown/dropdown.ts
Normal file
15
types/components/dropdown/dropdown.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user