A wrapper that adds animated transitions to a group of child elements.
1'use client';
2import { ReactNode } from 'react';
3import { motion, Variants } from 'motion/react';
4import React from 'react';
5
6export type PresetType =
7 | 'fade'
8 | 'slide'
9 | 'scale'
10 | 'blur'
11 | 'blur-slide'
12 | 'zoom'
13 | 'flip'
14 | 'bounce'
15 | 'rotate'
16 | 'swing';
17
18export type AnimatedGroupProps = {
19 children: ReactNode;
20 className?: string;
21 variants?: {
22 container?: Variants;
23 item?: Variants;
24 };
25 preset?: PresetType;
26 as?: React.ElementType;
27 asChild?: React.ElementType;
28};
29
30const defaultContainerVariants: Variants = {
31 visible: {
32 transition: {
33 staggerChildren: 0.1,
34 },
35 },
36};
37
38const defaultItemVariants: Variants = {
39 hidden: { opacity: 0 },
40 visible: { opacity: 1 },
41};
42
43const presetVariants: Record<
44 PresetType,
45 Variants
46> = {
47 fade: {
48 },
49 slide: {
50 hidden: { y: 20 },
51 visible: { y: 0 },
52 },
53 scale: {
54 hidden: { scale: 0.8 },
55 visible: { scale: 1 },
56 },
57 blur: {
58 hidden: { filter: 'blur(4px)' },
59 visible: { filter: 'blur(0px)' },
60 },
61 'blur-slide': {
62 hidden: { filter: 'blur(4px)', y: 20 },
63 visible: { filter: 'blur(0px)', y: 0 },
64 },
65 zoom: {
66 hidden: { scale: 0.5 },
67 visible: {
68 scale: 1,
69 transition: { type: 'spring', stiffness: 300, damping: 20 },
70 },
71 },
72 flip: {
73 hidden: { rotateX: -90 },
74 visible: {
75 rotateX: 0,
76 transition: { type: 'spring', stiffness: 300, damping: 20 },
77 },
78 },
79 bounce: {
80 hidden: { y: -50 },
81 visible: {
82 y: 0,
83 transition: { type: 'spring', stiffness: 400, damping: 10 },
84 },
85 },
86 rotate: {
87 hidden: { rotate: -180 },
88 visible: {
89 rotate: 0,
90 transition: { type: 'spring', stiffness: 200, damping: 15 },
91 },
92 },
93 swing: {
94 hidden: { rotate: -10 },
95 visible: {
96 rotate: 0,
97 transition: { type: 'spring', stiffness: 300, damping: 8 },
98 },
99 },
100};
101
102const addDefaultVariants = (variants: Variants) => ({
103 hidden: { ...defaultItemVariants.hidden, ...variants.hidden },
104 visible: { ...defaultItemVariants.visible, ...variants.visible },
105});
106
107function AnimatedGroup({
108 children,
109 className,
110 variants,
111 preset,
112 as = 'div',
113 asChild = 'div',
114}: AnimatedGroupProps) {
115 const selectedVariants = {
116 item: addDefaultVariants(preset ? presetVariants[preset] : {}),
117 container: addDefaultVariants(defaultContainerVariants)
118 };
119 const containerVariants = variants?.container || selectedVariants.container;
120 const itemVariants = variants?.item || selectedVariants.item;
121
122 const MotionComponent = motion.create(as as keyof JSX.IntrinsicElements);
123
124 const MotionChild = motion.create(asChild as keyof JSX.IntrinsicElements);
125
126 return (
127 <MotionComponent
128 initial='hidden'
129 animate='visible'
130 variants={containerVariants}
131 className={className}
132 >
133 {React.Children.map(children, (child, index) => (
134 <MotionChild key={index} variants={itemVariants}>
135 {child}
136 </MotionChild>
137 ))}
138 </MotionComponent>
139 );
140}
141
142export { AnimatedGroup };
143