mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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 -->
This commit is contained in:
1 parent
22d7bc0cfd
commit
819df2ae4d
6 files changed
+154
-119
No files matched your search
@@ -41,7 +41,7 @@ function BlogListItemSkeleton() {
|
||||
|
||||
function BlogGridItemSkeleton() {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-6">
|
||||
<div className="flex flex-col gap-2 p-5">
|
||||
<div className="relative w-full aspect-[1.91/1] overflow-hidden bg-foreground-muted/20 animate-pulse" />
|
||||
<div className="flex items-center space-x-1.5 mt-2">
|
||||
<div className="h-4 w-24 bg-foreground-muted/20 rounded animate-pulse" />
|
||||
|
||||
@@ -15,8 +15,8 @@ export const DevelopersDropdown = () => {
|
||||
const { jobsCount, latestBlogPosts } = staticContent
|
||||
|
||||
return (
|
||||
<div className="flex flex-col xl:flex-row">
|
||||
<div className="w-[550px] xl:w-[470px] py-8 px-8 bg-background grid gap-3 grid-cols-2">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<div className="w-full lg:w-[470px] px-6 py-5 bg-surface-75 grid gap-3 grid-cols-2">
|
||||
{DevelopersData['navigation'].map((column) => (
|
||||
<div key={column.label} className="p-0 flex flex-col gap-6">
|
||||
<label className="text-foreground-lighter text-xs uppercase tracking-widest font-mono">
|
||||
@@ -47,8 +47,8 @@ export const DevelopersDropdown = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="bg-surface-75 flex flex-col w-[550px] xl:w-[480px] border-t xl:border-t-0 xl:border-l">
|
||||
<div className="flex-col gap-2 py-8 px-10">
|
||||
<div className="bg-surface-75 flex flex-col w-full lg:flex-1 border-t lg:border-t-0 lg:border-l">
|
||||
<div className="flex-col gap-2 p-5">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="group flex items-center gap-1 text-foreground-lighter hover:text-foreground text-xs uppercase tracking-widest font-mono mb-5 focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative rounded-xs focus-visible:text-foreground"
|
||||
|
||||
@@ -18,92 +18,60 @@ export const ProductDropdown = () => {
|
||||
const isTablet = useBreakpoint(1279)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col xl:flex-row">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row py-6 px-6 gap-2">
|
||||
<ul className="flex flex-col gap-4 w-[280px] xl:w-[250px]">
|
||||
{Object.values(MainProductsData)
|
||||
.filter((product) => product.name !== 'Vector')
|
||||
.map((product) => (
|
||||
<NavigationMenuLink key={product.name} asChild>
|
||||
<MenuItem
|
||||
title={product.name}
|
||||
href={product.url}
|
||||
description={product.description_short}
|
||||
icon={product.icon}
|
||||
className="h-fit p-0"
|
||||
hasChevron
|
||||
/>
|
||||
</NavigationMenuLink>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex flex-col gap-4 w-[250px]">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<div className="flex flex-col bg-surface-75">
|
||||
<div className="flex flex-row p-6 pt-5 gap-6">
|
||||
<div className="flex flex-col gap-4 w-[280px] lg:w-[250px]">
|
||||
<div className="flex items-center gap-1 text-foreground-lighter text-xs uppercase tracking-widest font-mono">
|
||||
Features
|
||||
</div>
|
||||
<ul className="flex flex-col gap-4">
|
||||
{Object.values(MainProductsData)
|
||||
.filter((product) => product.name !== 'Vector')
|
||||
.map((product) => (
|
||||
<NavigationMenuLink key={product.name} asChild>
|
||||
<MenuItem
|
||||
title={product.name}
|
||||
href={product.url}
|
||||
description={product.description_short}
|
||||
icon={product.icon}
|
||||
className="h-fit p-0"
|
||||
hasChevron
|
||||
/>
|
||||
</NavigationMenuLink>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 w-72">
|
||||
<div className="group flex items-center gap-1 text-foreground-lighter text-xs uppercase tracking-widest font-mono">
|
||||
Modules
|
||||
</div>
|
||||
<ul className="flex flex-col gap-4">
|
||||
{Object.values(ProductModulesData).map((productModule) => (
|
||||
<NavigationMenuLink key={productModule.name} asChild>
|
||||
<Link
|
||||
<MenuItem
|
||||
title={productModule.name}
|
||||
href={productModule.url}
|
||||
className="
|
||||
h-fit group/menu-item
|
||||
flex items-start gap-2
|
||||
text-xs leading-none
|
||||
text-foreground-light hover:text-foreground
|
||||
no-underline select-none
|
||||
focus-ring focus-visible:text-foreground
|
||||
"
|
||||
>
|
||||
<div className="w-8 h-8 min-w-8 shrink-0 bg-background border flex items-center justify-center rounded-md">
|
||||
<svg
|
||||
className="h-4 w-4 group-hover/menu-item:text-foreground group-focus-visible/menu-item:text-foreground"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d={productModule.icon}
|
||||
stroke="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center">
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="leading-snug text-foreground">{productModule.name}</p>
|
||||
<ChevronRight
|
||||
strokeWidth={2}
|
||||
className="w-3 h-3 text-foreground transition-all will-change-transform -translate-x-1 opacity-0 group-hover/menu-item:translate-x-0 group-hover/menu-item:opacity-100"
|
||||
/>
|
||||
</div>
|
||||
{productModule.description_short && (
|
||||
<p className="line-clamp-2 leading-tight text-foreground-lighter group-hover/menu-item:text-foreground-light group-focus-visible/menu-item:text-foreground-light">
|
||||
{productModule.description_short}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
description={productModule.description_short}
|
||||
icon={productModule.icon}
|
||||
className="h-fit p-0"
|
||||
hasChevron
|
||||
/>
|
||||
</NavigationMenuLink>
|
||||
))}
|
||||
</ul>
|
||||
<div className="pt-4 border-t">
|
||||
<Link
|
||||
href="/features"
|
||||
className="
|
||||
h-fit group/menu-item
|
||||
flex items-start gap-2
|
||||
text-xs leading-none
|
||||
flex items-center gap-3
|
||||
text-sm leading-none
|
||||
text-foreground-light hover:text-foreground
|
||||
no-underline select-none
|
||||
focus-ring focus-visible:text-foreground
|
||||
"
|
||||
>
|
||||
<div className="w-8 h-8 min-w-8 shrink-0 bg-background border flex items-center justify-center rounded-md">
|
||||
<Sparkles size={16} strokeWidth={1.3} />
|
||||
<div className="w-10 h-10 min-w-10 shrink-0 bg-surface-200 border flex items-center justify-center rounded-lg">
|
||||
<Sparkles size={20} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div className="flex flex-col justify-center">
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -113,17 +81,17 @@ export const ProductDropdown = () => {
|
||||
className="w-3 h-3 text-foreground transition-all will-change-transform -translate-x-1 opacity-0 group-hover/menu-item:translate-x-0 group-hover/menu-item:opacity-100"
|
||||
/>
|
||||
</div>
|
||||
<span className="line-clamp-2 leading-tight text-foreground-lighter group-hover/menu-item:text-foreground-light group-focus-visible/menu-item:text-foreground-light">
|
||||
<span className="line-clamp-2 leading-snug text-xs text-foreground-lighter group-hover/menu-item:text-foreground-light group-focus-visible/menu-item:text-foreground-light">
|
||||
Explore everything you can do with Supabase.
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-surface-75 border-t xl:border-t-0 xl:border-l p-6 gap-8 flex flex-col w-full xl:w-[400px]">
|
||||
<div className="col-span-3 flex flex-row gap-8 xl:w-auto">
|
||||
<div className="bg-surface-75 border-t lg:border-t-0 lg:border-l p-6 gap-8 flex flex-col w-full lg:flex-1">
|
||||
<div className="col-span-3 flex flex-row gap-8 lg:w-auto">
|
||||
<div>
|
||||
<Link
|
||||
href="/customers"
|
||||
@@ -139,7 +107,7 @@ export const ProductDropdown = () => {
|
||||
href={customer.url}
|
||||
className="group flex items-center gap-3 focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative rounded-sm"
|
||||
>
|
||||
<div className="relative rounded-md bg-background border group-hover:border-foreground-muted/50 h-14 w-28 xl:h-14 xl:w-20 shrink-0 overflow-auto">
|
||||
<div className="relative rounded-md bg-background border group-hover:border-foreground-muted/50 h-14 w-28 lg:h-14 lg:w-20 shrink-0 overflow-auto">
|
||||
<Image
|
||||
src={`/${customer.imgUrl}`}
|
||||
alt={customer.title}
|
||||
@@ -158,24 +126,22 @@ export const ProductDropdown = () => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full col-span-2 grid grid-cols-4 xl:grid-cols-5 gap-6 xl:gap-2">
|
||||
<div className="col-span-2 xl:col-span-3">
|
||||
<p className="text-foreground-lighter text-xs uppercase tracking-widest font-mono mb-3">
|
||||
{ComparisonsData.label}
|
||||
</p>
|
||||
<ul className="flex flex-col gap-2">
|
||||
{ComparisonsData.comparisons.map((link) => (
|
||||
<li key={link.text}>
|
||||
<TextLink
|
||||
chevronAnimation="fadeIn"
|
||||
url={link.url}
|
||||
label={link.text}
|
||||
className="mt-0 hover:text-foreground focus-visible:text-foreground focus-visible:ring-offset-4 focus-visible:ring-offset-background-overlay"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<p className="text-foreground-lighter text-xs uppercase tracking-widest font-mono mb-3">
|
||||
{ComparisonsData.label}
|
||||
</p>
|
||||
<ul className="flex flex-col gap-2">
|
||||
{ComparisonsData.comparisons.map((link) => (
|
||||
<li key={link.text}>
|
||||
<TextLink
|
||||
chevronAnimation="fadeIn"
|
||||
url={link.url}
|
||||
label={link.text}
|
||||
className="mt-0 hover:text-foreground focus-visible:text-foreground focus-visible:ring-offset-4 focus-visible:ring-offset-background-overlay"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
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 { useWindowSize } from 'react-use'
|
||||
import { useMedia } from 'react-use'
|
||||
|
||||
type LinkProps = {
|
||||
text: string
|
||||
@@ -14,16 +15,16 @@ type LinkProps = {
|
||||
}
|
||||
|
||||
export const SolutionsDropdown = () => (
|
||||
<div className="flex flex-col xl:flex-row">
|
||||
<div className="w-[550px] xl:w-[600px] py-8 px-8 bg-background grid gap-3 grid-cols-2 xl:grid-cols-3">
|
||||
<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="bg-surface-75 flex flex-col w-[550px] xl:w-[480px] border-t xl:border-t-0 xl:border-l">
|
||||
<div className="flex flex-col gap-6 py-8 px-10">
|
||||
<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>
|
||||
@@ -68,26 +69,34 @@ const LinksGroup = ({ links, label }: { links: LinkProps[]; label: string }) =>
|
||||
const MotionLink = motion(Link)
|
||||
|
||||
const MigrationLinkCard = ({ link }: { link: LinkProps }) => {
|
||||
const [hovered, setHovered] = useState(false)
|
||||
const { width } = useWindowSize()
|
||||
const isDesktop = width >= 1280
|
||||
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={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
onMouseEnter={() => setIsHighlighted(true)}
|
||||
onMouseLeave={() => setIsHighlighted(false)}
|
||||
onFocus={() => setIsHighlighted(true)}
|
||||
onBlur={() => setIsHighlighted(false)}
|
||||
layout
|
||||
layoutDependency={isHighlighted}
|
||||
transition={{ duration, ease: 'easeInOut' }}
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{(hovered || !isDesktop) && (
|
||||
{isIconVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -10 }}
|
||||
transition={{ duration: isDesktop ? 0.2 : 0, ease: 'easeInOut' }}
|
||||
transition={{ duration, ease: 'easeInOut' }}
|
||||
layout
|
||||
layoutDependency={isHighlighted}
|
||||
>
|
||||
{link.icon && <link.icon className="size-6" />}
|
||||
</motion.div>
|
||||
@@ -97,7 +106,8 @@ const MigrationLinkCard = ({ link }: { link: LinkProps }) => {
|
||||
<motion.div
|
||||
className="size-6 flex items-center justify-center"
|
||||
layout
|
||||
transition={{ duration: isDesktop ? 0.2 : 0, ease: 'easeInOut' }}
|
||||
layoutDependency={isHighlighted}
|
||||
transition={{ duration, ease: 'easeInOut' }}
|
||||
>
|
||||
<ArrowLeftRight className="size-4 text-foreground-light" strokeWidth={1.3} />
|
||||
</motion.div>
|
||||
@@ -105,14 +115,16 @@ const MigrationLinkCard = ({ link }: { link: LinkProps }) => {
|
||||
<motion.div
|
||||
className="flex items-center gap-1"
|
||||
layout
|
||||
transition={{ duration: isDesktop ? 0.2 : 0, ease: 'easeInOut' }}
|
||||
layoutDependency={isHighlighted}
|
||||
transition={{ duration, ease: 'easeInOut' }}
|
||||
>
|
||||
<span className="text-base font-medium">{link.text}</span>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
layout
|
||||
transition={{ duration: isDesktop ? 0.2 : 0, ease: 'easeInOut' }}
|
||||
layoutDependency={isHighlighted}
|
||||
transition={{ duration, ease: 'easeInOut' }}
|
||||
className="ml-auto"
|
||||
>
|
||||
<ChevronRight
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuViewport,
|
||||
} from 'ui'
|
||||
import { AuthenticatedDropdownMenu } from 'ui-patterns/AuthenticatedDropdownMenu'
|
||||
import { AnnouncementBanner } from 'ui-patterns/Banners/AnnouncementBanner'
|
||||
@@ -39,10 +40,29 @@ const Nav = ({ hideNavbar, stickyNavbar = true }: Props) => {
|
||||
const pathname = usePathname()
|
||||
const { width } = useWindowSize()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [activeDropdown, setActiveDropdown] = useState('')
|
||||
const [visibleDropdown, setVisibleDropdown] = useState('')
|
||||
const [isSwitchingDropdown, setIsSwitchingDropdown] = useState(false)
|
||||
const [openedDropdowns, setOpenedDropdowns] = useState<string[]>([])
|
||||
const handleDropdownChange = (value: string) => {
|
||||
// animate card height between two open menus only
|
||||
setIsSwitchingDropdown(value !== '' && activeDropdown !== '')
|
||||
setActiveDropdown(value)
|
||||
if (value === '') return
|
||||
setVisibleDropdown(value)
|
||||
if (!openedDropdowns.includes(value)) setOpenedDropdowns([...openedDropdowns, value])
|
||||
}
|
||||
const isLoggedIn = useIsLoggedIn()
|
||||
const isUserLoading = useIsUserLoading()
|
||||
const user = useUser()
|
||||
const menu = getMenu()
|
||||
const dropdownTitles: string[] = menu.primaryNav
|
||||
.filter((menuItem) => menuItem.hasDropdown)
|
||||
.map((menuItem) => menuItem.title)
|
||||
const getDropdownSide = (title: string) => {
|
||||
if (title === visibleDropdown) return 'active'
|
||||
return dropdownTitles.indexOf(title) < dropdownTitles.indexOf(visibleDropdown) ? 'start' : 'end'
|
||||
}
|
||||
const sendTelemetryEvent = useSendTelemetryEvent()
|
||||
const userMenu = useDropdownMenu(user)
|
||||
|
||||
@@ -116,22 +136,43 @@ const Nav = ({ hideNavbar, stickyNavbar = true }: Props) => {
|
||||
</div>
|
||||
<NavigationMenu
|
||||
delayDuration={0}
|
||||
className="hidden pl-8 sm:space-x-4 lg:flex h-16"
|
||||
viewportClassName="rounded-xl bg-background"
|
||||
value={activeDropdown}
|
||||
onValueChange={handleDropdownChange}
|
||||
renderViewport={false}
|
||||
className="static hidden pl-8 lg:flex h-16 items-stretch"
|
||||
>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuList className="h-full space-x-0 items-stretch">
|
||||
{menu.primaryNav.map((menuItem) =>
|
||||
menuItem.hasDropdown ? (
|
||||
<NavigationMenuItem className="text-sm font-medium" key={menuItem.title}>
|
||||
<NavigationMenuItem
|
||||
className="text-sm font-medium"
|
||||
key={menuItem.title}
|
||||
value={menuItem.title}
|
||||
>
|
||||
<NavigationMenuTrigger
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'text', size: 'small' }),
|
||||
'bg-transparent! hover:text-brand-link data-open:text-brand-link! focus-ring focus-visible:text-foreground px-2 h-auto'
|
||||
'bg-transparent! hover:text-brand-link data-open:text-brand-link! focus-ring focus-visible:text-foreground px-2.5 h-full'
|
||||
)}
|
||||
>
|
||||
{menuItem.title}
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>{menuItem.dropdown}</NavigationMenuContent>
|
||||
<NavigationMenuContent
|
||||
forceMount
|
||||
inert={visibleDropdown !== menuItem.title}
|
||||
data-active={visibleDropdown === menuItem.title}
|
||||
data-side={getDropdownSide(menuItem.title)}
|
||||
className={cn(
|
||||
'md:w-full data-[motion^=from-]:animate-none! data-[motion^=to-]:animate-none!',
|
||||
'data-[active=false]:pointer-events-none data-[active=false]:opacity-0',
|
||||
'data-[side=start]:-translate-x-4 data-[side=end]:translate-x-4',
|
||||
'motion-safe:group-data-[switching=true]/viewport:transition-[opacity,translate]',
|
||||
'motion-safe:group-data-[switching=true]/viewport:duration-250',
|
||||
'motion-safe:group-data-[switching=true]/viewport:ease-in-out'
|
||||
)}
|
||||
>
|
||||
{openedDropdowns.includes(menuItem.title) ? menuItem.dropdown : null}
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
) : (
|
||||
<NavigationMenuItem className="text-sm font-medium" key={menuItem.title}>
|
||||
@@ -139,7 +180,7 @@ const Nav = ({ hideNavbar, stickyNavbar = true }: Props) => {
|
||||
<MenuItem
|
||||
href={menuItem.url}
|
||||
title={menuItem.title}
|
||||
className="group-hover:bg-transparent text-foreground focus-visible:text-brand-link"
|
||||
className="group-hover:bg-transparent text-foreground focus-visible:text-brand-link px-2.5 h-full"
|
||||
hoverColor="brand"
|
||||
/>
|
||||
</NavigationMenuLink>
|
||||
@@ -147,6 +188,22 @@ const Nav = ({ hideNavbar, stickyNavbar = true }: Props) => {
|
||||
)
|
||||
)}
|
||||
</NavigationMenuList>
|
||||
<NavigationMenuViewport
|
||||
forceMount
|
||||
data-open={activeDropdown !== ''}
|
||||
data-switching={isSwitchingDropdown}
|
||||
containerProps={{ className: 'inset-x-0' }}
|
||||
className={cn(
|
||||
'group/viewport origin-top scale-100 rounded-xl bg-surface-75 md:w-[960px]',
|
||||
'data-[state=open]:animate-none! data-[state=closed]:animate-none!',
|
||||
'data-[state=open]:duration-200 data-[state=open]:ease-out data-[state=closed]:duration-200',
|
||||
'data-[open=false]:invisible data-[open=false]:scale-[0.97] data-[open=false]:opacity-0',
|
||||
'data-[open=false]:pointer-events-none',
|
||||
'motion-safe:transition-[opacity,scale,visibility]',
|
||||
'motion-safe:data-[switching=true]:transition-[height,opacity,scale,visibility]',
|
||||
'motion-reduce:transition-none'
|
||||
)}
|
||||
/>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 opacity-0 animate-fade-in scale-100! delay-300">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'radix-ui'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'radix-ui'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '../../../lib/utils/cn'
|
||||
|
||||
Reference in new issue
Block a user