{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tooltip",
  "type": "registry:ui",
  "title": "Tooltip",
  "description": "Tooltip 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/tooltip/tooltip.tsx",
      "type": "registry:ui",
      "target": "components/ui/tooltip/tooltip.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 { getTooltipPosition, getTooltipState, tooltipStyleKeys, TOOLTIP_CLOSE_DELAY_MS } from '../lib/core';\nimport type { TooltipBaseProps, TooltipContentBaseProps, TooltipPosition, TooltipProviderBaseProps, TooltipTriggerBaseProps } from '../lib/core';\nimport { usePortalContainer, type PortalContainer } from '../portal';\nimport styles from './tooltip.module.scss';\n\ninterface TooltipContextValue {\n\topen: boolean;\n\ttriggerRef: React.RefObject<HTMLElement | null>;\n\tcontentId: string;\n\tsetOpen: (open: boolean) => void;\n}\n\nconst TooltipContext = createContext<TooltipContextValue | null>(null);\n\nfunction useTooltipContext(): TooltipContextValue {\n\tconst context = useContext(TooltipContext);\n\tif (!context) {\n\t\tthrow new Error('Tooltip sub-components must be used within <Tooltip>.');\n\t}\n\treturn context;\n}\n\ninterface TooltipProviderContextValue {\n\tdelayDuration: number;\n}\n\nconst TooltipProviderContext = createContext<TooltipProviderContextValue>({ delayDuration: 0 });\n\nexport interface TooltipProviderProps extends TooltipProviderBaseProps {\n\tchildren?: React.ReactNode;\n}\n\nfunction TooltipProvider({ delayDuration = 0, children }: TooltipProviderProps) {\n\treturn <TooltipProviderContext.Provider value={{ delayDuration }}>{children}</TooltipProviderContext.Provider>;\n}\n\nTooltipProvider.displayName = 'TooltipProvider';\n\nexport interface TooltipProps extends React.HTMLAttributes<HTMLDivElement>, TooltipBaseProps {}\n\nconst Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(({ className = '', open, defaultOpen = false, onOpenChange, children, ...props }, ref) => {\n\tconst [internalOpen, setInternalOpen] = useState(defaultOpen);\n\tconst isOpen = open !== undefined ? open : internalOpen;\n\tconst triggerRef = useRef<HTMLElement | null>(null);\n\tconst contentId = useId();\n\n\tconst setOpen = useCallback(\n\t\t(next: boolean) => {\n\t\t\tif (open === undefined) {\n\t\t\t\tsetInternalOpen(next);\n\t\t\t}\n\t\t\tonOpenChange?.(next);\n\t\t},\n\t\t[open, onOpenChange],\n\t);\n\n\treturn (\n\t\t<TooltipContext.Provider value={{ open: isOpen, triggerRef, contentId, setOpen }}>\n\t\t\t<div ref={ref} className={`${styles[tooltipStyleKeys.root]} ${className}`} {...props}>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</TooltipContext.Provider>\n\t);\n});\nTooltip.displayName = 'Tooltip';\n\nexport interface TooltipTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, TooltipTriggerBaseProps {}\n\nconst TooltipTrigger = React.forwardRef<HTMLButtonElement, TooltipTriggerProps>(({ className = '', asChild = false, children, onKeyDown, ...props }, ref) => {\n\tconst context = useTooltipContext();\n\tconst provider = useContext(TooltipProviderContext);\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), provider.delayDuration);\n\t}\n\n\tfunction scheduleClose() {\n\t\tclearCloseTimer();\n\t\tcloseTimer.current = setTimeout(() => context.setOpen(false), TOOLTIP_CLOSE_DELAY_MS);\n\t}\n\n\tfunction handleMouseEnter() {\n\t\tclearCloseTimer();\n\t\tscheduleOpen();\n\t}\n\n\tfunction handleMouseLeave() {\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\tclearCloseTimer();\n\t\tcontext.setOpen(false);\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 = getTooltipState(context.open);\n\n\treturn (\n\t\t<Comp\n\t\t\tref={setRef}\n\t\t\taria-describedby={context.open ? context.contentId : undefined}\n\t\t\tdata-state={state}\n\t\t\tclassName={asChild ? undefined : `${styles[tooltipStyleKeys.trigger]} ${className}`}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\tonMouseLeave={handleMouseLeave}\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});\nTooltipTrigger.displayName = 'TooltipTrigger';\n\nexport interface TooltipContentProps extends React.HTMLAttributes<HTMLDivElement>, TooltipContentBaseProps {\n\tcontainer?: PortalContainer;\n}\n\nconst TooltipContent = React.forwardRef<HTMLDivElement, TooltipContentProps>(({ className = '', side = 'top', sideOffset = 0, container, children, ...props }, ref) => {\n\tconst { open, triggerRef, contentId, setOpen } = useTooltipContext();\n\tconst portalContainer = usePortalContainer(container);\n\tconst [position, setPosition] = useState<TooltipPosition | null>(null);\n\tconst contentRef = useRef<HTMLDivElement | null>(null);\n\tconst closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\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 handlePointerEnter() {\n\t\tclearCloseTimer();\n\t\tsetOpen(true);\n\t}\n\n\tfunction handlePointerLeave() {\n\t\tclearCloseTimer();\n\t\tcloseTimer.current = setTimeout(() => setOpen(false), TOOLTIP_CLOSE_DELAY_MS);\n\t}\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tclearCloseTimer();\n\t\t};\n\t}, []);\n\n\tuseLayoutEffect(() => {\n\t\tif (!open) {\n\t\t\treturn;\n\t\t}\n\n\t\tfunction updatePosition() {\n\t\t\tconst trigger = triggerRef.current;\n\t\t\tconst content = contentRef.current;\n\t\t\tif (!trigger || !content) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst triggerRect = trigger.getBoundingClientRect();\n\t\t\tconst contentSize = { width: content.offsetWidth, height: content.offsetHeight };\n\t\t\tconst viewport = { width: window.innerWidth, height: window.innerHeight };\n\t\t\tsetPosition(getTooltipPosition({ triggerRect, contentSize, side, sideOffset, viewport }));\n\t\t}\n\n\t\tupdatePosition();\n\t\twindow.addEventListener('resize', updatePosition);\n\t\tdocument.addEventListener('scroll', updatePosition, true);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener('resize', updatePosition);\n\t\t\tdocument.removeEventListener('scroll', updatePosition, true);\n\t\t};\n\t}, [open, side, sideOffset, triggerRef]);\n\n\tfunction setRef(element: HTMLDivElement | null) {\n\t\tcontentRef.current = element;\n\t\tif (typeof ref === 'function') {\n\t\t\tref(element);\n\t\t} else if (ref) {\n\t\t\tref.current = element;\n\t\t}\n\t}\n\n\tconst state = getTooltipState(open);\n\tconst classes = [styles[tooltipStyleKeys.content], className].filter(Boolean).join(' ');\n\n\tconst content = (\n\t\t<div\n\t\t\tref={setRef}\n\t\t\tid={contentId}\n\t\t\trole=\"tooltip\"\n\t\t\tdata-state={state}\n\t\t\tdata-side={side}\n\t\t\tclassName={classes}\n\t\t\tstyle={position ? { top: `${position.top}px`, left: `${position.left}px` } : undefined}\n\t\t\tonPointerEnter={handlePointerEnter}\n\t\t\tonPointerLeave={handlePointerLeave}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t\t<span className={styles[tooltipStyleKeys.arrow]} aria-hidden=\"true\" />\n\t\t</div>\n\t);\n\n\tif (!open || !portalContainer) {\n\t\treturn null;\n\t}\n\n\treturn createPortal(content, portalContainer);\n});\nTooltipContent.displayName = 'TooltipContent';\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\nexport default Tooltip;\n"
    },
    {
      "path": "packages/styles/scss/components/tooltip.module.scss",
      "type": "registry:file",
      "target": "components/ui/tooltip/tooltip.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-tooltip;\n\twidth: fit-content;\n\tmax-width: 16rem;\n\tborder-radius: var(--tile-tooltip-radius, $radius);\n\tbackground-color: var(--tile-tooltip-background, $foreground);\n\tcolor: var(--tile-tooltip-color, $background);\n\tpadding: var(--tile-tooltip-padding, $spacing-1_5 $spacing-3);\n\tfont-size: var(--tile-tooltip-font-size, $text-xs);\n\tfont-weight: var(--tile-tooltip-font-weight, $font-medium);\n\tline-height: var(--tile-tooltip-leading, $leading-normal);\n\ttext-align: center;\n\tbox-shadow: var(--tile-tooltip-shadow, $shadow-md);\n\toutline: var(--tile-tooltip-outline, none);\n\toutline-offset: var(--tile-tooltip-outline-offset, 0);\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\n.arrow {\n\tposition: absolute;\n\twidth: 0.5rem;\n\theight: 0.5rem;\n\tborder-radius: var(--tile-tooltip-arrow-radius, $radius-xs);\n\tbackground-color: var(--tile-tooltip-background, $foreground);\n\tbox-shadow: var(--tile-tooltip-arrow-shadow, none);\n\tpointer-events: none;\n\ttransform: rotate(45deg);\n\n\t[data-side='top'] > & {\n\t\tbottom: -4px;\n\t\tleft: 50%;\n\t\tmargin-left: -4px;\n\t}\n\n\t[data-side='right'] > & {\n\t\tleft: -4px;\n\t\ttop: 50%;\n\t\tmargin-top: -4px;\n\t\tbox-shadow: var(--tile-tooltip-arrow-left-shadow, none);\n\t}\n\n\t[data-side='bottom'] > & {\n\t\tleft: 50%;\n\t\ttop: -4px;\n\t\tmargin-left: -4px;\n\t\tbox-shadow: var(--tile-tooltip-arrow-top-shadow, none);\n\t}\n\n\t[data-side='left'] > & {\n\t\tright: -4px;\n\t\ttop: 50%;\n\t\tmargin-top: -4px;\n\t\tbox-shadow: var(--tile-tooltip-arrow-right-shadow, none);\n\t}\n}\n"
    },
    {
      "path": "packages/react/src/components/tooltip/index.ts",
      "type": "registry:ui",
      "target": "components/ui/tooltip/index.ts",
      "content": "export {\n\tTooltip,\n\tTooltipTrigger,\n\tTooltipContent,\n\tTooltipProvider,\n\ttype TooltipProps,\n\ttype TooltipTriggerProps,\n\ttype TooltipContentProps,\n\ttype TooltipProviderProps,\n} from './tooltip';\n"
    }
  ]
}
