2022-08-12 19:04:23 +00:00
|
|
|
import {
|
|
|
|
arrow,
|
|
|
|
autoUpdate,
|
|
|
|
flip,
|
|
|
|
offset,
|
|
|
|
Placement,
|
|
|
|
shift,
|
|
|
|
useFloating,
|
2022-08-13 03:35:08 +00:00
|
|
|
useHover,
|
|
|
|
useInteractions,
|
|
|
|
useRole,
|
|
|
|
} from '@floating-ui/react-dom-interactions'
|
|
|
|
import { Transition } from '@headlessui/react'
|
|
|
|
import { ReactNode, useRef, useState } from 'react'
|
2022-08-12 19:04:23 +00:00
|
|
|
|
|
|
|
// 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
|
2022-09-01 03:52:12 +00:00
|
|
|
noFade?: boolean
|
2022-08-12 19:04:23 +00:00
|
|
|
}) {
|
2022-09-01 03:52:12 +00:00
|
|
|
const { text, children, className, placement = 'top', noTap, noFade } = props
|
2022-08-12 19:04:23 +00:00
|
|
|
|
|
|
|
const arrowRef = useRef(null)
|
|
|
|
|
2022-08-13 03:35:08 +00:00
|
|
|
const [open, setOpen] = useState(false)
|
|
|
|
|
|
|
|
const { x, y, reference, floating, strategy, middlewareData, context } =
|
2022-08-12 19:04:23 +00:00
|
|
|
useFloating({
|
2022-08-13 03:35:08 +00:00
|
|
|
open,
|
|
|
|
onOpenChange: setOpen,
|
2022-08-12 19:04:23 +00:00
|
|
|
whileElementsMounted: autoUpdate,
|
|
|
|
placement,
|
|
|
|
middleware: [
|
|
|
|
offset(8),
|
|
|
|
flip(),
|
|
|
|
shift({ padding: 4 }),
|
|
|
|
arrow({ element: arrowRef }),
|
|
|
|
],
|
|
|
|
})
|
|
|
|
|
|
|
|
const { x: arrowX, y: arrowY } = middlewareData.arrow ?? {}
|
|
|
|
|
2022-08-13 03:35:08 +00:00
|
|
|
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
|
|
useHover(context, { mouseOnly: noTap }),
|
|
|
|
useRole(context, { role: 'tooltip' }),
|
|
|
|
])
|
2022-08-12 19:04:23 +00:00
|
|
|
// 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
|
2022-06-18 03:28:16 +00:00
|
|
|
|
|
|
|
return text ? (
|
2022-08-27 20:46:35 +00:00
|
|
|
<>
|
|
|
|
<span className={className} ref={reference} {...getReferenceProps()}>
|
2022-08-12 19:04:23 +00:00
|
|
|
{children}
|
2022-08-27 20:46:35 +00:00
|
|
|
</span>
|
2022-08-13 03:35:08 +00:00
|
|
|
{/* conditionally render tooltip and fade in/out */}
|
|
|
|
<Transition
|
|
|
|
show={open}
|
2022-09-01 03:52:12 +00:00
|
|
|
enter="transition ease-out duration-50"
|
|
|
|
enterFrom="opacity-0"
|
2022-08-13 03:35:08 +00:00
|
|
|
enterTo="opacity-100"
|
2022-09-01 03:52:12 +00:00
|
|
|
leave={noFade ? '' : 'transition ease-in duration-150'}
|
2022-08-13 03:35:08 +00:00
|
|
|
leaveFrom="opacity-100"
|
|
|
|
leaveTo="opacity-0"
|
|
|
|
// div attributes
|
2022-08-12 19:04:23 +00:00
|
|
|
role="tooltip"
|
|
|
|
ref={floating}
|
|
|
|
style={{ position: strategy, top: y ?? 0, left: x ?? 0 }}
|
2022-08-20 20:05:33 +00:00
|
|
|
className="z-10 max-w-xs whitespace-normal rounded bg-slate-700 px-2 py-1 text-center text-sm text-white"
|
2022-08-13 03:35:08 +00:00
|
|
|
{...getFloatingProps()}
|
2022-08-12 19:04:23 +00:00
|
|
|
>
|
|
|
|
{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',
|
|
|
|
}}
|
|
|
|
/>
|
2022-08-13 03:35:08 +00:00
|
|
|
</Transition>
|
2022-08-27 20:46:35 +00:00
|
|
|
</>
|
2022-06-18 03:28:16 +00:00
|
|
|
) : (
|
|
|
|
<>{children}</>
|
|
|
|
)
|
|
|
|
}
|