mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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:
1 parent
10d0b63950
commit
4633992f81
4 files changed
+58
-41
No files matched your search
@@ -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
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in new issue
Block a user