'use client';
import { forwardRef, HTMLAttributes, ReactNode, Children, cloneElement, isValidElement, useMemo } from 'react';
import styles from './animated-list.module.css';
export type AnimationType = 'fade' | 'slide' | 'scale' | 'blur';
export type Direction = 'up' | 'down' | 'left' | 'right';
export interface AnimatedListProps extends Omit, 'animation'> {
/** List items (should be valid React elements) */
children: ReactNode;
/** Animation type */
animation?: AnimationType;
/** Delay between items in ms */
staggerDelay?: number;
/** Animation duration in ms */
duration?: number;
/** Direction for slide animations */
direction?: Direction;
/** Element type to render */
as?: 'ul' | 'ol' | 'div';
}
const animationClassMap: Record = {
fade: styles.itemFade,
slide: styles.itemSlideUp,
scale: styles.itemScale,
blur: styles.itemBlur,
};
const slideDirectionMap: Record = {
up: styles.itemSlideUp,
down: styles.itemSlideDown,
left: styles.itemSlideLeft,
right: styles.itemSlideRight,
};
export const AnimatedList = forwardRef(
(
{
children,
animation = 'fade',
staggerDelay = 100,
duration = 400,
direction = 'up',
as = 'ul',
className,
style,
...props
},
ref
) => {
const childrenArray = Children.toArray(children);
const itemClass = useMemo(() => {
if (animation === 'slide') {
return slideDirectionMap[direction];
}
return animationClassMap[animation];
}, [animation, direction]);
const containerStyle = useMemo(() => ({
'--anim-duration': `${duration}ms`,
'--anim-delay': `${staggerDelay}ms`,
...style,
}) as React.CSSProperties;
const Element = as;
return (
{childrenArray.map((child, index) => {
if (isValidElement(child)) {
return cloneElement(child, {
key: child.key ?? index,
className: `${styles.item} ${itemClass} ${child.props.className || ''}`,
style: {
'--item-delay': `${index * staggerDelay}ms`,
...child.props.style,
} as React.CSSProperties,
});
}
return (
{child}
);
})}
);
}
);
AnimatedList.displayName = 'AnimatedList';
export default AnimatedList;