Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 18 additions & 1 deletion src/components/TabSelector/TabSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ type TabSelectorProps = MaterialTopTabBarProps & {

/** Whether to show the label when the tab is inactive */
shouldShowLabelWhenInactive?: boolean;

/** Determines whether the product training tooltip should be displayed to the user. */
shouldShowProductTrainingTooltip?: boolean;

/** Function to render the content of the product training tooltip. */
renderProductTrainingTooltip?: () => React.JSX.Element;
};

type IconTitleAndTestID = {
Expand Down Expand Up @@ -53,7 +59,16 @@ function getIconTitleAndTestID(route: string, translate: LocaleContextProps['tra
}
}

function TabSelector({state, navigation, onTabPress = () => {}, position, onFocusTrapContainerElementChanged, shouldShowLabelWhenInactive = true}: TabSelectorProps) {
function TabSelector({
state,
navigation,
onTabPress = () => {},
position,
onFocusTrapContainerElementChanged,
shouldShowLabelWhenInactive = true,
shouldShowProductTrainingTooltip = false,
renderProductTrainingTooltip,
}: TabSelectorProps) {
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -109,6 +124,8 @@ function TabSelector({state, navigation, onTabPress = () => {}, position, onFocu
isActive={isActive}
testID={testID}
shouldShowLabelWhenInactive={shouldShowLabelWhenInactive}
shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip}
renderProductTrainingTooltip={renderProductTrainingTooltip}
/>
);
})}
Expand Down
79 changes: 54 additions & 25 deletions src/components/TabSelector/TabSelectorItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, {useState} from 'react';
import {Animated} from 'react-native';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Tooltip from '@components/Tooltip';
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import type IconAsset from '@src/types/utils/IconAsset';
Expand Down Expand Up @@ -38,6 +39,12 @@ type TabSelectorItemProps = {

/** Test identifier used to find elements in unit and e2e tests */
testID?: string;

/** Determines whether the product training tooltip should be displayed to the user. */
shouldShowProductTrainingTooltip?: boolean;

/** Function to render the content of the product training tooltip. */
renderProductTrainingTooltip?: () => React.JSX.Element;
};

function TabSelectorItem({
Expand All @@ -50,39 +57,61 @@ function TabSelectorItem({
isActive = false,
shouldShowLabelWhenInactive = true,
testID,
shouldShowProductTrainingTooltip = false,
renderProductTrainingTooltip,
}: TabSelectorItemProps) {
const styles = useThemeStyles();
const [isHovered, setIsHovered] = useState(false);

return (
<Tooltip
shouldRender={!shouldShowLabelWhenInactive && !isActive}
text={title}
const shouldShowEducationalTooltip = shouldShowProductTrainingTooltip && isActive;

const children = (
<AnimatedPressableWithFeedback
accessibilityLabel={title}
style={[styles.tabSelectorButton, styles.tabBackground(isHovered, isActive, backgroundColor), styles.userSelectNone]}
wrapperStyle={[styles.flexGrow1]}
onPress={onPress}
onHoverIn={() => setIsHovered(true)}
onHoverOut={() => setIsHovered(false)}
role={CONST.ROLE.BUTTON}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
testID={testID}
>
<AnimatedPressableWithFeedback
accessibilityLabel={title}
style={[styles.tabSelectorButton, styles.tabBackground(isHovered, isActive, backgroundColor), styles.userSelectNone]}
wrapperStyle={[styles.flexGrow1]}
onPress={onPress}
onHoverIn={() => setIsHovered(true)}
onHoverOut={() => setIsHovered(false)}
role={CONST.ROLE.BUTTON}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
testID={testID}
>
<TabIcon
icon={icon}
<TabIcon
icon={icon}
activeOpacity={styles.tabOpacity(isHovered, isActive, activeOpacity, inactiveOpacity).opacity}
inactiveOpacity={styles.tabOpacity(isHovered, isActive, inactiveOpacity, activeOpacity).opacity}
/>
{(shouldShowLabelWhenInactive || isActive) && (
<TabLabel
title={title}
activeOpacity={styles.tabOpacity(isHovered, isActive, activeOpacity, inactiveOpacity).opacity}
inactiveOpacity={styles.tabOpacity(isHovered, isActive, inactiveOpacity, activeOpacity).opacity}
/>
{(shouldShowLabelWhenInactive || isActive) && (
<TabLabel
title={title}
activeOpacity={styles.tabOpacity(isHovered, isActive, activeOpacity, inactiveOpacity).opacity}
inactiveOpacity={styles.tabOpacity(isHovered, isActive, inactiveOpacity, activeOpacity).opacity}
/>
)}
</AnimatedPressableWithFeedback>
)}
</AnimatedPressableWithFeedback>
);

return shouldShowEducationalTooltip ? (
<EducationalTooltip
shouldRender
renderTooltipContent={renderProductTrainingTooltip}
shouldHideOnNavigate
anchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
}}
wrapperStyle={styles.productTrainingTooltipWrapper}
computeHorizontalShiftForNative
>
{children}
</EducationalTooltip>
) : (
<Tooltip
shouldRender={!shouldShowLabelWhenInactive && !isActive}
text={title}
>
{children}
</Tooltip>
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Tooltip/BaseGenericTooltip/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function BaseGenericTooltip({
shouldTeleportPortalToModalLayer = false,
isEducationTooltip = false,
onTooltipPress = () => {},
computeHorizontalShiftForNative = 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 @@ -74,6 +75,7 @@ function BaseGenericTooltip({
wrapperStyle,
shouldAddHorizontalPadding: false,
isEducationTooltip,
computeHorizontalShiftForNative,
}),
[
StyleUtils,
Expand All @@ -91,6 +93,7 @@ function BaseGenericTooltip({
anchorAlignment,
wrapperStyle,
isEducationTooltip,
computeHorizontalShiftForNative,
],
);

Expand Down
11 changes: 10 additions & 1 deletion src/components/Tooltip/BaseGenericTooltip/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,16 @@ type BaseGenericTooltipProps = {
isEducationTooltip?: boolean;
} & Pick<
SharedTooltipProps,
'renderTooltipContent' | 'maxWidth' | 'numberOfLines' | 'text' | 'shouldForceRenderingBelow' | 'wrapperStyle' | 'anchorAlignment' | 'shouldUseOverlay' | 'onTooltipPress'
| 'renderTooltipContent'
| 'maxWidth'
| 'numberOfLines'
| 'text'
| 'shouldForceRenderingBelow'
| 'wrapperStyle'
| 'anchorAlignment'
| 'shouldUseOverlay'
| 'onTooltipPress'
| 'computeHorizontalShiftForNative'
>;

// eslint-disable-next-line import/prefer-default-export
Expand Down
2 changes: 2 additions & 0 deletions src/components/Tooltip/GenericTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function GenericTooltip({
shouldRender = true,
isEducationTooltip = false,
onTooltipPress = () => {},
computeHorizontalShiftForNative = false,
}: GenericTooltipProps) {
const {preferredLocale} = useLocalize();
const {windowWidth} = useWindowDimensions();
Expand Down Expand Up @@ -189,6 +190,7 @@ function GenericTooltip({
shouldTeleportPortalToModalLayer={shouldTeleportPortalToModalLayer}
onHideTooltip={onPressOverlay}
onTooltipPress={onTooltipPress}
computeHorizontalShiftForNative={computeHorizontalShiftForNative}
/>
)}
{/* eslint-disable-next-line react-compiler/react-compiler */}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Tooltip/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type SharedTooltipProps = {

/** Callback when tooltip is clicked */
onTooltipPress?: (event: GestureResponderEvent | KeyboardEvent | undefined) => void;

/** Whether to compute horizontal shift for native */
computeHorizontalShiftForNative?: boolean;
};

type GenericTooltipState = {
Expand Down
12 changes: 11 additions & 1 deletion src/libs/Navigation/OnyxTabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ type OnyxTabNavigatorProps = ChildrenProps & {
/** Disable swipe between tabs */
disableSwipe?: boolean;

/** Determines whether the product training tooltip should be displayed to the user. */
shouldShowProductTrainingTooltip?: boolean;

/** Function to render the content of the product training tooltip. */
renderProductTrainingTooltip?: () => React.JSX.Element;

/** Whether to lazy load the tab screens */
lazyLoadEnabled?: boolean;

Expand Down Expand Up @@ -78,6 +84,8 @@ function OnyxTabNavigator({
screenListeners,
shouldShowLabelWhenInactive = true,
disableSwipe = false,
shouldShowProductTrainingTooltip,
renderProductTrainingTooltip,
lazyLoadEnabled = false,
onTabSelect,
...rest
Expand Down Expand Up @@ -114,12 +122,14 @@ function OnyxTabNavigator({
<TabBar
onFocusTrapContainerElementChanged={onTabBarFocusTrapContainerElementChanged}
shouldShowLabelWhenInactive={shouldShowLabelWhenInactive}
shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip}
renderProductTrainingTooltip={renderProductTrainingTooltip}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
},
[TabBar, onTabBarFocusTrapContainerElementChanged, shouldShowLabelWhenInactive],
[TabBar, onTabBarFocusTrapContainerElementChanged, shouldShowLabelWhenInactive, shouldShowProductTrainingTooltip, renderProductTrainingTooltip],
);

// If the selected tab changes, we need to update the focus trap container element of the active tab
Expand Down
26 changes: 23 additions & 3 deletions src/pages/iou/request/IOURequestStartPage.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import {useFocusEffect} from '@react-navigation/native';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import DragAndDropProvider from '@components/DragAndDrop/Provider';
import FocusTrapContainerElement from '@components/FocusTrap/FocusTrapContainerElement';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {useProductTrainingContext} from '@components/ProductTrainingContext';
import ScreenWrapper from '@components/ScreenWrapper';
import TabSelector from '@components/TabSelector/TabSelector';
import useLocalize from '@hooks/useLocalize';
import usePermissions from '@hooks/usePermissions';
import usePolicy from '@hooks/usePolicy';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import {dismissProductTraining} from '@libs/actions/Welcome';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import Navigation from '@libs/Navigation/Navigation';
import OnyxTabNavigator, {TabScreenWithFocusTrapWrapper, TopTab} from '@libs/Navigation/OnyxTabNavigator';
import {getIsUserSubmittedExpenseOrScannedReceipt} from '@libs/OptionsListUtils';
import Performance from '@libs/Performance';
import {getPerDiemCustomUnit, getPerDiemCustomUnits} from '@libs/PolicyUtils';
import {getPerDiemCustomUnit, getPerDiemCustomUnits, isUserInvitedToWorkspace} from '@libs/PolicyUtils';
import {getPayeeName} from '@libs/ReportUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {IOURequestType} from '@userActions/IOU';
Expand Down Expand Up @@ -139,6 +142,19 @@ function IOURequestStartPage({
iouType !== CONST.IOU.TYPE.SPLIT && iouType !== CONST.IOU.TYPE.TRACK && ((!isFromGlobalCreate && doesCurrentPolicyPerDiemExist) || (isFromGlobalCreate && doesPerDiemPolicyExist));

const {isBetaEnabled} = usePermissions();
const setTestReceiptAndNavigateRef = useRef<() => void>();
const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip} = useProductTrainingContext(
CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP,
!getIsUserSubmittedExpenseOrScannedReceipt() && isBetaEnabled(CONST.BETAS.NEWDOT_MANAGER_MCTEST) && selectedTab === CONST.TAB_REQUEST.SCAN && !isUserInvitedToWorkspace(),
{
onConfirm: () => {
setTestReceiptAndNavigateRef?.current?.();
},
onDismiss: () => {
dismissProductTraining(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP, true);
},
},
);

return (
<AccessOrNotFoundWrapper
Expand Down Expand Up @@ -179,6 +195,8 @@ function IOURequestStartPage({
onTabBarFocusTrapContainerElementChanged={setTabBarContainerElement}
onActiveTabFocusTrapContainerElementChanged={setActiveTabContainerElement}
shouldShowLabelWhenInactive={!shouldShowPerDiemOption}
shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip}
renderProductTrainingTooltip={renderProductTrainingTooltip}
lazyLoadEnabled
disableSwipe={isSwipeDisabled}
>
Expand All @@ -199,10 +217,12 @@ function IOURequestStartPage({
<IOURequestStepScan
route={route}
navigation={navigation}
onLayout={(setTestReceiptAndNavigate) => {
setTestReceiptAndNavigateRef.current = setTestReceiptAndNavigate;
}}
setTabSwipeDisabled={setSwipeDisabled}
isMultiScanEnabled={isMultiScanEnabled}
setIsMultiScanEnabled={setIsMultiScanEnabled}
isTooltipAllowed
/>
</TabScreenWithFocusTrapWrapper>
)}
Expand Down
Loading