import * as React from 'react'; import { mergeRefs } from 'react-merge-refs'; import { useFloating, autoUpdate, offset as _offset, flip, shift, useHover, useFocus, useDismiss, useRole, useInteractions, FloatingPortal, arrow, } from '@floating-ui/react-dom-interactions'; import type { Placement } from '@floating-ui/react-dom-interactions'; import { INDEXES } from 'App/constants/zindex'; export function useTooltipState({ disabled = false, initialOpen = false, placement = 'top', offset = 5, delay, arrowRef = null, }: { disabled?: boolean; initialOpen?: boolean; placement?: Placement; offset?: number; delay?: number; arrowRef?: any; } = {}) { const [open, setOpen] = React.useState(initialOpen); const staticSide = mapPlacementSideToCSSProperty(placement); React.useEffect(() => { if (disabled) { setOpen(false); } }, [disabled]); const data = useFloating({ placement, open, onOpenChange: setOpen, whileElementsMounted: autoUpdate, middleware: [_offset(offset), flip(), shift(), arrow({ element: arrowRef })], }); const context = data.context; const hover = useHover(context, { move: false, restMs: delay, enabled: !disabled }); const focus = useFocus(context); const dismiss = useDismiss(context); const role = useRole(context, { role: 'tooltip' }); const interactions = useInteractions([hover, focus, dismiss, role]); return React.useMemo( () => ({ open, setOpen, arrowRef, ...interactions, ...data, staticSide, }), [open, setOpen, interactions, data] ); } type TooltipState = ReturnType; export const TooltipAnchor = React.forwardRef< HTMLElement, React.HTMLProps & { state: TooltipState; asChild?: boolean; } >(function TooltipAnchor({ children, state, asChild = false, ...props }, propRef) { const childrenRef = (children as any).ref; const ref = React.useMemo( () => mergeRefs([state.reference, propRef, childrenRef]), [state.reference, propRef, childrenRef] ); // `asChild` allows the user to pass any element as the anchor if (asChild && React.isValidElement(children)) { return React.cloneElement( children, state.getReferenceProps({ ref, ...props, ...children.props }) ); } return (
{children}
); }); export const FloatingTooltip = React.forwardRef< HTMLDivElement, React.HTMLProps & { state: TooltipState } >(function Tooltip({ state, ...props }, propRef) { const ref = React.useMemo(() => mergeRefs([state.floating, propRef]), [state.floating, propRef]); return ( {state.open && (
)} ); }); function mapPlacementSideToCSSProperty(placement: Placement) { const staticSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right', }[placement.split('-')[0]]; return staticSide; } export const FloatingArrow = React.forwardRef< HTMLDivElement, React.HTMLProps & { state: TooltipState } >(function Tooltip({ state, ...props }, propRef) { const ref = React.useMemo(() => state.arrowRef, [state.arrowRef, propRef]); const { x: arrowX, y: arrowY } = state.middlewareData?.arrow || { x: 0, y: 0 }; const staticSide = state.staticSide; return ( {state.open && (
)} ); });