All files / react/components/Tooltip Tooltip.jsx

100% Statements 16/16
100% Branches 7/7
100% Functions 2/2
100% Lines 16/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89                4x 4x                 4x 35x 35x 35x 35x   35x         32x 3x 3x 3x                 3x       35x                                                           4x             4x            
import React, { cloneElement, useLayoutEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import positionTip, { positionTriangle } from './position';
import { useTooltip, TooltipPopup } from '@reach/tooltip';
import { Portal } from '@reach/portal';
 
// TODO: investigate ways to allow more styling via css? the triangle
// positioning effect issue may need to be resolved to allow for this.
const TEXT_COLOR = '#FFF';
const BACKGROUND_COLOR = '#333';
 
/**
 * Generic styled tooltip component.
 * Corresponding styles: /styles/components/_tooltip.scss
 *
 * Extends implementation from @reach/tooltip:
 * https://reacttraining.com/reach-ui/tooltip
 */
const Tooltip = ({ children, label, ariaLabel, DEBUG_STYLE }) => {
  const [trigger, tooltip] = useTooltip({ DEBUG_STYLE });
  const { isVisible, triggerRect } = tooltip;
  const tooltipPopupRef = useRef();
  const triangleRef = useRef();
 
  useLayoutEffect(() => {
    // For some reason, the forwardRef for the tooltipPopup points to an undefined
    // dom element when we first render the portal containing the triangle. Using
    // an effect here will ensure that the ref is available when applying styles.
    // TODO: investigate and make issue for @reach-ui/tooltip?
    if (isVisible && tooltipPopupRef.current && triggerRect) {
      const tooltipRect = tooltipPopupRef.current.getBoundingClientRect();
      const positionCssText = positionTriangle(triggerRect, tooltipRect);
      const cssText = `
                  position: absolute;
                  width: 0;
                  height: 0;
                  ${positionCssText}
                  border-top-color: ${BACKGROUND_COLOR};
                  border-bottom-color: ${BACKGROUND_COLOR};
                  z-index: 1;
                `;
      triangleRef.current.style.cssText = cssText;
    }
  });
 
  return (
    <>
      {cloneElement(children, trigger)}
      <TooltipPopup
        {...tooltip}
        label={label}
        ariaLabel={ariaLabel}
        style={{
          background: BACKGROUND_COLOR,
          color: TEXT_COLOR,
        }}
        ref={tooltipPopupRef}
        position={positionTip}
      />
      {isVisible && (
        <Portal>
          <div
            ref={triangleRef}
            style={{
              position: 'absolute',
              width: 0,
              height: 0,
            }}
          />
        </Portal>
      )}
    </>
  );
};
 
Tooltip.propTypes = {
  children: PropTypes.node.isRequired,
  label: PropTypes.node.isRequired,
  ariaLabel: PropTypes.string,
  DEBUG_STYLE: PropTypes.bool,
};
 
Tooltip.defaultProps = {
  ariaLabel: undefined,
  DEBUG_STYLE: undefined,
};
 
export default Tooltip;