import * as React from 'react';
import {
Dimensions,
View,
LayoutChangeEvent,
StyleSheet,
Platform,
Pressable,
ViewStyle,
} from 'react-native';
import type { ThemeProp } from 'src/types';
import { getTooltipPosition, Measurement } from './utils';
import { useInternalTheme } from '../../core/theming';
import { addEventListener } from '../../utils/addEventListener';
import Portal from '../Portal/Portal';
import Text from '../Typography/Text';
export type Props = {
/**
* Tooltip reference element. Needs to be able to hold a ref.
*/
children: React.ReactElement;
/**
* The number of milliseconds a user must touch the element before showing the tooltip.
*/
enterTouchDelay?: number;
/**
* The number of milliseconds after the user stops touching an element before hiding the tooltip.
*/
leaveTouchDelay?: number;
/**
* Tooltip title
*/
title: string;
/**
* Specifies the largest possible scale a title font can reach.
*/
titleMaxFontSizeMultiplier?: number;
/**
* @optional
*/
theme?: ThemeProp;
};
/**
* Tooltips display informative text when users hover over, focus on, or tap an element.
*
* Plain tooltips, when activated, display a text label identifying an element, such as a description of its function. Tooltips should include only short, descriptive text and avoid restating visible UI text.
*
* ## Usage
* ```js
* import * as React from 'react';
* import { IconButton, Tooltip } from 'react-native-paper';
*
* const MyComponent = () => (
*
* {}} />
*
* );
*
* export default MyComponent;
* ```
*/
const Tooltip = ({
children,
enterTouchDelay = 500,
leaveTouchDelay = 1500,
title,
theme: themeOverrides,
titleMaxFontSizeMultiplier,
...rest
}: Props) => {
const isWeb = Platform.OS === 'web';
const theme = useInternalTheme(themeOverrides);
const [visible, setVisible] = React.useState(false);
const [measurement, setMeasurement] = React.useState({
children: {},
tooltip: {},
measured: false,
});
const showTooltipTimer = React.useRef([]);
const hideTooltipTimer = React.useRef([]);
const childrenWrapperRef = React.useRef() as React.MutableRefObject;
const touched = React.useRef(false);
React.useEffect(() => {
return () => {
if (showTooltipTimer.current.length) {
showTooltipTimer.current.forEach((t) => clearTimeout(t));
showTooltipTimer.current = [];
}
if (hideTooltipTimer.current.length) {
hideTooltipTimer.current.forEach((t) => clearTimeout(t));
hideTooltipTimer.current = [];
}
};
}, []);
React.useEffect(() => {
const subscription = addEventListener(Dimensions, 'change', () =>
setVisible(false)
);
return () => subscription.remove();
}, []);
const handleOnLayout = ({ nativeEvent: { layout } }: LayoutChangeEvent) => {
childrenWrapperRef.current.measure(
(_x, _y, width, height, pageX, pageY) => {
setMeasurement({
children: { pageX, pageY, height, width },
tooltip: { ...layout },
measured: true,
});
}
);
};
const handleTouchStart = () => {
if (hideTooltipTimer.current.length) {
hideTooltipTimer.current.forEach((t) => clearTimeout(t));
hideTooltipTimer.current = [];
}
if (isWeb) {
let id = setTimeout(() => {
touched.current = true;
setVisible(true);
}, enterTouchDelay) as unknown as NodeJS.Timeout;
showTooltipTimer.current.push(id);
} else {
touched.current = true;
setVisible(true);
}
};
const handleTouchEnd = () => {
touched.current = false;
if (showTooltipTimer.current.length) {
showTooltipTimer.current.forEach((t) => clearTimeout(t));
showTooltipTimer.current = [];
}
let id = setTimeout(() => {
setVisible(false);
setMeasurement({ children: {}, tooltip: {}, measured: false });
}, leaveTouchDelay) as unknown as NodeJS.Timeout;
hideTooltipTimer.current.push(id);
};
const mobilePressProps = {
onPress: React.useCallback(() => {
if (touched.current) {
return null;
} else {
if (children.props.disabled) return null;
return children.props.onPress?.();
}
}, [children.props]),
onLongPress: () => handleTouchStart(),
onPressOut: () => handleTouchEnd(),
delayLongPress: enterTouchDelay,
};
const webPressProps = {
onHoverIn: () => {
handleTouchStart();
children.props.onHoverIn?.();
},
onHoverOut: () => {
handleTouchEnd();
children.props.onHoverOut?.();
},
};
return (
<>
{visible && (
{title}
)}
{/* Need the xxPressProps in both places */}
{React.cloneElement(children, {
...rest,
...(isWeb ? webPressProps : mobilePressProps),
})}
>
);
};
Tooltip.displayName = 'Tooltip';
const styles = StyleSheet.create({
tooltip: {
alignSelf: 'flex-start',
justifyContent: 'center',
paddingHorizontal: 16,
height: 32,
maxHeight: 32,
},
visible: {
opacity: 1,
},
hidden: {
opacity: 0,
},
pressContainer: {
...(Platform.OS === 'web' && { cursor: 'default' }),
} as ViewStyle,
});
export default Tooltip;