Skip to content

Component: Tooltip#155

Merged
mdespuits merged 23 commits intomasterfrom
component/Tooltip-wip
Apr 7, 2020
Merged

Component: Tooltip#155
mdespuits merged 23 commits intomasterfrom
component/Tooltip-wip

Conversation

@mdespuits
Copy link
Copy Markdown
Contributor

@mdespuits mdespuits commented Apr 2, 2020

Finally, a way to add a basic tooltip for text as well as the configurability to display any content we want.

image
image

Comment on lines +63 to +89
if (direction === 'left' || direction === 'right') {
return {
top: (childrenHeight - tooltipHeight) / 2,
};
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the magic sauce that makes sure that tooltips positioned on the left and right are centered to the original content

Copy link
Copy Markdown
Contributor

@pixelbandito pixelbandito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/components/Tooltip/index.js
Comment thread src/components/Tooltip/index.js Outdated
children: PropTypes.node.isRequired,
closeable: PropTypes.bool,
closeIcon: PropTypes.string,
content: PropTypes.oneOfType([
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just PropTypes.node handles strings, elements, arrays, arrays of nodes, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I did not know this. Thanks!

Comment thread src/components/Tooltip/index.js Outdated
hover: PropTypes.bool,
onClose: PropTypes.func,
open: PropTypes.bool,
text: PropTypes.string,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/components/Tooltip/index.js Outdated
onClose: PropTypes.func,
open: PropTypes.bool,
text: PropTypes.string,
tooltipOpts: PropTypes.shape({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/components/Tooltip/index.js Outdated
direction: PropTypes.oneOf(directions),
hover: PropTypes.bool,
onClose: PropTypes.func,
open: PropTypes.bool,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/components/Tooltip/index.js
Comment thread src/components/Tooltip/index.js
Comment thread src/components/Tooltip/index.js Outdated
Tooltip.propTypes = {
children: PropTypes.node.isRequired,
closeable: PropTypes.bool,
closeIcon: PropTypes.string,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to force the default closeIcon and drop this prop? Seems like a fine place for consistency.

Comment thread src/components/Tooltip/index.js Outdated

const TooltipContent = (text || tooltipContent) && (
<div ref={tooltipRef} {...tooltipProps}>
<div style={tooltipStyle} className={styles.tooltipInnerWrapper}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably collapse these 2 divs, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ☹️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment thread src/components/Tooltip/index.js Outdated
<Icon name={closeIcon} />
</button>
)}
<div
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user provides a React node, you don't need the .textContent div at all, yeah?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope!

@mdespuits mdespuits requested a review from pixelbandito April 3, 2020 21:22
Comment thread src/components/Tooltip/index.js
);
};

EasyRichTooltip.propTypes = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning for a non-rich version? If not, would EasyTooltip work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@cision cision deleted a comment from mdespuits Apr 6, 2020
Comment thread src/components/Tooltip/index.js Outdated

Tooltip.propTypes = {
children: PropTypes.node.isRequired,
closeable: PropTypes.func,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the naming convention onClose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just commented about that, I should have refreshed the PR but I was in the middle of the review 😱

@pixelbandito
Copy link
Copy Markdown
Contributor

🌼🥪

Copy link
Copy Markdown
Contributor

@sebastianvera sebastianvera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty cool work, it looks solid!

Comment thread src/components/Tooltip/index.js Outdated
}
};

const handleEscape = e => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 () => {};
  }, []);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. 👍

Comment thread src/components/Tooltip/index.js Outdated
return () => {};
}, []);

useEffect(() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's actually something I hadn't considered. useLayoutEffect is still unknown to me, so time to dive in!

Comment thread src/components/Tooltip/index.js Outdated
Comment thread src/components/Tooltip/story.js Outdated
Comment thread src/components/Tooltip/style.css Outdated
border: none;
cursor: pointer;
color: var(--rvr-gray-lite-2);
margin: 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? I'd have to look at my CSS again (since I haven't looked at it in a week)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't see the line right after the comment. Thanks!

position: absolute;
right: 5px;
top: 5px;
z-index: 51;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why this is 51, does it have any special meaning?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/index.js Outdated
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';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong component name here, I think I prefer UncontrolledTooltip over EasyRichTooltip, but I'm ok with either 🚀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yes, I'll fix the export name :)

Comment thread src/components/Tooltip/index.js Outdated
const [hovered, setHovered] = useState(false);
const [tooltipHeight, setTooltipHeight] = useState(0);

const closeFunc = useCallback(onClose || function() {}, []);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need useCallback here, this is:

  1. not being used as a dependency for a hook
  2. 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 🙂

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@mdespuits mdespuits force-pushed the component/Tooltip-wip branch from 6316942 to 9a7132e Compare April 7, 2020 18:46
@mdespuits mdespuits merged commit 4ff12f5 into master Apr 7, 2020
@mdespuits mdespuits deleted the component/Tooltip-wip branch April 7, 2020 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants