diff --git a/src/components/DestinationPicker.tsx b/src/components/DestinationPicker.tsx index bda61f34b7337..261a815627df8 100644 --- a/src/components/DestinationPicker.tsx +++ b/src/components/DestinationPicker.tsx @@ -3,10 +3,10 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PerDiemRequestUtils from '@libs/PerDiemRequestUtils'; +import {getHeaderMessageForNonUserList} from '@libs/OptionsListUtils'; +import {getDestinationListSections} from '@libs/PerDiemRequestUtils'; import type {Destination} from '@libs/PerDiemRequestUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; +import {getPerDiemCustomUnit} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SelectionList from './SelectionList'; @@ -21,8 +21,8 @@ type DestinationPickerProps = { function DestinationPicker({selectedDestination, policyID, onSubmit}: DestinationPickerProps) { const policy = usePolicy(policyID); - const customUnit = PolicyUtils.getPerDiemCustomUnit(policy); - const [policyRecentlyUsedDestinations] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${policyID}`); + const customUnit = getPerDiemCustomUnit(policy); + const [policyRecentlyUsedDestinations] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${policyID}`, {canBeMissing: true}); const {translate} = useLocalize(); const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); @@ -49,7 +49,7 @@ function DestinationPicker({selectedDestination, policyID, onSubmit}: Destinatio }, [customUnit?.rates, selectedDestination]); const [sections, headerMessage, shouldShowTextInput] = useMemo(() => { - const destinationOptions = PerDiemRequestUtils.getDestinationListSections({ + const destinationOptions = getDestinationListSections({ searchValue: debouncedSearchValue, selectedOptions, destinations: Object.values(customUnit?.rates ?? {}), @@ -57,7 +57,7 @@ function DestinationPicker({selectedDestination, policyID, onSubmit}: Destinatio }); const destinationData = destinationOptions?.at(0)?.data ?? []; - const header = OptionsListUtils.getHeaderMessageForNonUserList(destinationData.length > 0, debouncedSearchValue); + const header = getHeaderMessageForNonUserList(destinationData.length > 0, debouncedSearchValue); const destinationsCount = Object.values(customUnit?.rates ?? {}).length; const isDestinationsCountBelowThreshold = destinationsCount < CONST.STANDARD_LIST_ITEM_LIMIT; const showInput = !isDestinationsCountBelowThreshold; @@ -81,6 +81,7 @@ function DestinationPicker({selectedDestination, policyID, onSubmit}: Destinatio ListItem={RadioListItem} initiallyFocusedOptionKey={selectedOptionKey ?? undefined} isRowMultilineSupported + shouldHideKeyboardOnScroll={false} /> ); } diff --git a/src/components/SelectionList/index.native.tsx b/src/components/SelectionList/index.native.tsx index 4207f65c639c0..359044dcc6bf7 100644 --- a/src/components/SelectionList/index.native.tsx +++ b/src/components/SelectionList/index.native.tsx @@ -4,13 +4,18 @@ import {Keyboard} from 'react-native'; import BaseSelectionList from './BaseSelectionList'; import type {ListItem, SelectionListHandle, SelectionListProps} from './types'; -function SelectionList(props: SelectionListProps, ref: ForwardedRef) { +function SelectionList({shouldHideKeyboardOnScroll = true, ...props}: SelectionListProps, ref: ForwardedRef) { return ( Keyboard.dismiss()} + onScrollBeginDrag={() => { + if (!shouldHideKeyboardOnScroll) { + return; + } + Keyboard.dismiss(); + }} /> ); } diff --git a/src/components/SelectionList/index.tsx b/src/components/SelectionList/index.tsx index f5ee4d4c4e2df..c5f26d8c3b1d8 100644 --- a/src/components/SelectionList/index.tsx +++ b/src/components/SelectionList/index.tsx @@ -7,7 +7,7 @@ import CONST from '@src/CONST'; import BaseSelectionList from './BaseSelectionList'; import type {ListItem, SelectionListHandle, SelectionListProps} from './types'; -function SelectionList({onScroll, ...props}: SelectionListProps, ref: ForwardedRef) { +function SelectionList({onScroll, shouldHideKeyboardOnScroll = true, ...props}: SelectionListProps, ref: ForwardedRef) { const [isScreenTouched, setIsScreenTouched] = useState(false); const touchStart = () => setIsScreenTouched(true); @@ -58,8 +58,8 @@ function SelectionList({onScroll, ...props}: SelectionLi // In SearchPageBottomTab we use useAnimatedScrollHandler from reanimated(for performance reasons) and it returns object instead of function. In that case we cannot change it to a function call, that's why we have to choose between onScroll and defaultOnScroll. const defaultOnScroll = () => { - // Only dismiss the keyboard whenever the user scrolls the screen - if (!isScreenTouched) { + // Only dismiss the keyboard whenever the user scrolls the screen or `shouldHideKeyboardOnScroll` is true + if (!isScreenTouched || !shouldHideKeyboardOnScroll) { return; } Keyboard.dismiss(); diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 8c2e66dbc04b5..38108c795773b 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -826,6 +826,9 @@ type SelectionListProps = Partial & { /** Whether product training tooltips can be displayed */ canShowProductTrainingTooltip?: boolean; + + /** Whether to hide the keyboard when scrolling a list */ + shouldHideKeyboardOnScroll?: boolean; } & TRightHandSideComponent; type SelectionListHandle = { diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 2b859abef349f..dd10cf805eccb 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {View} from 'react-native'; +import {Keyboard, View} from 'react-native'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import FocusTrapContainerElement from '@components/FocusTrap/FocusTrapContainerElement'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -14,6 +14,7 @@ import usePolicy from '@hooks/usePolicy'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import {dismissProductTraining} from '@libs/actions/Welcome'; +import {isMobile} from '@libs/Browser'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import OnyxTabNavigator, {TabScreenWithFocusTrapWrapper, TopTab} from '@libs/Navigation/OnyxTabNavigator'; @@ -115,6 +116,7 @@ function IOURequestStartPage({ const resetIOUTypeIfChanged = useCallback( (newIOUType: IOURequestType) => { + Keyboard.dismiss(); if (transaction?.iouRequestType === newIOUType) { return; } @@ -175,6 +177,7 @@ function IOURequestStartPage({ > {() => ( diff --git a/src/pages/iou/request/step/IOURequestStepDestination.tsx b/src/pages/iou/request/step/IOURequestStepDestination.tsx index d2d125e44c670..a6c6c54c03182 100644 --- a/src/pages/iou/request/step/IOURequestStepDestination.tsx +++ b/src/pages/iou/request/step/IOURequestStepDestination.tsx @@ -5,17 +5,20 @@ import Button from '@components/Button'; import DestinationPicker from '@components/DestinationPicker'; import FixedFooter from '@components/FixedFooter'; import * as Illustrations from '@components/Icon/Illustrations'; +import ScreenWrapper from '@components/ScreenWrapper'; import type {ListItem} from '@components/SelectionList/types'; import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import {getPerDiemCustomUnit, isPolicyAdmin} from '@libs/PolicyUtils'; import {getPolicyExpenseChat} from '@libs/ReportUtils'; +import variables from '@styles/variables'; import { clearSubrates, getIOURequestPolicyID, @@ -55,7 +58,7 @@ function IOURequestStepDestination({ const [policy, policyMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${explicitPolicyID ?? getIOURequestPolicyID(transaction, report)}`, {canBeMissing: false}); const {accountID} = useCurrentUserPersonalDetails(); const policyExpenseReport = policy?.id ? getPolicyExpenseChat(accountID, policy.id) : undefined; - + const {top} = useSafeAreaInsets(); const customUnit = getPerDiemCustomUnit(policy); const selectedDestination = transaction?.comment?.customUnit?.customUnitRateID; @@ -112,56 +115,62 @@ function IOURequestStepDestination({ }; return ( - - {isLoading && ( - - )} - {shouldShowOfflineView && {null}} - {shouldShowEmptyState && ( - - + {isLoading && ( + + )} + {shouldShowOfflineView && {null}} + {shouldShowEmptyState && ( + + + {isPolicyAdmin(policy) && !!policy?.areCategoriesEnabled && ( + +