Skip to content

Commit 9687e9b

Browse files
authored
Merge pull request Expensify#61824 from ishakakkad/61084
2 parents 48ee853 + 89eca23 commit 9687e9b

14 files changed

Lines changed: 262 additions & 196 deletions

File tree

src/components/TabSelector/TabSelector.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ type TabSelectorProps = MaterialTopTabBarProps & {
2222

2323
/** Whether to show the label when the tab is inactive */
2424
shouldShowLabelWhenInactive?: boolean;
25+
26+
/** Determines whether the product training tooltip should be displayed to the user. */
27+
shouldShowProductTrainingTooltip?: boolean;
28+
29+
/** Function to render the content of the product training tooltip. */
30+
renderProductTrainingTooltip?: () => React.JSX.Element;
2531
};
2632

2733
type IconTitleAndTestID = {
@@ -53,7 +59,16 @@ function getIconTitleAndTestID(route: string, translate: LocaleContextProps['tra
5359
}
5460
}
5561

56-
function TabSelector({state, navigation, onTabPress = () => {}, position, onFocusTrapContainerElementChanged, shouldShowLabelWhenInactive = true}: TabSelectorProps) {
62+
function TabSelector({
63+
state,
64+
navigation,
65+
onTabPress = () => {},
66+
position,
67+
onFocusTrapContainerElementChanged,
68+
shouldShowLabelWhenInactive = true,
69+
shouldShowProductTrainingTooltip = false,
70+
renderProductTrainingTooltip,
71+
}: TabSelectorProps) {
5772
const {translate} = useLocalize();
5873
const theme = useTheme();
5974
const styles = useThemeStyles();
@@ -109,6 +124,8 @@ function TabSelector({state, navigation, onTabPress = () => {}, position, onFocu
109124
isActive={isActive}
110125
testID={testID}
111126
shouldShowLabelWhenInactive={shouldShowLabelWhenInactive}
127+
shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip}
128+
renderProductTrainingTooltip={renderProductTrainingTooltip}
112129
/>
113130
);
114131
})}

src/components/TabSelector/TabSelectorItem.tsx

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, {useState} from 'react';
33
import {Animated} from 'react-native';
44
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
55
import Tooltip from '@components/Tooltip';
6+
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
67
import useThemeStyles from '@hooks/useThemeStyles';
78
import CONST from '@src/CONST';
89
import type IconAsset from '@src/types/utils/IconAsset';
@@ -38,6 +39,12 @@ type TabSelectorItemProps = {
3839

3940
/** Test identifier used to find elements in unit and e2e tests */
4041
testID?: string;
42+
43+
/** Determines whether the product training tooltip should be displayed to the user. */
44+
shouldShowProductTrainingTooltip?: boolean;
45+
46+
/** Function to render the content of the product training tooltip. */
47+
renderProductTrainingTooltip?: () => React.JSX.Element;
4148
};
4249

4350
function TabSelectorItem({
@@ -50,39 +57,61 @@ function TabSelectorItem({
5057
isActive = false,
5158
shouldShowLabelWhenInactive = true,
5259
testID,
60+
shouldShowProductTrainingTooltip = false,
61+
renderProductTrainingTooltip,
5362
}: TabSelectorItemProps) {
5463
const styles = useThemeStyles();
5564
const [isHovered, setIsHovered] = useState(false);
5665

57-
return (
58-
<Tooltip
59-
shouldRender={!shouldShowLabelWhenInactive && !isActive}
60-
text={title}
66+
const shouldShowEducationalTooltip = shouldShowProductTrainingTooltip && isActive;
67+
68+
const children = (
69+
<AnimatedPressableWithFeedback
70+
accessibilityLabel={title}
71+
style={[styles.tabSelectorButton, styles.tabBackground(isHovered, isActive, backgroundColor), styles.userSelectNone]}
72+
wrapperStyle={[styles.flexGrow1]}
73+
onPress={onPress}
74+
onHoverIn={() => setIsHovered(true)}
75+
onHoverOut={() => setIsHovered(false)}
76+
role={CONST.ROLE.BUTTON}
77+
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
78+
testID={testID}
6179
>
62-
<AnimatedPressableWithFeedback
63-
accessibilityLabel={title}
64-
style={[styles.tabSelectorButton, styles.tabBackground(isHovered, isActive, backgroundColor), styles.userSelectNone]}
65-
wrapperStyle={[styles.flexGrow1]}
66-
onPress={onPress}
67-
onHoverIn={() => setIsHovered(true)}
68-
onHoverOut={() => setIsHovered(false)}
69-
role={CONST.ROLE.BUTTON}
70-
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
71-
testID={testID}
72-
>
73-
<TabIcon
74-
icon={icon}
80+
<TabIcon
81+
icon={icon}
82+
activeOpacity={styles.tabOpacity(isHovered, isActive, activeOpacity, inactiveOpacity).opacity}
83+
inactiveOpacity={styles.tabOpacity(isHovered, isActive, inactiveOpacity, activeOpacity).opacity}
84+
/>
85+
{(shouldShowLabelWhenInactive || isActive) && (
86+
<TabLabel
87+
title={title}
7588
activeOpacity={styles.tabOpacity(isHovered, isActive, activeOpacity, inactiveOpacity).opacity}
7689
inactiveOpacity={styles.tabOpacity(isHovered, isActive, inactiveOpacity, activeOpacity).opacity}
7790
/>
78-
{(shouldShowLabelWhenInactive || isActive) && (
79-
<TabLabel
80-
title={title}
81-
activeOpacity={styles.tabOpacity(isHovered, isActive, activeOpacity, inactiveOpacity).opacity}
82-
inactiveOpacity={styles.tabOpacity(isHovered, isActive, inactiveOpacity, activeOpacity).opacity}
83-
/>
84-
)}
85-
</AnimatedPressableWithFeedback>
91+
)}
92+
</AnimatedPressableWithFeedback>
93+
);
94+
95+
return shouldShowEducationalTooltip ? (
96+
<EducationalTooltip
97+
shouldRender
98+
renderTooltipContent={renderProductTrainingTooltip}
99+
shouldHideOnNavigate
100+
anchorAlignment={{
101+
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER,
102+
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
103+
}}
104+
wrapperStyle={styles.productTrainingTooltipWrapper}
105+
computeHorizontalShiftForNative
106+
>
107+
{children}
108+
</EducationalTooltip>
109+
) : (
110+
<Tooltip
111+
shouldRender={!shouldShowLabelWhenInactive && !isActive}
112+
text={title}
113+
>
114+
{children}
86115
</Tooltip>
87116
);
88117
}

src/components/Tooltip/BaseGenericTooltip/index.native.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function BaseGenericTooltip({
4141
shouldTeleportPortalToModalLayer = false,
4242
isEducationTooltip = false,
4343
onTooltipPress = () => {},
44+
computeHorizontalShiftForNative = false,
4445
}: BaseGenericTooltipProps) {
4546
// The width of tooltip's inner content. Has to be undefined in the beginning
4647
// as a width of 0 will cause the content to be rendered of a width of 0,
@@ -74,6 +75,7 @@ function BaseGenericTooltip({
7475
wrapperStyle,
7576
shouldAddHorizontalPadding: false,
7677
isEducationTooltip,
78+
computeHorizontalShiftForNative,
7779
}),
7880
[
7981
StyleUtils,
@@ -91,6 +93,7 @@ function BaseGenericTooltip({
9193
anchorAlignment,
9294
wrapperStyle,
9395
isEducationTooltip,
96+
computeHorizontalShiftForNative,
9497
],
9598
);
9699

src/components/Tooltip/BaseGenericTooltip/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ type BaseGenericTooltipProps = {
3838
isEducationTooltip?: boolean;
3939
} & Pick<
4040
SharedTooltipProps,
41-
'renderTooltipContent' | 'maxWidth' | 'numberOfLines' | 'text' | 'shouldForceRenderingBelow' | 'wrapperStyle' | 'anchorAlignment' | 'shouldUseOverlay' | 'onTooltipPress'
41+
| 'renderTooltipContent'
42+
| 'maxWidth'
43+
| 'numberOfLines'
44+
| 'text'
45+
| 'shouldForceRenderingBelow'
46+
| 'wrapperStyle'
47+
| 'anchorAlignment'
48+
| 'shouldUseOverlay'
49+
| 'onTooltipPress'
50+
| 'computeHorizontalShiftForNative'
4251
>;
4352

4453
// eslint-disable-next-line import/prefer-default-export

src/components/Tooltip/GenericTooltip.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ function GenericTooltip({
3838
shouldRender = true,
3939
isEducationTooltip = false,
4040
onTooltipPress = () => {},
41+
computeHorizontalShiftForNative = false,
4142
}: GenericTooltipProps) {
4243
const {preferredLocale} = useLocalize();
4344
const {windowWidth} = useWindowDimensions();
@@ -189,6 +190,7 @@ function GenericTooltip({
189190
shouldTeleportPortalToModalLayer={shouldTeleportPortalToModalLayer}
190191
onHideTooltip={onPressOverlay}
191192
onTooltipPress={onTooltipPress}
193+
computeHorizontalShiftForNative={computeHorizontalShiftForNative}
192194
/>
193195
)}
194196
{/* eslint-disable-next-line react-compiler/react-compiler */}

src/components/Tooltip/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ type SharedTooltipProps = {
4545

4646
/** Callback when tooltip is clicked */
4747
onTooltipPress?: (event: GestureResponderEvent | KeyboardEvent | undefined) => void;
48+
49+
/** Whether to compute horizontal shift for native */
50+
computeHorizontalShiftForNative?: boolean;
4851
};
4952

5053
type GenericTooltipState = {

src/libs/Navigation/OnyxTabNavigator.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ type OnyxTabNavigatorProps = ChildrenProps & {
5050
/** Disable swipe between tabs */
5151
disableSwipe?: boolean;
5252

53+
/** Determines whether the product training tooltip should be displayed to the user. */
54+
shouldShowProductTrainingTooltip?: boolean;
55+
56+
/** Function to render the content of the product training tooltip. */
57+
renderProductTrainingTooltip?: () => React.JSX.Element;
58+
5359
/** Whether to lazy load the tab screens */
5460
lazyLoadEnabled?: boolean;
5561

@@ -78,6 +84,8 @@ function OnyxTabNavigator({
7884
screenListeners,
7985
shouldShowLabelWhenInactive = true,
8086
disableSwipe = false,
87+
shouldShowProductTrainingTooltip,
88+
renderProductTrainingTooltip,
8189
lazyLoadEnabled = false,
8290
onTabSelect,
8391
...rest
@@ -114,12 +122,14 @@ function OnyxTabNavigator({
114122
<TabBar
115123
onFocusTrapContainerElementChanged={onTabBarFocusTrapContainerElementChanged}
116124
shouldShowLabelWhenInactive={shouldShowLabelWhenInactive}
125+
shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip}
126+
renderProductTrainingTooltip={renderProductTrainingTooltip}
117127
// eslint-disable-next-line react/jsx-props-no-spreading
118128
{...props}
119129
/>
120130
);
121131
},
122-
[TabBar, onTabBarFocusTrapContainerElementChanged, shouldShowLabelWhenInactive],
132+
[TabBar, onTabBarFocusTrapContainerElementChanged, shouldShowLabelWhenInactive, shouldShowProductTrainingTooltip, renderProductTrainingTooltip],
123133
);
124134

125135
// If the selected tab changes, we need to update the focus trap container element of the active tab

src/pages/iou/request/IOURequestStartPage.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
import {useFocusEffect} from '@react-navigation/native';
2-
import React, {useCallback, useEffect, useMemo, useState} from 'react';
2+
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
33
import {View} from 'react-native';
44
import {useOnyx} from 'react-native-onyx';
55
import DragAndDropProvider from '@components/DragAndDrop/Provider';
66
import FocusTrapContainerElement from '@components/FocusTrap/FocusTrapContainerElement';
77
import HeaderWithBackButton from '@components/HeaderWithBackButton';
8+
import {useProductTrainingContext} from '@components/ProductTrainingContext';
89
import ScreenWrapper from '@components/ScreenWrapper';
910
import TabSelector from '@components/TabSelector/TabSelector';
1011
import useLocalize from '@hooks/useLocalize';
1112
import usePermissions from '@hooks/usePermissions';
1213
import usePolicy from '@hooks/usePolicy';
1314
import usePrevious from '@hooks/usePrevious';
1415
import useThemeStyles from '@hooks/useThemeStyles';
16+
import {dismissProductTraining} from '@libs/actions/Welcome';
1517
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
1618
import Navigation from '@libs/Navigation/Navigation';
1719
import OnyxTabNavigator, {TabScreenWithFocusTrapWrapper, TopTab} from '@libs/Navigation/OnyxTabNavigator';
20+
import {getIsUserSubmittedExpenseOrScannedReceipt} from '@libs/OptionsListUtils';
1821
import Performance from '@libs/Performance';
19-
import {getPerDiemCustomUnit, getPerDiemCustomUnits} from '@libs/PolicyUtils';
22+
import {getPerDiemCustomUnit, getPerDiemCustomUnits, isUserInvitedToWorkspace} from '@libs/PolicyUtils';
2023
import {getPayeeName} from '@libs/ReportUtils';
2124
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
2225
import type {IOURequestType} from '@userActions/IOU';
@@ -139,6 +142,19 @@ function IOURequestStartPage({
139142
iouType !== CONST.IOU.TYPE.SPLIT && iouType !== CONST.IOU.TYPE.TRACK && ((!isFromGlobalCreate && doesCurrentPolicyPerDiemExist) || (isFromGlobalCreate && doesPerDiemPolicyExist));
140143

141144
const {isBetaEnabled} = usePermissions();
145+
const setTestReceiptAndNavigateRef = useRef<() => void>();
146+
const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip} = useProductTrainingContext(
147+
CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP,
148+
!getIsUserSubmittedExpenseOrScannedReceipt() && isBetaEnabled(CONST.BETAS.NEWDOT_MANAGER_MCTEST) && selectedTab === CONST.TAB_REQUEST.SCAN && !isUserInvitedToWorkspace(),
149+
{
150+
onConfirm: () => {
151+
setTestReceiptAndNavigateRef?.current?.();
152+
},
153+
onDismiss: () => {
154+
dismissProductTraining(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP, true);
155+
},
156+
},
157+
);
142158

143159
return (
144160
<AccessOrNotFoundWrapper
@@ -179,6 +195,8 @@ function IOURequestStartPage({
179195
onTabBarFocusTrapContainerElementChanged={setTabBarContainerElement}
180196
onActiveTabFocusTrapContainerElementChanged={setActiveTabContainerElement}
181197
shouldShowLabelWhenInactive={!shouldShowPerDiemOption}
198+
shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip}
199+
renderProductTrainingTooltip={renderProductTrainingTooltip}
182200
lazyLoadEnabled
183201
disableSwipe={isSwipeDisabled}
184202
>
@@ -199,10 +217,12 @@ function IOURequestStartPage({
199217
<IOURequestStepScan
200218
route={route}
201219
navigation={navigation}
220+
onLayout={(setTestReceiptAndNavigate) => {
221+
setTestReceiptAndNavigateRef.current = setTestReceiptAndNavigate;
222+
}}
202223
setTabSwipeDisabled={setSwipeDisabled}
203224
isMultiScanEnabled={isMultiScanEnabled}
204225
setIsMultiScanEnabled={setIsMultiScanEnabled}
205-
isTooltipAllowed
206226
/>
207227
</TabScreenWithFocusTrapWrapper>
208228
)}

0 commit comments

Comments
 (0)