{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "tooltip", "type": "registry:ui", "title": "Tooltip", "description": "Press/long-press anchored hint bubble.", "registryDependencies": [ "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/utils.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/text.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/theme.json" ], "files": [ { "path": "registry/shared/ui/tooltip.tsx", "type": "registry:ui", "target": "components/ui/tooltip.tsx", "content": "import {\n cloneElement,\n createContext,\n isValidElement,\n type ReactElement,\n useContext,\n useRef,\n useState,\n} from 'react';\nimport {\n type GestureResponderEvent,\n Modal,\n Pressable,\n useWindowDimensions,\n View,\n type PressableProps,\n type ViewProps,\n} from 'react-native';\nimport { Text } from '@/components/ui/text';\nimport { cn, composeRefs } from '@/lib/utils';\n\ninterface Anchor {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst TooltipContext = createContext<{\n open: boolean;\n setOpen: (v: boolean) => void;\n anchor: Anchor | null;\n setAnchor: (a: Anchor | null) => void;\n}>({ open: false, setOpen: () => {}, anchor: null, setAnchor: () => {} });\n\nexport function Tooltip({ children }: { children: React.ReactNode }) {\n const [open, setOpen] = useState(false);\n const [anchor, setAnchor] = useState(null);\n return (\n \n {children}\n \n );\n}\n\nexport function TooltipTrigger({\n className,\n asChild,\n children,\n onPress,\n onLongPress,\n ...props\n}: Omit & {\n className?: string;\n /** Clone the child instead of wrapping it — avoids a nested Pressable\n * swallowing the press (shadcn/radix Slot pattern). */\n asChild?: boolean;\n children?: React.ReactNode;\n}) {\n const { setOpen, setAnchor } = useContext(TooltipContext);\n const ref = useRef(null);\n const show = () =>\n ref.current?.measureInWindow((x, y, width, height) => {\n setAnchor({ x, y, width, height });\n setOpen(true);\n });\n if (asChild && isValidElement(children)) {\n const child = children as ReactElement>;\n const childProps = child.props as {\n onPress?: (e: GestureResponderEvent) => void;\n onLongPress?: (e: GestureResponderEvent) => void;\n className?: string;\n ref?: React.Ref;\n };\n return cloneElement(child, {\n ...props,\n ref: composeRefs(ref, childProps.ref),\n collapsable: false,\n className: cn(className, childProps.className),\n onPress: (e: GestureResponderEvent) => {\n childProps.onPress?.(e);\n onPress?.(e);\n show();\n },\n onLongPress: (e: GestureResponderEvent) => {\n childProps.onLongPress?.(e);\n onLongPress?.(e);\n show();\n },\n });\n }\n return (\n \n {children}\n \n );\n}\n\nexport interface TooltipContentProps extends ViewProps {\n className?: string;\n sideOffset?: number;\n}\n\nexport function TooltipContent({\n className,\n sideOffset = 6,\n children,\n style,\n ...props\n}: TooltipContentProps) {\n const { open, setOpen, anchor } = useContext(TooltipContext);\n const { width: screenW } = useWindowDimensions();\n const [measured, setMeasured] = useState({ w: 0, h: 0 });\n const left = anchor\n ? Math.max(8, Math.min(anchor.x, screenW - (measured.w || 80) - 8))\n : 0;\n // Above the trigger by default; flip below when it would clip the top edge.\n const above = anchor ? anchor.y - measured.h - sideOffset : 0;\n const top =\n anchor && measured.h > 0 && above < 8\n ? anchor.y + anchor.height + sideOffset\n : Math.max(above, 8);\n return (\n \n setOpen(false)}>\n {anchor && (\n \n \n setMeasured({\n w: e.nativeEvent.layout.width,\n h: e.nativeEvent.layout.height,\n })\n }\n className={cn(\n 'rounded-md bg-primary px-3 py-1.5 shadow-md',\n className\n )}\n style={[{ maxWidth: screenW - 16 }, style]}\n {...props}\n >\n {typeof children === 'string' ? (\n \n {children}\n \n ) : (\n children\n )}\n \n \n )}\n \n \n );\n}\n" } ] }