89 lines
2.0 KiB
TypeScript
89 lines
2.0 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { type HTMLMotionProps, motion } from 'motion/react';
|
|
|
|
import { cn } from '@workspace/ui/lib/utils';
|
|
|
|
type FrameDot = [number, number];
|
|
type Frame = FrameDot[];
|
|
type Frames = Frame[];
|
|
|
|
type MotionGridProps = {
|
|
gridSize: [number, number];
|
|
frames: Frames;
|
|
duration?: number;
|
|
animate?: boolean;
|
|
cellClassName?: string;
|
|
cellProps?: HTMLMotionProps<'div'>;
|
|
cellActiveClassName?: string;
|
|
cellInactiveClassName?: string;
|
|
} & React.ComponentProps<'div'>;
|
|
|
|
const MotionGrid = ({
|
|
gridSize,
|
|
frames,
|
|
duration = 200,
|
|
animate = true,
|
|
cellClassName,
|
|
cellProps,
|
|
cellActiveClassName,
|
|
cellInactiveClassName,
|
|
className,
|
|
style,
|
|
...props
|
|
}: MotionGridProps) => {
|
|
const [index, setIndex] = React.useState(0);
|
|
const intervalRef = React.useRef<NodeJS.Timeout | null>(null);
|
|
|
|
React.useEffect(() => {
|
|
if (!animate || frames.length === 0) return;
|
|
intervalRef.current = setInterval(
|
|
() => setIndex((i) => (i + 1) % frames.length),
|
|
duration,
|
|
);
|
|
return () => clearInterval(intervalRef.current!);
|
|
}, [frames.length, duration, animate]);
|
|
|
|
const [cols, rows] = gridSize;
|
|
|
|
const active = new Set<number>(
|
|
frames[index]?.map(([x, y]) => y * cols + x) ?? [],
|
|
);
|
|
|
|
return (
|
|
<div
|
|
className={cn('grid w-fit gap-0.5', className)}
|
|
style={{
|
|
...style,
|
|
gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
|
|
gridAutoRows: '1fr',
|
|
}}
|
|
{...props}
|
|
>
|
|
{Array.from({ length: cols * rows }).map((_, i) => (
|
|
<motion.div
|
|
key={i}
|
|
className={cn(
|
|
'size-3 rounded-full aspect-square',
|
|
active.has(i)
|
|
? cn('bg-primary scale-110', cellActiveClassName)
|
|
: cn('bg-muted scale-100', cellInactiveClassName),
|
|
cellClassName,
|
|
)}
|
|
{...cellProps}
|
|
transition={{ duration, ease: 'easeInOut' }}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export {
|
|
MotionGrid,
|
|
type MotionGridProps,
|
|
type FrameDot,
|
|
type Frame,
|
|
type Frames,
|
|
};
|