From e3b28376f06d48fa7730ba755ad6522123bbf85e Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Mon, 17 Nov 2025 16:11:29 +0100 Subject: [PATCH 1/8] Make ReportChangeWorkspacePage use new SelectionList --- .../SelectionList/ListItem/types.ts | 8 +++++ src/hooks/useWorkspaceList.ts | 3 +- src/pages/ReportChangeWorkspacePage.tsx | 31 ++++++++++++------- 3 files changed, 29 insertions(+), 13 deletions(-) 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/hooks/useWorkspaceList.ts b/src/hooks/useWorkspaceList.ts index d42d497603298..3298740774f10 100644 --- a/src/hooks/useWorkspaceList.ts +++ b/src/hooks/useWorkspaceList.ts @@ -54,7 +54,7 @@ function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, search id: policy?.id, }, ], - keyForList: policy?.id, + keyForList: policy?.id ?? '', isPolicyAdmin: isPolicyAdmin(policy), isSelected: policy?.id && selectedPolicyIDs ? selectedPolicyIDs.includes(policy.id) : false, })); @@ -83,6 +83,7 @@ function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, search const shouldShowSearchInput = usersWorkspaces.length >= CONST.STANDARD_LIST_ITEM_LIMIT; return { + usersWorkspaces, sections, shouldShowNoResultsFoundMessage, shouldShowSearchInput, diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index 6e77369cf1753..804f5f6259940 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 {usersWorkspaces, 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,15 +167,12 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro {shouldShowLoadingIndicator ? ( ) : ( - + ListItem={UserListItem} - sections={sections} + data={usersWorkspaces} 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} /> )} From 606a12d3a8cc1b7a193c49c79d2d07f9e526f3a2 Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Tue, 18 Nov 2025 11:22:04 +0100 Subject: [PATCH 2/8] Fix scrolling issue --- src/components/SelectionList/BaseSelectionList.tsx | 2 ++ src/components/SelectionList/types.ts | 3 +++ src/hooks/useWorkspaceList.ts | 2 +- src/pages/ReportChangeWorkspacePage.tsx | 5 +++-- 4 files changed, 9 insertions(+), 3 deletions(-) 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/types.ts b/src/components/SelectionList/types.ts index fddf503cdf2a6..611e0643d0dbd 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 possition should change when focsued 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 3298740774f10..22c6fb758d49a 100644 --- a/src/hooks/useWorkspaceList.ts +++ b/src/hooks/useWorkspaceList.ts @@ -83,7 +83,7 @@ function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, search const shouldShowSearchInput = usersWorkspaces.length >= CONST.STANDARD_LIST_ITEM_LIMIT; return { - usersWorkspaces, + data: filteredAndSortedUserWorkspaces, sections, shouldShowNoResultsFoundMessage, shouldShowSearchInput, diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index 804f5f6259940..c82cb7275938e 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -125,7 +125,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro ], ); - const {usersWorkspaces, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ + const {data, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ policies, currentUserLogin: session?.email, shouldShowPendingDeletePolicy: false, @@ -169,11 +169,12 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro ) : ( ListItem={UserListItem} - data={usersWorkspaces} + data={data as WorkspaceListItemType[]} onSelectRow={(option) => selectPolicy(option.policyID)} textInputOptions={textInputOptions} initiallyFocusedItemKey={report.policyID} showLoadingPlaceholder={fetchStatus.status === 'loading' || !didScreenTransitionEnd} + disableMaintainingScrollPosition /> )} From f4150ac68cc254ad10a8adbf5c9086ac8f35b352 Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Tue, 18 Nov 2025 11:31:33 +0100 Subject: [PATCH 3/8] Fix lint --- src/hooks/useWorkspaceList.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hooks/useWorkspaceList.ts b/src/hooks/useWorkspaceList.ts index 22c6fb758d49a..12bb7c18583d2 100644 --- a/src/hooks/useWorkspaceList.ts +++ b/src/hooks/useWorkspaceList.ts @@ -1,6 +1,5 @@ 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 {isPolicyAdmin, shouldShowPolicy, sortWorkspacesBySelected} from '@libs/PolicyUtils'; @@ -10,6 +9,7 @@ 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'; +import {useMemoizedLazyExpensifyIcons} from './useLazyAsset'; type WorkspaceListItem = { text: string; @@ -29,6 +29,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 +49,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( () => From 7d7f03eb6bf49f129194ad2b4724f3bc427e197d Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Tue, 18 Nov 2025 11:53:54 +0100 Subject: [PATCH 4/8] Fix spelling --- src/components/SelectionList/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 611e0643d0dbd..1df2012e16648 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -111,8 +111,8 @@ type SelectionListProps = { /** Whether keyboard shortcuts should be disabled */ disableKeyboardShortcuts?: boolean; - /** Whether scroll possition should change when focsued item changes */ - disableMaintainingScrollPosition?: boolean; + /** Whether scroll position should change when focused item changes */ + disableMaintainingScrollPosition?: boolean; /** Whether to use the user skeleton view */ shouldUseUserSkeletonView?: boolean; From d931f6cd32f7abb0a4f49242fbc7dbdf9d70d6f5 Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Tue, 18 Nov 2025 11:57:22 +0100 Subject: [PATCH 5/8] Fix prettier --- src/components/SelectionList/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 1df2012e16648..2ec3636c8cdb6 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -112,7 +112,7 @@ type SelectionListProps = { disableKeyboardShortcuts?: boolean; /** Whether scroll position should change when focused item changes */ - disableMaintainingScrollPosition?: boolean; + disableMaintainingScrollPosition?: boolean; /** Whether to use the user skeleton view */ shouldUseUserSkeletonView?: boolean; From 4a152a9228e01b357d46a9a4ce0d5f11562054d2 Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Tue, 18 Nov 2025 13:51:35 +0100 Subject: [PATCH 6/8] Adjust to review --- src/components/SelectionList/components/TextInput.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/components/TextInput.tsx b/src/components/SelectionList/components/TextInput.tsx index 4f0bfa76a2d73..967bcea2cc12a 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; @@ -113,8 +113,8 @@ function TextInput({ onFocusChange?.(true)} - onBlur={() => onFocusChange?.(false)} + onFocus={() => onFocusChange(true)} + onBlur={() => onFocusChange(false)} label={label} accessibilityLabel={accessibilityLabel} hint={hint} From 8ca6428f3551925011082258a07929a866305f5e Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Tue, 18 Nov 2025 15:50:11 +0100 Subject: [PATCH 7/8] Add clear focus handling --- .../SelectionList/components/TextInput.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/components/TextInput.tsx b/src/components/SelectionList/components/TextInput.tsx index 967bcea2cc12a..80874e924830a 100644 --- a/src/components/SelectionList/components/TextInput.tsx +++ b/src/components/SelectionList/components/TextInput.tsx @@ -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} From 0e0808b05eaa24a47a38fba6737f237bd9acfea6 Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Thu, 20 Nov 2025 15:31:33 +0100 Subject: [PATCH 8/8] Adjust to review --- src/hooks/useWorkspaceList.ts | 11 ++--------- src/pages/ReportChangeWorkspacePage.tsx | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/hooks/useWorkspaceList.ts b/src/hooks/useWorkspaceList.ts index 12bb7c18583d2..4cf7c9bfdd82a 100644 --- a/src/hooks/useWorkspaceList.ts +++ b/src/hooks/useWorkspaceList.ts @@ -1,23 +1,16 @@ import {useMemo} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; 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'; import {useMemoizedLazyExpensifyIcons} from './useLazyAsset'; -type WorkspaceListItem = { - text: string; - policyID?: string; - isPolicyAdmin?: boolean; - brickRoadIndicator?: BrickRoad; -} & ListItem; - type UseWorkspaceListParams = { policies: OnyxCollection; currentUserLogin: string | undefined; diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index c82cb7275938e..ccbb8675add6f 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -169,7 +169,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro ) : ( ListItem={UserListItem} - data={data as WorkspaceListItemType[]} + data={data} onSelectRow={(option) => selectPolicy(option.policyID)} textInputOptions={textInputOptions} initiallyFocusedItemKey={report.policyID}