/* eslint-disable @typescript-eslint/ban-ts-comment */ import { clsx } from 'clsx'; import { AriaAttributes, PropsWithChildren, ReactElement, ReactNode, cloneElement, useEffect, useId, useRef, useState, } from 'react'; import { usePopper } from 'react-popper'; import { CommonProps, Position } from '../common'; import { PositionBottom, PositionLeft, PositionRight, PositionTop, } from '../common/propsValues/position'; export type TooltipProps = PropsWithChildren<{ position?: PositionTop | PositionRight | PositionBottom | PositionLeft; label: ReactNode; id?: string; }> & CommonProps; const Tooltip = ({ position = Position.TOP, children = undefined, label, id, className, }: TooltipProps) => { const [open, setOpen] = useState(false); const anchorReference = useRef(null); const [arrowElement, setArrowElement] = useState(null); const [popperElement, setPopperElement] = useState(null); const fallbackId = useId(); const tooltipId = id ?? fallbackId; const modifiers = []; modifiers.push({ name: 'arrow', options: { element: arrowElement, options: { padding: 8, // 8px from the edges of the popper }, }, }); // This lets you displace a popper element from its reference element. modifiers.push({ name: 'offset', options: { offset: [0, 16] } }); modifiers.push({ name: 'flip', options: { fallbackPlacements: Position.TOP, }, }); const { styles, attributes, forceUpdate } = usePopper(anchorReference.current, popperElement, { placement: position, modifiers, }); // If the trigger is not visible when the position is calculated, it will be incorrect. Because this can happen repeatedly (on resize for example), // it is most simple just to always position before opening useEffect(() => { if (open && forceUpdate) { forceUpdate(); } }, [open]); return ( setOpen(true)} onFocus={() => setOpen(true)} onMouseOut={() => setOpen(false)} onBlur={() => setOpen(false)} > {children ? cloneElement(children as ReactElement>, { 'aria-describedby': `${tooltipId}-tooltip`, }) : null}