'use client'; import * as React from 'react'; import { AnimatePresence, motion, useMotionValue, useSpring, useTransform, useInView, type SpringOptions, type UseInViewOptions, } from 'motion/react'; import { cn } from '@workspace/ui/lib/utils'; import { Star } from '@/registry/icons/star'; const formatter = new Intl.NumberFormat('en-US'); function generateRange( max: number, step: number, sideItemsCount: number, ): number[] { const result: number[] = []; const end = max + sideItemsCount * step; for (let value = end; value >= 0; value -= step) { result.push(value); } return result; } type StarsScrollingWheelProps = { stars: number; step?: number; itemHeight?: number; sideItemsCount?: number; transition?: SpringOptions; inView?: boolean; inViewOnce?: boolean; inViewMargin?: UseInViewOptions['margin']; delay?: number; } & React.ComponentProps<'div'>; function StarsScrollingWheel({ ref, stars, step = 100, itemHeight = 48, sideItemsCount = 2, transition = { stiffness: 90, damping: 30 }, inView = false, inViewOnce = true, inViewMargin = '0px', delay = 0, className, style, ...props }: StarsScrollingWheelProps) { const containerRef = React.useRef(null); React.useImperativeHandle(ref, () => containerRef.current as HTMLDivElement); const inViewResult = useInView(containerRef, { once: inViewOnce, margin: inViewMargin, }); const isInView = !inView || inViewResult; const displayedItemsCount = 1 + sideItemsCount * 2; const range = React.useMemo( () => generateRange(stars, step, sideItemsCount), [stars, step, sideItemsCount], ); const initialY = -(itemHeight * sideItemsCount); const finalY = itemHeight * (range.length - displayedItemsCount); const yMotion = useMotionValue(initialY); const ySpring = useSpring(yMotion, transition); React.useEffect(() => { if (!isInView) return; const timer = setTimeout(() => { yMotion.set(finalY); }, delay); return () => clearTimeout(timer); }, [isInView, finalY, yMotion, delay]); const currentIndex = useTransform( ySpring, (y) => y / itemHeight + sideItemsCount, ); const currentValue = useTransform(currentIndex, (idx) => idx * step); const completedTransform = useTransform( currentValue, (val) => val >= stars * 0.99, ); const [isCompleted, setCompleted] = React.useState( completedTransform.get(), ); React.useEffect(() => { const unsubscribe = completedTransform.on('change', (latest) => { if (latest) setCompleted(true); }); return unsubscribe; }, [completedTransform]); return (
{isCompleted && ( <> {[...Array(6)].map((_, i) => ( ))} )}
{range.map((value) => (
{formatter.format(value)}
))}
); } export { StarsScrollingWheel, type StarsScrollingWheelProps };