You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 😛
The text was updated successfully, but these errors were encountered:
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:
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 haveposition: absolute
. The position defined on aposition: absolute
element is relative to its closest ancestor that also has position explicitly defined (eitherrelative
orabsolute
). 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 withposition: 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
andonMouseOut
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:
Suggested Implementation(s)
withTooltip
HOC that wraps the target element, does NOT modify its DOM in any way, but simply augments itsonMouseOver
andonMouseOut
methods.transform3d
(this is how popper works)Suggested Starter API
position
: string enum;'top'
,'bottom'
,'left'
,'right'
; default'top'
label
: string or elementtarget
: 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 😛
The text was updated successfully, but these errors were encountered: