import React, { cloneElement, useRef, useState } from 'react'; import { arrow, autoUpdate, flip, offset, Placement, shift, Side, Strategy, useDismiss, useFloating, useFocus, useHover, useInteractions, useRole, } from '@floating-ui/react-dom-interactions'; import { AnimatePresence, motion } from 'framer-motion'; import classNames from 'classnames'; interface Props { rest?: number; delay?: number | Partial<{ open: number; close: number }>; alwaysOpen?: boolean; content: string | React.ReactChild; disabled?: boolean; arrow?: boolean; placement?: Placement; strategy?: Strategy; className?: string; children: React.ReactElement; } const arrowSides: Record = { top: 'bottom-[-6px] left-0', bottom: 'top-[-6px] left-0', right: 'top-0 left-[-6px]', left: 'top-0 right-[-6px]', }; export default ({ content, children, disabled = false, alwaysOpen = false, delay = 0, rest = 30, ...props }: Props) => { const arrowEl = useRef(null); const [open, setOpen] = useState(alwaysOpen || false); const { x, y, reference, floating, middlewareData, strategy, context } = useFloating({ open, placement: props.placement || 'top', strategy: props.strategy || 'absolute', middleware: [ offset(props.arrow ? 10 : 6), flip(), shift({ padding: 6 }), arrow({ element: arrowEl, padding: 6 }), ], onOpenChange: (o) => setOpen(o || alwaysOpen || false), whileElementsMounted: autoUpdate, }); const { getReferenceProps, getFloatingProps } = useInteractions([ useFocus(context), useHover(context, { restMs: rest, delay }), useRole(context, { role: 'tooltip' }), useDismiss(context), ]); const side = arrowSides[(props.placement || 'top').split('-')[0] as Side]; const { x: ax, y: ay } = middlewareData.arrow || {}; if (disabled) { return children; } return ( <> {cloneElement(children, getReferenceProps({ ref: reference, ...children.props }))} {open && ( {content} {props.arrow && (
)} )} ); };