Conversation
| if (direction === 'left' || direction === 'right') { | ||
| return { | ||
| top: (childrenHeight - tooltipHeight) / 2, | ||
| }; | ||
| } |
There was a problem hiding this comment.
This is the magic sauce that makes sure that tooltips positioned on the left and right are centered to the original content
pixelbandito
left a comment
There was a problem hiding this comment.
Sorry, it's a lot of feedback for a small component! Thanks for doing this.
I got to the point where I was speculating too much, I'll have another look after we resolve some of these questions and comments.
| children: PropTypes.node.isRequired, | ||
| closeable: PropTypes.bool, | ||
| closeIcon: PropTypes.string, | ||
| content: PropTypes.oneOfType([ |
There was a problem hiding this comment.
Just PropTypes.node handles strings, elements, arrays, arrays of nodes, etc.
There was a problem hiding this comment.
Nice. I did not know this. Thanks!
| hover: PropTypes.bool, | ||
| onClose: PropTypes.func, | ||
| open: PropTypes.bool, | ||
| text: PropTypes.string, |
There was a problem hiding this comment.
Do you need both content and text? The only difference I see is a conditional class, and you could just check typeof content === 'string' to do that.
There was a problem hiding this comment.
Yeah, I did do that. I switched over to this one, but I wasn't convinced either way. I didn't like doing the switch in either case. I'm happy to go back to a typeof check
| onClose: PropTypes.func, | ||
| open: PropTypes.bool, | ||
| text: PropTypes.string, | ||
| tooltipOpts: PropTypes.shape({ |
There was a problem hiding this comment.
Would this be clearer as just tooltipProps? afaik that's the common pattern, across libraries beyond just ours.
Also, it should maybe just be PropTypes.object, since there's really no limit to what the consumer might pass down, nor any hard dependencies on className or style
| direction: PropTypes.oneOf(directions), | ||
| hover: PropTypes.bool, | ||
| onClose: PropTypes.func, | ||
| open: PropTypes.bool, |
There was a problem hiding this comment.
Maybe defaultIsOpen instead?
Adding default makes it clearer that the component will control the isOpen once it mounts.
Adding is just helps disambiguate the verb / adjective.
| Tooltip.propTypes = { | ||
| children: PropTypes.node.isRequired, | ||
| closeable: PropTypes.bool, | ||
| closeIcon: PropTypes.string, |
There was a problem hiding this comment.
Would it make sense to force the default closeIcon and drop this prop? Seems like a fine place for consistency.
|
|
||
| const TooltipContent = (text || tooltipContent) && ( | ||
| <div ref={tooltipRef} {...tooltipProps}> | ||
| <div style={tooltipStyle} className={styles.tooltipInnerWrapper}> |
There was a problem hiding this comment.
You can probably collapse these 2 divs, right?
There was a problem hiding this comment.
Actually, no. To accomplish the tooltip arrow's "shadow", the z-index needed to be defined on an element nested within the outer ref. I'd have to walk through it again in a video to remind myself of why that was, but it wasn't possible without an extra nesting layer
| <Icon name={closeIcon} /> | ||
| </button> | ||
| )} | ||
| <div |
There was a problem hiding this comment.
If the user provides a React node, you don't need the .textContent div at all, yeah?
| ); | ||
| }; | ||
|
|
||
| EasyRichTooltip.propTypes = { |
There was a problem hiding this comment.
Are you planning for a non-rich version? If not, would EasyTooltip work?
There was a problem hiding this comment.
EasyTooltip would be just for text, and just with text can be done just using Tooltip by itself. All of the other props are mostly to make the rich content and interaction possible
|
|
||
| Tooltip.propTypes = { | ||
| children: PropTypes.node.isRequired, | ||
| closeable: PropTypes.func, |
There was a problem hiding this comment.
How about the naming convention onClose?
There was a problem hiding this comment.
I just commented about that, I should have refreshed the PR but I was in the middle of the review 😱
|
🌼🥪 |
sebastianvera
left a comment
There was a problem hiding this comment.
Pretty cool work, it looks solid!
| } | ||
| }; | ||
|
|
||
| const handleEscape = e => { |
There was a problem hiding this comment.
Since this is only being used within the following useEffect why not moving it to the if block inside?
useEffect(() => {
if (closeOnEscape) {
const handleEscape = e => {
if (closeable && e.key === 'Escape') {
closeable();
}
};
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('keydown', handleEscape);
};
}
return () => {};
}, []);| return () => {}; | ||
| }, []); | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
should we use useLayoutEffect instead, so we set the height before the browser's paint step? I've found that by using useLayoutEffect, for these cases where we need the content's width/height, we avoid flickering on the screen (we might need it if the tooltip is opened when rendering)
There was a problem hiding this comment.
Oh, that's actually something I hadn't considered. useLayoutEffect is still unknown to me, so time to dive in!
| border: none; | ||
| cursor: pointer; | ||
| color: var(--rvr-gray-lite-2); | ||
| margin: 0; |
There was a problem hiding this comment.
should this be removed?
There was a problem hiding this comment.
Maybe? I'd have to look at my CSS again (since I haven't looked at it in a week)
There was a problem hiding this comment.
Ah, I didn't see the line right after the comment. Thanks!
| position: absolute; | ||
| right: 5px; | ||
| top: 5px; | ||
| z-index: 51; |
There was a problem hiding this comment.
I'm wondering why this is 51, does it have any special meaning?
There was a problem hiding this comment.
This was just to make sure that the tooltip content, the white arrow, and the arrow shadow had correct z-index relative to each other.
This is a bit of a magical number. I should put it with the other tooltip CSS custom properties
| export { default as ExpansionPanel } from './components/ExpansionPanel'; | ||
| export { default as Accordion } from './components/Accordion'; | ||
| export { default as Avatar } from './components/Avatar'; | ||
| export { default as Tooltip, UncontrolledTooltip } from './components/Tooltip'; |
There was a problem hiding this comment.
Wrong component name here, I think I prefer UncontrolledTooltip over EasyRichTooltip, but I'm ok with either 🚀
There was a problem hiding this comment.
We have the Easy* prefixed used in other components. It's simply a pattern we've adopted for Rover. Not attached to it, but worth considering
There was a problem hiding this comment.
And yes, I'll fix the export name :)
| const [hovered, setHovered] = useState(false); | ||
| const [tooltipHeight, setTooltipHeight] = useState(0); | ||
|
|
||
| const closeFunc = useCallback(onClose || function() {}, []); |
There was a problem hiding this comment.
I think we don't need useCallback here, this is:
- not being used as a dependency for a hook
- not being pass as a prop down the tree, it's just being used in the button on line 119, so having a different reference no every render will not compromise performance or generate re-renders
Let me know if maybe I missed something 🙂
There was a problem hiding this comment.
I generally try to avoid useCallback myself. I think I added this thinking "I don't want to recreate an empty function endlessly". But I could just use an empty function const outside of the component or just use lodash/noop. Either way, I can drop the overhead of useCallback
There was a problem hiding this comment.
The only place you're passing closeFunc, it's already wrapped in a condition for onClose being truthy.
Just drop the closeFunc entirely, and you should be fine.
6316942 to
9a7132e
Compare
Finally, a way to add a basic tooltip for text as well as the configurability to display any content we want.