Skip to content

Commit

Permalink
Merge pull request #52446 from bernhardoj/fix/51987-missing-tooltip-f…
Browse files Browse the repository at this point in the history
…or-qab

Fix tooltip for QAB doesn't show for new user
  • Loading branch information
pecanoro authored Nov 26, 2024
2 parents 4dda201 + 8aec50b commit 09edde6
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import React, {memo, useCallback, useEffect, useRef, useState} from 'react';
import type {LayoutRectangle, NativeSyntheticEvent} from 'react-native';
import GenericTooltip from '@components/Tooltip/GenericTooltip';
import type {EducationalTooltipProps} from '@components/Tooltip/types';
import onyxSubscribe from '@libs/onyxSubscribe';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Modal} from '@src/types/onyx';
import measureTooltipCoordinate from './measureTooltipCoordinate';
import * as TooltipManager from './TooltipManager';

type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>;

Expand All @@ -18,30 +16,8 @@ function BaseEducationalTooltip({children, onHideTooltip, shouldRender = false,

const [shouldMeasure, setShouldMeasure] = useState(false);
const show = useRef<() => void>();
const [modal, setModal] = useState<Modal>({
willAlertModalBecomeVisible: false,
isVisible: false,
});

const shouldShow = !modal?.willAlertModalBecomeVisible && !modal?.isVisible && shouldRender;

useEffect(() => {
if (!shouldRender) {
return;
}
const unsubscribeOnyxModal = onyxSubscribe({
key: ONYXKEYS.MODAL,
callback: (modalArg) => {
if (modalArg === undefined) {
return;
}
setModal(modalArg);
},
});
return () => {
unsubscribeOnyxModal();
};
}, [shouldRender]);
const removeActiveTooltipRef = useRef(() => {});
const removePendingTooltipRef = useRef(() => {});

const didShow = useRef(false);

Expand All @@ -51,6 +27,7 @@ function BaseEducationalTooltip({children, onHideTooltip, shouldRender = false,
}
hideTooltipRef.current?.();
onHideTooltip?.();
removeActiveTooltipRef.current();
}, [onHideTooltip]);

useEffect(
Expand All @@ -70,34 +47,32 @@ function BaseEducationalTooltip({children, onHideTooltip, shouldRender = false,
return;
}

// If the modal is open, hide the tooltip immediately and clear the timeout
if (!shouldShow) {
closeTooltip();
return;
}

// Automatically hide tooltip after 5 seconds if shouldAutoDismiss is true
const timerID = setTimeout(() => {
closeTooltip();
}, 5000);
return () => {
clearTimeout(timerID);
};
}, [shouldAutoDismiss, shouldShow, closeTooltip]);
}, [shouldAutoDismiss, closeTooltip]);

useEffect(() => {
if (!shouldMeasure || !shouldShow || didShow.current) {
if (!shouldMeasure || !shouldRender || didShow.current) {
return;
}
// When tooltip is used inside an animated view (e.g. popover), we need to wait for the animation to finish before measuring content.
const timerID = setTimeout(() => {
removePendingTooltipRef.current();
show.current?.();
didShow.current = true;
removeActiveTooltipRef.current = TooltipManager.addActiveTooltip(closeTooltip);
}, 500);
removePendingTooltipRef.current = TooltipManager.addPendingTooltip(timerID);
return () => {
removePendingTooltipRef.current();
clearTimeout(timerID);
};
}, [shouldMeasure, shouldShow]);
}, [shouldMeasure, shouldRender, closeTooltip]);

useEffect(
() => closeTooltip,
Expand Down
30 changes: 30 additions & 0 deletions src/components/Tooltip/EducationalTooltip/TooltipManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// We store the timeouts for each pending tooltip here.
// We're using the timeout because when a tooltip is used inside an animated view (e.g., popover),
// we need to wait for the animation to finish before measuring content.
const pendingTooltips = new Set<NodeJS.Timeout>();

// We store the callback for closing a tooltip here.
const activeTooltips = new Set<() => void>();

function addPendingTooltip(timeout: NodeJS.Timeout) {
pendingTooltips.add(timeout);
return () => {
pendingTooltips.delete(timeout);
};
}

function addActiveTooltip(closeCallback: () => void) {
activeTooltips.add(closeCallback);
return () => {
activeTooltips.delete(closeCallback);
};
}

function cancelPendingAndActiveTooltips() {
pendingTooltips.forEach((timeout) => clearTimeout(timeout));
pendingTooltips.clear();
activeTooltips.forEach((closeCallback) => closeCallback());
activeTooltips.clear();
}

export {addPendingTooltip, addActiveTooltip, cancelPendingAndActiveTooltips};
3 changes: 3 additions & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {SearchContextProvider} from '@components/Search/SearchContext';
import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext';
import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal';
import TestToolsModal from '@components/TestToolsModal';
import * as TooltipManager from '@components/Tooltip/EducationalTooltip/TooltipManager';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useOnboardingFlowRouter from '@hooks/useOnboardingFlow';
import usePermissions from '@hooks/usePermissions';
Expand Down Expand Up @@ -206,6 +207,8 @@ const RootStack = createCustomStackNavigator<AuthScreensParamList>();

const modalScreenListeners = {
focus: () => {
// Since we don't cancel the tooltip in setModalVisibility, we need to do it here so it will be cancelled when a modal screen is shown.
TooltipManager.cancelPendingAndActiveTooltips();
Modal.setModalVisibility(true);
},
blur: () => {
Expand Down
7 changes: 7 additions & 0 deletions src/libs/actions/Modal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Onyx from 'react-native-onyx';
import * as TooltipManager from '@components/Tooltip/EducationalTooltip/TooltipManager';
import ONYXKEYS from '@src/ONYXKEYS';

const closeModals: Array<(isNavigating?: boolean) => void> = [];
Expand Down Expand Up @@ -86,6 +87,12 @@ function setDisableDismissOnEscape(disableDismissOnEscape: boolean) {
* isPopover indicates that the next open modal is popover or bottom docked
*/
function willAlertModalBecomeVisible(isVisible: boolean, isPopover = false) {
// We cancel the pending and active tooltips here instead of in setModalVisibility because
// we want to do it when a modal is going to show. If we do it when the modal is fully shown,
// the tooltip in that modal won't show.
if (isVisible) {
TooltipManager.cancelPendingAndActiveTooltips();
}
Onyx.merge(ONYXKEYS.MODAL, {willAlertModalBecomeVisible: isVisible, isPopover});
}

Expand Down

0 comments on commit 09edde6

Please sign in to comment.