Created
February 23, 2024 07:25
-
-
Save lunaleaps/eabd653d9864082ac1d3772dac217ab9 to your computer and use it in GitHub Desktop.
Async Tooltip Rendering Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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