import React, { cloneElement, useEffect, useId, useRef, useState, type ReactNode } from 'react' import ReactDOM from 'react-dom' import { type Placement } from '@popperjs/core' import classnames from 'classnames' import { usePopper } from 'react-popper' import { AnimationProvider, useAnimation } from './subcomponents/AppearanceAnim' import { isSemanticElement } from './utils/isSemanticElement' import styles from './Tooltip.module.scss' type Position = 'above' | 'below' | 'left' | 'right' type Mood = 'default' | 'informative' | 'positive' | 'cautionary' | 'highlight' export type TooltipProps = { /** * Unfortunately, the content needed to be wrapped in a div. This can sometimes * break the css layout. To get around this, we allow you to specify the css * display value directly. If you need to need to modify more values, feel free * to use the `classNameOverride` prop, but avoid it if you can. */ display?: 'block' | 'inline' | 'inline-block' | 'flex' | 'inline-flex' /** * This is more a "desired position". The tooltip will automatically change * its position, if there's not enough room to show it in the one specified. */ position?: Position /** The text content for the tooltip */ text: React.ReactNode /** * This is the interactable element that is being described by the tooltip `text` */ children?: React.ReactNode classNameOverride?: string mood?: Mood /** * Render the tooltip inside a react portal, given the ccs selector. * This is typically used for instances where the menu is a descendant of an * `overflow: scroll` or `overflow: hidden` element. */ portalSelector?: string /** * Should the tooltip be visible on the first render. Useful for visual * regression testing. */ isInitiallyVisible?: boolean animationDuration?: number } const positionToPlacement = new Map([ ['above', 'top'], ['below', 'bottom'], ['left', 'left'], ['right', 'right'], ]) // Sync with Tooltip.scss const arrowHeight = 7 const arrowWidth = 14 type TooltipContentProps = Pick & { tooltipId: string referenceElement: HTMLDivElement | null } const TooltipContent = ({ position, text, referenceElement, tooltipId, mood = 'default', }: TooltipContentProps): JSX.Element | null => { const [popperElement, setPopperElement] = useState(null) const [arrowElement, setArrowElement] = useState(null) const { styles: popperStyles, attributes } = usePopper(referenceElement, popperElement, { modifiers: [ { name: 'arrow', options: { element: arrowElement, // Ensures that the arrow doesn't go too far to the left or right // of the tooltip. padding: arrowWidth / 2 + 10, }, }, { name: 'offset', options: { offset: [0, arrowHeight + 6], }, }, { name: 'preventOverflow', options: { // Makes sure that the tooltip isn't flush up against the end of the // viewport padding: 8, altAxis: true, altBoundary: true, tetherOffset: 50, }, }, { name: 'flip', options: { padding: 8, altBoundary: true, fallbackPlacements: ['left', 'top', 'bottom', 'right'], }, }, ], placement: position ? positionToPlacement.get(position) : undefined, }) const { isVisible, isAnimIn, isAnimOut } = useAnimation() return isVisible || isAnimOut || isAnimIn ? (