{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hover-card",
  "type": "registry:ui",
  "title": "Hover Card",
  "description": "Hover card overlay for Tile UI.",
  "dependencies": [
    "@radix-ui/react-slot",
    "react-dom"
  ],
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/core",
    "@tile-ui/portal",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/hover-card/hover-card.tsx",
      "type": "registry:ui",
      "target": "components/ui/hover-card/hover-card.tsx",
      "content": "import React, { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Slot } from '@radix-ui/react-slot';\nimport { getHoverCardPosition, getHoverCardState, hoverCardStyleKeys } from '../lib/core';\nimport type { HoverCardBaseProps, HoverCardContentBaseProps, HoverCardPosition, HoverCardTriggerBaseProps } from '../lib/core';\nimport { usePortalContainer, type PortalContainer } from '../portal';\nimport styles from './hover-card.module.scss';\n\ninterface HoverCardContextValue {\n\topen: boolean;\n\ttriggerRef: React.RefObject<HTMLElement | null>;\n\tcontentId: string;\n\topenDelay: number;\n\tcloseDelay: number;\n\tsetOpen: (open: boolean) => void;\n}\n\nconst HoverCardContext = createContext<HoverCardContextValue | null>(null);\n\nfunction useHoverCardContext(): HoverCardContextValue {\n\tconst context = useContext(HoverCardContext);\n\tif (!context) {\n\t\tthrow new Error('HoverCard sub-components must be used within <HoverCard>.');\n\t}\n\treturn context;\n}\n\nexport interface HoverCardProps extends React.HTMLAttributes<HTMLDivElement>, HoverCardBaseProps {}\n\nconst HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>(\n\t({ className = '', open, defaultOpen = false, openDelay = 200, closeDelay = 300, onOpenChange, children, ...props }, ref) => {\n\t\tconst [internalOpen, setInternalOpen] = useState(defaultOpen);\n\t\tconst isOpen = open !== undefined ? open : internalOpen;\n\t\tconst triggerRef = useRef<HTMLElement | null>(null);\n\t\tconst contentId = useId();\n\n\t\tconst setOpen = useCallback(\n\t\t\t(next: boolean) => {\n\t\t\t\tif (open === undefined) {\n\t\t\t\t\tsetInternalOpen(next);\n\t\t\t\t}\n\t\t\t\tonOpenChange?.(next);\n\t\t\t},\n\t\t\t[open, onOpenChange],\n\t\t);\n\n\t\treturn (\n\t\t\t<HoverCardContext.Provider value={{ open: isOpen, triggerRef, contentId, openDelay, closeDelay, setOpen }}>\n\t\t\t\t<div ref={ref} className={`${styles[hoverCardStyleKeys.root]} ${className}`} {...props}>\n\t\t\t\t\t{children}\n\t\t\t\t</div>\n\t\t\t</HoverCardContext.Provider>\n\t\t);\n\t},\n);\nHoverCard.displayName = 'HoverCard';\n\nexport interface HoverCardTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, HoverCardTriggerBaseProps {}\n\nconst HoverCardTrigger = React.forwardRef<HTMLButtonElement, HoverCardTriggerProps>(({ className = '', asChild = false, children, onKeyDown, ...props }, ref) => {\n\tconst context = useHoverCardContext();\n\tconst openTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\tconst closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\tfunction clearOpenTimer() {\n\t\tif (openTimer.current) {\n\t\t\tclearTimeout(openTimer.current);\n\t\t\topenTimer.current = null;\n\t\t}\n\t}\n\n\tfunction clearCloseTimer() {\n\t\tif (closeTimer.current) {\n\t\t\tclearTimeout(closeTimer.current);\n\t\t\tcloseTimer.current = null;\n\t\t}\n\t}\n\n\tfunction scheduleOpen() {\n\t\tclearOpenTimer();\n\t\topenTimer.current = setTimeout(() => context.setOpen(true), context.openDelay);\n\t}\n\n\tfunction scheduleClose() {\n\t\tclearCloseTimer();\n\t\tcloseTimer.current = setTimeout(() => context.setOpen(false), context.closeDelay);\n\t}\n\n\tfunction handlePointerEnter() {\n\t\tclearCloseTimer();\n\t\tscheduleOpen();\n\t}\n\n\tfunction handlePointerLeave() {\n\t\tclearOpenTimer();\n\t\tscheduleClose();\n\t}\n\n\tfunction handleFocus() {\n\t\tclearCloseTimer();\n\t\tscheduleOpen();\n\t}\n\n\tfunction handleBlur() {\n\t\tclearOpenTimer();\n\t\tscheduleClose();\n\t}\n\n\tfunction handleKeyDown(event: React.KeyboardEvent<HTMLButtonElement>) {\n\t\tif (event.key === 'Escape') {\n\t\t\tclearOpenTimer();\n\t\t\tclearCloseTimer();\n\t\t\tcontext.setOpen(false);\n\t\t}\n\t\tonKeyDown?.(event);\n\t}\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tclearOpenTimer();\n\t\t\tclearCloseTimer();\n\t\t};\n\t}, []);\n\n\tfunction setRef(element: HTMLElement | null) {\n\t\tcontext.triggerRef.current = element;\n\t\tif (typeof ref === 'function') {\n\t\t\tref(element as HTMLButtonElement | null);\n\t\t} else if (ref) {\n\t\t\tref.current = element as HTMLButtonElement | null;\n\t\t}\n\t}\n\n\tconst Comp = asChild ? Slot : 'button';\n\tconst state = getHoverCardState(context.open);\n\n\treturn (\n\t\t<Comp\n\t\t\tref={setRef}\n\t\t\tdata-state={state}\n\t\t\tclassName={asChild ? undefined : `${styles[hoverCardStyleKeys.trigger]} ${className}`}\n\t\t\tonPointerEnter={handlePointerEnter}\n\t\t\tonPointerLeave={handlePointerLeave}\n\t\t\tonFocus={handleFocus}\n\t\t\tonBlur={handleBlur}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t</Comp>\n\t);\n});\nHoverCardTrigger.displayName = 'HoverCardTrigger';\n\nexport interface HoverCardContentProps extends React.HTMLAttributes<HTMLDivElement>, HoverCardContentBaseProps {\n\tcontainer?: PortalContainer;\n}\n\nconst HoverCardContent = React.forwardRef<HTMLDivElement, HoverCardContentProps>(\n\t({ className = '', side = 'bottom', align = 'center', sideOffset = 4, container, children, ...props }, ref) => {\n\t\tconst { open, triggerRef, contentId, closeDelay, setOpen } = useHoverCardContext();\n\t\tconst portalContainer = usePortalContainer(container);\n\t\tconst [position, setPosition] = useState<HoverCardPosition | null>(null);\n\t\tconst contentRef = useRef<HTMLDivElement | null>(null);\n\t\tconst closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\t\tfunction setRef(element: HTMLDivElement | null) {\n\t\t\tcontentRef.current = element;\n\t\t\tif (typeof ref === 'function') {\n\t\t\t\tref(element);\n\t\t\t} else if (ref) {\n\t\t\t\tref.current = element;\n\t\t\t}\n\t\t}\n\n\t\tfunction clearCloseTimer() {\n\t\t\tif (closeTimer.current) {\n\t\t\t\tclearTimeout(closeTimer.current);\n\t\t\t\tcloseTimer.current = null;\n\t\t\t}\n\t\t}\n\n\t\tfunction scheduleClose() {\n\t\t\tclearCloseTimer();\n\t\t\tcloseTimer.current = setTimeout(() => setOpen(false), closeDelay);\n\t\t}\n\n\t\tfunction handlePointerEnter() {\n\t\t\tclearCloseTimer();\n\t\t}\n\n\t\tfunction handlePointerLeave() {\n\t\t\tscheduleClose();\n\t\t}\n\n\t\tuseEffect(() => {\n\t\t\treturn () => {\n\t\t\t\tclearCloseTimer();\n\t\t\t};\n\t\t}, []);\n\n\t\tuseLayoutEffect(() => {\n\t\t\tif (!open) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfunction updatePosition() {\n\t\t\t\tconst trigger = triggerRef.current;\n\t\t\t\tconst content = contentRef.current;\n\t\t\t\tif (!trigger || !content) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst triggerRect = trigger.getBoundingClientRect();\n\t\t\t\tconst contentSize = { width: content.offsetWidth, height: content.offsetHeight };\n\t\t\t\tconst viewport = { width: window.innerWidth, height: window.innerHeight };\n\t\t\t\tsetPosition(getHoverCardPosition({ triggerRect, contentSize, side, align, sideOffset, viewport }));\n\t\t\t}\n\n\t\t\tupdatePosition();\n\t\t\twindow.addEventListener('resize', updatePosition);\n\t\t\tdocument.addEventListener('scroll', updatePosition, true);\n\n\t\t\treturn () => {\n\t\t\t\twindow.removeEventListener('resize', updatePosition);\n\t\t\t\tdocument.removeEventListener('scroll', updatePosition, true);\n\t\t\t};\n\t\t}, [open, side, align, sideOffset, triggerRef]);\n\n\t\tuseEffect(() => {\n\t\t\tif (!open) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfunction handleKeyDown(event: KeyboardEvent) {\n\t\t\t\tif (event.key === 'Escape') {\n\t\t\t\t\tclearCloseTimer();\n\t\t\t\t\tsetOpen(false);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdocument.addEventListener('keydown', handleKeyDown);\n\n\t\t\treturn () => {\n\t\t\t\tdocument.removeEventListener('keydown', handleKeyDown);\n\t\t\t};\n\t\t}, [open, setOpen]);\n\n\t\tconst state = getHoverCardState(open);\n\t\tconst classes = [styles[hoverCardStyleKeys.content], className].filter(Boolean).join(' ');\n\n\t\tconst content = (\n\t\t\t<div\n\t\t\t\tref={setRef}\n\t\t\t\tid={contentId}\n\t\t\t\trole=\"dialog\"\n\t\t\t\taria-modal=\"false\"\n\t\t\t\ttabIndex={-1}\n\t\t\t\tdata-state={state}\n\t\t\t\tdata-side={side}\n\t\t\t\tdata-align={align}\n\t\t\t\tclassName={classes}\n\t\t\t\tstyle={position ? { top: `${position.top}px`, left: `${position.left}px` } : undefined}\n\t\t\t\tonPointerEnter={handlePointerEnter}\n\t\t\t\tonPointerLeave={handlePointerLeave}\n\t\t\t\t{...props}>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t);\n\n\t\tif (!open || !portalContainer) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn createPortal(content, portalContainer);\n\t},\n);\nHoverCardContent.displayName = 'HoverCardContent';\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent };\nexport default HoverCard;\n"
    },
    {
      "path": "packages/styles/scss/components/hover-card.module.scss",
      "type": "registry:file",
      "target": "components/ui/hover-card/hover-card.module.scss",
      "content": "@use '../../../styles/variables/colors' as *;\n\n.root {\n\tdisplay: inline-flex;\n}\n\n.trigger {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tborder: none;\n\tbackground: transparent;\n\tpadding: 0;\n\tcolor: inherit;\n\tfont: inherit;\n\tcursor: pointer;\n}\n\n.content {\n\tposition: fixed;\n\tz-index: $z-popover;\n\twidth: 16rem;\n\tmax-width: calc(100vw - 2rem);\n\tborder-radius: var(--tile-hover-card-radius, $radius-md);\n\tborder: 1px solid var(--tile-hover-card-border, $border);\n\tbackground-color: var(--tile-hover-card-background, $popover);\n\tcolor: $popover-foreground;\n\tpadding: var(--tile-hover-card-padding, $spacing-4);\n\tfont-size: var(--tile-hover-card-font-size, inherit);\n\tline-height: var(--tile-hover-card-leading, inherit);\n\tbox-shadow: var(--tile-hover-card-shadow, $shadow-md);\n\toutline: none;\n\ttransition:\n\t\topacity $transition-fast,\n\t\tvisibility $transition-fast,\n\t\ttransform $transition-fast;\n\n\t&[data-state='closed'] {\n\t\topacity: 0;\n\t\tvisibility: hidden;\n\t\tpointer-events: none;\n\n\t\t&[data-side='top'] {\n\t\t\ttransform: translateY(0.25rem);\n\t\t}\n\n\t\t&[data-side='right'] {\n\t\t\ttransform: translateX(-0.25rem);\n\t\t}\n\n\t\t&[data-side='bottom'] {\n\t\t\ttransform: translateY(-0.25rem);\n\t\t}\n\n\t\t&[data-side='left'] {\n\t\t\ttransform: translateX(0.25rem);\n\t\t}\n\t}\n\n\t&[data-state='open'] {\n\t\topacity: 1;\n\t\tvisibility: visible;\n\t\tpointer-events: auto;\n\t\ttransform: translate(0, 0);\n\t}\n}\n"
    },
    {
      "path": "packages/react/src/components/hover-card/index.ts",
      "type": "registry:ui",
      "target": "components/ui/hover-card/index.ts",
      "content": "export { HoverCard, HoverCardTrigger, HoverCardContent, type HoverCardProps, type HoverCardTriggerProps, type HoverCardContentProps } from './hover-card';\n"
    }
  ]
}
