@gfazioli/mantine-clock
Version:
React Clock components and hooks for Mantine with timezone support, countdown timers, customization options, and real-time updates.
237 lines (234 loc) • 6.73 kB
JavaScript
'use client';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import isLeapYear from 'dayjs/plugin/isLeapYear';
import timezonePlugin from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import { useState, useRef, useEffect, useCallback } from 'react';
import { useMounted } from '@mantine/hooks';
dayjs.extend(utc);
dayjs.extend(timezonePlugin);
dayjs.extend(weekOfYear);
dayjs.extend(isLeapYear);
dayjs.extend(duration);
dayjs.extend(utc);
dayjs.extend(timezonePlugin);
dayjs.extend(weekOfYear);
dayjs.extend(isLeapYear);
dayjs.extend(duration);
function useClockCountDown({
enabled = true,
timezone = "UTC",
updateFrequency = 1e3,
use24Hours = true,
padSeconds = false,
padMinutes = false,
padHours = false,
targetDate,
hours = 0,
minutes = 0,
seconds = 0,
onCountDownCompleted
}) {
const mounted = useMounted();
const [remainingTime, setRemainingTime] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [isCompleted, setIsCompleted] = useState(false);
const intervalRef = useRef(null);
const initialDurationRef = useRef(0);
const targetDateRef = useRef(null);
const completedCallbackRef = useRef(onCountDownCompleted);
const initialEnabledRef = useRef(enabled);
useEffect(() => {
completedCallbackRef.current = onCountDownCompleted;
}, [onCountDownCompleted]);
const calculateInitialDuration = useCallback(() => {
if (!mounted) {
return 0;
}
let target;
if (targetDate) {
target = dayjs(targetDate).tz(timezone);
} else {
const now2 = dayjs().tz(timezone);
const h = Math.max(0, hours);
const m = Math.max(0, minutes);
const s = Math.max(0, seconds);
if (h === 0 && m === 0 && s === 0) {
target = now2.add(1, "hour");
} else {
target = now2.add(h, "hour").add(m, "minute").add(s, "second");
}
}
targetDateRef.current = target;
const now = dayjs().tz(timezone);
return Math.max(0, target.diff(now));
}, [targetDate, hours, minutes, seconds, timezone, mounted]);
useEffect(() => {
if (!mounted) {
return;
}
const duration2 = calculateInitialDuration();
initialDurationRef.current = duration2;
setRemainingTime(duration2);
setIsCompleted(duration2 <= 0);
if (enabled && duration2 > 0) {
setIsRunning(true);
} else {
setIsRunning(false);
}
}, [mounted, calculateInitialDuration, enabled]);
useEffect(() => {
if (!mounted || !isRunning || isCompleted) {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return;
}
intervalRef.current = setInterval(() => {
setRemainingTime((prev) => {
const newTime = Math.max(0, prev - updateFrequency);
if (newTime <= 0) {
setIsCompleted(true);
setIsRunning(false);
if (completedCallbackRef.current) {
completedCallbackRef.current();
}
}
return newTime;
});
}, updateFrequency);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [mounted, isRunning, isCompleted, updateFrequency]);
const start = useCallback(() => {
if (!mounted || isCompleted) {
return;
}
setIsRunning(true);
}, [mounted, isCompleted]);
const pause = useCallback(() => {
setIsRunning(false);
}, []);
const resume = useCallback(() => {
if (!mounted || isCompleted || remainingTime <= 0) {
return;
}
setIsRunning(true);
}, [mounted, isCompleted, remainingTime]);
const reset = useCallback(() => {
setIsRunning(false);
setIsCompleted(false);
const duration2 = calculateInitialDuration();
initialDurationRef.current = duration2;
setRemainingTime(duration2);
if (initialEnabledRef.current && duration2 > 0) {
setIsRunning(true);
}
}, [calculateInitialDuration]);
if (!mounted) {
const staticHours = padHours ? "00" : 0;
const staticMinutes = padMinutes ? "00" : 0;
const staticSeconds = padSeconds ? "00" : 0;
return {
year: 0,
month: 1,
day: 0,
week: 0,
isLeap: false,
hours: staticHours,
minutes: staticMinutes,
seconds: staticSeconds,
milliseconds: 0,
amPm: use24Hours ? void 0 : "AM",
isCompleted: false,
isRunning: false,
totalMilliseconds: 0,
start: () => {
},
pause: () => {
},
reset: () => {
},
resume: () => {
}
};
}
if (isCompleted || remainingTime <= 0) {
const staticHours = padHours ? "00" : 0;
const staticMinutes = padMinutes ? "00" : 0;
const staticSeconds = padSeconds ? "00" : 0;
return {
year: 0,
month: 0,
day: 0,
week: 0,
isLeap: false,
hours: staticHours,
minutes: staticMinutes,
seconds: staticSeconds,
milliseconds: 0,
amPm: use24Hours ? void 0 : "AM",
isCompleted: true,
isRunning: false,
totalMilliseconds: 0,
start: () => {
},
pause,
reset,
resume: () => {
}
};
}
const dur = dayjs.duration(remainingTime);
const remainingYears = Math.floor(dur.asYears());
const remainingMonths = Math.floor(dur.asMonths()) % 12;
const remainingDays = Math.floor(dur.asDays()) % 365;
const remainingWeeks = Math.floor(dur.asWeeks());
let remainingHours = Math.floor(dur.asHours()) % 24;
let remainingMinutes = dur.minutes();
let remainingSecondsCount = dur.seconds();
const remainingMilliseconds = dur.milliseconds();
let amPm;
if (!use24Hours) {
amPm = remainingHours >= 12 ? "PM" : "AM";
remainingHours = remainingHours % 12 || 12;
}
if (padHours) {
remainingHours = remainingHours.toString().padStart(2, "0");
}
if (padMinutes) {
remainingMinutes = remainingMinutes.toString().padStart(2, "0");
}
if (padSeconds) {
remainingSecondsCount = remainingSecondsCount.toString().padStart(2, "0");
}
const isLeap = targetDateRef.current ? targetDateRef.current.isLeapYear() : false;
return {
year: remainingYears,
month: remainingMonths + 1,
day: remainingDays,
week: remainingWeeks,
isLeap,
hours: remainingHours,
minutes: remainingMinutes,
seconds: remainingSecondsCount,
milliseconds: remainingMilliseconds,
amPm,
isCompleted: false,
isRunning,
totalMilliseconds: remainingTime,
start,
pause,
reset,
resume
};
}
export { useClockCountDown };
//# sourceMappingURL=use-clock-count-down.mjs.map