mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
## 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 -->
138 lines
4.6 KiB
TypeScript
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>
|
|
)
|
|
}
|