import React, { useState, useRef, useEffect, isValidElement, cloneElement, forwardRef, useMemo } from 'react'; import { View, Modal, Text, Pressable } from 'react-native'; import { tooltipStyles } from './Tooltip.styles'; import type { TooltipProps } from './types'; import { getNativeAccessibilityProps } from '../utils/accessibility'; import type { IdealystElement } from '../utils/refTypes'; const Tooltip = forwardRef(({ content, children, placement = 'top', delay: _delay = 200, intent = 'neutral', size = 'md', style, testID, id, // Accessibility props accessibilityLabel, accessibilityHint, accessibilityDisabled, accessibilityHidden, accessibilityRole, }, ref) => { // Generate native accessibility props const nativeA11yProps = useMemo(() => { return getNativeAccessibilityProps({ accessibilityLabel, accessibilityHint: accessibilityHint ?? (typeof content === 'string' ? content : undefined), accessibilityDisabled, accessibilityHidden, accessibilityRole: accessibilityRole ?? 'none', }); }, [accessibilityLabel, accessibilityHint, content, accessibilityDisabled, accessibilityHidden, accessibilityRole]); const [visible, setVisible] = useState(false); const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0, width: 0 }); const triggerRef = useRef(null); const timeoutRef = useRef(null); // Apply variants tooltipStyles.useVariants({ size, intent, }); useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); const calculateTooltipPosition = (x: number, y: number, width: number, height: number) => { const offset = 8; let top = 0; let left = 0; switch (placement) { case 'top': top = y - offset; left = x + width / 2; break; case 'bottom': top = y + height + offset; left = x + width / 2; break; case 'left': top = y + height / 2; left = x - offset; break; case 'right': top = y + height / 2; left = x + width + offset; break; } setTooltipPosition({ top, left, width }); }; const handleLongPress = () => { if (!triggerRef.current) return; triggerRef.current.measureInWindow((x: number, y: number, width: number, height: number) => { calculateTooltipPosition(x, y, width, height); setVisible(true); }); }; const handlePress = () => { if (visible) { setVisible(false); } }; // Clone child and inject long press handler const trigger = isValidElement(children) ? cloneElement(children as React.ReactElement, { onLongPress: () => { const originalOnLongPress = (children as any).props?.onLongPress; originalOnLongPress?.(); handleLongPress(); }, onPress: (e: any) => { const originalOnPress = (children as any).props?.onPress; originalOnPress?.(e); handlePress(); }, }) : children; const getPositionStyle = () => { switch (placement) { case 'top': return { bottom: tooltipPosition.top, left: tooltipPosition.left, transform: [{ translateX: -50 }], }; case 'bottom': return { top: tooltipPosition.top, left: tooltipPosition.left, transform: [{ translateX: -50 }], }; case 'left': return { top: tooltipPosition.top, right: tooltipPosition.left, transform: [{ translateY: -50 }], }; case 'right': return { top: tooltipPosition.top, left: tooltipPosition.left, transform: [{ translateY: -50 }], }; default: return { top: tooltipPosition.top, left: tooltipPosition.left, }; } }; return ( <> {trigger} {visible && ( setVisible(false)} supportedOrientations={['portrait', 'landscape']} testID={testID} > setVisible(false)}> {typeof content === 'string' ? ( {content} ) : ( content )} )} ); }); Tooltip.displayName = 'Tooltip'; export default Tooltip;