ComponentsPopover

Popover

Gustavo's image
Terminal
npm i framer-motion @radix-ui/react-hover-card
Popover.tsx
"use client"; // @NOTE: add in case you are using Next.js

import Image from "next/image";

import { useState } from "react";

import { AnimatePresence, motion } from "framer-motion";
import * as RadixPopover from "@radix-ui/react-hover-card";

export function PopoverExample() {
  return (
    <Popover>
      <a
        className="rounded-full"
        href="https://x.com/guhrodrrigues"
        target="_blank"
        rel="noreferrer noopener"
      >
        <Image
          className="rounded-full"
          src="https://github.com/guhrodrrigues.png"
          alt="Gustavo's image"
          width={45}
          height={45}
        />
      </a>
    </Popover>
  );
}

const container = {
  initial: {},
  animate: {
    transition: {
      staggerChildren: 0.05,
      delayChildren: 0.2,
    },
  },
};

const item = {
  initial: {
    opacity: 0,
    y: 16,
    filter: "blur(4px)",
  },
  animate: {
    opacity: 1,
    scale: 1,
    y: 0,
    filter: "blur(0px)",
    transition: {
      type: "spring",
      stiffness: 150,
      damping: 19,
      mass: 1.2,
    },
  },
};

function Popover({ children }: { children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(false);

  const MotionContent = motion(RadixPopover.Content);

  return (
    <RadixPopover.Root
      open={isOpen}
      onOpenChange={setIsOpen}
      openDelay={0}
      closeDelay={0}
    >
      <RadixPopover.Trigger asChild>{children}</RadixPopover.Trigger>
      <RadixPopover.Portal>
        <AnimatePresence mode="wait">
          {isOpen && (
            <MotionContent
              className="z-10 w-full max-w-[300px] rounded-lg border border-neutral-800 bg-neutral-950 p-5"
              initial={{ opacity: 0, scale: 0.9, filter: "blur(4px)" }}
              animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
              exit={{ opacity: 0, scale: 0.9, filter: "blur(4px)" }}
              transition={{
                type: "spring",
                stiffness: 150,
                damping: 19,
                mass: 1.2,
              }}
            >
              <motion.div
                variants={container}
                initial="initial"
                animate="animate"
                exit="initial"
                className="flex flex-col gap-[7px]"
              >
                <motion.div variants={item}>
                  <Image
                    className="rounded-full"
                    src="https://github.com/guhrodrrigues.png"
                    alt="Gustavo's image"
                    width={60}
                    height={60}
                  />
                </motion.div>
                <div className="flex flex-col gap-4">
                  <div>
                    <motion.h1
                      variants={item}
                      className="text-base font-medium text-white"
                    >
                      Gustavo Rodrigues
                    </motion.h1>
                    <motion.p
                      variants={item}
                      className="text-base text-neutral-400"
                    >
                      @guhrodrrigues
                    </motion.p>
                  </div>
                  <motion.span
                    variants={item}
                    className="text-base text-neutral-200"
                  >
                    My desire is to polish products by combining design and
                    code.
                  </motion.span>
                  <motion.div variants={item} className="flex gap-4">
                    <div className="flex gap-1.5">
                      <span className="text-base font-medium text-neutral-300">
                        0
                      </span>{" "}
                      <span className="text-base text-neutral-400">
                        Following
                      </span>
                    </div>
                    <div className="flex gap-1.5">
                      <span className="text-base font-medium text-neutral-300">
                        2,784
                      </span>{" "}
                      <span className="text-base text-neutral-400">
                        Followers
                      </span>
                    </div>
                  </motion.div>
                </div>
              </motion.div>
              <RadixPopover.Arrow className="fill-neutral-800" />
            </MotionContent>
          )}
        </AnimatePresence>
      </RadixPopover.Portal>
    </RadixPopover.Root>
  );
}