Fix inconsistent padding in www mobile menu (#46704)

## Context

As per PR title
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/4cf07471-a0c1-449d-9759-a0ccb3456e09"
/>

Also resolves FE-3565
Closes mobile menu when a link is clicked


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Mobile menu now closes reliably when selecting more navigation items
(Modules, various Developers/Solutions links and footer links).
* Enhanced link focus and interaction behavior in the mobile menu for
clearer keyboard/touch feedback.

* **New Features**
* Navigation links can now accept optional click handlers, enabling
custom onClick behavior.

* **Refactor**
* Internal reorganization of mobile menu styling and link composition to
simplify maintenance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-06-08 21:24:39 +08:00
1 parent 10d0b63950
commit 4633992f81
4 files changed
+58 -41

No files matched your search

+48 -33
View File
@@ -1,12 +1,13 @@
'use client'
import { type Menu } from '~/data/nav'
import { useIsLoggedIn, useIsUserLoading } from 'common'
import { AnimatePresence, domAnimation, LazyMotion, m } from 'framer-motion'
import { ChevronRight } from 'lucide-react'
import Link from 'next/link'
import { useEffect } from 'react'
import type { Dispatch, SetStateAction } from 'react'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button } from 'ui'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, cn } from 'ui'
import { TextLink } from 'ui-patterns/TextLink'
import MenuItem from './MenuItem'
@@ -19,10 +20,10 @@ import { useSendTelemetryEvent } from '@/lib/telemetry'
interface Props {
open: boolean
setOpen: Dispatch<SetStateAction<boolean>>
menu: any
menu: Menu
}
const MobileMenu = ({ open, setOpen, menu }: Props) => {
export const MobileMenu = ({ open, setOpen, menu }: Props) => {
const isLoggedIn = useIsLoggedIn()
const isUserLoading = useIsUserLoading()
const sendTelemetryEvent = useSendTelemetryEvent()
@@ -50,17 +51,18 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => {
return () => window.removeEventListener('keydown', onKeyDown)
}, [setOpen])
const AccordionMenuItem = ({ menuItem }: any) => (
const AccordionMenuItem = ({ menuItem }: { menuItem: Menu['primaryNav'][number] }) => (
<AccordionContent className="p-0">
{menuItem.title === 'Product' ? (
<>
{Object.values(menuItem.subMenu)?.map((component: any) => (
{Object.values(menuItem.subMenu)?.map((component) => (
<MenuItem
key={component.name}
title={component.name}
href={component.url}
description={component.description_short}
icon={component.icon}
onClick={() => setOpen(false)}
/>
))}
<div>
@@ -75,6 +77,7 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => {
href={productModule.url}
description={productModule.description_short}
icon={productModule.icon}
onClick={() => setOpen(false)}
/>
))}
</ul>
@@ -90,6 +93,7 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => {
focus-visible:text-foreground focus-visible:ring-2 focus-visible:outline-hidden
focus-visible:rounded-sm focus-visible:ring-foreground-lighter
"
onClick={() => setOpen(false)}
>
<div className="flex flex-col gap-1 leading-3!">
<span>Features</span>
@@ -105,14 +109,14 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => {
</>
) : menuItem.title === 'Developers' ? (
<div className="px-3 mb-2 flex flex-col gap-6">
{menuItem.subMenu['navigation'].map((column: any) => (
{menuItem.subMenu['navigation'].map((column) => (
<div key={column.label} className="flex flex-col gap-3">
{column.label !== 'Developers' && (
<label className="text-foreground-lighter text-xs uppercase tracking-widest font-mono">
{column.label}
</label>
)}
{column.links.map((link: any) => (
{column.links.map((link) => (
<TextLink
hasChevron={false}
key={link.text}
@@ -120,6 +124,7 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => {
label={link.text}
counter={link.text === 'Careers' && jobsCount > 0 ? jobsCount : undefined}
className="focus-visible:ring-offset-4 focus-visible:ring-offset-background-overlay mt-0!"
onClick={() => setOpen(false)}
/>
))}
</div>
@@ -134,31 +139,34 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => {
url={menuItem.subMenu['footer']['support'].url}
label={menuItem.subMenu['footer']['support'].text}
className="focus-visible:ring-offset-4 focus-visible:ring-offset-background-overlay"
onClick={() => setOpen(false)}
/>
<TextLink
hasChevron={false}
url={menuItem.subMenu['footer']['systemStatus'].url}
label={menuItem.subMenu['footer']['systemStatus'].text}
className="focus-visible:ring-offset-4 focus-visible:ring-offset-background-overlay"
onClick={() => setOpen(false)}
/>
</div>
</div>
) : menuItem.title === 'Solutions' ? (
<div className="px-3 mb-2 flex flex-col gap-6">
{menuItem.subMenu['navigation'].map((column: any) => (
{menuItem.subMenu['navigation'].map((column) => (
<div key={column.label} className="flex flex-col gap-3">
{column.label !== 'Solutions' && (
<label className="text-foreground-lighter text-xs uppercase tracking-widest font-mono">
{column.label}
</label>
)}
{column.links.map((link: any) => (
{column.links.map((link) => (
<TextLink
hasChevron={false}
key={link.text}
url={link.url}
label={link.text}
className="focus-visible:ring-offset-4 focus-visible:ring-offset-background-overlay mt-0!"
onClick={() => setOpen(false)}
/>
))}
</div>
@@ -168,29 +176,38 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => {
</AccordionContent>
)
const Menu = () => (
<Accordion type="multiple" className="px-0">
{menu.primaryNav.map((menuItem: any) => (
<m.div variants={listItem} className="border-b [&>div]:rounded-none!">
{menuItem.hasDropdown ? (
<AccordionItem id={menuItem.title} value={menuItem.title} className="border-none">
<AccordionTrigger className="py-2 pl-2 pr-4 text-base font-medium text-foreground hover:bg-surface-200">
const Menu = () => {
const className = 'py-2 pl-2 pr-4 text-base font-medium text-foreground hover:bg-surface-200'
return (
<Accordion type="multiple" className="px-0">
{menu.primaryNav.map((menuItem) => (
<m.div
key={menuItem.title}
variants={listItem}
className="border-b [&>div]:rounded-none!"
>
{menuItem.hasDropdown ? (
<AccordionItem id={menuItem.title} value={menuItem.title} className="border-none">
<AccordionTrigger className={className}>{menuItem.title}</AccordionTrigger>
<AccordionMenuItem menuItem={menuItem} />
</AccordionItem>
) : (
<Link
href={menuItem.url ?? '/'}
className={cn(
className,
'block focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:rounded-sm'
)}
onClick={() => setOpen(false)}
>
{menuItem.title}
</AccordionTrigger>
<AccordionMenuItem menuItem={menuItem} />
</AccordionItem>
) : (
<Link
href={menuItem.url}
className="block py-2 pl-3 pr-4 text-base font-medium text-foreground hover:bg-surface-200 focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:rounded-sm"
>
{menuItem.title}
</Link>
)}
</m.div>
))}
</Accordion>
)
</Link>
)}
</m.div>
))}
</Accordion>
)
}
return (
<LazyMotion features={domAnimation}>
@@ -306,5 +323,3 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => {
</LazyMotion>
)
}
export default MobileMenu
+1 -1
View File
@@ -23,7 +23,7 @@ import {
import GitHubButton from './GitHubButton'
import HamburgerButton from './HamburgerMenu'
import MenuItem from './MenuItem'
import MobileMenu from './MobileMenu'
import { MobileMenu } from './MobileMenu'
import RightClickBrandLogo from './RightClickBrandLogo'
import useDropdownMenu from './useDropdownMenu'
+8 -7
View File
@@ -1,44 +1,45 @@
import { DevelopersDropdown } from 'components/Nav/DevelopersDropdown'
import { ProductDropdown } from 'components/Nav/ProductDropdown'
import { SolutionsDropdown } from 'components/Nav/SolutionsDropdown'
import { data as DevelopersData } from 'data/Developers'
import MainProductsData from 'data/MainProducts'
import { navData as SolutionsData } from 'data/Solutions'
export type Menu = ReturnType<typeof getMenu>
export const getMenu = () => ({
primaryNav: [
{
title: 'Product',
title: 'Product' as const,
hasDropdown: true,
dropdown: <ProductDropdown />,
dropdownContainerClassName: 'rounded-xl',
subMenu: MainProductsData,
},
{
title: 'Developers',
title: 'Developers' as const,
hasDropdown: true,
dropdown: <DevelopersDropdown />,
dropdownContainerClassName: 'rounded-xl',
subMenu: DevelopersData,
},
{
title: 'Solutions',
title: 'Solutions' as const,
hasDropdown: true,
dropdown: <SolutionsDropdown />,
dropdownContainerClassName: 'rounded-xl',
subMenu: SolutionsData,
},
{
title: 'Pricing',
title: 'Pricing' as const,
url: '/pricing',
},
{
title: 'Docs',
title: 'Docs' as const,
url: '/docs',
},
{
title: 'Blog',
title: 'Blog' as const,
url: '/blog',
},
],
@@ -12,6 +12,7 @@ interface Props {
hasChevron?: boolean
chevronAnimation?: 'translate' | 'fadeIn'
target?: '_blank' | '_self'
onClick?: () => void
}
export function TextLink({