UI Elements

Morphing Button

A button that morphs into a floating circular chat button once the page is scrolled past a threshold, using shared layout animation.

morphing-button.tsx
"use client";

import { MessageCircle } from "lucide-react";
import {
  AnimatePresence,
  motion,
  useMotionValueEvent,
  useScroll,
} from "motion/react";
import { useState } from "react";
import { buttonVariants } from "@/components/ui/button";

const BtnChange = () => {
  const [isHidden, setIsHidden] = useState(false);
  const { scrollY } = useScroll();

  useMotionValueEvent(scrollY, "change", (y) => {
    if (y > 500) {
      setIsHidden(true);
    } else {
      setIsHidden(false);
    }
  });

  return (
    <div className="relative size-full overflow-hidden">
      <div className="size-full bg-background flex items-center justify-center">
        <motion.button
          layoutId="btn-change"
          layout="size"
          className={buttonVariants({ size: "lg" })}
        >
          Nothing
        </motion.button>
      </div>
      <ChatButton isHidden={isHidden} />
    </div>
  );
};

const ChatButton = ({ isHidden }: { isHidden: boolean }) => {
  return (
    <AnimatePresence mode="sync">
      {isHidden && (
        <motion.div
          layout="preserve-aspect"
          layoutId="ChatButton"
          animate={{
            width: 50,
            height: 50,
            opacity: isHidden ? 1 : 0,
          }}
          className="rounded-full bg-primary absolute items-center flex justify-center bottom-10 right-10 text-white cursor-pointer"
        >
          <MessageCircle className="h-4 w-4" />
        </motion.div>
      )}
    </AnimatePresence>
  );
};

export default BtnChange;