Skip to content

Commit

Permalink
Merge pull request #54710 from rayane-djouah/Make-it-possible-to-clic…
Browse files Browse the repository at this point in the history
…k-the-element-the-product-training-tooltip-is-pointing-to-pr

Feat: Make it possible to click the element the product training tooltip is pointing to
  • Loading branch information
puneetlath authored Jan 6, 2025
2 parents 0a304ce + 270a302 commit 553c410
Show file tree
Hide file tree
Showing 29 changed files with 151 additions and 169 deletions.
9 changes: 6 additions & 3 deletions src/components/FloatingActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {useOnyx} from 'react-native-onyx';
import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import Svg, {Path} from 'react-native-svg';
import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused';
import useIsCurrentRouteHome from '@hooks/useIsCurrentRouteHome';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -69,9 +70,11 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo
const isNarrowScreenOnWeb = shouldUseNarrowLayout && platform === CONST.PLATFORM.WEB;
const isFocused = useBottomTabIsFocused();
const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED, {initialValue: false});
const isActiveRouteHome = useIsCurrentRouteHome();
const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.GLOBAL_CREATE_TOOLTIP,
isFocused && isSidebarLoaded,
// On Home screen, We need to wait for the sidebar to load before showing the tooltip because there is the Concierge tooltip which is higher priority
isFocused && (!isActiveRouteHome || isSidebarLoaded),
);
const sharedValue = useSharedValue(isActive ? 1 : 0);
const buttonRef = ref;
Expand Down Expand Up @@ -108,6 +111,7 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo
);

const toggleFabAction = (event: GestureResponderEvent | KeyboardEvent | undefined) => {
hideProductTrainingTooltip();
// Drop focus to avoid blue focus ring.
fabPressable.current?.blur();
onPress(event);
Expand All @@ -120,11 +124,10 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo
horizontal: isNarrowScreenOnWeb ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
shouldUseOverlay
shiftHorizontal={isNarrowScreenOnWeb ? 0 : variables.fabTooltipShiftHorizontal}
renderTooltipContent={renderProductTrainingTooltip}
wrapperStyle={styles.productTrainingTooltipWrapper}
onHideTooltip={hideProductTrainingTooltip}
shouldHideOnNavigate={false}
>
<PressableWithoutFeedback
ref={(el) => {
Expand Down
22 changes: 13 additions & 9 deletions src/components/LHNOptionsList/OptionRowLHN.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SubscriptAvatar from '@components/SubscriptAvatar';
import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useIsCurrentRouteHome from '@hooks/useIsCurrentRouteHome';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
Expand Down Expand Up @@ -50,18 +51,22 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const session = useSession();
const isActiveWorkspaceChat = ReportUtils.isPolicyExpenseChat(report) && activePolicyID === report?.policyID && session?.accountID === report?.ownerAccountID;
const shouldShowWokspaceChatTooltip = ReportUtils.isPolicyExpenseChat(report) && activePolicyID === report?.policyID && session?.accountID === report?.ownerAccountID;
const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+');
const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report);
const isActiveRouteHome = useIsCurrentRouteHome();

const {tooltipToRender, shouldShowTooltip} = useMemo(() => {
const tooltip = shouldShowGetStartedTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.LHN_WORKSPACE_CHAT_TOOLTIP;
const shouldShowTooltips = shouldShowWokspaceChatTooltip || shouldShowGetStartedTooltip;
const shouldTooltipBeVisible = shouldUseNarrowLayout ? isScreenFocused && isActiveRouteHome : isActiveRouteHome;

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return {tooltipToRender: tooltip, shouldShowTooltip: shouldUseNarrowLayout ? isScreenFocused : true};
}, [shouldShowGetStartedTooltip, isScreenFocused, shouldUseNarrowLayout]);
return {tooltipToRender: tooltip, shouldShowTooltip: shouldShowTooltips && shouldTooltipBeVisible};
}, [shouldShowGetStartedTooltip, shouldShowWokspaceChatTooltip, isScreenFocused, shouldUseNarrowLayout, isActiveRouteHome]);

const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender, shouldShowTooltip);

const {translate} = useLocalize();
const [isContextMenuActive, setIsContextMenuActive] = useState(false);

Expand Down Expand Up @@ -159,16 +164,14 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
>
<EducationalTooltip
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
shouldRender={shouldShowProductTrainingTooltip && (isActiveWorkspaceChat || shouldShowGetStartedTooltip)}
shouldRender={shouldShowProductTrainingTooltip}
renderTooltipContent={renderProductTrainingTooltip}
anchorAlignment={{
horizontal: isActiveWorkspaceChat ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
horizontal: shouldShowWokspaceChatTooltip ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
}}
shouldUseOverlay
shiftHorizontal={isActiveWorkspaceChat ? variables.workspaceLHNtooltipShiftHorizontal : variables.gbrTooltipShiftHorizontal}
shiftVertical={isActiveWorkspaceChat ? 0 : variables.composerTooltipShiftVertical}
onHideTooltip={hideProductTrainingTooltip}
shiftHorizontal={shouldShowWokspaceChatTooltip ? variables.workspaceLHNtooltipShiftHorizontal : variables.gbrTooltipShiftHorizontal}
shiftVertical={shouldShowWokspaceChatTooltip ? 0 : variables.composerTooltipShiftVertical}
wrapperStyle={styles.productTrainingTooltipWrapper}
>
<View>
Expand All @@ -183,6 +186,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
event?.preventDefault();
// Enable Composer to focus on clicking the same chat after opening the context menu.
ReportActionComposeFocusManager.focus();
hideProductTrainingTooltip();
onSelectRow(optionItem, popoverAnchor);
}}
onMouseDown={(event) => {
Expand Down
11 changes: 5 additions & 6 deletions src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,6 @@ type MenuItemBaseProps = {
/** Should selected item be marked with checkmark */
shouldShowSelectedItemCheck?: boolean;

/** Handles what to do when hiding the tooltip */
onHideTooltip?: () => void;

/** Should use auto width for the icon container. */
shouldIconUseAutoWidthStyle?: boolean;

Expand All @@ -351,6 +348,9 @@ type MenuItemBaseProps = {

/** Pressable component Test ID. Used to locate the component in tests. */
pressableTestID?: string;

/** Whether to teleport the portal to the modal layer */
shouldTeleportPortalToModalLayer?: boolean;
};

type MenuItemProps = (IconProps | AvatarProps | NoIcon) & MenuItemBaseProps;
Expand Down Expand Up @@ -461,10 +461,10 @@ function MenuItem(
renderTooltipContent,
additionalIconStyles,
shouldShowSelectedItemCheck = false,
onHideTooltip,
shouldIconUseAutoWidthStyle = false,
shouldBreakWord = false,
pressableTestID,
shouldTeleportPortalToModalLayer,
}: MenuItemProps,
ref: PressableRef,
) {
Expand Down Expand Up @@ -600,8 +600,7 @@ function MenuItem(
wrapperStyle={tooltipWrapperStyle}
shiftHorizontal={tooltipShiftHorizontal}
shiftVertical={tooltipShiftVertical}
shouldAutoDismiss
onHideTooltip={onHideTooltip}
shouldTeleportPortalToModalLayer={shouldTeleportPortalToModalLayer}
>
<View>
<Hoverable>
Expand Down
5 changes: 3 additions & 2 deletions src/components/ProductTrainingContext/TOOLTIPS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
},
[WORKSAPCE_CHAT_CREATE]: {
content: [
{text: 'productTrainingTooltip.workspaceChatCreate.part1', isBold: true},
{text: 'productTrainingTooltip.workspaceChatCreate.part2', isBold: false},
{text: 'productTrainingTooltip.workspaceChatCreate.part1', isBold: false},
{text: 'productTrainingTooltip.workspaceChatCreate.part2', isBold: true},
{text: 'productTrainingTooltip.workspaceChatCreate.part3', isBold: false},
],
onHideTooltip: () => dismissProductTraining(WORKSAPCE_CHAT_CREATE),
name: WORKSAPCE_CHAT_CREATE,
Expand Down
23 changes: 17 additions & 6 deletions src/components/ProductTrainingContext/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ function ProductTrainingContextProvider({children}: ChildrenProps) {
const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING);
const {shouldUseNarrowLayout} = useResponsiveLayout();

const [modal] = useOnyx(ONYXKEYS.MODAL);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const isModalVisible = modal?.isVisible || modal?.willAlertModalBecomeVisible;

const [activeTooltips, setActiveTooltips] = useState<Set<ProductTrainingTooltipName>>(new Set());

const unregisterTooltip = useCallback(
Expand Down Expand Up @@ -92,11 +96,16 @@ function ProductTrainingContextProvider({children}: ChildrenProps) {
return false;
}

// We need to make an exception for the QAB tooltip because it is shown in a modal, otherwise it would be hidden if a modal is visible
if (tooltipName !== CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.QUICK_ACTION_BUTTON && isModalVisible) {
return false;
}

return tooltipConfig.shouldShow({
shouldUseNarrowLayout,
});
},
[dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout],
[dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout, isModalVisible],
);

const registerTooltip = useCallback(
Expand Down Expand Up @@ -156,11 +165,10 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou
useEffect(() => {
if (shouldShow) {
registerTooltip(tooltipName);
return () => {
unregisterTooltip(tooltipName);
};
}
return () => {};
return () => {
unregisterTooltip(tooltipName);
};
}, [tooltipName, registerTooltip, unregisterTooltip, shouldShow]);

const renderProductTrainingTooltip = useCallback(() => {
Expand Down Expand Up @@ -209,10 +217,13 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou
}, [shouldRenderTooltip, tooltipName, shouldShow]);

const hideProductTrainingTooltip = useCallback(() => {
if (!shouldShowProductTrainingTooltip) {
return;
}
const tooltip = TOOLTIPS[tooltipName];
tooltip.onHideTooltip();
unregisterTooltip(tooltipName);
}, [tooltipName, unregisterTooltip]);
}, [tooltipName, shouldShowProductTrainingTooltip, unregisterTooltip]);

return {
renderProductTrainingTooltip,
Expand Down
22 changes: 16 additions & 6 deletions src/components/Search/SearchPageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useIsFocused} from '@react-navigation/native';
import React, {useMemo, useState} from 'react';
import {useFocusEffect} from '@react-navigation/native';
import React, {useCallback, useMemo, useState} from 'react';
import {InteractionManager, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
Expand Down Expand Up @@ -58,14 +58,25 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
const [isDeleteExpensesConfirmModalVisible, setIsDeleteExpensesConfirmModalVisible] = useState(false);
const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
const isFocused = useIsFocused();

const [isScreenFocused, setIsScreenFocused] = useState(false);

const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP,
isFocused,
isScreenFocused,
);

const {status, hash} = queryJSON;

useFocusEffect(
useCallback(() => {
setIsScreenFocused(true);
return () => {
setIsScreenFocused(false);
};
}, []),
);

const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {});

const handleDeleteExpenses = () => {
Expand Down Expand Up @@ -334,6 +345,7 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
}

const onFiltersButtonPress = () => {
hideProductTrainingTooltip();
const filterFormValues = SearchQueryUtils.buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, cardList, reports, taxRates);
SearchActions.updateAdvancedFilters(filterFormValues);

Expand Down Expand Up @@ -362,11 +374,9 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
}}
shouldUseOverlay
shiftHorizontal={variables.searchFiltersTooltipShiftHorizontal}
wrapperStyle={styles.productTrainingTooltipWrapper}
renderTooltipContent={renderProductTrainingTooltip}
onHideTooltip={hideProductTrainingTooltip}
>
<Button
innerStyles={!isCannedQuery && [styles.searchRouterInputResults, styles.borderNone]}
Expand Down
8 changes: 7 additions & 1 deletion src/components/ThreeDotsMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function ThreeDotsMenu({
shouldOverlay = false,
shouldSetModalVisibility = true,
disabled = false,
hideProductTrainingTooltip,
}: ThreeDotsMenuProps) {
const [modal] = useOnyx(ONYXKEYS.MODAL);

Expand Down Expand Up @@ -57,13 +58,18 @@ function ThreeDotsMenu({
return (
<>
<View>
<Tooltip text={translate(iconTooltip)}>
<Tooltip
text={translate(iconTooltip)}
// We need to hide the extra "More" tooltip when we have an educational tooltip
shouldRender={!hideProductTrainingTooltip}
>
<PressableWithoutFeedback
onPress={() => {
if (isPopupMenuVisible) {
hidePopoverMenu();
return;
}
hideProductTrainingTooltip?.();
buttonRef.current?.blur();
showPopoverMenu();
if (onIconPress) {
Expand Down
3 changes: 3 additions & 0 deletions src/components/ThreeDotsMenu/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ type ThreeDotsMenuProps = {

/** Should we announce the Modal visibility changes? */
shouldSetModalVisibility?: boolean;

/** Function to hide the product training tooltip */
hideProductTrainingTooltip?: () => void;
};

export default ThreeDotsMenuProps;
3 changes: 2 additions & 1 deletion src/components/Tooltip/BaseGenericTooltip/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function BaseGenericTooltip({
wrapperStyle = {},
shouldUseOverlay = false,
onHideTooltip = () => {},
shouldTeleportPortalToModalLayer = false,
}: BaseGenericTooltipProps) {
// The width of tooltip's inner content. Has to be undefined in the beginning
// as a width of 0 will cause the content to be rendered of a width of 0,
Expand Down Expand Up @@ -110,7 +111,7 @@ function BaseGenericTooltip({
}

return (
<Portal hostName={!shouldUseOverlay ? 'modal' : undefined}>
<Portal hostName={shouldTeleportPortalToModalLayer ? 'modal' : undefined}>
{shouldUseOverlay && <TransparentOverlay onPress={onHideTooltip} />}
<Animated.View
ref={rootWrapper}
Expand Down
8 changes: 4 additions & 4 deletions src/components/Tooltip/BaseGenericTooltip/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ type BaseGenericTooltipProps = {

/** Handles what to do when hiding the tooltip */
onHideTooltip?: () => void;
} & Pick<
SharedTooltipProps,
'renderTooltipContent' | 'maxWidth' | 'numberOfLines' | 'text' | 'shouldForceRenderingBelow' | 'wrapperStyle' | 'anchorAlignment' | 'shouldUseOverlay' | 'onHideTooltip'
>;

/** Whether the tooltip should teleport to the modal layer */
shouldTeleportPortalToModalLayer?: boolean;
} & Pick<SharedTooltipProps, 'renderTooltipContent' | 'maxWidth' | 'numberOfLines' | 'text' | 'shouldForceRenderingBelow' | 'wrapperStyle' | 'anchorAlignment' | 'shouldUseOverlay'>;

// eslint-disable-next-line import/prefer-default-export
export type {BaseGenericTooltipProps};
Loading

0 comments on commit 553c410

Please sign in to comment.