forked from allusion-app/Allusion
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Tooltip.tsx
91 lines (83 loc) · 3.03 KB
/
Tooltip.tsx
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
80
81
82
83
84
85
86
87
88
89
90
91
import { VirtualElement, autoPlacement, offset, shift } from '@floating-ui/core';
import { useFloating } from '@floating-ui/react-dom';
import React, { useEffect, useRef, useState } from 'react';
export const TooltipLayer = ({ document }: { document: Document }) => {
const virtualElement = useRef<VirtualElement>({
getBoundingClientRect: () => new DOMRect(),
contextElement: null,
});
const { x, y, reference, floating, strategy, update } = useFloating({
middleware: [
offset({ mainAxis: 4, crossAxis: 0 }),
shift({ boundary: document.body, crossAxis: true, padding: 8 }),
autoPlacement(),
],
});
const [isOpen, setIsOpen] = useState(false);
const content = useRef<string>('');
const timerID = useRef<number>();
useEffect(() => {
reference(virtualElement.current);
const handleShow = (e: MouseEvent | FocusEvent): HTMLElement | undefined => {
const target = e.target as any;
if (target === null || !("dataset" in target) || !(target.dataset as any)['tooltip']) {
return;
}
content.current = target.dataset['tooltip'];
if (virtualElement.current.contextElement !== target) {
window.clearTimeout(timerID.current);
timerID.current = window.setTimeout(() => {
timerID.current = undefined;
setIsOpen(true);
update();
}, 500);
}
const boundingRect = target.getBoundingClientRect();
virtualElement.current.getBoundingClientRect = () => boundingRect;
virtualElement.current.contextElement = target;
};
const handleHide = (e: MouseEvent | FocusEvent) => {
if (virtualElement.current.contextElement?.contains(e.relatedTarget as Node)) {
return;
}
setIsOpen(false);
virtualElement.current.contextElement = null;
window.clearTimeout(timerID.current);
timerID.current = undefined;
};
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setIsOpen(false);
virtualElement.current.contextElement = null;
}
};
document.addEventListener('mouseover', handleShow, true);
document.addEventListener('mouseout', handleHide, true);
document.addEventListener('focus', handleShow, true);
document.addEventListener('blur', handleHide, true);
document.addEventListener('keydown', handleEscape, true);
return () => {
document.removeEventListener('mouseover', handleShow, true);
document.removeEventListener('mouseout', handleHide, true);
document.removeEventListener('focus', handleShow, true);
document.removeEventListener('blur', handleHide, true);
document.removeEventListener('keydown', handleEscape, true);
};
}, [document, update, reference]);
return (
<div
ref={floating}
style={{
position: strategy,
top: 0,
left: 0,
transform: `translate(${Math.round(x ?? 0.0)}px,${Math.round(y ?? 0.0)}px)`,
}}
role="tooltip"
data-popover
data-open={isOpen}
>
{content.current}
</div>
);
};