From daeb7218cbf0b6e11dfa893f33e635a142b73f41 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 26 Mar 2024 12:24:40 +0100 Subject: [PATCH 01/13] Replace OptionsSelector with SelectionList --- src/components/SelectionList/BaseListItem.tsx | 16 +++++ .../SelectionList/BaseSelectionList.tsx | 18 ++++-- src/components/SelectionList/types.ts | 18 +++++- src/components/TagPicker/index.js | 19 +++--- src/libs/OptionsListUtils.ts | 17 +++-- src/pages/EditReportFieldDropdownPage.tsx | 26 ++++---- src/pages/EditReportFieldPage.tsx | 2 +- src/pages/WorkspaceSwitcherPage.tsx | 64 +++++-------------- .../ShareLogList/BaseShareLogList.tsx | 21 +++--- 9 files changed, 102 insertions(+), 99 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 596951374099..95234dec7dde 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -130,6 +130,22 @@ function BaseListItem({ )} + {!item.isSelected && item.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && ( + + + + )} + {!item.isSelected && item.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO && ( + + + + )} {rightHandSideComponentRender()} {FooterComponent} diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 015fd284c0b4..c31ee75c9cf7 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -69,6 +69,9 @@ function BaseSelectionList( listHeaderWrapperStyle, isRowMultilineSupported = false, textInputRef, + textInputIconLeft, + sectionTitleStyles, + turnOffEnterDisabling, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -77,7 +80,7 @@ function BaseSelectionList( const listRef = useRef>>(null); const innerTextInputRef = useRef(null); const focusTimeoutRef = useRef(null); - const shouldShowTextInput = !!textInputLabel; + const shouldShowTextInput = !!textInputLabel || !!textInputIconLeft; const shouldShowSelectAll = !!onSelectAll; const activeElementRole = useActiveElementRole(); const isFocused = useIsFocused(); @@ -192,7 +195,7 @@ function BaseSelectionList( ); // Disable `Enter` shortcut if the active element is a button or checkbox - const disableEnterShortcut = activeElementRole && [CONST.ROLE.BUTTON, CONST.ROLE.CHECKBOX].includes(activeElementRole as ButtonOrCheckBoxRoles); + const disableEnterShortcut = !turnOffEnterDisabling && activeElementRole && [CONST.ROLE.BUTTON, CONST.ROLE.CHECKBOX].includes(activeElementRole as ButtonOrCheckBoxRoles); /** * Scrolls to the desired item index in the section list @@ -306,7 +309,7 @@ function BaseSelectionList( // We do this so that we can reference the height in `getItemLayout` – // we need to know the heights of all list items up-front in order to synchronously compute the layout of any given list item. // So be aware that if you adjust the content of the section header (for example, change the font size), you may need to adjust this explicit height as well. - + {section.title} ); @@ -492,8 +495,12 @@ function BaseSelectionList( return; } - // eslint-disable-next-line no-param-reassign - textInputRef.current = element as RNTextInput; + if (typeof textInputRef === 'function') { + textInputRef(element as RNTextInput); + } else { + // eslint-disable-next-line no-param-reassign + textInputRef.current = element as RNTextInput; + } }} label={textInputLabel} accessibilityLabel={textInputLabel} @@ -506,6 +513,7 @@ function BaseSelectionList( inputMode={inputMode} selectTextOnFocus spellCheck={false} + iconLeft={textInputIconLeft} onSubmitEditing={selectFocusedOption} blurOnSubmit={!!flattenedSections.allOptions.length} isLoading={isLoadingNewOptions} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index e691a5bdb191..55c8d48bc431 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -2,10 +2,12 @@ import type {MutableRefObject, ReactElement, ReactNode} from 'react'; import type {GestureResponderEvent, InputModeOptions, LayoutChangeEvent, SectionListData, StyleProp, TextInput, TextStyle, ViewStyle} from 'react-native'; import type {ValueOf} from 'type-fest'; import type {MaybePhraseKey} from '@libs/Localize'; +import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; import type CONST from '@src/CONST'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import type {ReceiptErrors} from '@src/types/onyx/Transaction'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import type IconAsset from '@src/types/utils/IconAsset'; import type RadioListItem from './RadioListItem'; import type TableListItem from './TableListItem'; import type UserListItem from './UserListItem'; @@ -110,6 +112,8 @@ type ListItem = { /** The search value from the selection list */ searchText?: string | null; + + brickRoadIndicator?: BrickRoad | '' | null; }; type ListItemProps = CommonListItemProps & { @@ -137,6 +141,7 @@ type BaseListItemProps = CommonListItemProps & { pendingAction?: PendingAction | null; FooterComponent?: ReactElement; children?: ReactElement | ((hovered: boolean) => ReactElement); + brickRoadIndicator?: BrickRoad | '' | null; }; type UserListItemProps = ListItemProps & { @@ -208,6 +213,9 @@ type BaseSelectionListProps = Partial & { /** Max length for the text input */ textInputMaxLength?: number; + /** Icon to display on the left side of TextInput */ + textInputIconLeft?: IconAsset | null; + /** Callback to fire when the text input changes */ onChangeText?: (text: string) => void; @@ -266,7 +274,7 @@ type BaseSelectionListProps = Partial & { disableInitialFocusOptionStyle?: boolean; /** Styles to apply to SelectionList container */ - containerStyle?: ViewStyle; + containerStyle?: StyleProp; /** Whether keyboard is visible on the screen */ isKeyboardShown?: boolean; @@ -299,7 +307,13 @@ type BaseSelectionListProps = Partial & { isRowMultilineSupported?: boolean; /** Ref for textInput */ - textInputRef?: MutableRefObject; + textInputRef?: MutableRefObject | ((ref: TextInput | null) => void); + + /** Styles for the section title */ + sectionTitleStyles?: StyleProp; + + /** Decides if selecting with Enter should be disabled */ + turnOffEnterDisabling?: boolean; }; type SelectionListHandle = { diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 341ea9cddae9..792a1e56ecfc 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -2,7 +2,8 @@ import lodashGet from 'lodash/get'; import React, {useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import OptionsSelector from '@components/OptionsSelector'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -58,20 +59,16 @@ function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTa const selectedOptionKey = lodashGet(_.filter(lodashGet(sections, '[0].data', []), (policyTag) => policyTag.searchText === selectedTag)[0], 'keyForList'); return ( - { // This is to remove unnecessary escaping backslash in tag name sent from backend. const cleanedName = PolicyUtils.getCleanedTagName(tag.name); + const selectedOptionsNames = (selectedOptions ?? []).map(({name}) => name); + return { text: cleanedName, keyForList: tag.name, searchText: tag.name, tooltipText: cleanedName, isDisabled: !tag.enabled, + isSelected: selectedOptionsNames.includes(tag.name), }; }); } @@ -1135,7 +1138,7 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt title: '', shouldShow: false, indexOffset, - data: getTagsOptions(selectedTagOptions), + data: getTagsOptions(selectedTagOptions, selectedOptions), }); return tagSections; @@ -1149,7 +1152,7 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt title: '', shouldShow: true, indexOffset, - data: getTagsOptions(searchTags), + data: getTagsOptions(searchTags, selectedOptions), }); return tagSections; @@ -1161,7 +1164,7 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt title: '', shouldShow: false, indexOffset, - data: getTagsOptions(enabledTags), + data: getTagsOptions(enabledTags, selectedOptions), }); return tagSections; @@ -1187,7 +1190,7 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt title: '', shouldShow: true, indexOffset, - data: getTagsOptions(selectedTagOptions), + data: getTagsOptions(selectedTagOptions, selectedOptions), }); indexOffset += selectedOptions.length; @@ -1201,7 +1204,7 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt title: Localize.translateLocal('common.recent'), shouldShow: true, indexOffset, - data: getTagsOptions(cutRecentlyUsedTags), + data: getTagsOptions(cutRecentlyUsedTags, selectedOptions), }); indexOffset += filteredRecentlyUsedTags.length; @@ -1212,7 +1215,7 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt title: Localize.translateLocal('common.all'), shouldShow: true, indexOffset, - data: getTagsOptions(filteredTags), + data: getTagsOptions(filteredTags, selectedOptions), }); return tagSections; diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index a3d9c2fd99ff..3211efb09a78 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -2,8 +2,9 @@ import React, {useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import OptionsSelector from '@components/OptionsSelector'; import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -71,6 +72,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, keyForList: option, searchText: option, tooltipText: option, + isSelected: option === fieldValue, })), }); } else { @@ -99,6 +101,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, keyForList: option, searchText: option, tooltipText: option, + isSelected: option === fieldValue, })), }); } @@ -113,6 +116,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, keyForList: option, searchText: option, tooltipText: option, + isSelected: option === fieldValue, })), }); } @@ -130,23 +134,15 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, {({insets}) => ( <> - ) => - onSubmit({ - [fieldKey]: fieldValue === option.text ? '' : option.text, - }) - } + sectionTitleStyles={styles.mt5} + textInputValue={searchValue} + onSelectRow={(option) => onSubmit({[fieldKey]: fieldValue === option.text ? '' : option.text})} onChangeText={setSearchValue} - highlightSelectedOptions isRowMultilineSupported headerMessage={headerMessage} /> diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index 8c8376468c0f..954c852f365b 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -106,7 +106,7 @@ function EditReportFieldPage({route, policy, report}: EditReportFieldPageProps) fieldKey={fieldKey} fieldName={Str.UCFirst(reportField.name)} fieldValue={fieldValue} - fieldOptions={reportField.values.filter((value) => !(value in reportField.disabledOptions))} + fieldOptions={reportField.values.filter((_, index) => !reportField.disabledOptions[index])} onSubmit={handleReportFieldChange} /> ); diff --git a/src/pages/WorkspaceSwitcherPage.tsx b/src/pages/WorkspaceSwitcherPage.tsx index 2eb5ecaf373f..9dccf8f911cd 100644 --- a/src/pages/WorkspaceSwitcherPage.tsx +++ b/src/pages/WorkspaceSwitcherPage.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import type {OnyxCollection} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -7,9 +7,10 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {MagnifyingGlass} from '@components/Icon/Expensicons'; import OptionRow from '@components/OptionRow'; -import OptionsSelector from '@components/OptionsSelector'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import UserListItem from '@components/SelectionList/UserListItem'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; @@ -57,7 +58,6 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const [selectedOption, setSelectedOption] = useState(); const [searchTerm, setSearchTerm] = useState(''); const {inputCallbackRef} = useAutoFocusInput(); const {translate} = useLocalize(); @@ -105,11 +105,6 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { const {policyID} = option; - if (policyID) { - setSelectedOption(option); - } else { - setSelectedOption(undefined); - } setActiveWorkspaceID(policyID); Navigation.goBack(); if (policyID !== activeWorkspaceID) { @@ -129,6 +124,7 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { .map((policy) => ({ text: policy?.name, policyID: policy?.id, + isSelected: policy?.id === activeWorkspaceID, brickRoadIndicator: getIndicatorTypeForPolicy(policy?.id), icons: [ { @@ -142,7 +138,7 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { keyForList: policy?.id, isPolicyAdmin: PolicyUtils.isPolicyAdmin(policy), })); - }, [policies, getIndicatorTypeForPolicy, hasUnreadData, isOffline]); + }, [policies, getIndicatorTypeForPolicy, hasUnreadData, isOffline, activeWorkspaceID]); const filteredAndSortedUserWorkspaces = useMemo( () => @@ -237,59 +233,31 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { {usersWorkspaces.length > 0 ? ( - = CONST.WORKSPACE_SWITCHER.MINIMUM_WORKSPACES_TO_SHOW_SEARCH} + textInputValue={searchTerm} onChangeText={setSearchTerm} - selectedOptions={selectedOption ? [selectedOption] : []} onSelectRow={selectPolicy} shouldPreventDefaultFocusOnSelectRow headerMessage={headerMessage} - highlightSelectedOptions - shouldShowOptions + shouldShowTooltips autoFocus={false} - canSelectMultipleOptions={false} - shouldShowSubscript={false} - showTitleTooltip={false} - contentContainerStyles={[styles.pt0, styles.mt0]} - textIconLeft={MagnifyingGlass} - // Null is to avoid selecting unfocused option when Global selected, undefined is to focus selected workspace - initiallyFocusedOptionKey={!activeWorkspaceID ? null : undefined} + containerStyle={[styles.pt0, styles.mt0]} + textInputIconLeft={usersWorkspaces.length >= CONST.WORKSPACE_SWITCHER.MINIMUM_WORKSPACES_TO_SHOW_SEARCH ? MagnifyingGlass : undefined} + initiallyFocusedOptionKey={activeWorkspaceID ?? undefined} + turnOffEnterDisabling /> ) : ( )} ), - [ - inputCallbackRef, - setSearchTerm, - searchTerm, - selectPolicy, - selectedOption, - styles, - theme.textSupporting, - translate, - usersWorkspaces.length, - usersWorkspacesSectionData, - activeWorkspaceID, - theme.icon, - headerMessage, - ], + [setSearchTerm, searchTerm, selectPolicy, styles, theme.textSupporting, translate, usersWorkspaces.length, usersWorkspacesSectionData, activeWorkspaceID, theme.icon, headerMessage], ); - useEffect(() => { - if (!activeWorkspaceID) { - return; - } - const optionToSet = usersWorkspaces.find((option) => option.policyID === activeWorkspaceID); - setSelectedOption(optionToSet); - }, [activeWorkspaceID, usersWorkspaces]); - return ( { + const attachLogToReport = (option: ListItem) => { if (!option.reportID) { return; } @@ -124,18 +125,18 @@ function BaseShareLogList({betas, reports, onAttachLogToReport}: BaseShareLogLis onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_CONSOLE)} /> - From 966d18ef418fc801be9c4a04d73e7c1e934c6345 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 26 Mar 2024 12:37:12 +0100 Subject: [PATCH 02/13] Fix lint --- src/pages/WorkspaceSwitcherPage.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/pages/WorkspaceSwitcherPage.tsx b/src/pages/WorkspaceSwitcherPage.tsx index 9dccf8f911cd..115e1ee25960 100644 --- a/src/pages/WorkspaceSwitcherPage.tsx +++ b/src/pages/WorkspaceSwitcherPage.tsx @@ -255,7 +255,20 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { )} ), - [setSearchTerm, searchTerm, selectPolicy, styles, theme.textSupporting, translate, usersWorkspaces.length, usersWorkspacesSectionData, activeWorkspaceID, theme.icon, headerMessage], + [ + inputCallbackRef, + setSearchTerm, + searchTerm, + selectPolicy, + styles, + theme.textSupporting, + translate, + usersWorkspaces.length, + usersWorkspacesSectionData, + activeWorkspaceID, + theme.icon, + headerMessage, + ], ); return ( From 5849045587450d2615dffd335922604c48f8a69d Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 26 Mar 2024 21:28:32 +0100 Subject: [PATCH 03/13] Fix failing tests --- tests/unit/OptionsListUtilsTest.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 7244b7830a29..499daf61d2ea 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -1120,6 +1120,7 @@ describe('OptionsListUtils', () => { searchText: 'Accounting', tooltipText: 'Accounting', isDisabled: false, + isSelected: false, }, { text: 'HR', @@ -1127,6 +1128,7 @@ describe('OptionsListUtils', () => { searchText: 'HR', tooltipText: 'HR', isDisabled: false, + isSelected: false, }, { text: 'Medical', @@ -1134,6 +1136,7 @@ describe('OptionsListUtils', () => { searchText: 'Medical', tooltipText: 'Medical', isDisabled: false, + isSelected: false, }, ], }, @@ -1150,6 +1153,7 @@ describe('OptionsListUtils', () => { searchText: 'Accounting', tooltipText: 'Accounting', isDisabled: false, + isSelected: false, }, ], }, @@ -1220,6 +1224,7 @@ describe('OptionsListUtils', () => { searchText: 'Medical', tooltipText: 'Medical', isDisabled: false, + isSelected: true, }, ], }, @@ -1234,6 +1239,7 @@ describe('OptionsListUtils', () => { searchText: 'HR', tooltipText: 'HR', isDisabled: false, + isSelected: false, }, ], }, @@ -1249,6 +1255,7 @@ describe('OptionsListUtils', () => { searchText: 'Accounting', tooltipText: 'Accounting', isDisabled: false, + isSelected: false, }, { text: 'Benefits', @@ -1256,6 +1263,7 @@ describe('OptionsListUtils', () => { searchText: 'Benefits', tooltipText: 'Benefits', isDisabled: false, + isSelected: false, }, { text: 'Cleaning', @@ -1263,6 +1271,7 @@ describe('OptionsListUtils', () => { searchText: 'Cleaning', tooltipText: 'Cleaning', isDisabled: false, + isSelected: false, }, { text: 'Food', @@ -1270,6 +1279,7 @@ describe('OptionsListUtils', () => { searchText: 'Food', tooltipText: 'Food', isDisabled: false, + isSelected: false, }, { text: 'HR', @@ -1277,6 +1287,7 @@ describe('OptionsListUtils', () => { searchText: 'HR', tooltipText: 'HR', isDisabled: false, + isSelected: false, }, { text: 'Software', @@ -1284,6 +1295,7 @@ describe('OptionsListUtils', () => { searchText: 'Software', tooltipText: 'Software', isDisabled: false, + isSelected: false, }, { text: 'Taxes', @@ -1291,6 +1303,7 @@ describe('OptionsListUtils', () => { searchText: 'Taxes', tooltipText: 'Taxes', isDisabled: false, + isSelected: false, }, ], }, @@ -1307,6 +1320,7 @@ describe('OptionsListUtils', () => { searchText: 'Accounting', tooltipText: 'Accounting', isDisabled: false, + isSelected: false, }, { text: 'Cleaning', @@ -1314,6 +1328,7 @@ describe('OptionsListUtils', () => { searchText: 'Cleaning', tooltipText: 'Cleaning', isDisabled: false, + isSelected: false, }, ], }, From 908363bc083d7b815e587dc4ac24b817a1f64b51 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 27 Mar 2024 08:35:12 +0100 Subject: [PATCH 04/13] CR fixes --- src/components/SelectionList/BaseListItem.tsx | 12 ++---------- src/libs/OptionsListUtils.ts | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 95234dec7dde..1b0f69786b74 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -130,19 +130,11 @@ function BaseListItem({ )} - {!item.isSelected && item.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && ( + {!item.isSelected && item.brickRoadIndicator && [CONST.BRICK_ROAD_INDICATOR_STATUS.INFO, CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR].includes(item.brickRoadIndicator) && ( - - )} - {!item.isSelected && item.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO && ( - - )} diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 88aa846fbbac..f4caaa9b977d 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1101,7 +1101,7 @@ function getTagsOptions(tags: Category[], selectedOptions?: Category[]): Option[ return tags.map((tag) => { // This is to remove unnecessary escaping backslash in tag name sent from backend. const cleanedName = PolicyUtils.getCleanedTagName(tag.name); - const selectedOptionsNames = (selectedOptions ?? []).map(({name}) => name); + const selectedOptionsNames = selectedOptions?.map(({name}) => name); return { text: cleanedName, @@ -1109,7 +1109,7 @@ function getTagsOptions(tags: Category[], selectedOptions?: Category[]): Option[ searchText: tag.name, tooltipText: cleanedName, isDisabled: !tag.enabled, - isSelected: selectedOptionsNames.includes(tag.name), + isSelected: selectedOptionsNames?.includes(tag.name), }; }); } From df13200cdb7d0c0db4a71d0976c5476a74d832c9 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 27 Mar 2024 09:11:17 +0100 Subject: [PATCH 05/13] Merge fix --- 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 098ce0e9355b..c35623e1c63e 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -226,7 +226,7 @@ type BaseSelectionListProps = Partial & { inputMode?: InputModeOptions; /** Item `keyForList` to focus initially */ - initiallyFocusedOptionKey?: string; + initiallyFocusedOptionKey?: string | null; /** Callback to fire when the list is scrolled */ onScroll?: () => void; From 8c0297b00c1c60d790f8b45af19bac31a5d4cc4a Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 27 Mar 2024 12:04:28 +0100 Subject: [PATCH 06/13] Fix isSelected state in EditReportFieldDropdownPage --- src/pages/EditReportFieldDropdownPage.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index d13233aeab80..b82589c03bf9 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -43,6 +43,7 @@ type ReportFieldDropdownData = { keyForList: string; searchText: string; tooltipText: string; + isSelected?: boolean; }; type ReportFieldDropdownSectionItem = { @@ -72,7 +73,6 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, keyForList: option, searchText: option, tooltipText: option, - isSelected: option === fieldValue, })), }); } else { @@ -86,6 +86,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, keyForList: selectedValue, searchText: selectedValue, tooltipText: selectedValue, + isSelected: true, }, ], }); @@ -101,7 +102,6 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, keyForList: option, searchText: option, tooltipText: option, - isSelected: option === fieldValue, })), }); } @@ -116,7 +116,6 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, keyForList: option, searchText: option, tooltipText: option, - isSelected: option === fieldValue, })), }); } From 50bc061e7bb511314276d644ed01df50a7ef6d3c Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 2 Apr 2024 12:43:06 +0200 Subject: [PATCH 07/13] review fixes --- src/components/SelectionList/BaseListItem.tsx | 2 +- .../SelectionList/BaseSelectionList.tsx | 3 +- src/components/SelectionList/types.ts | 6 +-- src/components/WorkspaceSwitcherButton.tsx | 6 ++- src/libs/OptionsListUtils.ts | 3 +- src/pages/WorkspaceSwitcherPage.tsx | 5 +-- .../ShareLogList/BaseShareLogList.tsx | 42 ++++++++----------- 7 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 9f2fa68111cf..3fee43dbb425 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -132,7 +132,7 @@ function BaseListItem({ )} - {!item.isSelected && item.brickRoadIndicator && [CONST.BRICK_ROAD_INDICATOR_STATUS.INFO, CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR].includes(item.brickRoadIndicator) && ( + {!item.isSelected && item.brickRoadIndicator && ( ( textInputRef, textInputIconLeft, sectionTitleStyles, - turnOffEnterDisabling, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -195,7 +194,7 @@ function BaseSelectionList( ); // Disable `Enter` shortcut if the active element is a button or checkbox - const disableEnterShortcut = !turnOffEnterDisabling && activeElementRole && [CONST.ROLE.BUTTON, CONST.ROLE.CHECKBOX].includes(activeElementRole as ButtonOrCheckBoxRoles); + const disableEnterShortcut = activeElementRole && [CONST.ROLE.BUTTON, CONST.ROLE.CHECKBOX].includes(activeElementRole as ButtonOrCheckBoxRoles); /** * Scrolls to the desired item index in the section list diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index c35623e1c63e..4a2c82e54ce9 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -144,7 +144,6 @@ type BaseListItemProps = CommonListItemProps & { pendingAction?: PendingAction | null; FooterComponent?: ReactElement; children?: ReactElement | ((hovered: boolean) => ReactElement); - brickRoadIndicator?: BrickRoad | '' | null; }; type UserListItemProps = ListItemProps & { @@ -217,7 +216,7 @@ type BaseSelectionListProps = Partial & { textInputMaxLength?: number; /** Icon to display on the left side of TextInput */ - textInputIconLeft?: IconAsset | null; + textInputIconLeft?: IconAsset; /** Callback to fire when the text input changes */ onChangeText?: (text: string) => void; @@ -314,9 +313,6 @@ type BaseSelectionListProps = Partial & { /** Styles for the section title */ sectionTitleStyles?: StyleProp; - - /** Decides if selecting with Enter should be disabled */ - turnOffEnterDisabling?: boolean; }; type SelectionListHandle = { diff --git a/src/components/WorkspaceSwitcherButton.tsx b/src/components/WorkspaceSwitcherButton.tsx index a94f54682c85..ffbdb55b7dce 100644 --- a/src/components/WorkspaceSwitcherButton.tsx +++ b/src/components/WorkspaceSwitcherButton.tsx @@ -1,4 +1,5 @@ -import React, {useMemo} from 'react'; +import React, {useMemo, useRef} from 'react'; +import type {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; @@ -22,6 +23,7 @@ type WorkspaceSwitcherButtonProps = WorkspaceSwitcherButtonOnyxProps; function WorkspaceSwitcherButton({policy}: WorkspaceSwitcherButtonProps) { const {translate} = useLocalize(); const theme = useTheme(); + const pressableRef = useRef(); const {source, name, type} = useMemo(() => { if (!policy) { @@ -40,10 +42,12 @@ function WorkspaceSwitcherButton({policy}: WorkspaceSwitcherButtonProps) { interceptAnonymousUser(() => { + pressableRef.current?.blur(); Navigation.navigate(ROUTES.WORKSPACE_SWITCHER); }) } diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 48d4a316e2ff..00e327c5bd27 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1095,7 +1095,6 @@ function getTagsOptions(tags: Array>, select return tags.map((tag) => { // This is to remove unnecessary escaping backslash in tag name sent from backend. const cleanedName = PolicyUtils.getCleanedTagName(tag.name); - const selectedOptionsNames = selectedOptions?.map(({name}) => name); return { text: cleanedName, @@ -1103,7 +1102,7 @@ function getTagsOptions(tags: Array>, select searchText: tag.name, tooltipText: cleanedName, isDisabled: !tag.enabled, - isSelected: selectedOptionsNames?.includes(tag.name), + isSelected: selectedOptions?.some((selectedTag) => selectedTag.name === tag.name), }; }); } diff --git a/src/pages/WorkspaceSwitcherPage.tsx b/src/pages/WorkspaceSwitcherPage.tsx index 115e1ee25960..80b53c9a7833 100644 --- a/src/pages/WorkspaceSwitcherPage.tsx +++ b/src/pages/WorkspaceSwitcherPage.tsx @@ -243,12 +243,9 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { onSelectRow={selectPolicy} shouldPreventDefaultFocusOnSelectRow headerMessage={headerMessage} - shouldShowTooltips - autoFocus={false} containerStyle={[styles.pt0, styles.mt0]} textInputIconLeft={usersWorkspaces.length >= CONST.WORKSPACE_SWITCHER.MINIMUM_WORKSPACES_TO_SHOW_SEARCH ? MagnifyingGlass : undefined} - initiallyFocusedOptionKey={activeWorkspaceID ?? undefined} - turnOffEnterDisabling + initiallyFocusedOptionKey={activeWorkspaceID} /> ) : ( diff --git a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx index 8412e317712b..04375609bc6f 100644 --- a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx +++ b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx @@ -118,30 +118,24 @@ function BaseShareLogList({betas, reports, onAttachLogToReport}: BaseShareLogLis testID={BaseShareLogList.displayName} includeSafeAreaPaddingBottom={false} > - {({safeAreaPaddingBottomStyle}) => ( - <> - Navigation.goBack(ROUTES.SETTINGS_CONSOLE)} - /> - - - - - )} + Navigation.goBack(ROUTES.SETTINGS_CONSOLE)} + /> + + + ); } From b55a0858ff5a189777b7f73b41181545c8611d04 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 2 Apr 2024 13:12:17 +0200 Subject: [PATCH 08/13] Remove autofocus --- src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx index 04375609bc6f..5e5478034c65 100644 --- a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx +++ b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx @@ -133,7 +133,6 @@ function BaseShareLogList({betas, reports, onAttachLogToReport}: BaseShareLogLis isLoadingNewOptions={!isOptionsDataReady} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} textInputHint={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} - autoFocus /> From c8a23e40005a77bbb51c6e637a7be6f13e4f0637 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 2 Apr 2024 15:13:19 +0200 Subject: [PATCH 09/13] Fix isSelected state for dropdown --- src/pages/EditReportFieldDropdownPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index b82589c03bf9..575f77e88b64 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -73,6 +73,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, keyForList: option, searchText: option, tooltipText: option, + isSelected: option === fieldValue, })), }); } else { From 9c11da7ca66010eb2817f4999b5a24eb72168794 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 3 Apr 2024 10:45:02 +0200 Subject: [PATCH 10/13] CR fixes --- src/components/SelectionList/BaseSelectionList.tsx | 5 ++++- src/pages/EditReportFieldDropdownPage.tsx | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index a44e6d02c19b..c8f442e6ccbb 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -521,7 +521,10 @@ function BaseSelectionList( /> )} - {!!headerMessage && ( + + {/* If we are loading new options we will avoid showing any header message. This is mostly because one of the header messages says there are no options. */} + {/* This is misleading because we might be in the process of loading fresh options from the server. */} + {!isLoadingNewOptions && headerMessage && ( {headerMessage} diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index 575f77e88b64..75c2a9c5be26 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -145,6 +145,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldKey, fieldValue, onChangeText={setSearchValue} isRowMultilineSupported headerMessage={headerMessage} + initiallyFocusedOptionKey={fieldValue} /> )} From 62528f3b87d02c17f480e0d6957900ae98f593e4 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Thu, 4 Apr 2024 09:11:27 +0200 Subject: [PATCH 11/13] Add textInputAutoFocus prop --- src/components/SelectionList/BaseSelectionList.tsx | 6 +++++- src/components/SelectionList/types.ts | 3 +++ src/pages/WorkspaceSwitcherPage.tsx | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index fdcee89899a8..dc69dfd39989 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -73,6 +73,7 @@ function BaseSelectionList( sectionTitleStyles, headerMessageStyle, shouldHideListOnInitialRender = true, + textInputAutoFocus = true, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -379,6 +380,9 @@ function BaseSelectionList( /** Focuses the text input when the component comes into focus and after any navigation animations finish. */ useFocusEffect( useCallback(() => { + if (!textInputAutoFocus) { + return; + } if (shouldShowTextInput) { focusTimeoutRef.current = setTimeout(() => { if (!innerTextInputRef.current) { @@ -393,7 +397,7 @@ function BaseSelectionList( } clearTimeout(focusTimeoutRef.current); }; - }, [shouldShowTextInput]), + }, [shouldShowTextInput, textInputAutoFocus]), ); const prevTextInputValue = usePrevious(textInputValue); diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 549cd9de87df..f4b1b990811f 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -221,6 +221,9 @@ type BaseSelectionListProps = Partial & { /** Icon to display on the left side of TextInput */ textInputIconLeft?: IconAsset; + /** Whether text input should be focused */ + textInputAutoFocus?: boolean; + /** Callback to fire when the text input changes */ onChangeText?: (text: string) => void; diff --git a/src/pages/WorkspaceSwitcherPage.tsx b/src/pages/WorkspaceSwitcherPage.tsx index 6ce5457aab99..406447d04c05 100644 --- a/src/pages/WorkspaceSwitcherPage.tsx +++ b/src/pages/WorkspaceSwitcherPage.tsx @@ -245,6 +245,7 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { containerStyle={[styles.pt0, styles.mt0]} textInputIconLeft={usersWorkspaces.length >= CONST.WORKSPACE_SWITCHER.MINIMUM_WORKSPACES_TO_SHOW_SEARCH ? MagnifyingGlass : undefined} initiallyFocusedOptionKey={activeWorkspaceID} + textInputAutoFocus={false} /> ) : ( From f044d837037bfd4a4fd6c8fa5947868023f57f16 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Thu, 4 Apr 2024 09:32:21 +0200 Subject: [PATCH 12/13] Remove autoFocus on WorkspaceSwitcher --- src/pages/WorkspaceSwitcherPage.tsx | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/pages/WorkspaceSwitcherPage.tsx b/src/pages/WorkspaceSwitcherPage.tsx index 406447d04c05..5905e39310da 100644 --- a/src/pages/WorkspaceSwitcherPage.tsx +++ b/src/pages/WorkspaceSwitcherPage.tsx @@ -14,7 +14,6 @@ import UserListItem from '@components/SelectionList/UserListItem'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; -import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; @@ -59,7 +58,6 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); const [searchTerm, setSearchTerm] = useState(''); - const {inputCallbackRef} = useAutoFocusInput(); const {translate} = useLocalize(); const {activeWorkspaceID, setActiveWorkspaceID} = useActiveWorkspace(); @@ -235,7 +233,6 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { ), - [ - inputCallbackRef, - setSearchTerm, - searchTerm, - selectPolicy, - styles, - theme.textSupporting, - translate, - usersWorkspaces.length, - usersWorkspacesSectionData, - activeWorkspaceID, - theme.icon, - headerMessage, - ], + [setSearchTerm, searchTerm, selectPolicy, styles, theme.textSupporting, translate, usersWorkspaces.length, usersWorkspacesSectionData, activeWorkspaceID, theme.icon, headerMessage], ); return ( From 3c3e295060ff666e012ed128d2183b98c14eeff8 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Thu, 4 Apr 2024 09:53:21 +0200 Subject: [PATCH 13/13] revert unnecessary change --- src/pages/WorkspaceSwitcherPage.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/pages/WorkspaceSwitcherPage.tsx b/src/pages/WorkspaceSwitcherPage.tsx index 5905e39310da..406447d04c05 100644 --- a/src/pages/WorkspaceSwitcherPage.tsx +++ b/src/pages/WorkspaceSwitcherPage.tsx @@ -14,6 +14,7 @@ import UserListItem from '@components/SelectionList/UserListItem'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; @@ -58,6 +59,7 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); const [searchTerm, setSearchTerm] = useState(''); + const {inputCallbackRef} = useAutoFocusInput(); const {translate} = useLocalize(); const {activeWorkspaceID, setActiveWorkspaceID} = useActiveWorkspace(); @@ -233,6 +235,7 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { ), - [setSearchTerm, searchTerm, selectPolicy, styles, theme.textSupporting, translate, usersWorkspaces.length, usersWorkspacesSectionData, activeWorkspaceID, theme.icon, headerMessage], + [ + inputCallbackRef, + setSearchTerm, + searchTerm, + selectPolicy, + styles, + theme.textSupporting, + translate, + usersWorkspaces.length, + usersWorkspacesSectionData, + activeWorkspaceID, + theme.icon, + headerMessage, + ], ); return (