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.
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.
Follow these simple steps to add the Animated Group component to your project:
Install Dependencies
pnpm add motion
Create a new file: components/motions/animated-group.tsx
and copy the code below:
"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 };
Adjust the import paths in both files according to your project's structure.