Command Palette

Search for a command to run...

Animated Group

A wrapper that adds animated transitions to a group of child elements. It's perfect for creating staggered animations for lists, grids, or any collection of components.

impressionist painting, uploaded to Cosmos
impressionist painting, uploaded to Cosmos
impressionist painting, uploaded to Cosmos

Installation

Install the following dependencies:

pnpm add motion

Create a file at components/motions/animated-group.tsx and paste the given code.

"use client";
import { JSX, ReactNode } from "react";
import { motion, Variants } from "motion/react";
import React from "react";
 
export type PresetType =
    | "fade"
    | "slide"
    | "scale"
    | "blur"
    | "blur-slide"
    | "zoom"
    | "flip"
    | "bounce"
    | "rotate"
    | "swing";
 
export type AnimatedGroupProps = {
    children: ReactNode;
    className?: string;
    variants?: {
        container?: Variants;
        item?: Variants;
    };
    preset?: PresetType;
    as?: React.ElementType;
    asChild?: React.ElementType;
};
 
const defaultContainerVariants: Variants = {
    visible: {
        transition: {
            staggerChildren: 0.1,
        },
    },
};
 
const defaultItemVariants: Variants = {
    hidden: { opacity: 0 },
    visible: { opacity: 1 },
};
 
const presetVariants: Record<PresetType, Variants> = {
    fade: {},
    slide: {
        hidden: { y: 20 },
        visible: { y: 0 },
    },
    scale: {
        hidden: { scale: 0.8 },
        visible: { scale: 1 },
    },
    blur: {
        hidden: { filter: "blur(4px)" },
        visible: { filter: "blur(0px)" },
    },
    "blur-slide": {
        hidden: { filter: "blur(4px)", y: 20 },
        visible: { filter: "blur(0px)", y: 0 },
    },
    zoom: {
        hidden: { scale: 0.5 },
        visible: {
            scale: 1,
            transition: { type: "spring", stiffness: 300, damping: 20 },
        },
    },
    flip: {
        hidden: { rotateX: -90 },
        visible: {
            rotateX: 0,
            transition: { type: "spring", stiffness: 300, damping: 20 },
        },
    },
    bounce: {
        hidden: { y: -50 },
        visible: {
            y: 0,
            transition: { type: "spring", stiffness: 400, damping: 10 },
        },
    },
    rotate: {
        hidden: { rotate: -180 },
        visible: {
            rotate: 0,
            transition: { type: "spring", stiffness: 200, damping: 15 },
        },
    },
    swing: {
        hidden: { rotate: -10 },
        visible: {
            rotate: 0,
            transition: { type: "spring", stiffness: 300, damping: 8 },
        },
    },
};
 
const addDefaultVariants = (variants: Variants) => ({
    hidden: { ...defaultItemVariants.hidden, ...variants.hidden },
    visible: { ...defaultItemVariants.visible, ...variants.visible },
});
 
function AnimatedGroup({
    children,
    className,
    variants,
    preset,
    as = "div",
    asChild = "div",
}: AnimatedGroupProps) {
    const selectedVariants = {
        item: addDefaultVariants(preset ? presetVariants[preset] : {}),
        container: addDefaultVariants(defaultContainerVariants),
    };
    const containerVariants = variants?.container || selectedVariants.container;
    const itemVariants = variants?.item || selectedVariants.item;
 
    const MotionComponent = motion.create(as as keyof JSX.IntrinsicElements);
 
    const MotionChild = motion.create(asChild as keyof JSX.IntrinsicElements);
 
    return (
        <MotionComponent
            initial="hidden"
            animate="visible"
            variants={containerVariants}
            className={className}
        >
            {React.Children.map(children, (child, index) => (
                <MotionChild key={index} variants={itemVariants}>
                    {child}
                </MotionChild>
            ))}
        </MotionComponent>
    );
}
 
export { AnimatedGroup };

Update the import paths to match your project setup.