Skip to content

Instantly share code, notes, and snippets.

@lunaleaps
Created February 23, 2024 07:25
Show Gist options
  • Save lunaleaps/eabd653d9864082ac1d3772dac217ab9 to your computer and use it in GitHub Desktop.
Save lunaleaps/eabd653d9864082ac1d3772dac217ab9 to your computer and use it in GitHub Desktop.
Async Tooltip Rendering Example
import * as React from 'react';
import {Pressable, Text, View} from 'react-native';
function calculateX(toolTip, target, rootView) {
// Try and center the tooltip
let toolTipX = target.x + target.width / 2 - toolTip.width / 2;
// If the tooltip is outside of root, we need to move it
if (toolTipX < rootView.x) {
toolTipX = target.x;
}
if (toolTipX + toolTip.width > rootView.x + rootView.width) {
toolTipX = rootView.x + rootView.width - toolTip.width;
}
return toolTipX - rootView.x;
}
function calculateY(toolTip, target, rootView) {
// Try and place tooltip above
let toolTipY = target.y - toolTip.height;
// If the tooltip is outside of root, we need to move it below
if (toolTipY < rootView.y) {
toolTipY = target.y + target.height;
}
return toolTipY - rootView.y;
}
function wait(ms) {
const end = Date.now() + ms;
while (Date.now() < end);
}
function ToolTip({position, targetRect, rootRect, children}) {
const ref = React.useRef(null);
const [rect, setRect] = React.useState(null);
const onLayout = React.useCallback(event => {
// Place an artificial delay to illustrate asynchronous update
wait(200);
ref.current?.measureInWindow((x, y, width, height) => {
setRect({x, y, width, height});
});
}, []);
let left = 0;
let top = 0;
if (rect != null && targetRect != null && rootRect != null) {
left = calculateX(rect, targetRect, rootRect);
top = calculateY(rect, targetRect, rootRect);
}
return (
<View
ref={ref}
onLayout={onLayout}
style={{
position: 'absolute',
borderColor: 'green',
borderRadius: 8,
borderWidth: 2,
padding: 4,
top,
left,
}}>
{children}
</View>
);
}
function Target({toolTipText, targetText, position, rootRect}) {
const targetRef = React.useRef(null);
const [rect, setRect] = React.useState(null);
const onLayout = React.useCallback(event => {
// Place an artificial delay to illustrate asynchronous update
wait(200);
targetRef.current?.measureInWindow((x, y, width, height) => {
setRect({x, y, width, height});
});
}, []);
return (
<>
<View
ref={targetRef}
onLayout={onLayout}
style={{
borderColor: 'red',
borderWidth: 2,
padding: 10,
}}>
<Text>{targetText}</Text>
</View>
<ToolTip position={position} rootRect={rootRect} targetRect={rect}>
<Text>{toolTipText}</Text>
</ToolTip>
</>
);
}
const positions = [
'top-left',
'top-right',
'center-center',
'bottom-left',
'bottom-right',
];
function Example() {
const toolTipText = 'This is the tooltip';
const targetText = 'This is the target';
const ref = React.useRef(null);
const [index, setIndex] = React.useState(0);
const [rect, setRect] = React.useState(null);
React.useEffect(() => {
const setPosition = setInterval(() => {
setIndex((index + 1) % positions.length);
}, 1000);
return () => {
clearInterval(setPosition);
};
});
const position = positions[index];
const style = getStyle(position);
const onLayout = React.useCallback(event => {
// Place an artificial delay to illustrate asynchronous update
wait(200);
ref.current?.measureInWindow((x, y, width, height) => {
setRect({x, y, width, height});
});
}, []);
return (
<>
<Text style={{margin: 20}}>Position: {position}</Text>
<View
style={{...style, flex: 1, borderWidth: 1}}
onLayout={onLayout}
ref={ref}>
<Target
toolTipText={toolTipText}
targetText={targetText}
rootRect={rect}
position={position}
/>
</View>
</>
);
}
function getStyle(position) {
switch (position) {
case 'top-left':
return {
justifyContent: 'flex-start',
alignItems: 'flex-start',
};
case 'top-center':
return {
justifyContent: 'flex-start',
alignItems: 'center',
};
case 'top-right':
return {
justifyContent: 'flex-start',
alignItems: 'flex-end',
};
case 'center-center':
return {
justifyContent: 'center',
alignItems: 'center',
};
case 'bottom-center':
return {
justifyContent: 'flex-end',
alignItems: 'center',
};
case 'bottom-left':
return {
justifyContent: 'flex-end',
alignItems: 'flex-start',
};
case 'bottom-right':
return {
alignItems: 'flex-end',
justifyContent: 'flex-end',
};
}
}
export default Example;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment