Skip to content

Commit 20ed423

Browse files
this works saving before i screw it up somehow
1 parent dbf1dcf commit 20ed423

File tree

2 files changed

+164
-25
lines changed

2 files changed

+164
-25
lines changed

packages/react-components/src/components/abstract-output-component.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,6 @@ export abstract class AbstractOutputComponent<
321321
}
322322

323323
protected closeTooltip(): void {
324-
this.setState({ tooltipContent: undefined, tooltipVisible: false });
324+
this.setState({ tooltipVisible: false });
325325
}
326326
}
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,170 @@
11
import * as React from 'react';
2-
import ReactTooltip from 'react-tooltip';
2+
import { createPortal } from 'react-dom';
3+
import { debounce } from 'lodash';
4+
const { useState, useEffect, useRef, useMemo } = React;
35

4-
export interface TooltipComponentProps {
6+
interface TooltipProps {
57
content?: React.ReactNode;
68
visible?: boolean;
9+
fadeTransition?: number;
710
}
811

9-
export class TooltipComponent extends React.Component<TooltipComponentProps> {
10-
render(): React.ReactNode {
11-
if (this.props.visible) {
12-
return (
13-
<ReactTooltip
14-
// css overrides the "__react_component_tooltip" class
15-
id="tooltip-component"
16-
effect="float"
17-
type="info"
18-
place="bottom"
19-
delayShow={500}
20-
delayUpdate={500}
21-
delayHide={500}
22-
clickable={true}
23-
scrollHide={true}
24-
arrowColor="transparent"
25-
>
26-
{this.props.content || '⏳'}
27-
</ReactTooltip>
28-
);
29-
}
30-
}
12+
interface TooltipState {
13+
content: React.ReactNode;
14+
visible: boolean;
15+
zIndex: number;
3116
}
17+
18+
export const TooltipComponent = ({ content = '⏳', visible = false, fadeTransition = 500 }: TooltipProps) => {
19+
const tooltipRef = useRef<HTMLDivElement>(null);
20+
const lastMousePosition = useRef({ x: 0, y: 0 });
21+
const [state, setState] = useState<TooltipState>({
22+
content,
23+
visible,
24+
zIndex: visible ? 99999 : -99999
25+
});
26+
const isMouseOverRef = useRef(false);
27+
28+
// Calculate position based on current mouse position and tooltip dimensions
29+
const calculateAndSetPosition = () => {
30+
if (!tooltipRef.current) return;
31+
32+
const viewportWidth = window.innerWidth;
33+
const viewportHeight = window.innerHeight;
34+
const tooltipRect = tooltipRef.current.getBoundingClientRect();
35+
36+
let x = lastMousePosition.current.x + 10;
37+
let y = lastMousePosition.current.y + 10;
38+
39+
if (x + tooltipRect.width > viewportWidth) {
40+
x = lastMousePosition.current.x - tooltipRect.width - 10;
41+
}
42+
43+
if (y + tooltipRect.height > viewportHeight) {
44+
y = lastMousePosition.current.y - tooltipRect.height - 10;
45+
}
46+
47+
tooltipRef.current.style.left = `${x}px`;
48+
tooltipRef.current.style.top = `${y}px`;
49+
};
50+
51+
const debouncedUpdate = useMemo(
52+
() =>
53+
debounce((newState: Partial<TooltipState>) => {
54+
setState(prevState => {
55+
const updatedState = { ...prevState, ...newState };
56+
57+
// Key behaviors are scripted here.
58+
const weAreHidingTooltip = prevState.visible === true && newState.visible === false;
59+
const weAreHoveringOverTooltip = isMouseOverRef.current;
60+
if (weAreHoveringOverTooltip) {
61+
debouncedUpdate.cancel();
62+
return prevState;
63+
} else if (weAreHidingTooltip) {
64+
// Don't update the content
65+
updatedState.content = prevState.content;
66+
// Keep z-index high during fade out
67+
updatedState.zIndex = prevState.zIndex;
68+
return updatedState;
69+
} else {
70+
calculateAndSetPosition();
71+
// Update z-index immediately when showing
72+
if (newState.visible) {
73+
updatedState.zIndex = 99999;
74+
}
75+
return updatedState;
76+
}
77+
});
78+
}, 500),
79+
[]
80+
);
81+
82+
// Track mouse position and detect when mouse leaves viewport
83+
useEffect(() => {
84+
const handleMouseMove = (e: MouseEvent) => {
85+
lastMousePosition.current = { x: e.clientX, y: e.clientY };
86+
};
87+
88+
const handleMouseLeave = (e: MouseEvent) => {
89+
// Check if the mouse has actually left the viewport
90+
if (
91+
e.clientY <= 0 ||
92+
e.clientY >= window.innerHeight ||
93+
e.clientX <= 0 ||
94+
e.clientX >= window.innerWidth
95+
) {
96+
debouncedUpdate({ visible: false });
97+
}
98+
};
99+
100+
document.addEventListener('mousemove', handleMouseMove);
101+
document.addEventListener('mouseleave', handleMouseLeave);
102+
103+
return () => {
104+
document.removeEventListener('mousemove', handleMouseMove);
105+
document.removeEventListener('mouseleave', handleMouseLeave);
106+
};
107+
}, [debouncedUpdate]);
108+
109+
// Handle content and visibility changes
110+
useEffect(() => {
111+
if (!isMouseOverRef.current) {
112+
debouncedUpdate({ content, visible });
113+
}
114+
}, [content, visible, debouncedUpdate]);
115+
116+
// Cleanup debounced function
117+
useEffect(() => {
118+
return () => {
119+
debouncedUpdate.cancel();
120+
};
121+
}, [debouncedUpdate]);
122+
123+
// Handle transition end
124+
useEffect(() => {
125+
const tooltip = tooltipRef.current;
126+
if (!tooltip) return;
127+
128+
const handleTransitionEnd = (e: TransitionEvent) => {
129+
if (e.propertyName === 'opacity' && !state.visible) {
130+
setState(prev => ({ ...prev, zIndex: -99999 }));
131+
}
132+
};
133+
134+
tooltip.addEventListener('transitionend', handleTransitionEnd);
135+
return () => {
136+
tooltip.removeEventListener('transitionend', handleTransitionEnd);
137+
};
138+
}, [state.visible]);
139+
140+
const tooltipStyle: React.CSSProperties = {
141+
position: 'fixed',
142+
pointerEvents: 'auto',
143+
opacity: state.visible ? 1 : 0,
144+
backgroundColor: 'white',
145+
padding: '10px',
146+
border: '1px solid black',
147+
borderRadius: '4px',
148+
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
149+
transition: `opacity ${fadeTransition}ms ease-in-out`,
150+
zIndex: state.zIndex
151+
};
152+
153+
return createPortal(
154+
<div
155+
ref={tooltipRef}
156+
id="hover-tooltip"
157+
style={tooltipStyle}
158+
onMouseEnter={() => {
159+
isMouseOverRef.current = true;
160+
}}
161+
onMouseLeave={() => {
162+
isMouseOverRef.current = false;
163+
debouncedUpdate({ visible: false });
164+
}}
165+
>
166+
{state.content}
167+
</div>,
168+
document.body
169+
);
170+
};

0 commit comments

Comments
 (0)