"use client" import * as React from "react" import { useFloating, autoUpdate, offset, flip, shift, useHover, useFocus, useDismiss, useRole, useInteractions, useMergeRefs, FloatingPortal, type Placement, type UseFloatingReturn, type ReferenceType, FloatingDelayGroup, } from "@floating-ui/react" import "@/components/tiptap-ui-primitive/tooltip/tooltip.scss" interface TooltipProviderProps { children: React.ReactNode initialOpen?: boolean placement?: Placement open?: boolean onOpenChange?: (open: boolean) => void delay?: number closeDelay?: number timeout?: number useDelayGroup?: boolean } interface TooltipTriggerProps extends Omit, "ref"> { asChild?: boolean children: React.ReactNode } interface TooltipContentProps extends Omit, "ref"> { children?: React.ReactNode portal?: boolean portalProps?: Omit, "children"> } interface TooltipContextValue extends UseFloatingReturn { open: boolean setOpen: (open: boolean) => void getReferenceProps: ( userProps?: React.HTMLProps ) => Record getFloatingProps: ( userProps?: React.HTMLProps ) => Record } function useTooltip({ initialOpen = false, placement = "top", open: controlledOpen, onOpenChange: setControlledOpen, delay = 600, closeDelay = 0, }: Omit = {}) { const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen) const open = controlledOpen ?? uncontrolledOpen const setOpen = setControlledOpen ?? setUncontrolledOpen const data = useFloating({ placement, open, onOpenChange: setOpen, whileElementsMounted: autoUpdate, middleware: [ offset(4), flip({ crossAxis: placement.includes("-"), fallbackAxisSideDirection: "start", padding: 4, }), shift({ padding: 4 }), ], }) const context = data.context const hover = useHover(context, { mouseOnly: true, move: false, restMs: delay, enabled: controlledOpen == null, delay: { close: closeDelay, }, }) 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] ) } const TooltipContext = React.createContext(null) function useTooltipContext() { const context = React.useContext(TooltipContext) if (context == null) { throw new Error("Tooltip components must be wrapped in ") } return context } export function Tooltip({ children, ...props }: TooltipProviderProps) { const tooltip = useTooltip(props) if (!props.useDelayGroup) { return ( {children} ) } return ( {children} ) } export const TooltipTrigger = React.forwardRef< HTMLElement, TooltipTriggerProps >(function TooltipTrigger({ children, asChild = false, ...props }, propRef) { const context = useTooltipContext() const childrenRef = React.isValidElement(children) ? parseInt(React.version, 10) >= 19 ? // eslint-disable-next-line @typescript-eslint/no-explicit-any (children as { props: { ref?: React.Ref } }).props.ref : // eslint-disable-next-line @typescript-eslint/no-explicit-any (children as any).ref : undefined const ref = useMergeRefs([context.refs.setReference, propRef, childrenRef]) if (asChild && React.isValidElement(children)) { const dataAttributes = { "data-tooltip-state": context.open ? "open" : "closed", } return React.cloneElement( children, context.getReferenceProps({ ref, ...props, ...(typeof children.props === "object" ? children.props : {}), ...dataAttributes, }) ) } return ( ) }) export const TooltipContent = React.forwardRef< HTMLDivElement, TooltipContentProps >(function TooltipContent( { style, children, portal = true, portalProps = {}, ...props }, propRef ) { const context = useTooltipContext() const ref = useMergeRefs([context.refs.setFloating, propRef]) if (!context.open) return null const content = (
{children}
) if (portal) { return {content} } return content }) Tooltip.displayName = "Tooltip" TooltipTrigger.displayName = "TooltipTrigger" TooltipContent.displayName = "TooltipContent"