From 69151527551c4f0b4e370c812c6ab8deedf22d1f Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 12 Dec 2024 22:13:38 +0530 Subject: [PATCH 01/27] Rename PRODUCT_TRAINING_TOOLTIP_DATA to TOOLTIPS and update references --- ...{PRODUCT_TRAINING_TOOLTIP_DATA.ts => TOOLTIPS.ts} | 4 ++-- src/components/ProductTrainingContext/index.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) rename src/components/ProductTrainingContext/{PRODUCT_TRAINING_TOOLTIP_DATA.ts => TOOLTIPS.ts} (94%) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts similarity index 94% rename from src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts rename to src/components/ProductTrainingContext/TOOLTIPS.ts index d7f2a27d94d2..a52601fb701e 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -19,7 +19,7 @@ type TooltipData = { shouldShow: (props: ShouldShowConditionProps) => boolean; }; -const PRODUCT_TRAINING_TOOLTIP_DATA: Record = { +const TOOLTIPS: Record = { [CONCEIRGE_LHN_GBR]: { content: [ {text: 'productTrainingTooltip.conciergeLHNGBR.part1', isBold: false}, @@ -63,5 +63,5 @@ const PRODUCT_TRAINING_TOOLTIP_DATA: Record boolean; @@ -58,7 +58,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const sortedTooltips = Array.from(activeTooltips) .map((name) => ({ name, - priority: PRODUCT_TRAINING_TOOLTIP_DATA[name]?.priority ?? 0, + priority: TOOLTIPS[name]?.priority ?? 0, })) .sort((a, b) => b.priority - a.priority); @@ -78,7 +78,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { if (isDismissed || !Permissions.shouldShowProductTrainingElements(allBetas)) { return false; } - const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + const tooltipConfig = TOOLTIPS[tooltipName]; if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { return false; @@ -156,7 +156,7 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou }, [tooltipName, registerTooltip, unregisterTooltip, shouldShow]); const renderProductTrainingTooltip = useCallback(() => { - const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + const tooltip = TOOLTIPS[tooltipName]; return ( { - const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + const tooltip = TOOLTIPS[tooltipName]; tooltip.onHideTooltip(); unregisterTooltip(tooltipName); }, [tooltipName, unregisterTooltip]); From d4e601e4fb54b2dad52d481b58e09754a0bfc974 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 12 Dec 2024 23:58:09 +0530 Subject: [PATCH 02/27] Add new tooltips for search filter, bottom navigation inbox, workspace chat, and global create actions and render tooltip for global create --- src/CONST.ts | 4 + src/components/FloatingActionButton.tsx | 92 ++++++++++++++----- .../ProductTrainingContext/TOOLTIPS.ts | 54 ++++++++++- .../ProductTrainingContext/index.tsx | 9 +- src/languages/en.ts | 17 ++++ src/languages/es.ts | 17 ++++ src/types/onyx/DismissedProductTraining.ts | 31 ++++++- 7 files changed, 195 insertions(+), 29 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 71c17db3fa52..d882517f9f01 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6411,6 +6411,10 @@ const CONST = { RENAME_SAVED_SEARCH: 'renameSavedSearch', QUICK_ACTION_BUTTON: 'quickActionButton', WORKSAPCE_CHAT_CREATE: 'workspaceChatCreate', + SEARCH_FILTER_BUTTON_TOOLTIP: 'filterButtonTooltip', + BOTTOM_NAV_INBOX_TOOLTIP: 'bottomNavInboxTooltip', + LHN_WORKSPACE_CHAT_TOOLTIP: 'workspaceChatLHNTooltip', + GLOBAL_CREATE_TOOLTIP: 'globalCreateTooltip', }, } as const; diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 3c831301db8b..43ee40726127 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -1,3 +1,4 @@ +import {useIsFocused as useIsFocusedOriginal, useNavigationState} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useEffect, useRef} from 'react'; // eslint-disable-next-line no-restricted-imports @@ -5,10 +6,19 @@ import type {GestureResponderEvent, Role, Text, View} from 'react-native'; import {Platform} from 'react-native'; import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import Svg, {Path} from 'react-native-svg'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import CENTRAL_PANE_SCREENS from '@libs/Navigation/AppNavigator/CENTRAL_PANE_SCREENS'; +import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; +import getTopmostFullScreenRoute from '@libs/Navigation/getTopmostFullScreenRoute'; +import type {CentralPaneName, FullScreenName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import SCREENS from '@src/SCREENS'; import {PressableWithoutFeedback} from './Pressable'; +import {useProductTrainingContext} from './ProductTrainingContext'; +import EducationalTooltip from './Tooltip/EducationalTooltip'; const AnimatedPath = Animated.createAnimatedComponent(Path); AnimatedPath.displayName = 'AnimatedPath'; @@ -50,12 +60,30 @@ type FloatingActionButtonProps = { /* An accessibility role for the button */ role: Role; }; - +const useIsFocused = () => { + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const isFocused = useIsFocusedOriginal(); + const topmostFullScreenName = useNavigationState | undefined>(getTopmostFullScreenRoute); + const topmostCentralPane = useNavigationState | undefined>(getTopmostCentralPaneRoute); + if (topmostFullScreenName) { + return false; + } + if (shouldUseNarrowLayout) { + return isFocused || topmostCentralPane?.name === SCREENS.SEARCH.CENTRAL_PANE; + } + return isFocused || Object.keys(CENTRAL_PANE_SCREENS).includes(topmostCentralPane?.name ?? ''); +}; function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: FloatingActionButtonProps, ref: ForwardedRef) { const {success, buttonDefaultBG, textLight, textDark} = useTheme(); const styles = useThemeStyles(); const borderRadius = styles.floatingActionButton.borderRadius; + const {shouldUseNarrowLayout} = useResponsiveLayout(); const fabPressable = useRef(null); + const isFocused = useIsFocused(); + const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.GLOBAL_CREATE_TOOLTIP, + isFocused, + ); const sharedValue = useSharedValue(isActive ? 1 : 0); const buttonRef = ref; @@ -97,32 +125,46 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo }; return ( - { - fabPressable.current = el ?? null; - if (buttonRef && 'current' in buttonRef) { - buttonRef.current = el ?? null; - } + {}} - role={role} - shouldUseHapticsOnLongPress={false} + shiftVertical={variables.composerTooltipShiftVertical} + shiftHorizontal={shouldUseNarrowLayout ? 0 : -15} + shouldUseOverlay + renderTooltipContent={renderProductTrainingTooltip} + wrapperStyle={styles.quickActionTooltipWrapper} + onHideTooltip={hideProductTrainingTooltip} > - - - - - - + { + fabPressable.current = el ?? null; + if (buttonRef && 'current' in buttonRef) { + buttonRef.current = el ?? null; + } + }} + style={[styles.h100, styles.bottomTabBarItem]} + accessibilityLabel={accessibilityLabel} + onPress={toggleFabAction} + onLongPress={() => {}} + role={role} + shouldUseHapticsOnLongPress={false} + > + + + + + + + ); } diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index a52601fb701e..d581c707f0fd 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -3,7 +3,16 @@ import {dismissProductTraining} from '@libs/actions/Welcome'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; -const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; +const { + CONCEIRGE_LHN_GBR, + RENAME_SAVED_SEARCH, + WORKSAPCE_CHAT_CREATE, + QUICK_ACTION_BUTTON, + SEARCH_FILTER_BUTTON_TOOLTIP, + BOTTOM_NAV_INBOX_TOOLTIP, + LHN_WORKSPACE_CHAT_TOOLTIP, + GLOBAL_CREATE_TOOLTIP, +} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; type ProductTrainingTooltipName = ValueOf; @@ -40,6 +49,17 @@ const TOOLTIPS: Record = { priority: 1250, shouldShow: ({shouldUseNarrowLayout}) => !shouldUseNarrowLayout, }, + [GLOBAL_CREATE_TOOLTIP]: { + content: [ + {text: 'productTrainingTooltip.globalCreateTooltip.part1', isBold: false}, + {text: 'productTrainingTooltip.globalCreateTooltip.part2', isBold: true}, + {text: 'productTrainingTooltip.globalCreateTooltip.part3', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(GLOBAL_CREATE_TOOLTIP), + name: GLOBAL_CREATE_TOOLTIP, + priority: 1200, + shouldShow: () => true, + }, [QUICK_ACTION_BUTTON]: { content: [ {text: 'productTrainingTooltip.quickActionButton.part1', isBold: true}, @@ -47,7 +67,7 @@ const TOOLTIPS: Record = { ], onHideTooltip: () => dismissProductTraining(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, - priority: 1200, + priority: 1150, shouldShow: () => true, }, [WORKSAPCE_CHAT_CREATE]: { @@ -61,6 +81,36 @@ const TOOLTIPS: Record = { priority: 1100, shouldShow: () => true, }, + [SEARCH_FILTER_BUTTON_TOOLTIP]: { + content: [ + {text: 'productTrainingTooltip.searchFilterButtonTooltip.part1', isBold: false}, + {text: 'productTrainingTooltip.searchFilterButtonTooltip.part2', isBold: true}, + ], + onHideTooltip: () => dismissProductTraining(SEARCH_FILTER_BUTTON_TOOLTIP), + name: SEARCH_FILTER_BUTTON_TOOLTIP, + priority: 1000, + shouldShow: () => true, + }, + [BOTTOM_NAV_INBOX_TOOLTIP]: { + content: [ + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part2', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(BOTTOM_NAV_INBOX_TOOLTIP), + name: BOTTOM_NAV_INBOX_TOOLTIP, + priority: 900, + shouldShow: () => true, + }, + [LHN_WORKSPACE_CHAT_TOOLTIP]: { + content: [ + {text: 'productTrainingTooltip.workspaceChatTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.workspaceChatTooltip.part2', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(LHN_WORKSPACE_CHAT_TOOLTIP), + name: LHN_WORKSPACE_CHAT_TOOLTIP, + priority: 800, + shouldShow: () => true, + }, }; export default TOOLTIPS; diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index da35392ee1c0..1b967875440e 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -10,6 +10,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; import Permissions from '@libs/Permissions'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {ProductTrainingTooltipName} from './TOOLTIPS'; @@ -84,6 +85,11 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return false; } + // if hasbeenaddedtonudgemigration is true, and welcome modal is not dismissed, don't show tooltip + if (hasBeenAddedToNudgeMigration && !dismissedProductTraining?.[CONST.MIGRATED_USER_WELCOME_MODAL]) { + return false; + } + return tooltipConfig.shouldShow({ shouldUseNarrowLayout, }); @@ -164,7 +170,7 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou fill={theme.tooltipHighlightText} medium /> - + {tooltip.content.map(({text, isBold}) => { const translatedText = translate(text); return ( @@ -189,6 +195,7 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou styles.quickActionTooltipSubtitle, styles.textAlignCenter, styles.textBold, + styles.textWrap, theme.tooltipHighlightText, tooltipName, translate, diff --git a/src/languages/en.ts b/src/languages/en.ts index 2dece7799259..44303a1343e2 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5467,6 +5467,23 @@ const translations = { part2: ' expenses', part3: ' here!', }, + searchFilterButtonTooltip: { + part1: 'Customize your search', + part2: ' here!', + }, + bottomNavInboxTooltip: { + part1: 'Your to-do list', + part2: ' comes with 🟢 for actions to take and 🔴 for errors', + }, + workspaceChatTooltip: { + part1: 'Review submitted expenses and chat with approvers in your', + part2: ' workspace chat.', + }, + globalCreateTooltip: { + part1: 'Where to ', + part2: ' create expenses,', + part3: '\nstart chatting, and more.', + }, }, }; diff --git a/src/languages/es.ts b/src/languages/es.ts index 015f595eb8b3..7e87e310c447 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5987,6 +5987,23 @@ const translations = { part2: ' gastos', part3: ' aquí', }, + searchFilterButtonTooltip: { + part1: 'Customize your search', + part2: ' here!', + }, + bottomNavInboxTooltip: { + part1: 'Your to-do list', + part2: ' comes with 🟢 for actions to take and 🔴 for errors', + }, + workspaceChatTooltip: { + part1: 'Review submitted expenses and chat with approvers in your', + part2: ' workspace chat.', + }, + globalCreateTooltip: { + part1: 'Where to ', + part2: ' create expenses,', + part3: '\nstart chatting, and more.', + }, }, }; diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts index fe24dc061aee..53df7c403ca0 100644 --- a/src/types/onyx/DismissedProductTraining.ts +++ b/src/types/onyx/DismissedProductTraining.ts @@ -1,6 +1,15 @@ import CONST from '@src/CONST'; -const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; +const { + CONCEIRGE_LHN_GBR, + RENAME_SAVED_SEARCH, + WORKSAPCE_CHAT_CREATE, + QUICK_ACTION_BUTTON, + SEARCH_FILTER_BUTTON_TOOLTIP, + BOTTOM_NAV_INBOX_TOOLTIP, + LHN_WORKSPACE_CHAT_TOOLTIP, + GLOBAL_CREATE_TOOLTIP, +} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; /** * This type is used to store the timestamp of when the user dismisses a product training ui elements. */ @@ -29,6 +38,26 @@ type DismissedProductTraining = { * When user dismisses the quickActionButton product training tooltip, we store the timestamp here. */ [QUICK_ACTION_BUTTON]: Date; + + /** + * When user dismisses the searchFilterButtonTooltip product training tooltip, we store the timestamp here. + */ + [SEARCH_FILTER_BUTTON_TOOLTIP]: Date; + + /** + * When user dismisses the bottomNavInboxTooltip product training tooltip, we store the timestamp here. + */ + [BOTTOM_NAV_INBOX_TOOLTIP]: Date; + + /** + * When user dismisses the lhnWorkspaceChatTooltip product training tooltip, we store the timestamp here. + */ + [LHN_WORKSPACE_CHAT_TOOLTIP]: Date; + + /** + * When user dismisses the globalCreateTooltip product training tooltip, we store the timestamp here. + */ + [GLOBAL_CREATE_TOOLTIP]: Date; }; export default DismissedProductTraining; From ef25dd7f06ffc720db7c9a3fd0b8e2d37afbd8d1 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 13 Dec 2024 01:21:47 +0530 Subject: [PATCH 03/27] update workspace chat tooltip --- .../LHNOptionsList/OptionRowLHN.tsx | 33 +++++++++++-------- .../ProductTrainingContext/TOOLTIPS.ts | 3 +- src/languages/en.ts | 5 +-- src/languages/es.ts | 5 +-- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 6b8cf173b0fd..fe2ba90da65a 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,5 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; -import React, {useCallback, useRef, useState} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import type {GestureResponderEvent, ViewStyle} from 'react-native'; import {StyleSheet, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -47,19 +47,26 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); - + const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); + const isActiveWorkspaceChat = ReportUtils.isPolicyExpenseChat(report) && report?.isOwnPolicyExpenseChat && activePolicyID === report?.policyID; const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const session = useSession(); - // Guides are assigned for the MANAGE_TEAM onboarding action, except for emails that have a '+'. - const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+'); - const shouldShowToooltipOnThisReport = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); + const tooltipToRender = useMemo(() => { + // Guides are assigned for the MANAGE_TEAM onboarding action, except for emails that have a '+'. + const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+'); + const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); - const shouldShowGetStartedTooltip = shouldShowToooltipOnThisReport && isScreenFocused; - const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( - CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR, - shouldShowGetStartedTooltip, - ); + if (shouldShowGetStartedTooltip) { + return CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR; + } + // Default to workspace chat tooltip if neither condition is met + return CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.LHN_WORKSPACE_CHAT_TOOLTIP; + }, [introSelected, report, session]); + + const shouldShowTooltip = tooltipToRender === CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.LHN_WORKSPACE_CHAT_TOOLTIP ? isActiveWorkspaceChat : isScreenFocused; + + const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender, shouldShowTooltip); const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); @@ -159,13 +166,13 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti shouldRender={shouldShowProductTrainingTooltip} renderTooltipContent={renderProductTrainingTooltip} anchorAlignment={{ - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, + horizontal: isActiveWorkspaceChat ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }} shouldUseOverlay - shiftHorizontal={variables.gbrTooltipShiftHorizontal} + shiftHorizontal={isActiveWorkspaceChat ? 26 : variables.gbrTooltipShiftHorizontal} + shiftVertical={isActiveWorkspaceChat ? 0 : variables.composerTooltipShiftVertical} onHideTooltip={hideProductTrainingTooltip} - shiftVertical={variables.composerTooltipShiftVertical} wrapperStyle={styles.quickActionTooltipWrapper} > diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index d581c707f0fd..48a6b47a2496 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -103,8 +103,9 @@ const TOOLTIPS: Record = { }, [LHN_WORKSPACE_CHAT_TOOLTIP]: { content: [ - {text: 'productTrainingTooltip.workspaceChatTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.workspaceChatTooltip.part1', isBold: false}, {text: 'productTrainingTooltip.workspaceChatTooltip.part2', isBold: false}, + {text: 'productTrainingTooltip.workspaceChatTooltip.part3', isBold: true}, ], onHideTooltip: () => dismissProductTraining(LHN_WORKSPACE_CHAT_TOOLTIP), name: LHN_WORKSPACE_CHAT_TOOLTIP, diff --git a/src/languages/en.ts b/src/languages/en.ts index 44303a1343e2..0b35ccc1fd45 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5476,8 +5476,9 @@ const translations = { part2: ' comes with 🟢 for actions to take and 🔴 for errors', }, workspaceChatTooltip: { - part1: 'Review submitted expenses and chat with approvers in your', - part2: ' workspace chat.', + part1: 'Review submitted expenses and chat', + part2: '\nwith approvers in your', + part3: ' workspace chat.', }, globalCreateTooltip: { part1: 'Where to ', diff --git a/src/languages/es.ts b/src/languages/es.ts index 7e87e310c447..761e6619d1a6 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5996,8 +5996,9 @@ const translations = { part2: ' comes with 🟢 for actions to take and 🔴 for errors', }, workspaceChatTooltip: { - part1: 'Review submitted expenses and chat with approvers in your', - part2: ' workspace chat.', + part1: 'Review submitted expenses and chat', + part2: '\nwith approvers in your', + part3: ' workspace chat.', }, globalCreateTooltip: { part1: 'Where to ', From b74ab713a537898f0a9e90024f39f2b65020ad62 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 13 Dec 2024 01:38:42 +0530 Subject: [PATCH 04/27] Refactor FloatingActionButton to use useBottomTabIsFocused hook and add new useBottomTabIsFocused hook for better navigation state management --- src/components/FloatingActionButton.tsx | 22 +----- src/hooks/useBottomTabIsFocused.ts | 23 ++++++ .../BottomTabBar.tsx | 74 +++++++++++++------ 3 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 src/hooks/useBottomTabIsFocused.ts diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 43ee40726127..22e0b347995c 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -1,4 +1,3 @@ -import {useIsFocused as useIsFocusedOriginal, useNavigationState} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useEffect, useRef} from 'react'; // eslint-disable-next-line no-restricted-imports @@ -6,16 +5,12 @@ import type {GestureResponderEvent, Role, Text, View} from 'react-native'; import {Platform} from 'react-native'; 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 useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import CENTRAL_PANE_SCREENS from '@libs/Navigation/AppNavigator/CENTRAL_PANE_SCREENS'; -import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; -import getTopmostFullScreenRoute from '@libs/Navigation/getTopmostFullScreenRoute'; -import type {CentralPaneName, FullScreenName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import SCREENS from '@src/SCREENS'; import {PressableWithoutFeedback} from './Pressable'; import {useProductTrainingContext} from './ProductTrainingContext'; import EducationalTooltip from './Tooltip/EducationalTooltip'; @@ -60,26 +55,13 @@ type FloatingActionButtonProps = { /* An accessibility role for the button */ role: Role; }; -const useIsFocused = () => { - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const isFocused = useIsFocusedOriginal(); - const topmostFullScreenName = useNavigationState | undefined>(getTopmostFullScreenRoute); - const topmostCentralPane = useNavigationState | undefined>(getTopmostCentralPaneRoute); - if (topmostFullScreenName) { - return false; - } - if (shouldUseNarrowLayout) { - return isFocused || topmostCentralPane?.name === SCREENS.SEARCH.CENTRAL_PANE; - } - return isFocused || Object.keys(CENTRAL_PANE_SCREENS).includes(topmostCentralPane?.name ?? ''); -}; function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: FloatingActionButtonProps, ref: ForwardedRef) { const {success, buttonDefaultBG, textLight, textDark} = useTheme(); const styles = useThemeStyles(); const borderRadius = styles.floatingActionButton.borderRadius; const {shouldUseNarrowLayout} = useResponsiveLayout(); const fabPressable = useRef(null); - const isFocused = useIsFocused(); + const isFocused = useBottomTabIsFocused(); const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.GLOBAL_CREATE_TOOLTIP, isFocused, diff --git a/src/hooks/useBottomTabIsFocused.ts b/src/hooks/useBottomTabIsFocused.ts new file mode 100644 index 000000000000..33141e7ac4d8 --- /dev/null +++ b/src/hooks/useBottomTabIsFocused.ts @@ -0,0 +1,23 @@ +import {useIsFocused, useNavigationState} from '@react-navigation/native'; +import CENTRAL_PANE_SCREENS from '@libs/Navigation/AppNavigator/CENTRAL_PANE_SCREENS'; +import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; +import getTopmostFullScreenRoute from '@libs/Navigation/getTopmostFullScreenRoute'; +import type {CentralPaneName, FullScreenName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types'; +import SCREENS from '@src/SCREENS'; +import useResponsiveLayout from './useResponsiveLayout'; + +const useBottomTabIsFocused = () => { + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const isFocused = useIsFocused(); + const topmostFullScreenName = useNavigationState | undefined>(getTopmostFullScreenRoute); + const topmostCentralPane = useNavigationState | undefined>(getTopmostCentralPaneRoute); + if (topmostFullScreenName) { + return false; + } + if (shouldUseNarrowLayout) { + return isFocused || topmostCentralPane?.name === SCREENS.SEARCH.CENTRAL_PANE; + } + return isFocused || Object.keys(CENTRAL_PANE_SCREENS).includes(topmostCentralPane?.name ?? ''); +}; + +export default useBottomTabIsFocused; diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 88a735884493..4dbe5e738cad 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -4,11 +4,15 @@ import {useOnyx} from 'react-native-onyx'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithFeedback} from '@components/Pressable'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import type {SearchQueryString} from '@components/Search/types'; import Text from '@components/Text'; +import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; @@ -74,9 +78,15 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const [chatTabBrickRoad, setChatTabBrickRoad] = useState(() => getChatTabBrickRoad(activeWorkspaceID, currentReportID, reports, betas, policies, priorityMode, transactionViolations), ); + const isFocused = useBottomTabIsFocused(); + const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.BOTTOM_NAV_INBOX_TOOLTIP, + isFocused, + ); useEffect(() => { setChatTabBrickRoad(getChatTabBrickRoad(activeWorkspaceID, currentReportID, reports, betas, policies, priorityMode, transactionViolations)); @@ -136,30 +146,50 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { /> )} - - - - {!!chatTabBrickRoad && ( - - )} - - - {translate('common.inbox')} - - + + + {!!chatTabBrickRoad && ( + + )} + + + {translate('common.inbox')} + + + Date: Fri, 13 Dec 2024 01:47:11 +0530 Subject: [PATCH 05/27] Enhance bottom navigation inbox tooltip with additional content and improve tooltip rendering logic --- src/components/ProductTrainingContext/TOOLTIPS.ts | 1 + src/languages/en.ts | 3 ++- src/languages/es.ts | 5 +++-- .../createCustomBottomTabNavigator/BottomTabBar.tsx | 8 ++------ 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index 48a6b47a2496..0bee2c0a6f22 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -95,6 +95,7 @@ const TOOLTIPS: Record = { content: [ {text: 'productTrainingTooltip.bottomNavInboxTooltip.part1', isBold: true}, {text: 'productTrainingTooltip.bottomNavInboxTooltip.part2', isBold: false}, + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part3', isBold: false}, ], onHideTooltip: () => dismissProductTraining(BOTTOM_NAV_INBOX_TOOLTIP), name: BOTTOM_NAV_INBOX_TOOLTIP, diff --git a/src/languages/en.ts b/src/languages/en.ts index 0b35ccc1fd45..a840351c7eeb 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5473,7 +5473,8 @@ const translations = { }, bottomNavInboxTooltip: { part1: 'Your to-do list', - part2: ' comes with 🟢 for actions to take and 🔴 for errors', + part2: ' comes with 🟢 for', + part3: '\n actions to take and 🔴 for errors', }, workspaceChatTooltip: { part1: 'Review submitted expenses and chat', diff --git a/src/languages/es.ts b/src/languages/es.ts index 761e6619d1a6..00e022ffc017 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5987,13 +5987,14 @@ const translations = { part2: ' gastos', part3: ' aquí', }, - searchFilterButtonTooltip: { + ssearchFilterButtonTooltip: { part1: 'Customize your search', part2: ' here!', }, bottomNavInboxTooltip: { part1: 'Your to-do list', - part2: ' comes with 🟢 for actions to take and 🔴 for errors', + part2: ' comes with 🟢 for', + part3: '\n actions to take and 🔴 for errors', }, workspaceChatTooltip: { part1: 'Review submitted expenses and chat', diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 4dbe5e738cad..1c0cd92c96ca 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -12,7 +12,6 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; @@ -78,14 +77,13 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const {shouldUseNarrowLayout} = useResponsiveLayout(); const [chatTabBrickRoad, setChatTabBrickRoad] = useState(() => getChatTabBrickRoad(activeWorkspaceID, currentReportID, reports, betas, policies, priorityMode, transactionViolations), ); const isFocused = useBottomTabIsFocused(); const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.BOTTOM_NAV_INBOX_TOOLTIP, - isFocused, + selectedTab === SCREENS.HOME && isFocused, ); useEffect(() => { @@ -149,11 +147,9 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { Date: Fri, 13 Dec 2024 01:48:28 +0530 Subject: [PATCH 06/27] fix typo --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 00e022ffc017..c1fa274afbb8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5987,7 +5987,7 @@ const translations = { part2: ' gastos', part3: ' aquí', }, - ssearchFilterButtonTooltip: { + searchFilterButtonTooltip: { part1: 'Customize your search', part2: ' here!', }, From 07d8869ae8575df2a0cc02e0f5aaca8e95112e61 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 13 Dec 2024 02:27:11 +0530 Subject: [PATCH 07/27] Add educational tooltips for search filter and update bottom navigation tooltip logic --- src/components/Search/SearchPageHeader.tsx | 33 +++++++++++++++---- .../BottomTabBar.tsx | 2 +- src/pages/Search/SearchTypeMenu.tsx | 9 +++-- src/pages/Search/SearchTypeMenuNarrow.tsx | 30 ++++++++++++++--- 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index a78845f126d2..3ded36a99a05 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -1,3 +1,4 @@ +import {useIsFocused} from '@react-navigation/native'; import React, {useMemo, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -8,6 +9,8 @@ import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import * as Expensicons from '@components/Icon/Expensicons'; import {usePersonalDetails} from '@components/OnyxProvider'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; +import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -55,6 +58,11 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { const [isDeleteExpensesConfirmModalVisible, setIsDeleteExpensesConfirmModalVisible] = useState(false); const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); + const isFocused = useIsFocused(); + const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP, + isFocused, + ); const {status, hash} = queryJSON; @@ -348,12 +356,25 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { shouldUseStyleUtilityForAnchorPosition /> ) : ( -