Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tooltip component #73

Closed
arizzitano opened this issue Nov 13, 2017 · 3 comments
Closed

Tooltip component #73

arizzitano opened this issue Nov 13, 2017 · 3 comments

Comments

@arizzitano
Copy link
Contributor

It's time to solve one of the Hard Problems of frontend: let's add a tooltip component! @MichaelRoytman started working on this in #64, but we ran into some tricky gotchas and I wanted to capture all of it here for future iterations.

#thestruggle

At their core, tooltips are only a little bit difficult. You have to make sure they:

  • appear at an appropriate z-index
  • have a cute little triangular arrow centered relative to both the target and the tooltip text (depending which direction the tooltip is pointing), pointing towards the target, and pointing away from the tooltip text
  • are positioned appropriately relative to their target element
    • if positioned to the top or bottom it should be horizontally centered; if to the left or right it should be vertically centered
    • all positioning must take the size of the tooltip itself into account. We cannot count on a tooltip always being the same size -- it may have varying heights or widths based on what's inside it
  • have an appropriate max-width so they text-wrap when it makes sense
  • allow consumers to insert complex HTML (tbh I'd rather not, so let's avoid this as long as we can)

But CSS though

The tooltip problem gets WAY harder when we take reusability and positioning into account. Tooltips' position must be set explicitly -- although it's possible to do via CSS alone, it's not practical as it relies on :before and :after pseudoelements. If the target element already has pseudoelements, it won't work.

Anyway, for an explicit (that is, left: 343px; top: 123px;) position to work, the tooltip element itself must have position: absolute. The position defined on a position: absolute element is relative to its closest ancestor that also has position explicitly defined (either relative or absolute). To ensure predictable behavior when positioning the tooltip element itself, the tooltip component should be responsible for rendering its container as well (likely just a container element with position: relative. I'd like to avoid inserting this container element into the DOM inline with the target element, though -- if the target element itself is absolutely positioned, or if it sits within a group of statically positioned elements, it could cause some display weirdness.

We'll probably want to use something like getBoundingClientRect to retrieve the position of the target element, then do some fancy math based on the tooltip's size (we'll have to render it outside the viewport to get its size, then cache the size) to center it.

Also what about event handling

The tooltip's target element needs onMouseOver and onMouseOut handlers to show and hide the tooltip at the appropriate times. It's preferable for the consumer not to need to explicitly define these handlers on the target element, and have the tooltip itself handle everything.

We can get around this in one of two ways:

  • tooltip wraps the target element and passes in the handlers
  • tooltip is implemented as a HOC to augment existing handlers, removing the need for excess, unnecessary DOM elements (note: React 16 can render arrays of elements with no need for a top level wrapper, so maybe this isn't as much of a problem?)

Suggested Implementation(s)

  • VERY STRONG RECOMMENDATION: tooltip uses Bootstrap's classes, but NOT Bootstrap's javascript
  • RECOMMENDATION: implement a withTooltip HOC that wraps the target element, does NOT modify its DOM in any way, but simply augments its onMouseOver and onMouseOut methods.
  • RECOMMENDATION: Use a portal to render the tooltip elsewhere in the document, so we don't have to worry about absolute/relative positioning. This may require an upgrade to React 16 (React 16 + Enzyme upgrade #45) but is the lowest-CSS solution.
  • Tooltip renders alongside its target and is positioned using transform3d (this is how popper works)
  • Tooltip is implemented as a wrapper component around the target element
    • we can't get a ref to the target element if it's stateless, so we need to render it within an inline-block positioned div
    • setting an explicit position on this target wrapper could mess with other CSS adjacent to it
    • this is just kind of a janky solution in general -- it results in a lot of extra, meaningless DOM elements

Suggested Starter API

position: string enum; 'top', 'bottom', 'left', 'right'; default 'top'
label: string or element
target: element (or a ref to an element?)

I want to say this component is dependent on #45. Upgrading React in Paragon means we ought to update it across all our projects that use it. I don't think we have any warnings in any of our React code, so the upgrade should go smoothly.

cc @MichaelRoytman and @jaebradley -- maybe we can put our heads together and hack on this at an office hours sometime 😛

@jaebradley
Copy link
Contributor

Useful React tooltip libraries to look at:

@thallada
Copy link
Contributor

All of those problems seem like something other people should have solved so I definitely think we should use some existing library.

In addition to @jaebradley's list there is react-tippy which is a react wrapper around Tippy.js, and react-popper which wraps Popper.js

@abutterworth
Copy link
Contributor

Tooltip from React Bootstrap has been added to the library.

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

No branches or pull requests

4 participants