Notch

iPhone notch expandable effect

2(bkm)

Bossadi Zenith: I build things that live on the internet
Book a call

Installation

Run the following command

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

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

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import { cn } from "@/lib/utils";
import { AnimatePresence, motion } from "framer-motion";
import Image from "next/image";
import { useState } from "react";
 
const Notch = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [showContent, setShowContent] = useState(false);
 
  const presence = {
    enter: {
      opacity: 0,
      scale: 0.9,
    },
    center: {
      opacity: 1,
      scale: 1,
      transition: {
        staggerChildren: 0.1,
        delayChildren: 0.2,
      },
    },
    exit: {
      opacity: 0,
      scale: 0.9,
      transition: {
        when: "afterChildren",
        staggerChildren: 0.1,
        staggerDirection: -1,
      },
    },
  };
 
  const itemVariants = {
    enter: {
      opacity: 0,
      y: 20,
    },
    center: {
      opacity: 1,
      y: 0,
      transition: {
        duration: 0.3,
      },
    },
    exit: {
      opacity: 0,
      y: 20,
      transition: {
        duration: 0.2,
      },
    },
  };
 
  const handleToggle = () => {
    if (isOpen) {
      setShowContent(false);
    } else {
      setIsOpen(true);
      setTimeout(() => setShowContent(true), 50);
    }
  };
 
  return (
    <div className="h-full center w-full">
      <motion.div
        whileHover={{
          scale: isOpen ? 1 : 0.95,
        }}
        className={cn(
          "bg-primary w-[500px] rounded-full h-16 px-4 cursor-pointer",
          isOpen && "rounded-3xl"
        )}
        animate={{ height: isOpen ? 240 : 64, width: isOpen ? 500 : 300 }}
        transition={{ duration: 0.2 }}
        onClick={handleToggle}
      >
        <div
          className={cn(
            "flex items-center justify-between relative h-16",
            isOpen && "pt-5"
          )}
        >
          <p className="text-xl text-primary-foreground">2(bkm)</p>
          <motion.div
            className="rounded-3xl overflow-hidden relative"
            animate={{
              height: isOpen ? 200 : 40,
              width: isOpen ? 200 : 40,
              y: isOpen ? 75 : 0,
            }}
            transition={{ duration: 0.2 }}
          >
            <Image
              src="/zenith.jpeg"
              alt="Bossadi Zenith: I build things that live on the internet"
              fill
              className={cn(
                "transition-all duration-150 grayscale",
                isOpen && "grayscale-0"
              )}
            />
            <div
              className={cn(
                "absolute opacity-0 bg-black/50 top-0 left-0 flex items-center justify-center text-primary-foreground font-semibold text-2xl h-full w-full transition-all duration-150",
                isOpen && "hover:opacity-100"
              )}
            >
              Book a call
            </div>
          </motion.div>
        </div>
        <AnimatePresence onExitComplete={() => setIsOpen(false)}>
          {showContent && (
            <motion.ul
              initial="enter"
              animate="center"
              exit="exit"
              variants={presence}
              transition={{ duration: 0.2 }}
              className="text-primary-foreground mt-6"
            >
              {["Home", "About", "Contact"].map((item, index) => (
                <motion.li key={item + index} variants={itemVariants}>
                  {item}
                </motion.li>
              ))}
            </motion.ul>
          )}
        </AnimatePresence>
      </motion.div>
    </div>
  );
};
 
export default Notch;

Credits

Built by Bossadi Zenith