{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hover-card",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["@radix-ui/react-popover", "@radix-ui/react-slot", "motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "hover-card.tsx",
      "content": "\"use client\";\n\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst controlCornerClassName =\n  \"rounded-lg supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[11px]\";\n\nconst surfaceCornerClassName =\n  \"rounded-lg supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[12px]\";\n\nconst hoverCardThemeClassName =\n  \"[--hc-surface:#ffffff] [--hc-foreground:#111111] [--hc-border:#e3e7ec] [--hc-ring:rgba(17,17,17,0.16)] dark:[--hc-surface:#111111] dark:[--hc-foreground:#f6f3ec] dark:[--hc-border:#2b2a25] dark:[--hc-ring:rgba(246,243,236,0.18)]\";\n\nconst hoverCardPanelClassName = cn(\n  surfaceCornerClassName,\n  \"relative z-50 w-72 border border-[color:var(--hc-border)] bg-[color:var(--hc-surface)] p-4 text-[color:var(--hc-foreground)] shadow-2xl outline-none\"\n);\n\nconst hoverCardTriggerClassName = cn(\n  controlCornerClassName,\n  \"inline-flex min-h-9 cursor-pointer items-center px-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:color-mix(in_oklch,var(--hc-ring),transparent_40%)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--hc-surface)]\"\n);\n\ntype Side = \"top\" | \"right\" | \"bottom\" | \"left\";\ntype Align = \"start\" | \"center\" | \"end\";\ntype CollisionPadding = React.ComponentPropsWithoutRef<\n  typeof PopoverPrimitive.Content\n>[\"collisionPadding\"];\n\ntype HoverCardContextValue = {\n  open: boolean;\n  contentId: string;\n  triggerId: string;\n  setContentNode: (node: HTMLDivElement | null) => void;\n  setTriggerNode: (node: HTMLElement | null) => void;\n  handleFocusEnd: (event: React.FocusEvent<HTMLElement>) => void;\n  handleFocusStart: () => void;\n  handleHoverEnd: (event: React.PointerEvent<HTMLElement>) => void;\n  handleHoverStart: (event: React.PointerEvent<HTMLElement>) => void;\n};\n\nconst HoverCardContext = React.createContext<HoverCardContextValue | null>(\n  null\n);\n\nconst initialOffset: Record<Side, { x: number; y: number }> = {\n  top: { x: 0, y: 8 },\n  right: { x: -8, y: 0 },\n  bottom: { x: 0, y: -8 },\n  left: { x: 8, y: 0 },\n};\n\nconst hoverBridgeStyles: Record<Side, string> = {\n  top: \"before:pointer-events-auto before:absolute before:right-0 before:bottom-[calc(var(--hover-card-bridge-size)*-1)] before:left-0 before:h-[var(--hover-card-bridge-size)] before:bg-transparent before:content-['']\",\n  right:\n    \"before:pointer-events-auto before:absolute before:top-0 before:bottom-0 before:left-[calc(var(--hover-card-bridge-size)*-1)] before:w-[var(--hover-card-bridge-size)] before:bg-transparent before:content-['']\",\n  bottom:\n    \"before:pointer-events-auto before:absolute before:top-[calc(var(--hover-card-bridge-size)*-1)] before:right-0 before:left-0 before:h-[var(--hover-card-bridge-size)] before:bg-transparent before:content-['']\",\n  left: \"before:pointer-events-auto before:absolute before:top-0 before:right-[calc(var(--hover-card-bridge-size)*-1)] before:bottom-0 before:w-[var(--hover-card-bridge-size)] before:bg-transparent before:content-['']\",\n};\n\nconst assignRef = <T,>(ref: React.ForwardedRef<T>, value: T | null) => {\n  if (typeof ref === \"function\") {\n    ref(value);\n    return;\n  }\n\n  if (ref) {\n    ref.current = value;\n  }\n};\n\nconst useHoverCard = () => {\n  const context = React.useContext(HoverCardContext);\n\n  if (!context) {\n    throw new Error(\"HoverCard components must be used inside HoverCard\");\n  }\n\n  return context;\n};\n\ntype HoverCardProps = {\n  className?: string;\n  openDelay?: number;\n  closeDelay?: number;\n  children?: React.ReactNode;\n};\n\nconst HoverCard = ({\n  className,\n  openDelay = 80,\n  closeDelay = 120,\n  children,\n}: HoverCardProps) => {\n  const [open, setOpen] = React.useState(false);\n  const timeoutRef = React.useRef<number | null>(null);\n  const triggerRef = React.useRef<HTMLElement | null>(null);\n  const contentRef = React.useRef<HTMLDivElement | null>(null);\n  const reactId = React.useId();\n\n  const clearTimer = React.useCallback(() => {\n    if (timeoutRef.current) {\n      window.clearTimeout(timeoutRef.current);\n      timeoutRef.current = null;\n    }\n  }, []);\n\n  const containsInteractiveNode = React.useCallback((node: Node | null) => {\n    return Boolean(\n      node &&\n        (triggerRef.current?.contains(node) ||\n          contentRef.current?.contains(node))\n    );\n  }, []);\n\n  const hasFocusWithin = React.useCallback(() => {\n    const activeElement = document.activeElement;\n    return (\n      activeElement instanceof Node && containsInteractiveNode(activeElement)\n    );\n  }, [containsInteractiveNode]);\n\n  const scheduleOpen = React.useCallback(() => {\n    clearTimer();\n    timeoutRef.current = window.setTimeout(() => setOpen(true), openDelay);\n  }, [clearTimer, openDelay]);\n\n  const scheduleClose = React.useCallback(() => {\n    clearTimer();\n    timeoutRef.current = window.setTimeout(() => setOpen(false), closeDelay);\n  }, [clearTimer, closeDelay]);\n\n  const handleHoverStart = React.useCallback(\n    (event: React.PointerEvent<HTMLElement>) => {\n      if (event.pointerType === \"touch\") return;\n\n      if (open) {\n        clearTimer();\n        return;\n      }\n\n      scheduleOpen();\n    },\n    [clearTimer, open, scheduleOpen]\n  );\n\n  const handleHoverEnd = React.useCallback(\n    (event: React.PointerEvent<HTMLElement>) => {\n      if (event.pointerType === \"touch\") return;\n\n      const nextTarget = event.relatedTarget;\n\n      if (nextTarget instanceof Node && containsInteractiveNode(nextTarget)) {\n        return;\n      }\n\n      if (hasFocusWithin()) return;\n\n      scheduleClose();\n    },\n    [containsInteractiveNode, hasFocusWithin, scheduleClose]\n  );\n\n  const handleFocusStart = React.useCallback(() => {\n    clearTimer();\n    setOpen(true);\n  }, [clearTimer]);\n\n  const handleFocusEnd = React.useCallback(\n    (event: React.FocusEvent<HTMLElement>) => {\n      const nextTarget = event.relatedTarget;\n\n      if (nextTarget instanceof Node && containsInteractiveNode(nextTarget)) {\n        return;\n      }\n\n      clearTimer();\n      setOpen(false);\n    },\n    [clearTimer, containsInteractiveNode]\n  );\n\n  React.useEffect(() => clearTimer, [clearTimer]);\n\n  return (\n    <HoverCardContext.Provider\n      value={{\n        open,\n        contentId: `${reactId}-content`,\n        triggerId: `${reactId}-trigger`,\n        setContentNode: (node) => {\n          contentRef.current = node;\n        },\n        setTriggerNode: (node) => {\n          triggerRef.current = node;\n        },\n        handleFocusEnd,\n        handleFocusStart,\n        handleHoverEnd,\n        handleHoverStart,\n      }}\n    >\n      <PopoverPrimitive.Root\n        modal={false}\n        onOpenChange={(nextOpen) => {\n          clearTimer();\n          setOpen(nextOpen);\n        }}\n        open={open}\n      >\n        <span\n          className={cn(\n            hoverCardThemeClassName,\n            \"inline-flex w-fit\",\n            className\n          )}\n        >\n          {children}\n        </span>\n      </PopoverPrimitive.Root>\n    </HoverCardContext.Provider>\n  );\n};\n\ntype HoverCardTriggerProps = React.ComponentPropsWithoutRef<\"button\"> & {\n  asChild?: boolean;\n};\n\nconst HoverCardTrigger = React.forwardRef<\n  HTMLButtonElement,\n  HoverCardTriggerProps\n>(\n  (\n    {\n      asChild,\n      className,\n      onBlur,\n      onFocus,\n      onPointerEnter,\n      onPointerLeave,\n      type,\n      ...props\n    },\n    ref\n  ) => {\n    const {\n      open,\n      contentId,\n      triggerId,\n      setTriggerNode,\n      handleFocusEnd,\n      handleFocusStart,\n      handleHoverEnd,\n      handleHoverStart,\n    } = useHoverCard();\n    const Comp = asChild ? Slot : \"button\";\n\n    return (\n      <PopoverPrimitive.Anchor asChild>\n        <Comp\n          aria-controls={contentId}\n          aria-expanded={open}\n          aria-haspopup=\"dialog\"\n          className={cn(\n            asChild\n              ? \"inline-flex cursor-pointer items-center focus-visible:outline-none\"\n              : hoverCardTriggerClassName,\n            className\n          )}\n          data-state={open ? \"open\" : \"closed\"}\n          id={triggerId}\n          onBlur={(event) => {\n            onBlur?.(event);\n\n            if (event.defaultPrevented) {\n              return;\n            }\n\n            handleFocusEnd(event);\n          }}\n          onFocus={(event) => {\n            onFocus?.(event);\n\n            if (event.defaultPrevented) {\n              return;\n            }\n\n            handleFocusStart();\n          }}\n          onPointerEnter={(event) => {\n            onPointerEnter?.(event);\n\n            if (event.defaultPrevented) {\n              return;\n            }\n\n            handleHoverStart(event);\n          }}\n          onPointerLeave={(event) => {\n            onPointerLeave?.(event);\n\n            if (event.defaultPrevented) {\n              return;\n            }\n\n            handleHoverEnd(event);\n          }}\n          ref={(node: HTMLButtonElement | null) => {\n            setTriggerNode(node);\n            assignRef(ref, node);\n          }}\n          type={asChild ? undefined : (type ?? \"button\")}\n          {...props}\n        />\n      </PopoverPrimitive.Anchor>\n    );\n  }\n);\nHoverCardTrigger.displayName = \"HoverCardTrigger\";\n\ntype HoverCardContentProps = Omit<\n  React.ComponentPropsWithoutRef<typeof motion.div>,\n  \"initial\" | \"animate\" | \"exit\" | \"transition\"\n> & {\n  align?: Align;\n  alignOffset?: number;\n  avoidCollisions?: boolean;\n  collisionPadding?: CollisionPadding;\n  side?: Side;\n  sideOffset?: number;\n};\n\nconst HoverCardContentBody = React.forwardRef<\n  HTMLDivElement,\n  HoverCardContentProps\n>(\n  (\n    {\n      className,\n      children,\n      style,\n      onBlur,\n      onFocus,\n      onPointerEnter,\n      onPointerLeave,\n      align = \"center\",\n      alignOffset = 0,\n      avoidCollisions = true,\n      collisionPadding = 12,\n      side = \"bottom\",\n      sideOffset = 12,\n      ...props\n    },\n    ref\n  ) => {\n    const {\n      contentId,\n      triggerId,\n      open,\n      setContentNode,\n      handleFocusEnd,\n      handleFocusStart,\n      handleHoverEnd,\n      handleHoverStart,\n    } = useHoverCard();\n\n    return (\n      <PopoverPrimitive.Content\n        align={align}\n        alignOffset={alignOffset}\n        asChild\n        avoidCollisions={avoidCollisions}\n        collisionPadding={collisionPadding}\n        onCloseAutoFocus={(event) => event.preventDefault()}\n        onOpenAutoFocus={(event) => event.preventDefault()}\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <motion.div\n          animate={{ opacity: 1, x: 0, y: 0, scale: 1, filter: \"blur(0px)\" }}\n          aria-labelledby={triggerId}\n          className={cn(\n            hoverCardThemeClassName,\n            hoverCardPanelClassName,\n            hoverBridgeStyles[side],\n            className\n          )}\n          data-state={open ? \"open\" : \"closed\"}\n          exit={{\n            opacity: 0,\n            scale: 0.97,\n            filter: \"blur(6px)\",\n            ...initialOffset[side],\n          }}\n          id={contentId}\n          initial={{\n            opacity: 0,\n            scale: 0.96,\n            filter: \"blur(8px)\",\n            ...initialOffset[side],\n          }}\n          onBlur={(event) => {\n            onBlur?.(event);\n\n            if (event.defaultPrevented) {\n              return;\n            }\n\n            handleFocusEnd(event);\n          }}\n          onFocus={(event) => {\n            onFocus?.(event);\n\n            if (event.defaultPrevented) {\n              return;\n            }\n\n            handleFocusStart();\n          }}\n          onPointerEnter={(event) => {\n            onPointerEnter?.(event);\n\n            if (event.defaultPrevented) {\n              return;\n            }\n\n            handleHoverStart(event);\n          }}\n          onPointerLeave={(event) => {\n            onPointerLeave?.(event);\n\n            if (event.defaultPrevented) {\n              return;\n            }\n\n            handleHoverEnd(event);\n          }}\n          ref={(node: HTMLDivElement | null) => {\n            setContentNode(node);\n            assignRef(ref, node);\n          }}\n          style={\n            {\n              \"--hover-card-bridge-size\": `${sideOffset}px`,\n              transformOrigin: \"var(--radix-popover-content-transform-origin)\",\n              ...style,\n            } as React.CSSProperties\n          }\n          transition={{\n            type: \"spring\",\n            stiffness: 280,\n            damping: 24,\n            mass: 0.55,\n          }}\n          {...props}\n        >\n          {children}\n        </motion.div>\n      </PopoverPrimitive.Content>\n    );\n  }\n);\nHoverCardContentBody.displayName = \"HoverCardContentBody\";\n\nconst HoverCardContent = React.forwardRef<\n  HTMLDivElement,\n  HoverCardContentProps\n>((props, ref) => {\n  const { open } = useHoverCard();\n\n  return (\n    <AnimatePresence>\n      {open ? (\n        <PopoverPrimitive.Portal forceMount>\n          <HoverCardContentBody ref={ref} {...props} />\n        </PopoverPrimitive.Portal>\n      ) : null}\n    </AnimatePresence>\n  );\n});\nHoverCardContent.displayName = \"HoverCardContent\";\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Hover Card",
  "description": "Inline hover card with hover-only timing delays, instant focus reveal, collision-aware Popover positioning, Slot-based custom triggers, and a spring-driven content panel."
}
