Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/resources/scripts/components/elements/tooltip/Tooltip.tsx
10288 views
1
import React, { cloneElement, useRef, useState } from 'react';
2
import {
3
arrow,
4
autoUpdate,
5
flip,
6
offset,
7
Placement,
8
shift,
9
Side,
10
useClick,
11
useDismiss,
12
useFloating,
13
useFocus,
14
useHover,
15
useInteractions,
16
useRole,
17
} from '@floating-ui/react-dom-interactions';
18
import { AnimatePresence, motion } from 'framer-motion';
19
import classNames from 'classnames';
20
21
type Interaction = 'hover' | 'click' | 'focus';
22
23
interface Props {
24
rest?: number;
25
delay?: number | Partial<{ open: number; close: number }>;
26
content: string | React.ReactChild;
27
disabled?: boolean;
28
arrow?: boolean;
29
interactions?: Interaction[];
30
placement?: Placement;
31
className?: string;
32
children: React.ReactElement;
33
}
34
35
const arrowSides: Record<Side, string> = {
36
top: 'bottom-[-6px] left-0',
37
bottom: 'top-[-6px] left-0',
38
right: 'top-0 left-[-6px]',
39
left: 'top-0 right-[-6px]',
40
};
41
42
export default ({ children, ...props }: Props) => {
43
const arrowEl = useRef<HTMLDivElement>(null);
44
const [open, setOpen] = useState(false);
45
46
const { x, y, reference, floating, middlewareData, strategy, context } = useFloating({
47
open,
48
strategy: 'fixed',
49
placement: props.placement || 'top',
50
middleware: [
51
offset(props.arrow ? 10 : 6),
52
flip(),
53
shift({ padding: 6 }),
54
arrow({ element: arrowEl, padding: 6 }),
55
],
56
onOpenChange: setOpen,
57
whileElementsMounted: autoUpdate,
58
});
59
60
const interactions = props.interactions || ['hover', 'focus'];
61
const { getReferenceProps, getFloatingProps } = useInteractions([
62
useHover(context, {
63
restMs: props.rest ?? 30,
64
delay: props.delay ?? 0,
65
enabled: interactions.includes('hover'),
66
}),
67
useFocus(context, { enabled: interactions.includes('focus') }),
68
useClick(context, { enabled: interactions.includes('click') }),
69
useRole(context, { role: 'tooltip' }),
70
useDismiss(context),
71
]);
72
73
const side = arrowSides[(props.placement || 'top').split('-')[0] as Side];
74
const { x: ax, y: ay } = middlewareData.arrow || {};
75
76
if (props.disabled) {
77
return children;
78
}
79
80
return (
81
<>
82
{cloneElement(children, getReferenceProps({ ref: reference, ...children.props }))}
83
<AnimatePresence>
84
{open && (
85
<motion.div
86
initial={{ opacity: 0, scale: 0.85 }}
87
animate={{ opacity: 1, scale: 1 }}
88
exit={{ opacity: 0 }}
89
transition={{ type: 'spring', damping: 20, stiffness: 300, duration: 0.075 }}
90
{...getFloatingProps({
91
ref: floating,
92
className:
93
'bg-gray-900 text-sm text-gray-200 px-3 py-2 rounded pointer-events-none max-w-[24rem]',
94
style: {
95
position: strategy,
96
top: `${y || 0}px`,
97
left: `${x || 0}px`,
98
},
99
})}
100
>
101
{props.content}
102
{props.arrow && (
103
<div
104
ref={arrowEl}
105
style={{
106
transform: `translate(${Math.round(ax || 0)}px, ${Math.round(
107
ay || 0
108
)}px) rotate(45deg)`,
109
}}
110
className={classNames('absolute bg-gray-900 w-3 h-3', side)}
111
/>
112
)}
113
</motion.div>
114
)}
115
</AnimatePresence>
116
</>
117
);
118
};
119
120