Component

Curved Navbar

The Curved Navigation Menu is an elegantly designed user interface component built with React.js, TailwindCSS, and Framer Motion. This menu provides a smooth and interactive way to navigate through different sections of a website, enhancing the user experience with modern animations and a sleek curved design

Features

  • Responsive Design : Tailored to work seamlessly across various devices and screen sizes, ensuring an optimal user experience on mobile, tablet, and desktop.
  • Smooth Animations : Utilizes Framer Motion to deliver fluid and engaging animations, enhancing the visual appeal and interaction quality.
  • Interactive Hover Effects : Dynamic hover effects that highlight menu items and provide visual feedback to users, improving navigation clarity.
  • Customizable Styles : Built with TailwindCSS, offering extensive customization options to fit different design needs and themes.
  • User-Friendly : Intuitive navigation structure that makes it easy for users to explore the website's content.
LOGO
Installation

01.Install Dependencies

npm install motion react-icons
Source Code
'use client'
import { useState } from 'react';
import { usePathname } from 'next/navigation';
import { motion } from 'motion/react';
import { IoClose } from "react-icons/io5";
import Link from 'next/link';

const navItems = [
  {
    title: "Home",
    href: "/",
  },
  {
    title: "Work",
    href: "/work",
  },
  {
    title: "About",
    href: "/about",
  },
  {
    title: "Contact",
    href: "/contact",
  },
]

const menuSlide = {
  initial: {x: "calc(100% + 100px)"},
  enter: {x: "0", transition: {duration: 0.8, ease: [0.76, 0, 0.24, 1]}},
  exit: {x: "calc(100% + 100px)", transition: {duration: 0.8, ease: [0.76, 0, 0.24, 1]}}
}

const slide = {
  initial: {x: 80},
  enter: i => ({x: 0, transition: {duration: 0.8, ease: [0.76, 0, 0.24, 1], delay: 0.05 * i}}),
  exit: i => ({x: 80, transition: {duration: 0.8, ease: [0.76, 0, 0.24, 1], delay: 0.05 * i}})
}

const scale = {
  open: {scale: 1, transition: {duration: 0.3}},
  closed: {scale: 0, transition: {duration: 0.4}}
}

function Curve() {
  const initialPath = `M100 0 L200 0 L200 ${window.innerHeight} L100 ${window.innerHeight} Q-100 ${window.innerHeight/2} 100 0`
  const targetPath = `M100 0 L200 0 L200 ${window.innerHeight} L100 ${window.innerHeight} Q100 ${window.innerHeight/2} 100 0`
  
  const curve = {
    initial: {
      d: initialPath
    },
    enter: {
      d: targetPath,
      transition: {duration: 1, ease: [0.76, 0, 0.24, 1]}
    },
    exit: {
      d: initialPath,
      transition: {duration: 0.8, ease: [0.76, 0, 0.24, 1]}
    }
  }

  return (
    <svg className={`absolute top-0 -left-[99px] w-[100px] stroke-none h-full fill-[rgb(255,224,69)]`}>
      <motion.path variants={curve} initial="initial" animate="enter" exit="exit"></motion.path>
    </svg>
  )
}

function Footer() {
  return (
    <div className={`flex w-full text-sm justify-between`}>
      <a>Link 1</a>
      <a>Link 2</a>
      <a>Link 3</a>
      <a>Link 4</a>
    </div>
  )
}

function NavLink({data, isActive, setSelectedIndicator}) {
  const { title, href, index} = data;

  return (
    <motion.div 
      className={`relative flex items-center`} 
      onMouseEnter={() => {setSelectedIndicator(href)}} 
      custom={index} 
      variants={slide} 
      initial="initial" 
      animate="enter" 
      exit="exit"
    >
      <motion.div 
        variants={scale} 
        animate={isActive ? "open" : "closed"} 
        className={`w-2.5 h-2.5 bg-black rounded-full absolute -left-8`}
      ></motion.div>
      <Link href={href} className='uppercase font-black'>{title}</Link>
    </motion.div>
  )
}

export default function CurvedNavbar({isActive, setIsActive}) {
  const pathname = usePathname();
  const [selectedIndicator, setSelectedIndicator] = useState(pathname);

  return (
    <motion.div 
      variants={menuSlide} 
      initial="initial" 
      animate="enter" 
      exit="exit" 
      className={`h-screen w-screen max-w-screen-sm fixed right-0 top-0 text-black bg-[rgb(255,224,69)] z-10`}
    >
      <div className='w-full flex justify-end text-3xl p-4' onClick={() => setIsActive(false)}>
        <IoClose className='text-3xl'/>
      </div>
      <div className={`h-full p-24 flex flex-col justify-between`}>
        <div onMouseLeave={() => {setSelectedIndicator(pathname)}} className={`flex flex-col text-5xl gap-3 mt-20`}>
          <div className={`text-gray-900 border-b border-gray-800 uppercase text-sm mb-10`}>
            <p>Brand Logo</p>
          </div>
          {
            navItems.map((data, index) => {
              return <NavLink key={index} data={{...data, index}} isActive={selectedIndicator == data.href} setSelectedIndicator={setSelectedIndicator}></NavLink>
            })
          }
        </div>
        <Footer />
      </div>
      <Curve />
    </motion.div>
  )
}
sparkui