{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "tooltip", "files": [ { "path": "registry/new-york/ui/tooltip.tsx", "content": "import * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface TooltipProviderContextValue {\n\tdelayDuration: number;\n}\n\nconst TooltipProviderContext = React.createContext(\n\t{\n\t\tdelayDuration: 200,\n\t},\n);\n\ninterface TooltipContextValue {\n\topen: boolean;\n\tsetOpen: (open: boolean) => void;\n\ttriggerRef: React.RefObject;\n}\n\nconst TooltipContext = React.createContext(null);\n\nfunction useTooltipContext() {\n\tconst context = React.useContext(TooltipContext);\n\tif (!context) {\n\t\tthrow new Error(\"Tooltip components must be used within a Tooltip\");\n\t}\n\treturn context;\n}\n\nfunction TooltipProvider({\n\tchildren,\n\tdelayDuration = 200,\n}: {\n\tchildren: React.ReactNode;\n\tdelayDuration?: number;\n}) {\n\treturn (\n\t\t\n\t\t\t{children}\n\t\t\n\t);\n}\n\nfunction Tooltip({ children }: { children: React.ReactNode }) {\n\tconst [open, setOpen] = React.useState(false);\n\tconst triggerRef = React.useRef(null);\n\n\treturn (\n\t\t\n\t\t\t{children}\n\t\t\n\t);\n}\n\ninterface TooltipTriggerProps extends React.ComponentProps<\"button\"> {\n\tasChild?: boolean;\n\tchildren: React.ReactNode;\n}\n\nfunction composeEventHandlers(\n\tuserHandler: ((event: E) => void) | undefined,\n\tinternalHandler: (event: E) => void,\n) {\n\treturn (event: E) => {\n\t\tuserHandler?.(event);\n\t\tinternalHandler(event);\n\t};\n}\n\nfunction TooltipTrigger({ asChild, children, ...props }: TooltipTriggerProps) {\n\tconst { delayDuration } = React.useContext(TooltipProviderContext);\n\tconst { setOpen, triggerRef } = useTooltipContext();\n\tconst timerRef = React.useRef | null>(null);\n\n\tconst openTooltip = React.useCallback(() => {\n\t\tif (timerRef.current) {\n\t\t\tclearTimeout(timerRef.current);\n\t\t}\n\t\tif (delayDuration <= 0) {\n\t\t\tsetOpen(true);\n\t\t\treturn;\n\t\t}\n\t\ttimerRef.current = setTimeout(() => setOpen(true), delayDuration);\n\t}, [delayDuration, setOpen]);\n\n\tconst closeTooltip = React.useCallback(() => {\n\t\tif (timerRef.current) {\n\t\t\tclearTimeout(timerRef.current);\n\t\t\ttimerRef.current = null;\n\t\t}\n\t\tsetOpen(false);\n\t}, [setOpen]);\n\n\tReact.useEffect(\n\t\t() => () => {\n\t\t\tif (timerRef.current) {\n\t\t\t\tclearTimeout(timerRef.current);\n\t\t\t}\n\t\t},\n\t\t[],\n\t);\n\n\tconst setRef = React.useCallback(\n\t\t(node: HTMLElement | null) => {\n\t\t\ttriggerRef.current = node;\n\t\t},\n\t\t[triggerRef],\n\t);\n\n\tif (asChild && React.isValidElement(children)) {\n\t\tconst child = children as React.ReactElement<{\n\t\t\tref?: React.Ref;\n\t\t\tonMouseEnter?: React.MouseEventHandler;\n\t\t\tonMouseLeave?: React.MouseEventHandler;\n\t\t\tonFocus?: React.FocusEventHandler;\n\t\t\tonBlur?: React.FocusEventHandler;\n\t\t}>;\n\n\t\treturn React.cloneElement(child, {\n\t\t\tref: setRef,\n\t\t\tonMouseEnter: composeEventHandlers(child.props.onMouseEnter, () =>\n\t\t\t\topenTooltip(),\n\t\t\t),\n\t\t\tonMouseLeave: composeEventHandlers(child.props.onMouseLeave, () =>\n\t\t\t\tcloseTooltip(),\n\t\t\t),\n\t\t\tonFocus: composeEventHandlers(child.props.onFocus, () => openTooltip()),\n\t\t\tonBlur: composeEventHandlers(child.props.onBlur, () => closeTooltip()),\n\t\t});\n\t}\n\n\treturn (\n\t\t}\n\t\t\ttype=\"button\"\n\t\t\tdata-slot=\"tooltip-trigger\"\n\t\t\tonMouseEnter={openTooltip}\n\t\t\tonMouseLeave={closeTooltip}\n\t\t\tonFocus={openTooltip}\n\t\t\tonBlur={closeTooltip}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t\n\t);\n}\n\ninterface TooltipContentProps extends React.ComponentProps<\"div\"> {\n\tside?: \"top\" | \"right\" | \"bottom\" | \"left\";\n}\n\nfunction TooltipContent({\n\tclassName,\n\tside = \"top\",\n\tchildren,\n\t...props\n}: TooltipContentProps) {\n\tconst { open, triggerRef } = useTooltipContext();\n\tconst [position, setPosition] = React.useState({ top: 0, left: 0 });\n\n\tReact.useLayoutEffect(() => {\n\t\tif (open && triggerRef.current) {\n\t\t\tconst rect = triggerRef.current.getBoundingClientRect();\n\t\t\tlet top = 0;\n\t\t\tlet left = 0;\n\n\t\t\tswitch (side) {\n\t\t\t\tcase \"top\":\n\t\t\t\t\ttop = rect.top - 8;\n\t\t\t\t\tleft = rect.left + rect.width / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"right\":\n\t\t\t\t\ttop = rect.top + rect.height / 2;\n\t\t\t\t\tleft = rect.right + 8;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bottom\":\n\t\t\t\t\ttop = rect.bottom + 8;\n\t\t\t\t\tleft = rect.left + rect.width / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"left\":\n\t\t\t\t\ttop = rect.top + rect.height / 2;\n\t\t\t\t\tleft = rect.left - 8;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tsetPosition({ top, left });\n\t\t}\n\t}, [open, side, triggerRef]);\n\n\tif (!open) return null;\n\n\tconst content = (\n\t\t\n\t\t\t{children}\n\t\t\n\t);\n\n\treturn createPortal(content, document.body);\n}\n\nexport { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent };\n", "type": "registry:ui" } ], "type": "registry:ui" }