Spaces:
Running
Running
deepsite
/
Website-Generation-Request (1)
/src
/components
/blocks
/navbars
/navbar-with-children.tsx
"use client"; | |
import { cn } from "@/lib/utils"; | |
import { IconChevronDown, IconMenu2, IconX } from "@tabler/icons-react"; | |
import { motion, AnimatePresence } from "motion/react"; | |
import Image from "next/image"; | |
import Link from "next/link"; | |
import React, { useState } from "react"; | |
export function NavbarWithChildren() { | |
return <Navbar />; | |
} | |
const Navbar = () => { | |
const navItems = [ | |
{ | |
name: "Services", | |
link: "#", | |
children: [ | |
{ name: "Web Development", link: "#" }, | |
{ name: "Interface Design", link: "#" }, | |
{ name: "Search Engine Optimization", link: "#" }, | |
{ name: "Branding", link: "#" }, | |
], | |
}, | |
{ | |
name: "Products", | |
link: "#", | |
children: [ | |
{ name: "Algochurn", link: "#" }, | |
{ name: "Tailwind Master Kit", link: "#" }, | |
], | |
}, | |
{ | |
name: "Pricing", | |
link: "#", | |
children: [ | |
{ name: "Hobby", link: "#" }, | |
{ name: "Individual", link: "#" }, | |
{ name: "Team", link: "#" }, | |
], | |
}, | |
]; | |
return ( | |
<div className="w-full"> | |
<DesktopNav navItems={navItems} /> | |
<MobileNav navItems={navItems} /> | |
</div> | |
); | |
}; | |
const DesktopNav = ({ navItems }: any) => { | |
const [active, setActive] = useState<string | null>(null); | |
return ( | |
<motion.div | |
className={cn( | |
"relative z-[60] mx-auto hidden w-full max-w-7xl flex-row items-center justify-between self-start rounded-full bg-white px-4 py-2 lg:flex dark:bg-neutral-950", | |
"sticky inset-x-0 top-40" | |
)} | |
> | |
<Logo /> | |
<div className="hidden flex-1 flex-row items-center justify-center space-x-2 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800 lg:flex lg:space-x-2"> | |
<Menu setActive={setActive}> | |
<MenuItem setActive={setActive} active={active} item="Services"> | |
<div className="flex flex-col space-y-4 text-sm"> | |
<HoveredLink href="/#">Web Development</HoveredLink> | |
<HoveredLink href="/#">Interface Design</HoveredLink> | |
<HoveredLink href="/seo">Search Engine Optimization</HoveredLink> | |
<HoveredLink href="/branding">Branding</HoveredLink> | |
</div> | |
</MenuItem> | |
<MenuItem setActive={setActive} active={active} item="Products"> | |
<div className="grid grid-cols-2 gap-10 p-4 text-sm"> | |
<ProductItem | |
title="Algochurn" | |
href="https://algochurn.com" | |
src="https://assets.aceternity.com/demos/algochurn.webp" | |
description="Prepare for tech interviews like never before." | |
/> | |
<ProductItem | |
title="Tailwind Master Kit" | |
href="https://tailwindmasterkit.com" | |
src="https://assets.aceternity.com/demos/tailwindmasterkit.webp" | |
description="Production ready Tailwind css components for your next project" | |
/> | |
</div> | |
</MenuItem> | |
<MenuItem setActive={setActive} active={active} item="Pricing"> | |
<div className="flex flex-col space-y-4 text-sm"> | |
<HoveredLink href="/hobby">Hobby</HoveredLink> | |
<HoveredLink href="/individual">Individual</HoveredLink> | |
<HoveredLink href="/team">Team</HoveredLink> | |
<HoveredLink href="/enterprise">Enterprise</HoveredLink> | |
</div> | |
</MenuItem> | |
</Menu> | |
</div> | |
<button className="hidden rounded-full bg-black px-8 py-2 text-sm font-bold text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] md:block dark:bg-white dark:text-black"> | |
Book a call | |
</button> | |
</motion.div> | |
); | |
}; | |
const MobileNav = ({ navItems }: any) => { | |
const [open, setOpen] = useState(false); | |
return ( | |
<> | |
<motion.div | |
animate={{ borderRadius: open ? "4px" : "2rem" }} | |
key={String(open)} | |
className="relative mx-auto flex w-full max-w-[calc(100vw-2rem)] flex-col items-center justify-between bg-white px-4 py-2 lg:hidden dark:bg-neutral-950" | |
> | |
<div className="flex w-full flex-row items-center justify-between"> | |
<Logo /> | |
{open ? ( | |
<IconX | |
className="text-black dark:text-white" | |
onClick={() => setOpen(!open)} | |
/> | |
) : ( | |
<IconMenu2 | |
className="text-black dark:text-white" | |
onClick={() => setOpen(!open)} | |
/> | |
)} | |
</div> | |
<AnimatePresence> | |
{open && ( | |
<motion.div | |
initial={{ opacity: 0 }} | |
animate={{ opacity: 1 }} | |
exit={{ opacity: 0 }} | |
className="absolute inset-x-0 top-16 z-20 flex w-full flex-col items-start justify-start gap-4 rounded-lg bg-white px-4 py-8 dark:bg-neutral-950" | |
> | |
{navItems.map((navItem: any, idx: number) => ( | |
<div key={`navItem-${idx}`} className="w-full"> | |
{navItem.children ? ( | |
<MobileChildNavItems navItem={navItem} /> | |
) : ( | |
<Link | |
href={navItem.link} | |
className="relative text-neutral-600 dark:text-neutral-300" | |
> | |
<motion.span className="block"> | |
{navItem.name} | |
</motion.span> | |
</Link> | |
)} | |
</div> | |
))} | |
<button className="w-full rounded-lg bg-black px-8 py-2 font-medium text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] dark:bg-white dark:text-black"> | |
Book a call | |
</button> | |
</motion.div> | |
)} | |
</AnimatePresence> | |
</motion.div> | |
</> | |
); | |
}; | |
const MobileChildNavItems = ({ navItem }: { navItem: any }) => { | |
const [open, setOpen] = useState(false); | |
return ( | |
<motion.div className="overflow-hidden"> | |
<button | |
onClick={() => setOpen(!open)} | |
className="relative flex w-full justify-between text-neutral-600 dark:text-neutral-300" | |
> | |
<motion.span className="block">{navItem.name}</motion.span> | |
<IconChevronDown className="text-neutral-700 dark:text-neutral-300" /> | |
</button> | |
<AnimatePresence> | |
{open && ( | |
<motion.div | |
initial={{ height: 0, opacity: 0 }} | |
animate={{ height: "auto", opacity: 1 }} | |
exit={{ height: 0 }} | |
className="pl-4" | |
> | |
{navItem.children.map((child: any, childIdx: number) => ( | |
<Link | |
key={`child-${childIdx}`} | |
href={child.link} | |
className="relative text-neutral-600 dark:text-neutral-300" | |
> | |
<motion.span className="block">{child.name}</motion.span> | |
</Link> | |
))} | |
</motion.div> | |
)} | |
</AnimatePresence> | |
</motion.div> | |
); | |
}; | |
const Logo = () => { | |
return ( | |
<Link | |
href="/" | |
className="relative z-20 mr-4 flex items-center space-x-2 px-2 py-1 text-sm font-normal text-black" | |
> | |
<Image | |
src="https://assets.aceternity.com/logo-dark.png" | |
alt="logo" | |
width={30} | |
height={30} | |
/> | |
<span className="font-medium text-black dark:text-white">DevStudio</span> | |
</Link> | |
); | |
}; | |
const transition = { | |
mass: 0.5, | |
damping: 11.5, | |
stiffness: 100, | |
restDelta: 0.001, | |
restSpeed: 0.001, | |
}; | |
export const MenuItem = ({ | |
setActive, | |
active, | |
item, | |
children, | |
}: { | |
setActive: (item: string) => void; | |
active: string | null; | |
item: string; | |
children?: React.ReactNode; | |
}) => { | |
return ( | |
<div onMouseEnter={() => setActive(item)} className="relative"> | |
<motion.p | |
transition={{ duration: 0.3 }} | |
className="cursor-pointer text-neutral-700 hover:opacity-[0.9] dark:text-neutral-300" | |
> | |
{item} | |
</motion.p> | |
{active !== null && ( | |
<motion.div | |
initial={{ opacity: 0, scale: 0.85, y: 10 }} | |
animate={{ opacity: 1, scale: 1, y: 0 }} | |
transition={transition} | |
> | |
{active === item && ( | |
<div className="absolute left-1/2 top-[calc(100%_+_0.2rem)] -translate-x-1/2 transform pt-4"> | |
<div className=""> | |
<motion.div | |
transition={transition} | |
layoutId="active" // layoutId ensures smooth animation | |
className="mt-4 overflow-hidden rounded-2xl bg-white shadow-xl backdrop-blur-sm dark:bg-neutral-950" | |
> | |
<motion.div | |
layout // layout ensures smooth animation | |
className="h-full w-max p-4" | |
> | |
{children} | |
</motion.div> | |
</motion.div> | |
</div> | |
</div> | |
)} | |
</motion.div> | |
)} | |
</div> | |
); | |
}; | |
export const Menu = ({ | |
setActive, | |
children, | |
}: { | |
setActive: (item: string | null) => void; | |
children: React.ReactNode; | |
}) => { | |
return ( | |
<nav | |
onMouseLeave={() => setActive(null)} // resets the state | |
className="relative flex justify-center space-x-4 rounded-full bg-white px-4 py-3 dark:bg-neutral-950" | |
> | |
{children} | |
</nav> | |
); | |
}; | |
export const ProductItem = ({ | |
title, | |
description, | |
href, | |
src, | |
}: { | |
title: string; | |
description: string; | |
href: string; | |
src: string; | |
}) => { | |
return ( | |
<Link href={href} className="flex gap-4"> | |
<Image | |
src={src} | |
width={140} | |
height={70} | |
alt={title} | |
className="flex-shrink-0 rounded-md shadow-2xl" | |
/> | |
<div> | |
<h4 className="mb-1 text-base font-normal text-black dark:text-white"> | |
{title} | |
</h4> | |
<p className="max-w-[10rem] text-sm text-neutral-700 dark:text-neutral-300"> | |
{description} | |
</p> | |
</div> | |
</Link> | |
); | |
}; | |
export const HoveredLink = ({ children, ...rest }: any) => { | |
return ( | |
<Link | |
{...rest} | |
className="text-neutral-700 hover:text-black dark:text-neutral-200" | |
> | |
{children} | |
</Link> | |
); | |
}; | |