Docs

Sticky Banner

A banner component that sticks to top, hides when user scrolls down

Installation

Install dependencies

npm install framer-motion lucide-react

Run the following command

It will create a new file sticky-banner.tsx inside the components/mage-ui/accordion directory.

mkdir -p components/mage-ui/accordion && touch components/mage-ui/accordion/sticky-banner.tsx

Paste the code

Open the newly created file and paste the following code:

import React, { SVGProps, useState } from "react";
import { motion, useMotionValueEvent, useScroll } from "framer-motion";
 
// Utility function to merge class names
const cn = (...classes: (string | undefined | null | false)[]) => {
  return classes.filter(Boolean).join(' ');
};
 
// StickyBanner Component
const StickyBanner = ({
  className,
  children,
  hideOnScroll = false,
}: {
  className?: string;
  children: React.ReactNode;
  hideOnScroll?: boolean;
}) => {
  const [open, setOpen] = useState(true);
  const { scrollY } = useScroll();
 
  useMotionValueEvent(scrollY, "change", (latest) => {
    console.log(latest);
    if (hideOnScroll && latest > 40) {
      setOpen(false);
    } else {
      setOpen(true);
    }
  });
 
  return (
    <motion.div
      className={cn(
        "sticky inset-x-0 top-0 z-40 flex h-screen w-screen items-center justify-center bg-transparent px-4 py-1",
        className,
      )}
      initial={{
        y: -100,
        opacity: 0,
      }}
      animate={{
        y: open ? 0 : -100,
        opacity: open ? 1 : 0,
      }}
      transition={{
        duration: 0.3,
        ease: "easeInOut",
      }}
    >
      {children}
      <motion.button
        initial={{
          scale: 0,
        }}
        animate={{
          scale: 1,
        }}
        className="absolute top-1/2 right-2 -translate-y-1/2 cursor-pointer"
        onClick={() => setOpen(!open)}
      >
        <CloseIcon className="h-5 w-5 text-white" />
      </motion.button>
    </motion.div>
  );
};
 
// Close Icon Component
const CloseIcon = (props: SVGProps<SVGSVGElement>) => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path stroke="none" d="M0 0h24v24H0z" fill="none" />
      <path d="M18 6l-12 12" />
      <path d="M6 6l12 12" />
    </svg>
  );
};
 
// Dummy Content Component
const DummyContent = () => {
  return (
    <div className="relative mx-auto flex w-full max-w-7xl flex-col gap-10 py-8 px-4">
      <div className="h-96 w-full animate-pulse rounded-lg bg-gray-200" />
      <div className="h-96 w-full animate-pulse rounded-lg bg-gray-200" />
      <div className="h-96 w-full animate-pulse rounded-lg bg-gray-200" />
      <div className="h-96 w-full animate-pulse rounded-lg bg-gray-200" />
      <div className="h-96 w-full animate-pulse rounded-lg bg-gray-200" />
    </div>
  );
};
 
// Main Demo Component
const StickyBannerDemo = () => {
  return (
    <div className="relative flex h-screen w-full flex-col overflow-y-auto">
      <StickyBanner className="bg-gradient-to-b from-blue-500 to-blue-600">
        <p className="mx-0 max-w-[90%] text-white drop-shadow-md text-center">
          Announcing $10M seed funding from project mayhem ventures.{" "}
          <a href="#" className="transition duration-200 hover:underline font-semibold">
            Read announcement
          </a>
        </p>
      </StickyBanner>
      <DummyContent />
    </div>
  );
};
 
export default StickyBannerDemo;