A animated-group component from Motion Primitives.
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<PresetType, Variants> = {
44 fade: {},
45 slide: {
46 hidden: { y: 20 },
47 visible: { y: 0 },
48 },
49 scale: {
50 hidden: { scale: 0.8 },
51 visible: { scale: 1 },
52 },
53 blur: {
54 hidden: { filter: 'blur(4px)' },
55 visible: { filter: 'blur(0px)' },
56 },
57 'blur-slide': {
58 hidden: { filter: 'blur(4px)', y: 20 },
59 visible: { filter: 'blur(0px)', y: 0 },
60 },
61 zoom: {
62 hidden: { scale: 0.5 },
63 visible: {
64 scale: 1,
65 transition: { type: 'spring', stiffness: 300, damping: 20 },
66 },
67 },
68 flip: {
69 hidden: { rotateX: -90 },
70 visible: {
71 rotateX: 0,
72 transition: { type: 'spring', stiffness: 300, damping: 20 },
73 },
74 },
75 bounce: {
76 hidden: { y: -50 },
77 visible: {
78 y: 0,
79 transition: { type: 'spring', stiffness: 400, damping: 10 },
80 },
81 },
82 rotate: {
83 hidden: { rotate: -180 },
84 visible: {
85 rotate: 0,
86 transition: { type: 'spring', stiffness: 200, damping: 15 },
87 },
88 },
89 swing: {
90 hidden: { rotate: -10 },
91 visible: {
92 rotate: 0,
93 transition: { type: 'spring', stiffness: 300, damping: 8 },
94 },
95 },
96};
97
98const addDefaultVariants = (variants: Variants) => ({
99 hidden: { ...defaultItemVariants.hidden, ...variants.hidden },
100 visible: { ...defaultItemVariants.visible, ...variants.visible },
101});
102
103function AnimatedGroup({
104 children,
105 className,
106 variants,
107 preset,
108 as = 'div',
109 asChild = 'div',
110}: AnimatedGroupProps) {
111 const selectedVariants = {
112 item: addDefaultVariants(preset ? presetVariants[preset] : {}),
113 container: addDefaultVariants(defaultContainerVariants),
114 };
115 const containerVariants = variants?.container || selectedVariants.container;
116 const itemVariants = variants?.item || selectedVariants.item;
117
118 const MotionComponent = React.useMemo(
119 () => motion.create(as as keyof JSX.IntrinsicElements),
120 [as]
121 );
122 const MotionChild = React.useMemo(
123 () => motion.create(asChild as keyof JSX.IntrinsicElements),
124 [asChild]
125 );
126
127 return (
128 <MotionComponent
129 initial='hidden'
130 animate='visible'
131 variants={containerVariants}
132 className={className}
133 >
134 {React.Children.map(children, (child, index) => (
135 <MotionChild key={index} variants={itemVariants}>
136 {child}
137 </MotionChild>
138 ))}
139 </MotionComponent>
140 );
141}
142
143export { AnimatedGroup };
144