Docs

Animated List

A list that animates each item in sequence with a delay. Used to showcase notifications or events on your landing page.

Installation

Install dependencies

npm install framer-motion lucide-react

Run the following command

It will create a new file animated-list.tsx inside the components/mage-ui/list directory.

mkdir -p components/mage-ui/list && touch components/mage-ui/list/animated-list.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 React, {
  ComponentPropsWithoutRef,
  useEffect,
  useMemo,
  useState,
} from "react";
 
// Animated List Item Component
export function AnimatedListItem({ children }: { children: React.ReactNode }) {
  const animations = {
    initial: { scale: 0, opacity: 0 },
    animate: { scale: 1, opacity: 1, originY: 0 },
    exit: { scale: 0, opacity: 0 },
    transition: { type: "spring", stiffness: 350, damping: 40 },
  };
 
  return (
    <motion.div {...animations} layout className="mx-auto w-full">
      {children}
    </motion.div>
  );
}
 
// Animated List Component
export interface AnimatedListProps extends ComponentPropsWithoutRef<"div"> {
  children: React.ReactNode;
  delay?: number;
}
 
export const AnimatedList = React.memo(
  ({ children, className, delay = 1000, ...props }: AnimatedListProps) => {
    const [index, setIndex] = useState(0);
    const childrenArray = useMemo(
      () => React.Children.toArray(children),
      [children],
    );
 
    useEffect(() => {
      if (index < childrenArray.length - 1) {
        const timeout = setTimeout(() => {
          setIndex((prevIndex) => (prevIndex + 1) % childrenArray.length);
        }, delay);
 
        return () => clearTimeout(timeout);
      }
    }, [index, delay, childrenArray.length]);
 
    const itemsToShow = useMemo(() => {
      return childrenArray.slice(0, index + 1).reverse();
    }, [index, childrenArray]);
 
    return (
      <div
        className={cn(`flex flex-col justify-center items-center gap-4`, className)}
        {...props}
      >
        <AnimatePresence>
          {itemsToShow.map((item) => (
            <AnimatedListItem key={(item as React.ReactElement).key}>
              {item}
            </AnimatedListItem>
          ))}
        </AnimatePresence>
      </div>
    );
  },
);
 
AnimatedList.displayName = "AnimatedList";
 
// Notification Component
interface Item {
  name: string;
  description: string;
  icon: string;
  color: string;
  time: string;
}
 
const notifications: Item[] = [
  { name: "Payment received", description: "Mage UI", time: "15m ago", icon: "💸", color: "#00C9A7" },
  { name: "User signed up", description: "Mage UI", time: "10m ago", icon: "👤", color: "#FFB800" },
  { name: "New message", description: "Mage UI", time: "5m ago", icon: "💬", color: "#FF3D71" },
  { name: "New event", description: "Mage UI", time: "2m ago", icon: "🗞️", color: "#1E86FF" },
].flat();
 
const Notification = ({ name, description, icon, color, time }: Item) => {
  return (
    <figure className={cn(
      "relative mx-auto w-full max-w-[400px] cursor-pointer overflow-hidden rounded-2xl p-4",
      "transition-all duration-200 ease-in-out hover:scale-[103%]",
      "bg-white shadow-md dark:bg-gray-800 dark:border dark:border-gray-700"
    )}>
      <div className="flex items-center gap-3">
        <div className="flex size-10 items-center justify-center rounded-2xl" style={{ backgroundColor: color }}>
          <span className="text-lg">{icon}</span>
        </div>
        <div className="flex flex-col overflow-hidden">
          <figcaption className="flex items-center text-lg font-medium dark:text-white">
            <span className="text-sm sm:text-lg">{name}</span>
            <span className="mx-1">·</span>
            <span className="text-xs text-gray-500">{time}</span>
          </figcaption>
          <p className="text-sm font-normal dark:text-white/60">{description}</p>
        </div>
      </div>
    </figure>
  );
};
 
// Animated List Demo Page Component
export default function AnimatedListDemo({ className }: { className?: string }) {
  return (
    <div className={cn("relative flex h-[500px] w-full flex-col overflow-hidden p-2", className)}>
      <AnimatedList>
        {notifications.map((item, idx) => (
          <Notification {...item} key={idx} />
        ))}
      </AnimatedList>
      <div className="pointer-events-none absolute inset-x-0 bottom-0 h-1/4 bg-gradient-to-t from-background"></div>
    </div>
  );
}

Props

Animated List

PropTypeDefaultDescription
classNamestring-The class name for the component
delaynumber1000The delay between each item in ms