import { useTheme } from '@emotion/react'; import type { Placement } from '@floating-ui/react'; import { autoUpdate, flip, FloatingPortal, offset, shift, useDismiss, useFloating, useFocus, useHover, useInteractions, useMergeRefs, useRole, } from '@floating-ui/react'; import * as React from 'react'; type FloatingUITooltipOptions = { initialOpen?: boolean; onOpenChange?: (open: boolean) => void; open?: boolean; placement?: Placement; }; function useFloatingUITooltip({ initialOpen = false, placement = 'top', open: controlledOpen, onOpenChange: setControlledOpen, }: FloatingUITooltipOptions = {}) { const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen); const open = controlledOpen ?? uncontrolledOpen; const setOpen = setControlledOpen ?? setUncontrolledOpen; const data = useFloating({ middleware: [ offset(5), flip({ fallbackAxisSideDirection: 'start', }), shift({ padding: 5 }), ], onOpenChange: setOpen, open, placement, whileElementsMounted: autoUpdate, }); const context = data.context; const hover = useHover(context, { enabled: controlledOpen == null, move: false, }); const focus = useFocus(context, { enabled: controlledOpen == null, }); const dismiss = useDismiss(context); const role = useRole(context, { role: 'tooltip' }); const interactions = useInteractions([hover, focus, dismiss, role]); return React.useMemo( () => ({ open, setOpen, ...interactions, ...data, }), [open, setOpen, interactions, data], ); } type ContextType = ReturnType | null; const FloatingUITooltipContext = React.createContext(null); const useFloatingUITooltipContext = () => { const context = React.useContext(FloatingUITooltipContext); if (context == null) { throw new Error('Tooltip components must be wrapped in '); } return context; }; export function FloatingUITooltip({ children, ...options }: { children: React.ReactNode } & FloatingUITooltipOptions) { // This can accept any props as options, e.g. `placement`, // or other positioning options. const tooltip = useFloatingUITooltip(options); return ( {children} ); } export const FloatingUITooltipTrigger = React.forwardRef>( function FloatingUITooltipTrigger({ children, ...props }, propRef) { const context = useFloatingUITooltipContext(); const childrenRef = (children as unknown as { ref: React.Ref }).ref; const ref = useMergeRefs([context.refs.setReference, propRef, childrenRef]); if (React.isValidElement(children)) { return React.cloneElement( children, context.getReferenceProps({ ref, ...props, ...children.props, 'data-state': context.open ? 'open' : 'closed', } as never), ); } return null; }, ); export const FloatingUITooltipContent = React.forwardRef< HTMLDivElement, React.HTMLProps >(function FloatingUITooltipContent(props, propRef) { const context = useFloatingUITooltipContext(); const ref = useMergeRefs([context.refs.setFloating, propRef]); const theme = useTheme(); return ( {context.open && (
)} ); });