Skip to content

Commit 14c6e44

Browse files
authored
feat(pauseOnHover): add pauseOnHover options (#335)
1 parent 6b662dc commit 14c6e44

File tree

6 files changed

+95
-4
lines changed

6 files changed

+95
-4
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ props details:
136136
<td>false</td>
137137
<td>show with progress bar for auto-closing notification</td>
138138
</tr>
139+
<tr>
140+
<td>pauseOnHover</td>
141+
<td>boolean</td>
142+
<td>true</td>
143+
<td>keep the timer running or not on hover</td>
144+
</tr>
139145
<tr>
140146
<td>style</td>
141147
<td>Object</td>

docs/examples/showProgress.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ export default () => {
1818
>
1919
Show With Progress
2020
</button>
21+
<button
22+
onClick={() => {
23+
notice.open({
24+
content: `${new Date().toISOString()}`,
25+
pauseOnHover: false,
26+
});
27+
}}
28+
>
29+
Not Pause On Hover
30+
</button>
2131
{contextHolder}
2232
</>
2333
);

src/Notice.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const Notify = React.forwardRef<HTMLDivElement, NoticeProps & { times?: number }
2222
className,
2323
duration = 4.5,
2424
showProgress,
25+
pauseOnHover = true,
2526

2627
eventKey,
2728
content,
@@ -63,15 +64,17 @@ const Notify = React.forwardRef<HTMLDivElement, NoticeProps & { times?: number }
6364
);
6465

6566
return () => {
66-
clearTimeout(timeout);
67+
if (pauseOnHover) {
68+
clearTimeout(timeout);
69+
}
6770
setSpentTime(Date.now() - start);
6871
};
6972
}
7073
// eslint-disable-next-line react-hooks/exhaustive-deps
7174
}, [duration, mergedHovering, times]);
7275

7376
React.useEffect(() => {
74-
if (!mergedHovering && mergedShowProgress) {
77+
if (!mergedHovering && mergedShowProgress && (pauseOnHover || spentTime === 0)) {
7578
const start = performance.now();
7679
let animationFrame: number;
7780

@@ -90,11 +93,13 @@ const Notify = React.forwardRef<HTMLDivElement, NoticeProps & { times?: number }
9093
calculate();
9194

9295
return () => {
93-
cancelAnimationFrame(animationFrame);
96+
if (pauseOnHover) {
97+
cancelAnimationFrame(animationFrame);
98+
}
9499
};
95100
}
96101
// eslint-disable-next-line react-hooks/exhaustive-deps
97-
}, [duration, mergedHovering, mergedShowProgress, times]);
102+
}, [duration, spentTime, mergedHovering, mergedShowProgress, times]);
98103

99104
// ======================== Closable ========================
100105
const closableObj = React.useMemo(() => {

src/hooks/useNotification.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface NotificationConfig {
1818
maxCount?: number;
1919
duration?: number;
2020
showProgress?: boolean;
21+
pauseOnHover?: boolean;
2122
/** @private. Config for notification holder style. Safe to remove if refactor */
2223
className?: (placement: Placement) => string;
2324
/** @private. Config for notification holder style. Safe to remove if refactor */

src/interface.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface NoticeConfig {
88
content?: React.ReactNode;
99
duration?: number | null;
1010
showProgress?: boolean;
11+
pauseOnHover?: boolean;
1112
closeIcon?: React.ReactNode;
1213
closable?: boolean | ({ closeIcon?: React.ReactNode } & React.AriaAttributes);
1314
className?: string;

tests/index.test.tsx

+68
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,74 @@ describe('Notification.Basic', () => {
309309
expect(document.querySelector('.test')).toBeFalsy();
310310
});
311311

312+
describe('pauseOnHover is false', () => {
313+
it('does not freeze when pauseOnHover is false', () => {
314+
const { instance } = renderDemo();
315+
316+
act(() => {
317+
instance.open({
318+
content: (
319+
<p id="not-freeze" className="not-freeze">
320+
not freeze
321+
</p>
322+
),
323+
duration: 0.3,
324+
pauseOnHover: false,
325+
});
326+
});
327+
328+
expect(document.querySelectorAll('.not-freeze')).toHaveLength(1);
329+
330+
// Mouse in should remove
331+
fireEvent.mouseEnter(document.querySelector('.rc-notification-notice'));
332+
act(() => {
333+
vi.runAllTimers();
334+
});
335+
expect(document.querySelectorAll('.not-freeze')).toHaveLength(0);
336+
});
337+
338+
it('continue timing after hover', () => {
339+
const { instance } = renderDemo({
340+
duration: 1,
341+
pauseOnHover: false,
342+
});
343+
344+
act(() => {
345+
instance.open({
346+
content: <p className="test">1</p>,
347+
});
348+
});
349+
350+
expect(document.querySelector('.test')).toBeTruthy();
351+
352+
// Wait for 500ms
353+
act(() => {
354+
vi.advanceTimersByTime(500);
355+
});
356+
expect(document.querySelector('.test')).toBeTruthy();
357+
358+
// Mouse in should not remove
359+
fireEvent.mouseEnter(document.querySelector('.rc-notification-notice'));
360+
act(() => {
361+
vi.advanceTimersByTime(200);
362+
});
363+
expect(document.querySelector('.test')).toBeTruthy();
364+
365+
// Mouse out should not remove until 500ms later
366+
fireEvent.mouseLeave(document.querySelector('.rc-notification-notice'));
367+
act(() => {
368+
vi.advanceTimersByTime(200);
369+
});
370+
expect(document.querySelector('.test')).toBeTruthy();
371+
372+
//
373+
act(() => {
374+
vi.advanceTimersByTime(100);
375+
});
376+
expect(document.querySelector('.test')).toBeFalsy();
377+
});
378+
});
379+
312380
describe('maxCount', () => {
313381
it('remove work when maxCount set', () => {
314382
const { instance } = renderDemo({

0 commit comments

Comments
 (0)