Animated Menubar

Animated and cool Menubar effect with animated dropdown

Introduction

A typewriter effect component for displaying animated text.

Demo

Open in

Menu

Menu

Close

Close

Installation

To install the component, you can use npm or yarn. Run one of the following commands in your project directory:

npx shadcn@latest add https://ahs-lab.vercel.app/r/AnimatedMenubar.json

Code

"use client";
import React, { useState } from "react";
import {
  AnimatePresence,
  motion,
  TargetAndTransition,
} from "motion/react";
import Link from "next/link";

const NavLinks = [
  {
    title: "Home",
    href: "/",
  },
  {
    title: "Projects",
    href: "/",
  },
  {
    title: "Docs",
    href: "/",
  },
  {
    title: "Blogs",
    href: "/",
  },
];

const NavFooterLinks = [
  {
    title: "Facebook",
    href: "/",
  },
  {
    title: "Twitter",
    href: "/",
  },
  {
    title: "Instragram",
    href: "/",
  },
  {
    title: "Github",
    href: "/",
  },
];

const Header = () => {
  const [isActive, setIsActive] = useState<boolean>(false);
  return (
    <header className="fixed top-[50px] right-[50px]">
      <Menu isActive={isActive} />
      <div
        onClick={() => setIsActive(!isActive)}
        className="h-[40px] w-[100px] rounded-[25px] cursor-pointer absolute top-0 right-0 uppercase font-medium overflow-hidden"
      >
        <motion.div
          className=" h-full w-full absolute top-0 left-0 "
          animate={{
            top: isActive ? "-100%" : "0",
          }}
          transition={{
            duration: 0.5,
            ease: [0.76, 0, 0.24, 1],
          }}
        >
          <div className="h-full w-full bg-[#C9FD74] flex items-center justify-center text-black">
            <ParspectiveText label="Menu" />
          </div>
          <div className="h-full w-full flex items-center justify-center absolute top-[100%] bg-black text-white">
            <ParspectiveText label="Close" />
          </div>
        </motion.div>
      </div>
    </header>
  );
};

export default Header;

const ParspectiveText = ({ label }: { label: string }) => {
  return (
    <div className="w-full h-full justify-center items-center flex transition-all duration-700 ease-in-out hover:rotate-x-[90deg] transform-3d group ">
      <p className="group-hover:translate-y-[-100%] transition-all duration-700 ease-in-out hover:rotate-x-[90deg] transform-3d group-hover:opacity-0">
        {label}
      </p>
      <p className="absolute rotate-x-[-90deg] group-hover:opacity-100">
        {label}
      </p>
    </div>
  );
};

const Menu = ({ isActive }: { isActive: boolean }) => {
  const variants = {
    open: {
      width: 480,
      height: 650,
      top: "-25px",
      right: "-25px",
    },
    closed: {
      width: 100,
      height: 40,
      top: "0px",
      right: "0px",
      transition: { delay: 0.35 },
    },
  };
  return (
    <motion.div
      variants={variants}
      animate={isActive ? "open" : "closed"}
      transition={{
        duration: 0.75,
        ease: [0.76, 0, 0.24, 1],
      }}
      className="w-[480px] h-[650px] bg-[#C9FD74] rounded-[25px] relative text-black"
    >
      <AnimatePresence>{!!isActive && <Nav />}</AnimatePresence>
    </motion.div>
  );
};

type DynamicVariants = {
  [key: string]: TargetAndTransition | ((idx: number) => TargetAndTransition);
};

const Parspective: DynamicVariants = {
  initial: {
    opacity: 0,
    rotateX: 90,
    translateY: 80,
    translateX: -90,
  },
  enter: (idx: number) => ({
    opacity: 1,
    transition: {
      delay: 0.5 + idx * 0.1,
      duration: 0.65,
      opacity: {
        duration: 0.35,
        delay: 0.7,
      },
      ease: [0.215, 0.61, 0.355, 1],
    },
    rotateX: 0,
    translateY: 0,
    translateX: 0,
  }),
  exit: {
    opacity: 0,
    transition: {
      duration: 0.5,
      ease: [0.76, 0, 0.24, 1],
    },
  },
};

const Slide: DynamicVariants = {
  initial: {
    opacity: 0,
    y: 20,
  },
  enter: (idx: number) => ({
    opacity: 1,
    y: 0,
    transition: {
      delay: 0.5 + idx * 0.1,
      duration: 0.75,
      ease: [0.215, 0.61, 0.355, 1],
    },
  }),
  exit: {
    opacity: 0,
    transition: {
      duration: 0.5,
      ease: [0.76, 0, 0.24, 1],
    },
  },
};
const Nav = () => {
  return (
    <nav className="h-full p-[100px] px-[40px] pb-[50px] box-border flex flex-col justify-between">
      <div>
        {NavLinks.map((itm, idx) => (
          <div
            key={idx}
            className="perspective-[120px] perspective-origin-bottom"
          >
            <motion.div
              custom={idx}
              variants={Parspective}
              animate="enter"
              exit="exit"
              initial="initial"
              className="flex flex-col gap-[1px]"
            >
              <Link
                href={itm.href}
                className="text-black text-[46px] font-semibold"
              >
                {itm.title}
              </Link>
            </motion.div>
          </div>
        ))}
      </div>
      <div>
        {NavFooterLinks.map((itm, idx) => (
          <motion.div
            key={idx}
            custom={idx}
            variants={Slide}
            animate="enter"
            exit="exit"
            initial="initial"
            className="flex justify-between items-center"
          >
            <Link href={itm.href} className="text-xl font-medium">
              {itm.title}
            </Link>
          </motion.div>
        ))}
      </div>
    </nav>
  );
};