Files
supabase/apps/www/components/Nav/SolutionsDropdown.tsx
Anthony Lio 819df2ae4d feat(www): menu nav enhancements (#50282)
## What kind of change does this PR introduce?

feature reworks the www header dropdowns

## What is the current behavior?

menu dropdown navigation animation between items feels scattered

## What is the new behavior?

- adds dropdown card resize with a transition and the content crossfades
when switching
- removes dead zone between or under nav items
- sets card is centered on the screen + enhance tablet bp
- adds slight ui refresh spacing, colors, sizes

| state | preview |
| -------|------|
| before | <video
src="https://github.com/user-attachments/assets/acd8e161-a697-4070-b751-4f4e9f1eab19"
/> |
| after | <video
src="https://github.com/user-attachments/assets/fe403afd-0074-4cb5-9223-fd6cdd2f7d5a"
/> |



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

* **New Features**
* Navigation dropdowns now provide smoother directional transitions,
keyboard-focus states, and reduced-motion support.
* Product navigation is organized into clearer Products and Modules
sections.
* Navigation layouts adapt earlier across screen sizes with responsive
two-column arrangements.

* **Style**
* Updated dropdown spacing, colors, borders, menu item styling, and
customer imagery sizing.
  * Refined blog loading placeholders with slightly tighter spacing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-14 15:51:39 +03:00

138 lines
4.6 KiB
TypeScript

import { useBreakpoint, useReducedMotion } from 'common'
import { navData as DevelopersData } from 'data/Solutions'
import { AnimatePresence, motion } from 'framer-motion'
import { ArrowLeftRight, ChevronRight } from 'lucide-react'
import Link from 'next/link'
import { useState } from 'react'
import { useMedia } from 'react-use'
type LinkProps = {
text: string
description?: string
url?: string
icon?: any
svg?: any
}
export const SolutionsDropdown = () => (
<div className="bg-surface-75 flex flex-col lg:flex-row">
<div className="w-full lg:w-[600px] px-6 py-5 grid gap-3 grid-cols-2 lg:grid-cols-3">
{/* Skill Level, Who it's for, App Type */}
{DevelopersData['navigation'].slice(0, 3).map((column) => (
<LinksGroup key={column.label} links={column.links} label={column.label} />
))}
</div>
<div className="flex flex-col w-full lg:flex-1 border-t lg:border-t-0 lg:border-l">
<div className="flex flex-col gap-6 p-5">
<label className="text-foreground-lighter text-xs uppercase tracking-widest font-mono">
{DevelopersData['navigation'][3].label}
</label>
<div className="flex flex-col gap-4">
{DevelopersData['navigation'][3].links.map((link) => (
<MigrationLinkCard key={link.text} link={link} />
))}
</div>
</div>
</div>
</div>
)
const LinksGroup = ({ links, label }: { links: LinkProps[]; label: string }) => {
return (
<div key={label} className="p-0 flex flex-col gap-6">
<label className="text-foreground-lighter text-xs uppercase tracking-widest font-mono">
{label}
</label>
<ul className="flex flex-col gap-4">
{links.map(({ icon: Icon, ...link }: LinkProps) => (
<li key={link.text}>
<Link
href={link.url!}
className="flex group items-center gap-2 text-foreground-light text-sm hover:text-foreground focus-visible:text-foreground focus-ring rounded-sm"
>
{Icon && <Icon size={16} strokeWidth={1.3} />}
<span>{link.text}</span>
<ChevronRight
strokeWidth={2}
className="w-3 -ml-1 transition-all will-change-transform -translate-x-1 opacity-0 group-hover:translate-x-0 group-hover:opacity-100"
/>
</Link>
</li>
))}
</ul>
</div>
)
}
const MotionLink = motion(Link)
const MigrationLinkCard = ({ link }: { link: LinkProps }) => {
const [isHighlighted, setIsHighlighted] = useState(false)
const canHover = useMedia('(hover: hover)', true)
const reduceMotion = useReducedMotion()
const isDesktop = !useBreakpoint('lg')
const duration = isDesktop && !reduceMotion ? 0.2 : 0
const isIconVisible = isHighlighted || !isDesktop || !canHover
return (
<MotionLink
href={link.url!}
className="bg-background p-3 rounded-md border flex items-center gap-4 group overflow-hidden"
onMouseEnter={() => setIsHighlighted(true)}
onMouseLeave={() => setIsHighlighted(false)}
onFocus={() => setIsHighlighted(true)}
onBlur={() => setIsHighlighted(false)}
layout
layoutDependency={isHighlighted}
transition={{ duration, ease: 'easeInOut' }}
>
<AnimatePresence mode="popLayout">
{isIconVisible && (
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -10 }}
transition={{ duration, ease: 'easeInOut' }}
layout
layoutDependency={isHighlighted}
>
{link.icon && <link.icon className="size-6" />}
</motion.div>
)}
</AnimatePresence>
<motion.div
className="size-6 flex items-center justify-center"
layout
layoutDependency={isHighlighted}
transition={{ duration, ease: 'easeInOut' }}
>
<ArrowLeftRight className="size-4 text-foreground-light" strokeWidth={1.3} />
</motion.div>
<motion.div
className="flex items-center gap-1"
layout
layoutDependency={isHighlighted}
transition={{ duration, ease: 'easeInOut' }}
>
<span className="text-base font-medium">{link.text}</span>
</motion.div>
<motion.div
layout
layoutDependency={isHighlighted}
transition={{ duration, ease: 'easeInOut' }}
className="ml-auto"
>
<ChevronRight
strokeWidth={2}
className="w-3 transition-all will-change-transform -translate-x-1 opacity-0 group-hover:translate-x-0 group-hover:opacity-100"
/>
</motion.div>
</MotionLink>
)
}