Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8f09752
Add new tooltips
rayane-d Apr 9, 2025
a402e56
add types
rayane-d Apr 9, 2025
357ec91
prettier
rayane-d Apr 9, 2025
8a21857
Merge branch 'main' into Add-Outstanding-filter,-Settings-tab,-and-wo…
rayane-d Apr 15, 2025
1cfa482
update type
rayane-d Apr 15, 2025
c629032
Merge branch 'main' into Add-Outstanding-filter,-Settings-tab,-and-wo…
rayane-d Apr 22, 2025
03037c5
Merge branch 'Expensify:main' into Add-Outstanding-filter,-Settings-t…
rayane-d Apr 23, 2025
9395121
Merge branch 'main' into Add-Outstanding-filter,-Settings-tab,-and-wo…
rayane-d Apr 30, 2025
8965e09
Merge branch 'main' into Add-Outstanding-filter,-Settings-tab,-and-wo…
rayane-d May 1, 2025
88dd247
update translations
rayane-d May 1, 2025
0671876
Add Outstanding filter tooltip
rayane-d May 1, 2025
ad548b6
Settings tab tooltip
rayane-d May 1, 2025
e2d0e4b
Workspaces settings tooltip
rayane-d May 1, 2025
7190dce
prettier
rayane-d May 1, 2025
7c4d09e
fix settings tab tooltip
rayane-d May 1, 2025
f1fa820
Hide tooltip on scroll
rayane-d May 1, 2025
ab5b700
prettier
rayane-d May 1, 2025
eb921d3
Merge branch 'Expensify:main' into Add-Outstanding-filter,-Settings-t…
rayane-d May 1, 2025
f318d72
Fix tests
rayane-d May 1, 2025
ed4237b
useIsAccountSettingsRouteActive for the condition
rayane-d May 1, 2025
1eb720c
Merge branch 'main' into Add-Outstanding-filter,-Settings-tab,-and-wo…
rayane-d May 1, 2025
8b7056d
remove duplicate declaration
rayane-d May 1, 2025
900639b
feedback
rayane-d May 4, 2025
43f51ff
Merge branch 'main' into Add-Outstanding-filter,-Settings-tab,-and-wo…
rayane-d May 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6992,6 +6992,9 @@ const CONST = {
SCAN_TEST_TOOLTIP: 'scanTestTooltip',
SCAN_TEST_TOOLTIP_MANAGER: 'scanTestTooltipManager',
SCAN_TEST_CONFIRMATION: 'scanTestConfirmation',
OUTSANDING_FILTER: 'outstandingFilter',
SETTINGS_TAB: 'settingsTab',
WORKSPACES_SETTINGS: 'workspacesSettings',
GBR_RBR_CHAT: 'chatGBRRBR',
ACCOUNT_SWITCHER: 'accountSwitcher',
EXPENSE_REPORTS_FILTER: 'expenseReportsFilter',
Expand Down
5 changes: 5 additions & 0 deletions src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ type MenuItemBaseProps = {
/** Callback to fire when the education tooltip is pressed */
onEducationTooltipPress?: () => void;

/** Whether the tooltip should hide on scroll */
shouldHideOnScroll?: boolean;

shouldShowLoadingSpinnerIcon?: boolean;

/** Should selected item be marked with checkmark */
Expand Down Expand Up @@ -474,6 +477,7 @@ function MenuItem(
onBlur,
avatarID,
shouldRenderTooltip = false,
shouldHideOnScroll = false,
tooltipAnchorAlignment,
tooltipWrapperStyle = {},
tooltipShiftHorizontal = 0,
Expand Down Expand Up @@ -639,6 +643,7 @@ function MenuItem(
shiftVertical={tooltipShiftVertical}
shouldTeleportPortalToModalLayer={shouldTeleportPortalToModalLayer}
onTooltipPress={onEducationTooltipPress}
shouldHideOnScroll={shouldHideOnScroll}
>
<View>
<Hoverable>
Expand Down
25 changes: 15 additions & 10 deletions src/components/Navigation/NavigationTabBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
const [chatTabBrickRoad, setChatTabBrickRoad] = useState<BrickRoad>(undefined);
const platform = getPlatform();
const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP;
const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.BOTTOM_NAV_INBOX_TOOLTIP,
isTooltipAllowed && selectedTab !== NAVIGATION_TABS.HOME,
);
const {
renderProductTrainingTooltip: renderInboxTooltip,
shouldShowProductTrainingTooltip: shouldShowInboxTooltip,
hideProductTrainingTooltip: hideInboxTooltip,
} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.BOTTOM_NAV_INBOX_TOOLTIP, isTooltipAllowed && selectedTab !== NAVIGATION_TABS.HOME);
const StyleUtils = useStyleUtils();

// On a wide layout DebugTabView should be rendered only within the navigation tab bar displayed directly on screens.
Expand All @@ -82,9 +83,9 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
return;
}

hideProductTrainingTooltip();
hideInboxTooltip();
Navigation.navigate(ROUTES.HOME);
}, [hideProductTrainingTooltip, selectedTab]);
}, [hideInboxTooltip, selectedTab]);

const navigateToSearch = useCallback(() => {
if (selectedTab === NAVIGATION_TABS.SEARCH) {
Expand Down Expand Up @@ -220,13 +221,13 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
/>
</PressableWithFeedback>
<EducationalTooltip
shouldRender={shouldShowProductTrainingTooltip}
shouldRender={shouldShowInboxTooltip}
anchorAlignment={{
horizontal: isWebOrDesktop ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
shiftHorizontal={isWebOrDesktop ? 0 : variables.navigationTabBarInboxTooltipShiftHorizontal}
renderTooltipContent={renderProductTrainingTooltip}
renderTooltipContent={renderInboxTooltip}
wrapperStyle={styles.productTrainingTooltipWrapper}
shouldHideOnNavigate={false}
onTooltipPress={navigateToChats}
Expand Down Expand Up @@ -293,6 +294,8 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
style={styles.leftNavigationTabBarItem}
isSelected={selectedTab === NAVIGATION_TABS.SETTINGS}
onPress={showSettingsPage}
isWebOrDesktop={isWebOrDesktop}
isTooltipAllowed={isTooltipAllowed}
/>
</View>
<View style={styles.leftNavigationTabBarItem}>
Expand All @@ -314,13 +317,13 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
)}
<View style={styles.navigationTabBarContainer}>
<EducationalTooltip
shouldRender={shouldShowProductTrainingTooltip}
shouldRender={shouldShowInboxTooltip}
anchorAlignment={{
horizontal: isWebOrDesktop ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
shiftHorizontal={isWebOrDesktop ? 0 : variables.navigationTabBarInboxTooltipShiftHorizontal}
renderTooltipContent={renderProductTrainingTooltip}
renderTooltipContent={renderInboxTooltip}
wrapperStyle={styles.productTrainingTooltipWrapper}
shouldHideOnNavigate={false}
onTooltipPress={navigateToChats}
Expand Down Expand Up @@ -387,6 +390,8 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
style={styles.navigationTabBarItem}
isSelected={selectedTab === NAVIGATION_TABS.SETTINGS}
onPress={showSettingsPage}
isWebOrDesktop={isWebOrDesktop}
isTooltipAllowed={isTooltipAllowed}
/>
<View style={[styles.flex1, styles.navigationTabBarItem]}>
<NavigationTabBarFloatingActionButton isTooltipAllowed={isTooltipAllowed} />
Expand Down
33 changes: 33 additions & 0 deletions src/components/ProductTrainingContext/TOOLTIPS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const {
SCAN_TEST_TOOLTIP,
SCAN_TEST_TOOLTIP_MANAGER,
SCAN_TEST_CONFIRMATION,
OUTSANDING_FILTER,
SETTINGS_TAB,
WORKSPACES_SETTINGS,
GBR_RBR_CHAT,
ACCOUNT_SWITCHER,
EXPENSE_REPORTS_FILTER,
Expand Down Expand Up @@ -160,6 +163,36 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
priority: 1100,
shouldShow: () => true,
},
[OUTSANDING_FILTER]: {
content: [
{text: 'productTrainingTooltip.outstandingFilter.part1', isBold: false},
{text: 'productTrainingTooltip.outstandingFilter.part2', isBold: true},
],
onHideTooltip: () => dismissProductTraining(OUTSANDING_FILTER),
name: OUTSANDING_FILTER,
priority: 1925,
shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin,
},
[SETTINGS_TAB]: {
content: [
{text: 'productTrainingTooltip.settingsTab.part1', isBold: false},
{text: 'productTrainingTooltip.settingsTab.part2', isBold: true},
],
onHideTooltip: () => dismissProductTraining(SETTINGS_TAB),
name: SETTINGS_TAB,
priority: 1750,
shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin,
},
[WORKSPACES_SETTINGS]: {
content: [
{text: 'productTrainingTooltip.workspacesSettings.part1', isBold: false},
{text: 'productTrainingTooltip.workspacesSettings.part2', isBold: true},
],
onHideTooltip: () => dismissProductTraining(WORKSPACES_SETTINGS),
name: WORKSPACES_SETTINGS,
priority: 1550,
shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin,
},
};

export default TOOLTIPS;
Expand Down
86 changes: 66 additions & 20 deletions src/components/Search/SearchPageHeader/SearchStatusBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {useMemo, useRef} from 'react';
import {useFocusEffect} from '@react-navigation/native';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
// eslint-disable-next-line no-restricted-imports
import type {ScrollView as RNScrollView, ViewStyle} from 'react-native';
Expand All @@ -7,13 +8,16 @@ import Button from '@components/Button';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import * as Expensicons from '@components/Icon/Expensicons';
import {useProductTrainingContext} from '@components/ProductTrainingContext';
import ScrollView from '@components/ScrollView';
import {useSearchContext} from '@components/Search/SearchContext';
import type {ChatSearchStatus, ExpenseSearchStatus, InvoiceSearchStatus, SearchGroupBy, SearchQueryJSON, TripSearchStatus} from '@components/Search/types';
import SearchStatusSkeleton from '@components/Skeletons/SearchStatusSkeleton';
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useScrollEventEmitter from '@hooks/useScrollEventEmitter';
import useSingleExecution from '@hooks/useSingleExecution';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
Expand Down Expand Up @@ -229,6 +233,27 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear
const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: false});
const {isOffline} = useNetwork();

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

useFocusEffect(
useCallback(() => {
setIsScreenFocused(true);
return () => {
setIsScreenFocused(false);
};
}, []),
);
const isOutstandingStatusActive = Array.isArray(queryJSON.status)
? queryJSON.status.includes(CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING)
: queryJSON.status === CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING;
const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.OUTSANDING_FILTER,
isScreenFocused && !isOutstandingStatusActive,
);
// Controls the visibility of the educational tooltip based on user scrolling.
// Hides the tooltip when the user is scrolling and displays it once scrolling stops.
const triggerScrollEvent = useScrollEventEmitter();

const selectedTransactionsKeys = useMemo(() => Object.keys(selectedTransactions ?? {}), [selectedTransactions]);
const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || (!!selectionMode && selectionMode.isEnabled));

Expand Down Expand Up @@ -279,9 +304,16 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear
ref={scrollRef}
horizontal
showsHorizontalScrollIndicator={false}
onScroll={() => {
triggerScrollEvent();
}}
>
{options.map((item, index) => {
const isOutstanding = item.status === CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING;
const onPress = singleExecution(() => {
if (isOutstanding) {
hideProductTrainingTooltip();
}
onStatusChange?.();
const query = buildSearchQueryString({...queryJSON, status: item.status});
Navigation.setParams({q: query});
Expand All @@ -291,27 +323,41 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear
const isLastItem = index === options.length - 1;

return (
<Button
<EducationalTooltip
key={item.status}
onLayout={(e) => {
if (!isActive || isScrolledRef.current || !('left' in e.nativeEvent.layout)) {
return;
}
isScrolledRef.current = true;
scrollRef.current?.scrollTo({x: (e.nativeEvent.layout.left as number) - styles.pl5.paddingLeft});
shouldRender={isOutstanding && shouldShowProductTrainingTooltip}
anchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
}}
text={translate(item.text)}
onPress={onPress}
icon={item.icon}
iconFill={isActive ? theme.success : undefined}
iconHoverFill={theme.success}
innerStyles={!isActive && styles.bgTransparent}
hoverStyles={StyleUtils.getBackgroundColorStyle(!isActive ? theme.highlightBG : theme.border)}
textStyles={!isActive && StyleUtils.getTextColorStyle(theme.textSupporting)}
textHoverStyles={StyleUtils.getTextColorStyle(theme.text)}
// We add padding to the first and last items so that they align with the header and table but can overflow outside the screen when scrolled.
style={[isFirstItem && styles.pl5, isLastItem && styles.pr5]}
/>
shiftHorizontal={0}
renderTooltipContent={renderProductTrainingTooltip}
wrapperStyle={styles.productTrainingTooltipWrapper}
shouldHideOnScroll
shouldHideOnNavigate={false}
onTooltipPress={onPress}
>
<Button
onLayout={(e) => {
if (!isActive || isScrolledRef.current || !('left' in e.nativeEvent.layout)) {
return;
}
isScrolledRef.current = true;
scrollRef.current?.scrollTo({x: (e.nativeEvent.layout.left as number) - styles.pl5.paddingLeft});
}}
text={translate(item.text)}
onPress={onPress}
icon={item.icon}
iconFill={isActive ? theme.success : undefined}
iconHoverFill={theme.success}
innerStyles={!isActive && styles.bgTransparent}
hoverStyles={StyleUtils.getBackgroundColorStyle(!isActive ? theme.highlightBG : theme.border)}
textStyles={!isActive && StyleUtils.getTextColorStyle(theme.textSupporting)}
textHoverStyles={StyleUtils.getTextColorStyle(theme.text)}
// We add padding to the first and last items so that they align with the header and table but can overflow outside the screen when scrolled.
style={[isFirstItem && styles.pl5, isLastItem && styles.pr5]}
/>
</EducationalTooltip>
);
})}
</ScrollView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,26 @@ function BaseEducationalTooltip({children, shouldRender = false, shouldHideOnNav

getTooltipCoordinates(tooltipElementRef.current, (bounds) => {
updateTargetBounds(bounds);
const {y, height} = bounds;
const {x, y, width: elementWidth, height} = bounds;

const offset = 10; // Tooltip hides when content moves 10px past header/footer.
const dimensions = Dimensions.get('window');
const top = y - (insets.top || 0);
const bottom = y + height + insets.bottom || 0;

const left = x - (insets.left || 0);
const right = x + elementWidth + (insets.right || 0);
// Calculate the available space at the top, considering the header height and offset
const availableHeightForTop = top - (variables.contentHeaderHeight - offset);

// Calculate the total height available after accounting for the bottom tab and offset
const availableHeightForBottom = dimensions.height - (bottom + variables.bottomTabHeight - offset);

if (availableHeightForTop < 0 || availableHeightForBottom < 0) {
// Calculate available horizontal space, taking into account safe-area insets
const availableWidthForLeft = left - offset;
const availableWidthForRight = dimensions.width - (right - offset);

// Hide if the element scrolled out vertically or horizontally
if (availableHeightForTop < 0 || availableHeightForBottom < 0 || availableWidthForLeft < 0 || availableWidthForRight < 0) {
hideTooltip();
} else {
showTooltip();
Expand Down
12 changes: 12 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6257,6 +6257,18 @@ const translations = {
tryItOut: 'Try it out',
noThanks: 'No thanks',
},
outstandingFilter: {
part1: 'Filter for expenses\nthat ',
part2: 'need approval',
},
settingsTab: {
part1: 'Explore your ',
part2: 'workspace\nand account settings',
},
workspacesSettings: {
part1: 'View your ',
part2: 'workspaces',
},
},
discardChangesConfirmation: {
title: 'Discard changes?',
Expand Down
12 changes: 12 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6781,6 +6781,18 @@ const translations = {
tryItOut: 'Prueba esto',
noThanks: 'No, gracias',
},
outstandingFilter: {
part1: 'Filtra los gastos\nque ',
part2: 'necesitan aprobación',
},
settingsTab: {
part1: 'Explora ',
part2: 'tu espacio de trabajo\ny la configuración de tu cuenta',
},
workspacesSettings: {
part1: 'Ver tus ',
part2: 'espacios de trabajo',
},
Comment thread
rayane-d marked this conversation as resolved.
},
discardChangesConfirmation: {
title: '¿Descartar cambios?',
Expand Down
Loading