From 9a98fec4b8cd32d9df9d78844119861e72bc2678 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 4 Nov 2024 15:25:29 +0100 Subject: [PATCH 1/7] Add cards list search bar --- src/CONST.ts | 1 + .../companyCards/assignCard/CardSelectionStep.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 437ee4e7fd42..60b72a839d54 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2789,6 +2789,7 @@ const CONST = { RESTRICT: 'corporate', ALLOW: 'personal', }, + BIG_CARD_LIST_ITEMS_AMOUNT: 8, EXPORT_CARD_TYPES: { /** * Name of Card NVP for QBO custom export accounts diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index b2c95ca22f29..b6d32104023f 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; @@ -36,6 +36,7 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {environmentURL} = useEnvironment(); + const [searchText, setSearchText] = useState(''); const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD); const [list] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${feed}`); const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); @@ -117,6 +118,10 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { const listOptions = accountCardList?.length > 0 ? accountCardListOptions : cardListOptions; + const searchedListOptions = useMemo(() => { + return listOptions.filter((option) => option.text.toLowerCase().includes(searchText)); + }, [searchText, listOptions]); + return ( CONST.COMPANY_CARDS.BIG_CARD_LIST_ITEMS_AMOUNT} + textInputLabel={translate('common.search')} + textInputValue={searchText} + onChangeText={setSearchText} ListItem={RadioListItem} onSelectRow={({value}) => handleSelectCard(value)} initiallyFocusedOptionKey={cardSelected} From 1da4eae6c83c6bc78238c4655707a5214157442e Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 6 Nov 2024 16:41:06 +0100 Subject: [PATCH 2/7] Fix keyboard overlapping on small devices --- .../SelectionList/BaseSelectionList.tsx | 94 +++++++++++-------- src/components/SelectionList/types.ts | 3 + .../assignCard/CardSelectionStep.tsx | 31 ++++-- 3 files changed, 78 insertions(+), 50 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 3e1b3a3c2d70..03cd5028ecb2 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -89,6 +89,7 @@ function BaseSelectionList( textInputIconLeft, sectionTitleStyles, textInputAutoFocus = true, + shouldShowTextInputAfterHeader = false, shouldTextInputInterceptSwipe = false, listHeaderContent, onEndReached = () => {}, @@ -500,6 +501,48 @@ function BaseSelectionList( return null; }; + const renderInput = () => { + return ( + + { + innerTextInputRef.current = element as RNTextInput; + + if (!textInputRef) { + return; + } + + if (typeof textInputRef === 'function') { + textInputRef(element as RNTextInput); + } else { + // eslint-disable-next-line no-param-reassign + textInputRef.current = element as RNTextInput; + } + }} + onFocus={() => (isTextInputFocusedRef.current = true)} + onBlur={() => (isTextInputFocusedRef.current = false)} + label={textInputLabel} + accessibilityLabel={textInputLabel} + hint={textInputHint} + role={CONST.ROLE.PRESENTATION} + value={textInputValue} + placeholder={textInputPlaceholder} + maxLength={textInputMaxLength} + onChangeText={onChangeText} + inputMode={inputMode} + selectTextOnFocus + spellCheck={false} + iconLeft={textInputIconLeft} + onSubmitEditing={selectFocusedOption} + blurOnSubmit={!!flattenedSections.allOptions.length} + isLoading={isLoadingNewOptions} + testID="selection-list-text-input" + shouldInterceptSwipe={shouldTextInputInterceptSwipe} + /> + + ); + }; + const scrollToFocusedIndexOnFirstRender = useCallback( (nativeEvent: LayoutChangeEvent) => { if (shouldUseDynamicMaxToRenderPerBatch) { @@ -677,45 +720,7 @@ function BaseSelectionList( {({safeAreaPaddingBottomStyle}) => ( - {shouldShowTextInput && ( - - { - innerTextInputRef.current = element as RNTextInput; - - if (!textInputRef) { - return; - } - - if (typeof textInputRef === 'function') { - textInputRef(element as RNTextInput); - } else { - // eslint-disable-next-line no-param-reassign - textInputRef.current = element as RNTextInput; - } - }} - onFocus={() => (isTextInputFocusedRef.current = true)} - onBlur={() => (isTextInputFocusedRef.current = false)} - label={textInputLabel} - accessibilityLabel={textInputLabel} - hint={textInputHint} - role={CONST.ROLE.PRESENTATION} - value={textInputValue} - placeholder={textInputPlaceholder} - maxLength={textInputMaxLength} - onChangeText={onChangeText} - inputMode={inputMode} - selectTextOnFocus - spellCheck={false} - iconLeft={textInputIconLeft} - onSubmitEditing={selectFocusedOption} - blurOnSubmit={!!flattenedSections.allOptions.length} - isLoading={isLoadingNewOptions} - testID="selection-list-text-input" - shouldInterceptSwipe={shouldTextInputInterceptSwipe} - /> - - )} + {shouldShowTextInput && !shouldShowTextInputAfterHeader && renderInput()} {/* 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 !== translate('common.noResultsFound') || (flattenedSections.allOptions.length === 0 && !showLoadingPlaceholder)) && @@ -759,7 +764,16 @@ function BaseSelectionList( testID="selection-list" onLayout={onSectionListLayout} style={[(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0, sectionListStyle]} - ListHeaderComponent={listHeaderContent} + ListHeaderComponent={ + shouldShowTextInput && shouldShowTextInputAfterHeader ? ( + <> + {listHeaderContent} + {renderInput()} + + ) : ( + listHeaderContent + ) + } ListFooterComponent={listFooterContent ?? ShowMoreButtonInstance} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 8fb50456182c..eba6cf274d6b 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -443,6 +443,9 @@ type BaseSelectionListProps = Partial & { /** Item `keyForList` to focus initially */ initiallyFocusedOptionKey?: string | null; + /** Whether the text input should be shown after list header */ + shouldShowTextInputAfterHeader?: boolean; + /** Callback to fire when the list is scrolled */ onScroll?: (event: NativeSyntheticEvent) => void; diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index 139c3e025907..42b4671f4d0d 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -4,6 +4,7 @@ import {useOnyx} from 'react-native-onyx'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import Icon from '@components/Icon'; import * as Illustrations from '@components/Icon/Illustrations'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; @@ -126,8 +127,6 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { @@ -152,13 +151,6 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { ) : ( <> - {translate('workspace.companyCards.chooseCard')} - - {translate('workspace.companyCards.chooseCardFor', { - assignee: assigneeDisplayName, - feed: CardUtils.getCardFeedName(feed), - })} - CONST.COMPANY_CARDS.BIG_CARD_LIST_ITEMS_AMOUNT} @@ -168,6 +160,25 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { ListItem={RadioListItem} onSelectRow={({value}) => handleSelectCard(value)} initiallyFocusedOptionKey={cardSelected} + listHeaderContent={ + + + + + {translate('workspace.companyCards.chooseCard')} + + {translate('workspace.companyCards.chooseCardFor', { + assignee: assigneeDisplayName, + feed: CardUtils.getCardFeedName(feed), + })} + + + } + shouldShowTextInputAfterHeader + shouldShowListEmptyContent={false} shouldUpdateFocusedIndex /> )} From a3e9c13649f2678af64cb5cd39e229e2e148b2d2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 6 Nov 2024 17:10:19 +0100 Subject: [PATCH 3/7] Minor fix --- .../workspace/companyCards/assignCard/CardSelectionStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index 42b4671f4d0d..8a4ebc5e4c79 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -187,7 +187,7 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { isAlertVisible={shouldShowError} containerStyles={styles.ph5} message={translate('common.error.pleaseSelectOne')} - buttonStyles={styles.mv5} + buttonStyles={[styles.mb5, styles.mt1]} /> )} From b46bc0c5cb1e28198c98daa60afbb71775a06f55 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 7 Nov 2024 09:13:31 +0100 Subject: [PATCH 4/7] Rename CARD_LIST_THRESHOLD const --- src/CONST.ts | 2 +- .../workspace/companyCards/assignCard/CardSelectionStep.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0d6498e79ad2..c2ba0cd59c84 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2803,7 +2803,7 @@ const CONST = { RESTRICT: 'corporate', ALLOW: 'personal', }, - BIG_CARD_LIST_ITEMS_AMOUNT: 8, + CARD_LIST_THRESHOLD: 8, EXPORT_CARD_TYPES: { /** * Name of Card NVP for QBO custom export accounts diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index 8a4ebc5e4c79..7a4ad1a14416 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -153,7 +153,7 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { <> CONST.COMPANY_CARDS.BIG_CARD_LIST_ITEMS_AMOUNT} + shouldShowTextInput={listOptions.length > CONST.COMPANY_CARDS.CARD_LIST_THRESHOLD} textInputLabel={translate('common.search')} textInputValue={searchText} onChangeText={setSearchText} From 952c0f07be9e3edc25251c99438bb7e03e6738a2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 7 Nov 2024 14:39:01 +0100 Subject: [PATCH 5/7] Update startTransactionDate -> transactionStartDate --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - .../workspace/companyCards/assignCard/ConfirmationStep.tsx | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 38b11e9fea38..cc69ed78e6a1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3150,7 +3150,6 @@ const translations = { confirmationDescription: 'We’ll begin importing transactions immediately.', cardholder: 'Cardholder', card: 'Card', - startTransactionDate: 'Start transaction date', cardName: 'Card name', brokenConnectionErrorFirstPart: `Card feed connection is broken. Please `, brokenConnectionErrorLink: 'log into your bank ', diff --git a/src/languages/es.ts b/src/languages/es.ts index 11a31c836add..b3e98d538215 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3189,7 +3189,6 @@ const translations = { confirmationDescription: 'Comenzaremos a importar transacciones inmediatamente.', cardholder: 'Titular de la tarjeta', card: 'Tarjeta', - startTransactionDate: 'Fecha de inicio de transacciones', cardName: 'Nombre de la tarjeta', brokenConnectionErrorFirstPart: `La conexión de la fuente de tarjetas está rota. Por favor, `, brokenConnectionErrorLink: 'inicia sesión en tu banco ', diff --git a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx index 01933f827362..9e25187032fe 100644 --- a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx @@ -80,7 +80,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { onPress={() => editStep(CONST.COMPANY_CARD.STEP.CARD)} /> editStep(CONST.COMPANY_CARD.STEP.TRANSACTION_START_DATE)} From 8082a9a52a6050e8865a95edda3d4a4444839e52 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 8 Nov 2024 09:59:05 +0100 Subject: [PATCH 6/7] Remove padding above the button --- .../workspace/companyCards/assignCard/CardSelectionStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index 7a4ad1a14416..303b5c644489 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -187,7 +187,7 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { isAlertVisible={shouldShowError} containerStyles={styles.ph5} message={translate('common.error.pleaseSelectOne')} - buttonStyles={[styles.mb5, styles.mt1]} + buttonStyles={styles.mb5} /> )} From a9bdbe2ca61436f8b31f78f04925274a004a4662 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 8 Nov 2024 10:36:36 +0100 Subject: [PATCH 7/7] Remove extra padding above the button on ios --- src/components/SelectionList/BaseSelectionList.tsx | 3 ++- src/components/SelectionList/types.ts | 3 +++ .../workspace/companyCards/assignCard/CardSelectionStep.tsx | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 03cd5028ecb2..82a2ae4b3c14 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -90,6 +90,7 @@ function BaseSelectionList( sectionTitleStyles, textInputAutoFocus = true, shouldShowTextInputAfterHeader = false, + includeSafeAreaPaddingBottom = true, shouldTextInputInterceptSwipe = false, listHeaderContent, onEndReached = () => {}, @@ -719,7 +720,7 @@ function BaseSelectionList( return ( {({safeAreaPaddingBottomStyle}) => ( - + {shouldShowTextInput && !shouldShowTextInputAfterHeader && renderInput()} {/* 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. */} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index eba6cf274d6b..d7016b9306fc 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -446,6 +446,9 @@ type BaseSelectionListProps = Partial & { /** Whether the text input should be shown after list header */ shouldShowTextInputAfterHeader?: boolean; + /** Whether to include padding bottom */ + includeSafeAreaPaddingBottom?: boolean; + /** Callback to fire when the list is scrolled */ onScroll?: (event: NativeSyntheticEvent) => void; diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index 303b5c644489..12440bd58f5d 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -178,6 +178,7 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { } shouldShowTextInputAfterHeader + includeSafeAreaPaddingBottom={false} shouldShowListEmptyContent={false} shouldUpdateFocusedIndex />