-
Notifications
You must be signed in to change notification settings - Fork 103
Expand file tree
/
Copy pathusePopoverError.ts
More file actions
79 lines (65 loc) · 2.15 KB
/
usePopoverError.ts
File metadata and controls
79 lines (65 loc) · 2.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import { useCallback, useEffect, useRef, useState } from "react";
export interface PopoverErrorState {
id: string;
error: string;
position: { top: number; left: number };
}
export interface UsePopoverErrorResult {
error: PopoverErrorState | null;
showError: (id: string, error: string, anchor?: { top: number; left: number }) => void;
clearError: () => void;
}
/**
* Hook for managing popover error state with auto-dismiss and click-outside behavior.
* @param autoDismissMs - Time in ms before auto-dismissing (default: 5000)
*/
export function usePopoverError(autoDismissMs = 5000): UsePopoverErrorResult {
const [error, setError] = useState<PopoverErrorState | null>(null);
const timeoutRef = useRef<number | null>(null);
const clearError = useCallback(() => {
setError(null);
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}, []);
const showError = useCallback(
(id: string, errorMsg: string, anchor?: { top: number; left: number }) => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
const position = anchor ?? {
top: window.scrollY + 32,
left: Math.max(window.innerWidth - 420, 16),
};
setError({ id, error: errorMsg, position });
timeoutRef.current = window.setTimeout(() => {
setError(null);
timeoutRef.current = null;
}, autoDismissMs);
},
[autoDismissMs]
);
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
};
}, []);
// Click-outside to dismiss
useEffect(() => {
if (!error) return;
const handleClickOutside = () => clearError();
// Delay to avoid immediate dismissal from the triggering click
const timeoutId = window.setTimeout(() => {
document.addEventListener("click", handleClickOutside, { once: true });
}, 0);
return () => {
window.clearTimeout(timeoutId);
document.removeEventListener("click", handleClickOutside);
};
}, [error, clearError]);
return { error, showError, clearError };
}