diff --git a/.changeset/chilly-jars-jog.md b/.changeset/chilly-jars-jog.md new file mode 100644 index 00000000000..cb414e294b4 --- /dev/null +++ b/.changeset/chilly-jars-jog.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Hide `TooltipV2` tooltips on `touchend` event diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 9fe332b5dc8..1c50c8f10bc 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -146,14 +146,18 @@ export type TooltipProps = React.PropsWithChildren< > & React.HTMLAttributes -type TriggerPropsType = { - 'aria-describedby'?: string - 'aria-labelledby'?: string - 'aria-label'?: string - onBlur?: React.FocusEventHandler - onFocus?: React.FocusEventHandler - onMouseEnter?: React.MouseEventHandler - onMouseLeave?: React.MouseEventHandler +type TriggerPropsType = Pick< + React.HTMLAttributes, + | 'aria-describedby' + | 'aria-labelledby' + | 'onBlur' + | 'onTouchEnd' + | 'onFocus' + | 'onMouseOverCapture' + | 'onMouseLeave' + | 'onTouchCancel' + | 'onTouchEnd' +> & { ref?: React.RefObject } @@ -214,7 +218,7 @@ export const Tooltip = React.forwardRef( const [isPopoverOpen, setIsPopoverOpen] = useState(false) - const timeoutRef = React.useRef(null) + const openTimeoutRef = React.useRef(null) const {safeSetTimeout, safeClearTimeout} = useSafeTimeout() @@ -261,6 +265,10 @@ export const Tooltip = React.forwardRef( } } const closeTooltip = () => { + if (openTimeoutRef.current) { + safeClearTimeout(openTimeoutRef.current) + openTimeoutRef.current = null + } try { if ( tooltipElRef.current && @@ -362,6 +370,13 @@ export const Tooltip = React.forwardRef( closeTooltip() child.props.onBlur?.(event) }, + onTouchEnd: (event: React.TouchEvent) => { + child.props.onTouchEnd?.(event) + + // Hide tooltips on tap to essentially disable them on touch devices; + // this still allows viewing the tooltip on tap-and-hold + safeSetTimeout(() => closeTooltip(), 10) + }, onFocus: (event: React.FocusEvent) => { // only show tooltip on :focus-visible, not on :focus try { @@ -374,19 +389,17 @@ export const Tooltip = React.forwardRef( openTooltip() child.props.onFocus?.(event) }, - onMouseEnter: (event: React.MouseEvent) => { - // show tooltip after mosue has been hovering for at least 50ms + onMouseOverCapture: (event: React.MouseEvent) => { + // We use a `capture` event to ensure this is called first before + // events that might cancel the opening timeout (like `onTouchEnd`) + // show tooltip after mouse has been hovering for at least 50ms // (prevent showing tooltip when mouse is just passing through) - timeoutRef.current = safeSetTimeout(() => { + openTimeoutRef.current = safeSetTimeout(() => { openTooltip() child.props.onMouseEnter?.(event) }, 50) }, onMouseLeave: (event: React.MouseEvent) => { - if (timeoutRef.current) { - safeClearTimeout(timeoutRef.current) - timeoutRef.current = null - } closeTooltip() child.props.onMouseLeave?.(event) }, diff --git a/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap index 3d19d97cca1..818ffd62114 100644 --- a/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap @@ -3325,8 +3325,9 @@ exports[`TextInput renders trailingAction icon button 1`] = ` onBlur={[Function]} onClick={[MockFunction]} onFocus={[Function]} - onMouseEnter={[Function]} onMouseLeave={[Function]} + onMouseOverCapture={[Function]} + onTouchEnd={[Function]} type="button" >