({ days: 0, hours: 0, minutes: 0, seconds: 0 });
const completedRef = useRef(false);
const defaultLabels = {
days: labels.days || 'DAYS',
hours: labels.hours || 'HOURS',
minutes: labels.minutes || 'MIN',
seconds: labels.seconds || 'SEC',
};
useEffect(() => {
const calculateTimeLeft = (): TimeLeft => {
let totalSeconds: number;
if (target instanceof Date) {
totalSeconds = Math.max(0, Math.floor((target.getTime() - Date.now()) / 1000));
} else {
totalSeconds = Math.max(0, target);
}
return {
days: Math.floor(totalSeconds / (24 * 60 * 60)),
hours: Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60)),
minutes: Math.floor((totalSeconds % (60 * 60)) / 60),
seconds: totalSeconds % 60,
};
};
const tick = () => {
const newTime = calculateTimeLeft();
const totalSeconds = newTime.days * 86400 + newTime.hours * 3600 + newTime.minutes * 60 + newTime.seconds;
// Detect changes for flip animation
if (flip) {
const changed: string[] = [];
if (newTime.days !== prevValues.current.days) changed.push('days');
if (newTime.hours !== prevValues.current.hours) changed.push('hours');
if (newTime.minutes !== prevValues.current.minutes) changed.push('minutes');
if (newTime.seconds !== prevValues.current.seconds) changed.push('seconds');
setChanging(changed);
setTimeout(() => setChanging([]), 300);
}
prevValues.current = newTime;
setTimeLeft(newTime);
setIsUrgent(totalSeconds <= urgentThreshold && totalSeconds > 0);
if (totalSeconds === 0 && !completedRef.current) {
completedRef.current = true;
onComplete?.();
}
};
tick();
const interval = setInterval(tick, 1000);
return () => clearInterval(interval);
}, [target, urgentThreshold, flip, onComplete]);
const pad = (num: number) => String(num).padStart(2, '0');
const containerClasses = [
styles.container,
styles[size],
styles[variant],
flip && styles.flip,
isUrgent && styles.urgent,
!showLabels && styles.hideLabels,
compact && styles.compact,
className
].filter(Boolean).join(' ');
const renderBlock = (value: number, label: string, key: string) => (
{pad(value)}
{showLabels && {label}}
);
const renderSeparator = (key: number) => (
:
);
const renderBlocks = () => {
const blocks = [];
if (format === 'dhms' || format === 'full') {
blocks.push(renderBlock(timeLeft.days, defaultLabels.days, 'days'));
blocks.push(renderSeparator(1));
}
if (format !== 'ms') {
blocks.push(renderBlock(timeLeft.hours, defaultLabels.hours, 'hours'));
blocks.push(renderSeparator(2));
}
blocks.push(renderBlock(timeLeft.minutes, defaultLabels.minutes, 'minutes'));
blocks.push(renderSeparator(3));
blocks.push(renderBlock(timeLeft.seconds, defaultLabels.seconds, 'seconds'));
return blocks;
};
return (
{renderBlocks()}
);
}
);
CountdownDisplay.displayName = 'CountdownDisplay';
export default CountdownDisplay;