turkmentv_front/components/lottery/RollingCounter/RollingCounter.tsx

192 lines
5.3 KiB
TypeScript
Raw Normal View History

2024-12-18 13:30:13 +00:00
'use client';
import { motion } from 'framer-motion';
2024-12-24 13:33:55 +00:00
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
2024-12-18 13:30:13 +00:00
interface RollingCounterProps {
2024-12-20 13:23:26 +00:00
numberString: string;
2024-12-18 13:30:13 +00:00
}
2024-12-24 12:19:08 +00:00
// Move constants outside component
2024-12-20 13:23:26 +00:00
const ROLLS = 2;
const DIGIT_HEIGHT = 104;
2024-12-24 12:58:33 +00:00
const EXTRA_NUMBERS_BEFORE = 2;
2024-12-20 13:23:26 +00:00
const EXTRA_NUMBERS_AFTER = 5;
2024-12-24 12:58:33 +00:00
const CONTAINER_HEIGHT = 180;
const INITIAL_OFFSET = (CONTAINER_HEIGHT - DIGIT_HEIGHT) / 2 - DIGIT_HEIGHT * 2;
2024-12-20 13:23:26 +00:00
2024-12-24 12:19:08 +00:00
// Memoize number generation function
const getNumbers = (targetValue: number) => {
2024-12-20 13:23:26 +00:00
const numbers = [];
2024-12-24 12:58:33 +00:00
// Add extra numbers before
for (let n = 10 - EXTRA_NUMBERS_BEFORE; n < 10; n++) {
numbers.push(n);
}
// Start with zeros
for (let n = 0; n <= targetValue; n++) {
numbers.push(n);
}
// Add complete rolls after the initial sequence
2024-12-24 12:19:08 +00:00
for (let i = 0; i < ROLLS; i++) {
for (let n = 0; n < 10; n++) {
numbers.push(n);
2024-12-20 13:23:26 +00:00
}
2024-12-24 12:19:08 +00:00
}
2024-12-20 13:23:26 +00:00
2024-12-24 12:19:08 +00:00
// Add sequence to target
for (let n = 0; n <= targetValue; n++) {
numbers.push(n);
}
2024-12-20 13:23:26 +00:00
2024-12-24 12:19:08 +00:00
// Add extra numbers after target
for (let n = (targetValue + 1) % 10; n < ((targetValue + 1) % 10) + EXTRA_NUMBERS_AFTER; n++) {
numbers.push(n % 10);
2024-12-20 13:23:26 +00:00
}
return numbers;
};
const RollingDigit = ({
targetValue,
index,
onAnimationComplete,
isStopped,
showHyphen,
2024-12-24 12:58:33 +00:00
totalDigits,
2024-12-24 13:33:55 +00:00
isRollingBack,
2024-12-20 13:23:26 +00:00
}: {
targetValue: number;
index: number;
onAnimationComplete: () => void;
isStopped: boolean;
showHyphen: boolean;
2024-12-24 12:58:33 +00:00
totalDigits: number;
2024-12-24 13:33:55 +00:00
isRollingBack: boolean;
2024-12-20 13:23:26 +00:00
}) => {
2024-12-24 12:19:08 +00:00
const numbers = useMemo(() => getNumbers(targetValue), [targetValue]);
2024-12-20 13:23:26 +00:00
return (
<div className="flex items-center">
<div className="overflow-hidden h-[180px] w-[77px] relative">
<motion.div
2024-12-24 12:19:08 +00:00
initial={{ y: INITIAL_OFFSET }}
2024-12-20 13:23:26 +00:00
animate={{
2024-12-24 13:33:55 +00:00
y: isRollingBack
? INITIAL_OFFSET
: -(numbers.length - EXTRA_NUMBERS_AFTER - 3) * DIGIT_HEIGHT + INITIAL_OFFSET,
2024-12-20 13:23:26 +00:00
}}
transition={{
2024-12-24 13:33:55 +00:00
duration: isRollingBack ? 2 : 2,
delay: isRollingBack ? index * 0.2 : (totalDigits - 1 - index) * 0.2,
2024-12-20 13:23:26 +00:00
ease: 'easeInOut',
}}
onAnimationComplete={onAnimationComplete}
className="absolute flex flex-col">
{numbers.map((num, i) => (
<div
key={`${index}-${i}`}
className={`h-[${DIGIT_HEIGHT}px] w-[77px] flex items-center justify-center numeric-display-1 transition-colors duration-500 ${
2024-12-24 13:33:55 +00:00
isStopped && !isRollingBack && num === targetValue ? 'text-white' : 'text-[#B0B1CD]'
2024-12-20 13:23:26 +00:00
}`}>
{num}
</div>
))}
</motion.div>
</div>
{showHyphen && <div className="w-[32px] h-[4px] bg-lightOnPrimary mx-5 self-center" />}
</div>
);
};
2024-12-18 13:30:13 +00:00
const RollingCounter: React.FC<RollingCounterProps> = ({ numberString }) => {
2024-12-20 13:23:26 +00:00
const [isStopped, setIsStopped] = useState<boolean[]>([]);
2024-12-24 13:33:55 +00:00
const [isRollingBack, setIsRollingBack] = useState(false);
const [isTransitioning, setIsTransitioning] = useState(false);
const [currentNumbers, setCurrentNumbers] = useState<number[]>([]);
const prevNumberStringRef = useRef(numberString);
2024-12-20 13:23:26 +00:00
2024-12-24 12:19:08 +00:00
const { numbers, isInitialLoading } = useMemo(() => {
if (!numberString) {
return { numbers: [], isInitialLoading: true };
}
2024-12-20 13:23:26 +00:00
2024-12-24 13:33:55 +00:00
const parsed = numberString.split('-').flatMap((pair) => {
return pair.split('').map((char) => parseInt(char, 10));
});
2024-12-20 13:23:26 +00:00
2024-12-24 12:19:08 +00:00
return {
numbers: parsed,
isInitialLoading: false,
};
}, [numberString]);
2024-12-20 13:23:26 +00:00
2024-12-24 13:33:55 +00:00
// Initialize currentNumbers
useEffect(() => {
if (!currentNumbers.length && numbers.length) {
setCurrentNumbers(numbers);
}
}, [numbers, currentNumbers.length]);
// Handle number changes
useEffect(() => {
if (prevNumberStringRef.current !== numberString && !isRollingBack) {
setIsTransitioning(true);
setIsRollingBack(true);
setIsStopped(new Array(numbers.length).fill(false));
setTimeout(() => {
setIsRollingBack(false);
setCurrentNumbers(numbers);
prevNumberStringRef.current = numberString;
}, 2000);
}
}, [numberString, numbers, isRollingBack]);
const handleAnimationComplete = useCallback(
(index: number) => {
if (!isRollingBack) {
setIsStopped((prev) => {
const newState = [...prev];
newState[index] = true;
if (newState.every((stopped) => stopped)) {
setIsTransitioning(false);
}
return newState;
});
}
},
[isRollingBack],
);
2024-12-20 13:23:26 +00:00
if (isInitialLoading) {
return (
<div className="flex items-center justify-center bg-lightPrimary text-white py-4 px-6 rounded-full">
Loading...
</div>
);
}
2024-12-18 13:30:13 +00:00
return (
2024-12-25 13:46:20 +00:00
<div className="flex items-center max-w-[1132px] justify-center bg-lightPrimary text-white py-4 px-6 rounded-full">
2024-12-24 13:33:55 +00:00
{currentNumbers.map((num, index) => (
2024-12-20 13:23:26 +00:00
<RollingDigit
2024-12-24 13:33:55 +00:00
key={`${index}`} // Simplified key to prevent re-renders
2024-12-20 13:23:26 +00:00
targetValue={num}
index={index}
onAnimationComplete={() => handleAnimationComplete(index)}
2024-12-24 13:33:55 +00:00
isStopped={isStopped[index] && !isTransitioning}
2024-12-20 13:23:26 +00:00
showHyphen={(index + 1) % 2 === 0 && index !== numbers.length - 1}
2024-12-24 12:58:33 +00:00
totalDigits={numbers.length}
2024-12-24 13:33:55 +00:00
isRollingBack={isRollingBack}
2024-12-20 13:23:26 +00:00
/>
2024-12-18 13:30:13 +00:00
))}
</div>
);
};
export default RollingCounter;