Notch-two

Notch animation. Vercel's style

Installation

Run the following command

It will create a new file notch-two.tsx inside the components/cards/notch-two.tsx directory.

mkdir -p components/cards && touch components/cards/notch-two.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
  Layout,
  MessageCircle,
  X,
  Menu,
  PackageOpen,
  Archive,
  Target,
} from "lucide-react";
 
const items = [
  {
    name: "Developer tools",
    items: [
      {
        name: "Layout shifts",
        icon: Layout,
      },
      {
        name: "Interaction Timing",
        icon: Layout,
      },
      {
        name: "Accessibility",
        icon: Layout,
      },
      {
        name: "Bundle size",
        icon: Layout,
      },
      {
        name: "Open graph",
        icon: Layout,
      },
    ],
  },
  {
    name: "Toolbar",
    items: [
      {
        name: "Comment",
        icon: MessageCircle,
      },
      {
        name: "Feature flags",
        icon: MessageCircle,
      },
      {
        name: "Draft mode",
        icon: MessageCircle,
      },
    ],
  },
];
 
const Icons = [
  {
    name: "Icons 1",
    icon: MessageCircle,
  },
  {
    name: "Icons 2",
    icon: PackageOpen,
  },
  {
    name: "Icons 3",
    icon: Archive,
  },
  {
    name: "Icons 4",
    icon: Target,
  },
  {
    name: "Icons 5",
    icon: Menu,
  },
];
 
const NotchTwo = () => {
  const [isExpanded, setIsExpanded] = useState(false);
 
  const containerVariants = {
    collapsed: { height: 60, width: 300 },
    expanded: { height: "100%", width: 600 },
  };
 
  const contentVariants = {
    collapsed: { opacity: 0 },
    expanded: { opacity: 1, transition: { delay: 0.3 } },
  };
 
  const iconsContainerVariants = {
    collapsed: { y: 0 },
    expanded: { y: "100%", transition: { delay: 0.2 } },
  };
 
  const iconVariants = {
    collapsed: (i: number) => ({
      opacity: 1,
      y: 0,
      transition: { delay: i * 0.05 },
    }),
    expanded: (i: number) => ({
      opacity: 0,
      y: 20,
      transition: { delay: i * 0.05 },
    }),
  };
 
  return (
    <div className="h-full center w-full">
      <div className="h-3/4 w-3/4 flex items-end justify-center">
        <motion.div
          initial="collapsed"
          animate={isExpanded ? "expanded" : "collapsed"}
          variants={containerVariants}
          transition={{ duration: 0.5, ease: "easeInOut" }}
          className=" rounded-[30px] overflow-hidden border bg-black dark:bg-white relative"
        >
          <AnimatePresence>
            {isExpanded && (
              <motion.div
                key="content"
                initial="collapsed"
                animate="expanded"
                exit="collapsed"
                variants={contentVariants}
                className="text-white mt-6 px-10 flex flex-col gap-8"
              >
                <motion.div className="flex items-center justify-between">
                  <div className="rounded border border-muted-foreground/80 w-fit bg-neutral-700/80 px-2 text-base text-neutral-400">
                    Vercel toolbar
                  </div>
                  <div
                    onClick={() => setIsExpanded(false)}
                    className="border-muted-foreground/80 text-neutral-400 cursor-pointer
                   hover:bg-neutral-700/80 transition-all duration-300 border center h-10 w-10 rounded-full"
                  >
                    <X className="h-4 w-4" />
                  </div>
                </motion.div>
                <div className="h-14 border-b border-muted-foreground/80">
                  <input
                    type="text"
                    className="h-full w-full bg-transparent outline-none text-xl text-muted-foreground placeholder:text-muted-foreground"
                    placeholder="What do you need?"
                  />
                </div>
                {items.map((item, index) => (
                  <div key={index} className="flex flex-col gap-2">
                    <p className="text-muted-foreground">{item.name}</p>
                    <motion.ul className="flex flex-col gap-2">
                      {item.items.map((item) => (
                        <li
                          className="flex items-center gap-2 text-xl text-primary-foreground "
                          key={item.name}
                        >
                          <item.icon className="h-5 w-5" />
                          <span>{item.name}</span>
                        </li>
                      ))}
                    </motion.ul>
                  </div>
                ))}
              </motion.div>
            )}
          </AnimatePresence>
 
          <motion.div
            variants={iconsContainerVariants}
            className="h-[60px] flex items-center w-fit gap-2 justify-between px-10 cursor-pointer absolute bottom-0 left-0 right-0 mx-auto"
            onClick={() => setIsExpanded(true)}
          >
            {Icons.map((item, index) => {
              // some code here
              const isLast = Icons[Icons.length - 1];
 
              return (
                <motion.div
                  key={index}
                  custom={index}
                  variants={iconVariants}
                  className="h-10 w-10 center"
                >
                  <item.icon className={`h-5 w-5 text-primary-foreground`} />
                </motion.div>
              );
            })}
          </motion.div>
        </motion.div>
      </div>
    </div>
  );
};
 
export default NotchTwo;

Credits

Built by Bossadi Zenith