Files
36-character-flower/src/features/game/components/desktop/desktop-countdown.tsx
2026-05-21 13:40:32 +08:00

88 lines
2.2 KiB
TypeScript

import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import { useEffect, useMemo, useState } from 'react'
import { cn } from '@/lib/utils'
dayjs.extend(duration)
function formatCountdown(remainingMs: number) {
const totalSeconds = Math.max(0, Math.ceil(remainingMs / 1000))
const countdown = dayjs.duration(totalSeconds, 'seconds')
const minutes = String(Math.floor(countdown.asMinutes())).padStart(2, '0')
const seconds = String(countdown.seconds()).padStart(2, '0')
return `${minutes}:${seconds}`
}
interface DesktopCountdownProps {
className?: string
initialMs?: number
initialSeconds?: number
onComplete?: () => void
onRemainingMsChange?: (remainingMs: number) => void
}
export function DesktopCountdown({
className,
initialMs,
initialSeconds,
onComplete,
onRemainingMsChange,
}: DesktopCountdownProps) {
const initialCountdownMs = useMemo(() => {
if (typeof initialMs === 'number') {
return Math.max(0, initialMs)
}
if (typeof initialSeconds === 'number') {
return Math.max(0, initialSeconds * 1000)
}
return 30_000
}, [initialMs, initialSeconds])
const [remainingMs, setRemainingMs] = useState(initialCountdownMs)
useEffect(() => {
setRemainingMs(initialCountdownMs)
onRemainingMsChange?.(initialCountdownMs)
}, [initialCountdownMs, onRemainingMsChange])
useEffect(() => {
if (initialCountdownMs <= 0) {
setRemainingMs(0)
onComplete?.()
return
}
const startedAt = Date.now()
const timer = window.setInterval(() => {
const elapsedMs = Date.now() - startedAt
const nextRemainingMs = Math.max(0, initialCountdownMs - elapsedMs)
setRemainingMs(nextRemainingMs)
onRemainingMsChange?.(nextRemainingMs)
if (nextRemainingMs === 0) {
window.clearInterval(timer)
onComplete?.()
}
}, 250)
return () => {
window.clearInterval(timer)
}
}, [initialCountdownMs, onComplete, onRemainingMsChange])
return (
<div
className={cn(
'relative z-10 flex items-center justify-center font-countdown text-design-56 leading-none tracking-[0.08em] text-[#4BFFFE]',
className,
)}
>
{formatCountdown(remainingMs)}
</div>
)
}