Skip to content

Commit fb51848

Browse files
committed
refactor notice timer hook
1 parent abea35d commit fb51848

2 files changed

Lines changed: 61 additions & 37 deletions

File tree

src/Notification.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
2020
const onEventClose = useEvent(onClose);
2121

2222
// ======================== Duration ========================
23-
const [onResume, onPause] = useNoticeTimer(duration, onEventClose);
23+
const [onResume, onPause] = useNoticeTimer(duration, onEventClose, () => {});
2424

2525
// ========================= Render =========================
2626
return (
27-
<div ref={ref} onClick={onClick} onMouseEnter={onPause} onMouseLeave={onResume}>
27+
<div
28+
ref={ref}
29+
onClick={onClick}
30+
onMouseEnter={pauseOnHover ? onPause : undefined}
31+
onMouseLeave={pauseOnHover ? onResume : undefined}
32+
>
2833
{content}
2934
{close && (
3035
<button

src/hooks/useNoticeTimer.ts

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,63 @@
11
import * as React from 'react';
2-
3-
export default function useNoticeTimer(duration: number | false, onClose: VoidFunction) {
4-
// Normalize: `false` means no auto-close
5-
const mergedDuration: number = typeof duration === 'number' ? duration : 0;
6-
7-
const startTimestampRef = React.useRef(0);
8-
const leftTimeRef = React.useRef(mergedDuration * 1000);
9-
const timerRef = React.useRef<NodeJS.Timeout>();
10-
11-
const clear = () => {
12-
clearTimeout(timerRef.current);
13-
};
14-
15-
const onResume = () => {
16-
clear();
17-
18-
// Only start timer when there is remaining time
19-
if (leftTimeRef.current > 0) {
20-
startTimestampRef.current = Date.now();
21-
timerRef.current = setTimeout(() => {
22-
onClose();
23-
}, leftTimeRef.current);
2+
import raf from '@rc-component/util/es/raf';
3+
import useEvent from '@rc-component/util/es/hooks/useEvent';
4+
5+
export default function useNoticeTimer(
6+
duration: number | false,
7+
onClose: VoidFunction,
8+
onUpdate: (ptg: number) => void,
9+
) {
10+
const mergedDuration = typeof duration === 'number' ? duration : 0;
11+
const durationMs = Math.max(mergedDuration, 0) * 1000;
12+
const onEventClose = useEvent(onClose);
13+
const onEventUpdate = useEvent(onUpdate);
14+
15+
const [walking, setWalking] = React.useState(durationMs > 0);
16+
const startTimestampRef = React.useRef<number | null>(null);
17+
const passTimeRef = React.useRef(0);
18+
19+
function onPause() {
20+
setWalking(false);
21+
}
22+
23+
function onResume() {
24+
setWalking(true);
25+
}
26+
27+
function updateProgress() {
28+
if (durationMs) {
29+
const now = Date.now();
30+
const passedTime = now - (startTimestampRef.current || now);
31+
startTimestampRef.current = now;
32+
passTimeRef.current += passedTime;
33+
onEventUpdate(Math.min(passTimeRef.current / durationMs, 1));
34+
35+
// Return true if timesup
36+
return passTimeRef.current >= durationMs;
2437
}
25-
};
38+
return false;
39+
}
2640

27-
const onPause = () => {
28-
clear();
41+
React.useEffect(() => {
42+
if (walking && durationMs > 0) {
43+
let rafId: number | null = null;
2944

30-
// Record how much time is left so onResume can continue from here
31-
leftTimeRef.current -= Date.now() - startTimestampRef.current;
32-
};
45+
function step() {
46+
if (updateProgress()) {
47+
onEventClose();
48+
} else {
49+
rafId = raf(step);
50+
}
51+
}
3352

34-
React.useEffect(() => {
35-
// Reset remaining time whenever duration changes, then (re)start the timer
36-
leftTimeRef.current = mergedDuration * 1000;
37-
onResume();
53+
startTimestampRef.current = Date.now();
54+
rafId = raf(step);
3855

39-
// Clear the timer on unmount or before next effect run
40-
return clear;
41-
}, []);
56+
return () => {
57+
raf.cancel(rafId);
58+
};
59+
}
60+
}, [walking]);
4261

4362
return [onResume, onPause] as const;
4463
}

0 commit comments

Comments
 (0)