1+ import { AnimatePresence , motion } from "framer-motion" ;
2+ import { useEffect , useRef , useState } from "react" ;
3+
4+ // NOTE: Change this date to whatever date you want to countdown to :)
5+ const COUNTDOWN_FROM = "11/08/2024 14:30:00" ;
6+
7+ const SECOND = 1000 ;
8+ const MINUTE = SECOND * 60 ;
9+ const HOUR = MINUTE * 60 ;
10+ const DAY = HOUR * 24 ;
11+
12+ const HacknightCounter = ( ) => {
13+ const intervalRef = useRef < ReturnType < typeof setInterval > | null > ( null ) ;
14+
15+ const [ remaining , setRemaining ] = useState ( {
16+ days : 0 ,
17+ hours : 0 ,
18+ minutes : 0 ,
19+ seconds : 0 ,
20+ } ) ;
21+ const [ hasStarted , setStarted ] = useState ( false ) ;
22+ const [ elapsedTime , setElapsedTime ] = useState ( {
23+ days : 0 ,
24+ hours : 0 ,
25+ minutes : 0 ,
26+ seconds : 0 ,
27+ } ) ;
28+
29+ useEffect ( ( ) => {
30+ intervalRef . current = setInterval ( handleCountdown , 1000 ) ;
31+
32+ return ( ) => clearInterval ( intervalRef . current || undefined ) ;
33+ } , [ ] ) ;
34+
35+ const handleCountdown = ( ) => {
36+ const end = new Date ( COUNTDOWN_FROM ) ;
37+
38+ const now = new Date ( ) ;
39+
40+ const distance = + end - + now ;
41+ if ( distance <= 0 ) {
42+ setStarted ( true ) ;
43+ updateElapsed ( ) ;
44+ }
45+ else {
46+ const days = Math . floor ( distance / DAY ) ;
47+ const hours = Math . floor ( ( distance % DAY ) / HOUR ) ;
48+ const minutes = Math . floor ( ( distance % HOUR ) / MINUTE ) ;
49+ const seconds = Math . floor ( ( distance % MINUTE ) / SECOND ) ;
50+
51+ setRemaining ( {
52+ days,
53+ hours,
54+ minutes,
55+ seconds,
56+ } ) ;
57+ } } ;
58+
59+ const updateElapsed = ( ) => {
60+ const start = new Date ( COUNTDOWN_FROM ) ;
61+ const now = new Date ( ) ;
62+ const distance = + now - + start ;
63+
64+ const days = Math . floor ( distance / DAY ) ;
65+ const hours = Math . floor ( ( distance % DAY ) / HOUR ) ;
66+ const minutes = Math . floor ( ( distance % HOUR ) / MINUTE ) ;
67+ const seconds = Math . floor ( ( distance % MINUTE ) / SECOND ) ;
68+
69+ setElapsedTime ( {
70+ days,
71+ hours,
72+ minutes,
73+ seconds,
74+ } ) ;
75+ } ;
76+
77+ return (
78+ < div className = "md:py-10 py-5 pb-10" >
79+ < div className = "w-full max-w-5xl mx-auto flex items-center" >
80+ { hasStarted ?(
81+ < >
82+ < CountdownItem num = { elapsedTime . days } text = "days" />
83+ < CountdownItem num = { elapsedTime . hours } text = "hours" />
84+ < CountdownItem num = { elapsedTime . minutes } text = "minutes" />
85+ < CountdownItem num = { elapsedTime . seconds } text = "seconds" />
86+ </ >
87+ ) :
88+ (
89+ < >
90+ < CountdownItem num = { remaining . days } text = "days" />
91+ < CountdownItem num = { remaining . hours } text = "hours" />
92+ < CountdownItem num = { remaining . minutes } text = "minutes" />
93+ < CountdownItem num = { remaining . seconds } text = "seconds" />
94+ </ >
95+ )
96+ }
97+ </ div >
98+ </ div >
99+ ) ;
100+ } ;
101+
102+ const CountdownItem = ( { num, text } : { num : number ; text : string } ) => {
103+ return (
104+ < div className = "w-1/4 h-24 md:h-36 flex flex-col gap-1 md:gap-2 items-center justify-center" >
105+ < div className = "w-full text-center relative overflow-hidden" >
106+ < AnimatePresence mode = "popLayout" >
107+ < motion . span
108+ key = { num }
109+ initial = { { y : "100%" } }
110+ animate = { { y : "0%" } }
111+ exit = { { y : "-100%" } }
112+ transition = { { ease : "backIn" , duration : 0.75 } }
113+ className = "block text-3xl md:text-4xl lg:text-5xl text-primary font-medium"
114+ >
115+ { num }
116+ </ motion . span >
117+ </ AnimatePresence >
118+ </ div >
119+ < span className = "text-xs md:text-sm lg:text-base font-light text-white" >
120+ { text }
121+ </ span >
122+ </ div >
123+ ) ;
124+ } ;
125+
126+ export default HacknightCounter ;
0 commit comments