import { arrow, autoUpdate, flip, offset, Placement, shift, useFloating, useHover, useInteractions, useRole, } from '@floating-ui/react-dom-interactions' import { Transition } from '@headlessui/react' import clsx from 'clsx' import { ReactNode, useRef, useState } from 'react' // See https://floating-ui.com/docs/react-dom export function Tooltip(props: { text: string | false | undefined | null children: ReactNode className?: string placement?: Placement noTap?: boolean }) { const { text, children, className, placement = 'top', noTap } = props const arrowRef = useRef(null) const [open, setOpen] = useState(false) const { x, y, reference, floating, strategy, middlewareData, context } = useFloating({ open, onOpenChange: setOpen, whileElementsMounted: autoUpdate, placement, middleware: [ offset(8), flip(), shift({ padding: 4 }), arrow({ element: arrowRef }), ], }) const { x: arrowX, y: arrowY } = middlewareData.arrow ?? {} const { getReferenceProps, getFloatingProps } = useInteractions([ useHover(context, { mouseOnly: noTap }), useRole(context, { role: 'tooltip' }), ]) // which side of tooltip arrow is on. like: if tooltip is top-left, arrow is on bottom of tooltip const arrowSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right ', }[placement.split('-')[0]] as string return text ? ( <div className="contents"> <div className={clsx('inline-block', className)} ref={reference} {...getReferenceProps()} > {children} </div> {/* conditionally render tooltip and fade in/out */} <Transition show={open} enter="transition ease-out duration-200" enterFrom="opacity-0 " enterTo="opacity-100" leave="transition ease-in duration-150" leaveFrom="opacity-100" leaveTo="opacity-0" // div attributes role="tooltip" ref={floating} style={{ position: strategy, top: y ?? 0, left: x ?? 0 }} className="z-10 max-w-xs whitespace-normal rounded bg-slate-700 px-2 py-1 text-center text-sm text-white" {...getFloatingProps()} > {text} <div ref={arrowRef} className="absolute h-2 w-2 rotate-45 bg-slate-700" style={{ top: arrowY != null ? arrowY : '', left: arrowX != null ? arrowX : '', right: '', bottom: '', [arrowSide]: '-4px', }} /> </Transition> </div> ) : ( <>{children}</> ) }