{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tooltip",
  "title": "Tooltip",
  "description": "A popup that displays information related to an element.",
  "dependencies": [
    "@base-ui/react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://optics.agusmayol.com.ar/r/button.json",
    "https://optics.agusmayol.com.ar/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/optics/tooltip.jsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Tooltip as TooltipPrimitive } from \"@base-ui/react/tooltip\";\n\nimport { cn } from \"@/lib/utils\";\nimport { buttonVariants } from \"@/registry/optics/button\";\n\nconst TooltipProviderContext = React.createContext({\n\thasProvider: false,\n\tdelay: 400,\n});\n\n// Context local para cada instancia de Tooltip para manejar estados de interacción\nconst TooltipInstanceContext = React.createContext({\n\tshouldKeepOpenRef: { current: false },\n\tsetShouldKeepOpen: () => {},\n\tisPointerOverTriggerRef: { current: false },\n\tisPointerOverContentRef: { current: false },\n\topenTooltip: () => {},\n\tdelay: 400,\n});\n\nfunction TooltipProvider({\n\tdelay = 400,\n\tdelayDuration = undefined,\n\tskipDelayDuration = 0,\n\tchildren,\n\t...props\n} = {}) {\n\tconst resolvedDelayDuration = delayDuration ?? delay;\n\n\t// Configuración de delays:\n\t// - delay/delayDuration: 400ms por defecto para la apertura inicial del tooltip\n\t// - skipDelayDuration: 0ms para transiciones instantáneas entre tooltips cuando ya hay uno abierto\n\t// Para que skipDelayDuration funcione entre múltiples tooltips, todos deben compartir el mismo TooltipProvider\n\treturn (\n\t\t<TooltipProviderContext.Provider\n\t\t\tvalue={{ hasProvider: true, delay: resolvedDelayDuration }}\n\t\t>\n\t\t\t<TooltipPrimitive.Provider\n\t\t\t\tdata-slot=\"tooltip-provider\"\n\t\t\t\tdelay={resolvedDelayDuration}\n\t\t\t\tdelayDuration={resolvedDelayDuration}\n\t\t\t\tskipDelayDuration={skipDelayDuration}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</TooltipPrimitive.Provider>\n\t\t</TooltipProviderContext.Provider>\n\t);\n}\n\nfunction Tooltip({\n\topen: controlledOpen,\n\tonOpenChange,\n\tdelay: localDelay,\n\t...props\n} = {}) {\n\tconst { hasProvider, delay: providerDelay } = React.useContext(\n\t\tTooltipProviderContext,\n\t);\n\tconst [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);\n\n\tconst isControlled = controlledOpen !== undefined;\n\tconst isOpen = isControlled ? controlledOpen : uncontrolledOpen;\n\n\t// El delay efectivo viene del prop local, del provider, o del default (400)\n\tconst effectiveDelay = localDelay ?? providerDelay ?? 400;\n\n\t// Refs locales para cada instancia de Tooltip\n\tconst shouldKeepOpenRef = React.useRef(false);\n\tconst isPointerOverTriggerRef = React.useRef(false);\n\tconst isPointerOverContentRef = React.useRef(false);\n\n\tconst setShouldKeepOpen = React.useCallback((value) => {\n\t\tshouldKeepOpenRef.current = value;\n\t}, []);\n\n\tconst openTooltip = React.useCallback(() => {\n\t\tif (isControlled) {\n\t\t\tonOpenChange?.(true);\n\t\t} else {\n\t\t\tsetUncontrolledOpen(true);\n\t\t}\n\t}, [isControlled, onOpenChange]);\n\n\tfunction handleOpenChange(nextOpen, eventDetails) {\n\t\t// Lógica para mantener abierto si el mouse está sobre el trigger o el contenido\n\t\tif (!nextOpen) {\n\t\t\tif (\n\t\t\t\tisPointerOverTriggerRef.current ||\n\t\t\t\tisPointerOverContentRef.current ||\n\t\t\t\tshouldKeepOpenRef.current\n\t\t\t) {\n\t\t\t\tif (isControlled) {\n\t\t\t\t\tonOpenChange?.(true);\n\t\t\t\t} else {\n\t\t\t\t\tsetUncontrolledOpen(true);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (!isControlled) {\n\t\t\tsetUncontrolledOpen(nextOpen);\n\t\t}\n\n\t\tif (nextOpen) {\n\t\t\tsetShouldKeepOpen(true);\n\t\t} else {\n\t\t\tsetShouldKeepOpen(false);\n\t\t}\n\n\t\tonOpenChange?.(nextOpen, eventDetails);\n\t}\n\n\tconst tooltipRoot = (\n\t\t<TooltipInstanceContext.Provider\n\t\t\tvalue={{\n\t\t\t\tshouldKeepOpenRef,\n\t\t\t\tsetShouldKeepOpen,\n\t\t\t\tisPointerOverTriggerRef,\n\t\t\t\tisPointerOverContentRef,\n\t\t\t\topenTooltip,\n\t\t\t\tdelay: effectiveDelay,\n\t\t\t}}\n\t\t>\n\t\t\t<TooltipPrimitive.Root\n\t\t\t\tdata-slot=\"tooltip\"\n\t\t\t\topen={isOpen}\n\t\t\t\tonOpenChange={handleOpenChange}\n\t\t\t\tdelay={effectiveDelay}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</TooltipInstanceContext.Provider>\n\t);\n\n\tif (hasProvider) {\n\t\treturn tooltipRoot;\n\t}\n\n\treturn (\n\t\t<TooltipProvider delay={effectiveDelay}>{tooltipRoot}</TooltipProvider>\n\t);\n}\n\nfunction TooltipTrigger({\n\tonClick,\n\tonPointerDown,\n\tonPointerLeave,\n\tonPointerEnter,\n\tonTouchStart,\n\tonTouchEnd,\n\tonTouchCancel,\n\t...props\n} = {}) {\n\tconst {\n\t\tsetShouldKeepOpen,\n\t\tisPointerOverTriggerRef,\n\t\tisPointerOverContentRef,\n\t\topenTooltip,\n\t\tdelay,\n\t} = React.useContext(TooltipInstanceContext);\n\n\tconst longPressTimeoutRef = React.useRef(null);\n\tconst isTouchActiveRef = React.useRef(false);\n\n\tReact.useEffect(() => {\n\t\treturn () => {\n\t\t\tif (longPressTimeoutRef.current) {\n\t\t\t\tclearTimeout(longPressTimeoutRef.current);\n\t\t\t}\n\t\t};\n\t}, []);\n\n\tconst handleClick = React.useCallback(\n\t\t(event) => {\n\t\t\tevent.stopPropagation();\n\t\t\tonClick?.(event);\n\t\t},\n\t\t[onClick],\n\t);\n\n\tconst handlePointerDown = React.useCallback(\n\t\t(event) => {\n\t\t\tsetShouldKeepOpen(true);\n\t\t\tisPointerOverTriggerRef.current = true;\n\t\t\tevent.stopPropagation();\n\t\t\tonPointerDown?.(event);\n\t\t},\n\t\t[onPointerDown, setShouldKeepOpen],\n\t);\n\n\tconst handlePointerEnter = React.useCallback(\n\t\t(event) => {\n\t\t\tsetShouldKeepOpen(true);\n\t\t\tisPointerOverTriggerRef.current = true;\n\t\t\tonPointerEnter?.(event);\n\t\t},\n\t\t[onPointerEnter, setShouldKeepOpen],\n\t);\n\n\tconst handlePointerLeave = React.useCallback(\n\t\t(event) => {\n\t\t\tisPointerOverTriggerRef.current = false;\n\t\t\tif (!isPointerOverContentRef.current) {\n\t\t\t\tsetShouldKeepOpen(false);\n\t\t\t}\n\t\t\tonPointerLeave?.(event);\n\t\t},\n\t\t[onPointerLeave, setShouldKeepOpen, isPointerOverContentRef],\n\t);\n\n\tconst handleTouchStart = React.useCallback(\n\t\t(event) => {\n\t\t\tisTouchActiveRef.current = true;\n\t\t\tif (longPressTimeoutRef.current) {\n\t\t\t\tclearTimeout(longPressTimeoutRef.current);\n\t\t\t}\n\n\t\t\tlongPressTimeoutRef.current = setTimeout(() => {\n\t\t\t\tif (isTouchActiveRef.current) {\n\t\t\t\t\tsetShouldKeepOpen(true);\n\t\t\t\t\tisPointerOverTriggerRef.current = true;\n\t\t\t\t\topenTooltip();\n\t\t\t\t\t// En mobile, el preventDefault evita el menú contextual al hacer long press\n\t\t\t\t\tif (event.cancelable) event.preventDefault();\n\t\t\t\t}\n\t\t\t}, delay); // Usamos el delay del componente\n\n\t\t\tonTouchStart?.(event);\n\t\t},\n\t\t[onTouchStart, setShouldKeepOpen, openTooltip, delay],\n\t);\n\n\tconst handleTouchEnd = React.useCallback(\n\t\t(event) => {\n\t\t\tisTouchActiveRef.current = false;\n\t\t\tif (longPressTimeoutRef.current) {\n\t\t\t\tclearTimeout(longPressTimeoutRef.current);\n\t\t\t\tlongPressTimeoutRef.current = null;\n\t\t\t}\n\t\t\tonTouchEnd?.(event);\n\t\t},\n\t\t[onTouchEnd],\n\t);\n\n\tconst handleTouchCancel = React.useCallback(\n\t\t(event) => {\n\t\t\tisTouchActiveRef.current = false;\n\t\t\tif (longPressTimeoutRef.current) {\n\t\t\t\tclearTimeout(longPressTimeoutRef.current);\n\t\t\t\tlongPressTimeoutRef.current = null;\n\t\t\t}\n\t\t\tonTouchCancel?.(event);\n\t\t},\n\t\t[onTouchCancel],\n\t);\n\n\treturn (\n\t\t<TooltipPrimitive.Trigger\n\t\t\tdata-slot=\"tooltip-trigger\"\n\t\t\tonClick={handleClick}\n\t\t\tonPointerDown={handlePointerDown}\n\t\t\tonPointerEnter={handlePointerEnter}\n\t\t\tonPointerLeave={handlePointerLeave}\n\t\t\tonTouchStart={handleTouchStart}\n\t\t\tonTouchEnd={handleTouchEnd}\n\t\t\tonTouchCancel={handleTouchCancel}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction TooltipContent({\n\tclassName = \"\",\n\tside = \"top\",\n\tsideOffset = 4,\n\talign = \"center\",\n\talignOffset = 0,\n\tvariant = \"raised\",\n\tchildren = null,\n\tonPointerEnter,\n\tonPointerLeave,\n\t...props\n}) {\n\tconst {\n\t\tsetShouldKeepOpen,\n\t\tisPointerOverTriggerRef,\n\t\tisPointerOverContentRef,\n\t} = React.useContext(TooltipInstanceContext);\n\n\tconst handlePointerEnter = React.useCallback(\n\t\t(event) => {\n\t\t\tsetShouldKeepOpen(true);\n\t\t\tisPointerOverContentRef.current = true;\n\t\t\tonPointerEnter?.(event);\n\t\t},\n\t\t[onPointerEnter, setShouldKeepOpen],\n\t);\n\n\tconst handlePointerLeave = React.useCallback(\n\t\t(event) => {\n\t\t\tisPointerOverContentRef.current = false;\n\t\t\tif (!isPointerOverTriggerRef.current) {\n\t\t\t\tsetShouldKeepOpen(false);\n\t\t\t}\n\t\t\tonPointerLeave?.(event);\n\t\t},\n\t\t[onPointerLeave, setShouldKeepOpen, isPointerOverTriggerRef],\n\t);\n\n\treturn (\n\t\t<TooltipPrimitive.Portal>\n\t\t\t<TooltipPrimitive.Positioner\n\t\t\t\talign={align}\n\t\t\t\talignOffset={alignOffset}\n\t\t\t\tside={side}\n\t\t\t\tsideOffset={sideOffset}\n\t\t\t\tclassName=\"isolate z-50\"\n\t\t\t>\n\t\t\t\t<TooltipPrimitive.Popup\n\t\t\t\t\tdata-slot=\"tooltip-content\"\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 rounded-md px-3 py-1.5 text-xs **:data-[slot=kbd]:rounded-md bg-background text-foreground dark:bg-sidebar z-50 w-fit max-w-xs origin-(--transform-origin)\",\n\t\t\t\t\t\tbuttonVariants({ variant, size: \"default\", animation: \"none\" }),\n\t\t\t\t\t\tclassName,\n\t\t\t\t\t)}\n\t\t\t\t\tonPointerEnter={handlePointerEnter}\n\t\t\t\t\tonPointerLeave={handlePointerLeave}\n\t\t\t\t\t{...props}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"flex flex-col gap-2 z-50\">{children}</div>\n\n\t\t\t\t\t<TooltipPrimitive.Arrow className=\"size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-background fill-background dark:bg-sidebar dark:fill-sidebar z-40 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5\" />\n\t\t\t\t</TooltipPrimitive.Popup>\n\t\t\t</TooltipPrimitive.Positioner>\n\t\t</TooltipPrimitive.Portal>\n\t);\n}\n\nTooltip.displayName = \"Tooltip\";\nTooltipTrigger.displayName = \"TooltipTrigger\";\nTooltipContent.displayName = \"TooltipContent\";\nTooltipProvider.displayName = \"TooltipProvider\";\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n",
      "type": "registry:component",
      "target": "components/optics/tooltip.jsx"
    }
  ],
  "type": "registry:component"
}