diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 80c7e0bf78f06..b583f8ef36cf2 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -65,6 +65,7 @@ function BaseSelectionList({ showScrollIndicator = true, canSelectMultiple = false, disableKeyboardShortcuts = false, + disableMaintainingScrollPosition = false, shouldUseUserSkeletonView, shouldShowTooltips = true, shouldIgnoreFocus = false, @@ -410,6 +411,7 @@ function BaseSelectionList({ style={style?.listStyle as ViewStyle} initialScrollIndex={initialFocusedIndex} onScrollBeginDrag={onScrollBeginDrag} + maintainVisibleContentPosition={{disabled: disableMaintainingScrollPosition}} ListHeaderComponent={ <> {customListHeaderContent} diff --git a/src/components/SelectionList/ListItem/types.ts b/src/components/SelectionList/ListItem/types.ts index 53fc743f14251..b5e0d660b21ae 100644 --- a/src/components/SelectionList/ListItem/types.ts +++ b/src/components/SelectionList/ListItem/types.ts @@ -286,6 +286,13 @@ type InviteMemberListItemProps = UserListItemProps = BaseListItemProps< TItem & { /** Value of the domain */ @@ -310,4 +317,5 @@ export type { SpendCategorySelectorListItemProps, UserListItemProps, InviteMemberListItemProps, + WorkspaceListItemType, }; diff --git a/src/components/SelectionList/components/TextInput.tsx b/src/components/SelectionList/components/TextInput.tsx index 4f0bfa76a2d73..80874e924830a 100644 --- a/src/components/SelectionList/components/TextInput.tsx +++ b/src/components/SelectionList/components/TextInput.tsx @@ -34,7 +34,7 @@ type TextInputProps = { onKeyPress?: (event: TextInputKeyPressEvent) => void; /** Function called when the text input focus changes */ - onFocusChange?: (focused: boolean) => void; + onFocusChange: (focused: boolean) => void; /** Whether to show the text input */ shouldShowTextInput?: boolean; @@ -103,6 +103,14 @@ function TextInput({ }, [shouldShowTextInput, disableAutoFocus, focusTextInput]), ); + const handleFocus = useCallback(() => { + onFocusChange(true); + }, [onFocusChange]); + + const handleBlur = useCallback(() => { + onFocusChange(false); + }, [onFocusChange]); + if (!shouldShowTextInput) { return null; } @@ -113,8 +121,8 @@ function TextInput({ onFocusChange?.(true)} - onBlur={() => onFocusChange?.(false)} + onFocus={handleFocus} + onBlur={handleBlur} label={label} accessibilityLabel={accessibilityLabel} hint={hint} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index fddf503cdf2a6..2ec3636c8cdb6 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -111,6 +111,9 @@ type SelectionListProps = { /** Whether keyboard shortcuts should be disabled */ disableKeyboardShortcuts?: boolean; + /** Whether scroll position should change when focused item changes */ + disableMaintainingScrollPosition?: boolean; + /** Whether to use the user skeleton view */ shouldUseUserSkeletonView?: boolean; diff --git a/src/hooks/useWorkspaceList.ts b/src/hooks/useWorkspaceList.ts index d42d497603298..4cf7c9bfdd82a 100644 --- a/src/hooks/useWorkspaceList.ts +++ b/src/hooks/useWorkspaceList.ts @@ -1,22 +1,15 @@ import {useMemo} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import * as Expensicons from '@components/Icon/Expensicons'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; -import type {ListItem, SectionListDataType} from '@components/SelectionListWithSections/types'; +import type {WorkspaceListItemType as WorkspaceListItem} from '@components/SelectionList/ListItem/types'; +import type {SectionListDataType} from '@components/SelectionListWithSections/types'; import {isPolicyAdmin, shouldShowPolicy, sortWorkspacesBySelected} from '@libs/PolicyUtils'; import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; import tokenizedSearch from '@libs/tokenizedSearch'; -import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; import CONST from '@src/CONST'; import type {Policy} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; - -type WorkspaceListItem = { - text: string; - policyID?: string; - isPolicyAdmin?: boolean; - brickRoadIndicator?: BrickRoad; -} & ListItem; +import {useMemoizedLazyExpensifyIcons} from './useLazyAsset'; type UseWorkspaceListParams = { policies: OnyxCollection; @@ -29,6 +22,7 @@ type UseWorkspaceListParams = { }; function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, searchTerm, shouldShowPendingDeletePolicy, localeCompare, additionalFilter}: UseWorkspaceListParams) { + const icons = useMemoizedLazyExpensifyIcons(['FallbackWorkspaceAvatar'] as const); const usersWorkspaces = useMemo(() => { if (!policies || isEmptyObject(policies)) { return []; @@ -48,17 +42,17 @@ function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, search icons: [ { source: policy?.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy?.name), - fallbackIcon: Expensicons.FallbackWorkspaceAvatar, + fallbackIcon: icons.FallbackWorkspaceAvatar, name: policy?.name, type: CONST.ICON_TYPE_WORKSPACE, id: policy?.id, }, ], - keyForList: policy?.id, + keyForList: `${policy?.id}`, isPolicyAdmin: isPolicyAdmin(policy), isSelected: policy?.id && selectedPolicyIDs ? selectedPolicyIDs.includes(policy.id) : false, })); - }, [policies, shouldShowPendingDeletePolicy, currentUserLogin, additionalFilter, selectedPolicyIDs]); + }, [policies, shouldShowPendingDeletePolicy, currentUserLogin, additionalFilter, icons.FallbackWorkspaceAvatar, selectedPolicyIDs]); const filteredAndSortedUserWorkspaces = useMemo( () => @@ -83,6 +77,7 @@ function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, search const shouldShowSearchInput = usersWorkspaces.length >= CONST.STANDARD_LIST_ITEM_LIMIT; return { + data: filteredAndSortedUserWorkspaces, sections, shouldShowNoResultsFoundMessage, shouldShowSearchInput, diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index 6e77369cf1753..ccbb8675add6f 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -1,11 +1,12 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useSession} from '@components/OnyxListItemProvider'; import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionListWithSections'; -import UserListItem from '@components/SelectionListWithSections/UserListItem'; +import SelectionList from '@components/SelectionList'; +import type {WorkspaceListItemType} from '@components/SelectionList/ListItem/types'; +import UserListItem from '@components/SelectionList/ListItem/UserListItem'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -13,7 +14,6 @@ import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {WorkspaceListItem} from '@hooks/useWorkspaceList'; import useWorkspaceList from '@hooks/useWorkspaceList'; import {changeReportPolicy, changeReportPolicyAndInviteSubmitter, moveIOUReportToPolicy, moveIOUReportToPolicyAndInviteSubmitter} from '@libs/actions/Report'; import Navigation from '@libs/Navigation/Navigation'; @@ -125,7 +125,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro ], ); - const {sections, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ + const {data, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ policies, currentUserLogin: session?.email, shouldShowPendingDeletePolicy: false, @@ -135,6 +135,16 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro additionalFilter: (newPolicy) => isWorkspaceEligibleForReportChange(submitterEmail, newPolicy), }); + const textInputOptions = useMemo( + () => ({ + label: shouldShowSearchInput ? translate('common.search') : undefined, + value: searchTerm, + onChangeText: setSearchTerm, + headerMessage: shouldShowNoResultsFoundMessage ? translate('common.noResultsFound') : '', + }), + [searchTerm, setSearchTerm, shouldShowNoResultsFoundMessage, shouldShowSearchInput, translate], + ); + if (!isMoneyRequestReport(report) || isMoneyRequestReportPendingDeletion(report)) { return ; } @@ -157,16 +167,14 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro {shouldShowLoadingIndicator ? ( ) : ( - + ListItem={UserListItem} - sections={sections} + data={data} onSelectRow={(option) => selectPolicy(option.policyID)} - textInputLabel={shouldShowSearchInput ? translate('common.search') : undefined} - textInputValue={searchTerm} - onChangeText={setSearchTerm} - headerMessage={shouldShowNoResultsFoundMessage ? translate('common.noResultsFound') : ''} - initiallyFocusedOptionKey={report.policyID} + textInputOptions={textInputOptions} + initiallyFocusedItemKey={report.policyID} showLoadingPlaceholder={fetchStatus.status === 'loading' || !didScreenTransitionEnd} + disableMaintainingScrollPosition /> )}