{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hover-card",
  "title": "Hover Card",
  "description": "For sighted users to preview content available behind a link.",
  "dependencies": [
    "@base-ui/react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://optics.agusmayol.com.ar/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/optics/hover-card.jsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { PreviewCard as PreviewCardPrimitive } from \"@base-ui/react/preview-card\";\n\nimport { cn } from \"@/lib/utils\";\n\n// Context local para cada instancia de HoverCard para manejar estados de interacción\nconst HoverCardInstanceContext = React.createContext({\n\tshouldKeepOpenRef: { current: false },\n\tsetShouldKeepOpen: () => {},\n\tisPointerOverTriggerRef: { current: false },\n\tisPointerOverContentRef: { current: false },\n\topenHoverCard: () => {},\n\tdelay: 600,\n});\n\nfunction HoverCard({\n\topen: controlledOpen,\n\tonOpenChange,\n\tdelay = 600,\n\t...props\n} = {}) {\n\tconst [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);\n\n\tconst isControlled = controlledOpen !== undefined;\n\tconst isOpen = isControlled ? controlledOpen : uncontrolledOpen;\n\n\t// Refs locales para cada instancia de HoverCard\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 openHoverCard = 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\treturn (\n\t\t<HoverCardInstanceContext.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\topenHoverCard,\n\t\t\t\tdelay,\n\t\t\t}}\n\t\t>\n\t\t\t<PreviewCardPrimitive.Root\n\t\t\t\tdata-slot=\"hover-card\"\n\t\t\t\topen={isOpen}\n\t\t\t\tonOpenChange={handleOpenChange}\n\t\t\t\tdelay={delay}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</HoverCardInstanceContext.Provider>\n\t);\n}\n\nfunction HoverCardTrigger({\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\topenHoverCard,\n\t\tdelay,\n\t} = React.useContext(HoverCardInstanceContext);\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\topenHoverCard();\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);\n\n\t\t\tonTouchStart?.(event);\n\t\t},\n\t\t[onTouchStart, setShouldKeepOpen, openHoverCard, 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<PreviewCardPrimitive.Trigger\n\t\t\tdata-slot=\"hover-card-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 HoverCardContent({\n\tclassName = \"\",\n\tside = \"bottom\",\n\tsideOffset = 4,\n\talign = \"center\",\n\talignOffset = 4,\n\tonPointerEnter,\n\tonPointerLeave,\n\t...props\n}) {\n\tconst {\n\t\tsetShouldKeepOpen,\n\t\tisPointerOverTriggerRef,\n\t\tisPointerOverContentRef,\n\t} = React.useContext(HoverCardInstanceContext);\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<PreviewCardPrimitive.Portal data-slot=\"hover-card-portal\">\n\t\t\t<PreviewCardPrimitive.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<PreviewCardPrimitive.Popup\n\t\t\t\t\tdata-slot=\"hover-card-content\"\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-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 ring-foreground/10 bg-popover text-popover-foreground w-72 rounded-lg p-2.5 text-xs/relaxed shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden\",\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</PreviewCardPrimitive.Positioner>\n\t\t</PreviewCardPrimitive.Portal>\n\t);\n}\n\nHoverCard.displayName = \"HoverCard\";\nHoverCardTrigger.displayName = \"HoverCardTrigger\";\nHoverCardContent.displayName = \"HoverCardContent\";\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent };\n",
      "type": "registry:component",
      "target": "components/optics/hover-card.jsx"
    }
  ],
  "type": "registry:component"
}