Skip to content

Commit

Permalink
Merge pull request #51955 from callstack-internal/VickyStash/feature/…
Browse files Browse the repository at this point in the history
…51906-card-list-search

[Workspace feeds] Add search bar if cards assignment list has 8+ cards
  • Loading branch information
mountiny authored Nov 8, 2024
2 parents b7acfbf + a9bdbe2 commit 81c17e8
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 55 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2806,6 +2806,7 @@ const CONST = {
RESTRICT: 'corporate',
ALLOW: 'personal',
},
CARD_LIST_THRESHOLD: 8,
EXPORT_CARD_TYPES: {
/**
* Name of Card NVP for QBO custom export accounts
Expand Down
97 changes: 56 additions & 41 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ function BaseSelectionList<TItem extends ListItem>(
textInputIconLeft,
sectionTitleStyles,
textInputAutoFocus = true,
shouldShowTextInputAfterHeader = false,
includeSafeAreaPaddingBottom = true,
shouldTextInputInterceptSwipe = false,
listHeaderContent,
onEndReached = () => {},
Expand Down Expand Up @@ -531,6 +533,48 @@ function BaseSelectionList<TItem extends ListItem>(
return null;
};

const renderInput = () => {
return (
<View style={[styles.ph5, styles.pb3]}>
<TextInput
ref={(element) => {
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}
/>
</View>
);
};

const scrollToFocusedIndexOnFirstRender = useCallback(
(nativeEvent: LayoutChangeEvent) => {
if (shouldUseDynamicMaxToRenderPerBatch) {
Expand Down Expand Up @@ -707,46 +751,8 @@ function BaseSelectionList<TItem extends ListItem>(
return (
<SafeAreaConsumer>
{({safeAreaPaddingBottomStyle}) => (
<View style={[styles.flex1, (!isKeyboardShown || !!footerContent || showConfirmButton) && safeAreaPaddingBottomStyle, containerStyle]}>
{shouldShowTextInput && (
<View style={[styles.ph5, styles.pb3]}>
<TextInput
ref={(element) => {
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}
/>
</View>
)}
<View style={[styles.flex1, (!isKeyboardShown || !!footerContent || showConfirmButton) && includeSafeAreaPaddingBottom && safeAreaPaddingBottomStyle, containerStyle]}>
{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)) &&
Expand Down Expand Up @@ -790,7 +796,16 @@ function BaseSelectionList<TItem extends ListItem>(
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}
Expand Down
6 changes: 6 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,12 @@ type BaseSelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {
/** Item `keyForList` to focus initially */
initiallyFocusedOptionKey?: string | null;

/** 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<NativeScrollEvent>) => void;

Expand Down
1 change: 0 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3199,7 +3199,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 ',
Expand Down
1 change: 0 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3238,7 +3238,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 ',
Expand Down
43 changes: 32 additions & 11 deletions src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
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';
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';
Expand Down Expand Up @@ -36,6 +37,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}`);
Expand Down Expand Up @@ -117,12 +119,14 @@ 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 (
<InteractiveStepWrapper
wrapperID={CardSelectionStep.displayName}
handleBackButtonPress={handleBackButtonPress}
startStepIndex={listOptions.length ? 1 : undefined}
stepNames={listOptions.length ? CONST.COMPANY_CARD.STEP_NAMES : undefined}
headerTitle={translate('workspace.companyCards.assignCard')}
headerSubtitle={assigneeDisplayName}
>
Expand All @@ -147,18 +151,35 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) {
</View>
) : (
<>
<Text style={[styles.textHeadlineLineHeightXXL, styles.ph5, styles.mt3]}>{translate('workspace.companyCards.chooseCard')}</Text>
<Text style={[styles.textSupporting, styles.ph5, styles.mv3]}>
{translate('workspace.companyCards.chooseCardFor', {
assignee: assigneeDisplayName,
feed: CardUtils.getCardFeedName(feed),
})}
</Text>
<SelectionList
sections={[{data: listOptions}]}
sections={[{data: searchedListOptions}]}
shouldShowTextInput={listOptions.length > CONST.COMPANY_CARDS.CARD_LIST_THRESHOLD}
textInputLabel={translate('common.search')}
textInputValue={searchText}
onChangeText={setSearchText}
ListItem={RadioListItem}
onSelectRow={({value}) => handleSelectCard(value)}
initiallyFocusedOptionKey={cardSelected}
listHeaderContent={
<View>
<View style={[styles.ph5, styles.mb5, styles.mt3, {height: CONST.BANK_ACCOUNT.STEPS_HEADER_HEIGHT}]}>
<InteractiveStepSubHeader
startStepIndex={1}
stepNames={CONST.COMPANY_CARD.STEP_NAMES}
/>
</View>
<Text style={[styles.textHeadlineLineHeightXXL, styles.ph5, styles.mt3]}>{translate('workspace.companyCards.chooseCard')}</Text>
<Text style={[styles.textSupporting, styles.ph5, styles.mv3]}>
{translate('workspace.companyCards.chooseCardFor', {
assignee: assigneeDisplayName,
feed: CardUtils.getCardFeedName(feed),
})}
</Text>
</View>
}
shouldShowTextInputAfterHeader
includeSafeAreaPaddingBottom={false}
shouldShowListEmptyContent={false}
shouldUpdateFocusedIndex
/>
<FormAlertWithSubmitButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) {
onPress={() => editStep(CONST.COMPANY_CARD.STEP.CARD)}
/>
<MenuItemWithTopDescription
description={translate('workspace.companyCards.startTransactionDate')}
description={translate('workspace.moreFeatures.companyCards.transactionStartDate')}
title={data?.dateOption === CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.FROM_BEGINNING ? translate('workspace.companyCards.fromTheBeginning') : data?.startDate}
shouldShowRightIcon
onPress={() => editStep(CONST.COMPANY_CARD.STEP.TRANSACTION_START_DATE)}
Expand Down

0 comments on commit 81c17e8

Please sign in to comment.