diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 4b3f0f70db24..0fd3cc0728ca 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -118,6 +118,9 @@ type ButtonProps = Partial & { /** Whether the button should use split style or not */ isSplitButton?: boolean; + + /** Whether button's content should be centered */ + isContentCentered?: boolean; }; type KeyboardShortcutComponentProps = Pick; @@ -202,6 +205,7 @@ function Button( id = '', accessibilityLabel = '', isSplitButton = false, + isContentCentered = false, ...rest }: ButtonProps, ref: ForwardedRef, @@ -239,7 +243,7 @@ function Button( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (icon || shouldShowRightIcon) { return ( - + {icon && ( diff --git a/src/components/Search/SearchListWithHeader.tsx b/src/components/Search/SearchListWithHeader.tsx index 48d9a2b4ae3a..02da657609ba 100644 --- a/src/components/Search/SearchListWithHeader.tsx +++ b/src/components/Search/SearchListWithHeader.tsx @@ -1,7 +1,12 @@ import type {ForwardedRef} from 'react'; -import React, {forwardRef, useEffect, useMemo, useState} from 'react'; +import React, {forwardRef, useCallback, useEffect, useMemo, useState} from 'react'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import Modal from '@components/Modal'; import SelectionList from '@components/SelectionList'; import type {BaseSelectionListProps, ReportListItemType, SelectionListHandle, TransactionListItemType} from '@components/SelectionList/types'; +import useLocalize from '@hooks/useLocalize'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import * as SearchUtils from '@libs/SearchUtils'; import CONST from '@src/CONST'; import type {SearchDataTypes, SearchQuery} from '@src/types/onyx/SearchResults'; @@ -13,6 +18,8 @@ type SearchListWithHeaderProps = Omit void; }; function mapTransactionItemToSelectedEntry(item: TransactionListItemType): [string, SelectedTransactionInfo] { @@ -33,7 +40,14 @@ function mapToItemWithSelectionInfo(item: TransactionListItemType | ReportListIt }; } -function SearchListWithHeader({ListItem, onSelectRow, query, hash, data, searchType, ...props}: SearchListWithHeaderProps, ref: ForwardedRef) { +function SearchListWithHeader( + {ListItem, onSelectRow, query, hash, data, searchType, isMobileSelectionModeActive, setIsMobileSelectionModeActive, ...props}: SearchListWithHeaderProps, + ref: ForwardedRef, +) { + const {isSmallScreenWidth} = useWindowDimensions(); + const {translate} = useLocalize(); + const [isModalVisible, setIsModalVisible] = useState(false); + const [longPressedItem, setLongPressedItem] = useState(null); const [selectedItems, setSelectedItems] = useState({}); const clearSelectedItems = () => setSelectedItems({}); @@ -42,39 +56,72 @@ function SearchListWithHeader({ListItem, onSelectRow, query, hash, data, searchT clearSelectedItems(); }, [hash]); - const toggleTransaction = (item: TransactionListItemType | ReportListItemType) => { - if (SearchUtils.isTransactionListItemType(item)) { - if (!item.keyForList) { + const toggleTransaction = useCallback( + (item: TransactionListItemType | ReportListItemType) => { + if (SearchUtils.isTransactionListItemType(item)) { + if (!item.keyForList) { + return; + } + + setSelectedItems((prev) => { + if (prev[item.keyForList]?.isSelected) { + const {[item.keyForList]: omittedTransaction, ...transactions} = prev; + return transactions; + } + return {...prev, [item.keyForList]: {isSelected: true, canDelete: item.canDelete, action: item.action}}; + }); + return; } - setSelectedItems((prev) => { - if (prev[item.keyForList]?.isSelected) { - const {[item.keyForList]: omittedTransaction, ...transactions} = prev; - return transactions; - } - return {...prev, [item.keyForList]: {isSelected: true, canDelete: item.canDelete, action: item.action}}; + if (item.transactions.every((transaction) => selectedItems[transaction.keyForList]?.isSelected)) { + const reducedSelectedItems: SelectedTransactions = {...selectedItems}; + + item.transactions.forEach((transaction) => { + delete reducedSelectedItems[transaction.keyForList]; + }); + + setSelectedItems(reducedSelectedItems); + return; + } + + setSelectedItems({ + ...selectedItems, + ...Object.fromEntries(item.transactions.map(mapTransactionItemToSelectedEntry)), }); + }, + [selectedItems], + ); + const openBottomModal = (item: TransactionListItemType | ReportListItemType | null) => { + if (!isSmallScreenWidth) { return; } - if (item.transactions.every((transaction) => selectedItems[transaction.keyForList]?.isSelected)) { - const reducedSelectedItems: SelectedTransactions = {...selectedItems}; + setLongPressedItem(item); + setIsModalVisible(true); + }; - item.transactions.forEach((transaction) => { - delete reducedSelectedItems[transaction.keyForList]; - }); + const turnOnSelectionMode = useCallback(() => { + setIsMobileSelectionModeActive?.(true); + setIsModalVisible(false); + + if (longPressedItem) { + toggleTransaction(longPressedItem); + } + }, [longPressedItem, setIsMobileSelectionModeActive, toggleTransaction]); - setSelectedItems(reducedSelectedItems); + const closeBottomModal = useCallback(() => { + setIsModalVisible(false); + }, []); + + useEffect(() => { + if (isMobileSelectionModeActive) { return; } - setSelectedItems({ - ...selectedItems, - ...Object.fromEntries(item.transactions.map(mapTransactionItemToSelectedEntry)), - }); - }; + setSelectedItems({}); + }, [setSelectedItems, isMobileSelectionModeActive]); const toggleAllTransactions = () => { const areItemsOfReportType = searchType === CONST.SEARCH.DATA_TYPES.REPORT; @@ -104,6 +151,8 @@ function SearchListWithHeader({ListItem, onSelectRow, query, hash, data, searchT clearSelectedItems={clearSelectedItems} query={query} hash={hash} + isMobileSelectionModeActive={isMobileSelectionModeActive} + setIsMobileSelectionModeActive={setIsMobileSelectionModeActive} /> // eslint-disable-next-line react/jsx-props-no-spreading @@ -111,10 +160,24 @@ function SearchListWithHeader({ListItem, onSelectRow, query, hash, data, searchT sections={[{data: sortedSelectedData, isDisabled: false}]} ListItem={ListItem} onSelectRow={onSelectRow} + onLongPressRow={openBottomModal} ref={ref} onCheckboxPress={toggleTransaction} onSelectAll={toggleAllTransactions} + isMobileSelectionModeActive={isMobileSelectionModeActive} /> + + + + ); } diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index 8d42f9e6da36..b0f2acfb57d1 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react'; +import React, {useMemo} from 'react'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -10,6 +10,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as SearchActions from '@libs/actions/Search'; +import SearchSelectedNarrow from '@pages/Search/SearchSelectedNarrow'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {SearchQuery} from '@src/types/onyx/SearchResults'; @@ -22,11 +23,13 @@ type SearchHeaderProps = { selectedItems?: SelectedTransactions; clearSelectedItems?: () => void; hash: number; + isMobileSelectionModeActive?: boolean; + setIsMobileSelectionModeActive?: (isMobileSelectionModeActive: boolean) => void; }; type SearchHeaderOptionValue = DeepValueOf | undefined; -function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems}: SearchHeaderProps) { +function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchHeaderProps) { const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); @@ -39,12 +42,13 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems}: finished: {icon: Illustrations.CheckmarkCircle, title: translate('common.finished')}, }; - const getHeaderButtons = useCallback(() => { + const selectedItemsKeys = Object.keys(selectedItems ?? []); + + const headerButtonsOptions = useMemo(() => { const options: Array> = []; - const selectedItemsKeys = Object.keys(selectedItems ?? []); if (selectedItemsKeys.length === 0) { - return null; + return options; } const itemsToDelete = selectedItemsKeys.filter((id) => selectedItems[id].canDelete); @@ -56,6 +60,9 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems}: value: CONST.SEARCH.BULK_ACTION_TYPES.DELETE, onSelected: () => { clearSelectedItems?.(); + if (isMobileSelectionModeActive) { + setIsMobileSelectionModeActive?.(false); + } SearchActions.deleteMoneyRequestOnSearch(hash, itemsToDelete); }, }); @@ -70,6 +77,9 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems}: value: CONST.SEARCH.BULK_ACTION_TYPES.HOLD, onSelected: () => { clearSelectedItems?.(); + if (isMobileSelectionModeActive) { + setIsMobileSelectionModeActive?.(false); + } SearchActions.holdMoneyRequestOnSearch(hash, itemsToHold, ''); }, }); @@ -84,6 +94,9 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems}: value: CONST.SEARCH.BULK_ACTION_TYPES.UNHOLD, onSelected: () => { clearSelectedItems?.(); + if (isMobileSelectionModeActive) { + setIsMobileSelectionModeActive?.(false); + } SearchActions.unholdMoneyRequestOnSearch(hash, itemsToUnhold); }, }); @@ -107,21 +120,18 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems}: }); } - return ( - null} - shouldAlwaysShowDropdownMenu - pressOnEnter - buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {selectedNumber: selectedItemsKeys.length})} - options={options} - isSplitButton={false} - isDisabled={isOffline} - /> - ); - }, [clearSelectedItems, hash, isOffline, selectedItems, styles.colorMuted, styles.fontWeightNormal, theme.icon, translate]); + return options; + }, [clearSelectedItems, hash, selectedItems, selectedItemsKeys, styles, theme, translate, isMobileSelectionModeActive, setIsMobileSelectionModeActive]); if (isSmallScreenWidth) { + if (isMobileSelectionModeActive) { + return ( + + ); + } return null; } @@ -131,11 +141,23 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems}: icon={headerContent[query]?.icon} shouldShowBackButton={false} > - {getHeaderButtons()} + {headerButtonsOptions.length > 0 && ( + null} + shouldAlwaysShowDropdownMenu + pressOnEnter + buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} + customText={translate('workspace.common.selected', {selectedNumber: selectedItemsKeys.length})} + options={headerButtonsOptions} + isSplitButton={false} + isDisabled={isOffline} + /> + )} ); } SearchPageHeader.displayName = 'SearchPageHeader'; +export type {SearchHeaderOptionValue}; export default SearchPageHeader; diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index fc5c23d5c9ec..8b1c89c21a1c 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -34,6 +34,8 @@ type SearchProps = { policyIDs?: string; sortBy?: SearchColumnType; sortOrder?: SortOrder; + isMobileSelectionModeActive?: boolean; + setIsMobileSelectionModeActive?: (isMobileSelectionModeActive: boolean) => void; }; const sortableSearchTabs: SearchQuery[] = [CONST.SEARCH.TAB.ALL]; @@ -41,11 +43,10 @@ const transactionItemMobileHeight = 100; const reportItemTransactionHeight = 52; const listItemPadding = 12; // this is equivalent to 'mb3' on every transaction/report list item const searchHeaderHeight = 54; - -function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) { +function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchProps) { const {isOffline} = useNetwork(); const styles = useThemeStyles(); - const {isLargeScreenWidth} = useWindowDimensions(); + const {isLargeScreenWidth, isSmallScreenWidth} = useWindowDimensions(); const navigation = useNavigation>(); const lastSearchResultsRef = useRef>(); const {setCurrentSearchHash} = useSearchContext(); @@ -165,6 +166,8 @@ function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) { const shouldShowYear = SearchUtils.shouldShowYear(searchResults?.data); + const canSelectMultiple = isSmallScreenWidth ? isMobileSelectionModeActive : true; + return ( + !isLargeScreenWidth ? null : ( + + ) } - canSelectMultiple={isLargeScreenWidth} + canSelectMultiple={canSelectMultiple} customListHeaderHeight={searchHeaderHeight} // To enhance the smoothness of scrolling and minimize the risk of encountering blank spaces during scrolling, // we have configured a larger windowSize and a longer delay between batch renders. @@ -205,6 +210,8 @@ function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) { showScrollIndicator={false} onEndReachedThreshold={0.75} onEndReached={fetchMoreResults} + setIsMobileSelectionModeActive={setIsMobileSelectionModeActive} + isMobileSelectionModeActive={isMobileSelectionModeActive} listFooterContent={ isLoadingMoreItems ? ( ({ shouldSyncFocus = true, onFocus = () => {}, hoverStyle, + onLongPressRow, }: BaseListItemProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -42,7 +43,7 @@ function BaseListItem({ // Sync focus on an item useSyncFocus(pressableRef, !!isFocused, shouldSyncFocus); - const handleMouseUp = (e: React.MouseEvent) => { + const handleMouseLeave = (e: React.MouseEvent) => { e.stopPropagation(); setMouseUp(); }; @@ -71,6 +72,9 @@ function BaseListItem({ // eslint-disable-next-line react/jsx-props-no-spreading {...bind} ref={pressableRef} + onLongPress={() => { + onLongPressRow?.(item); + }} onPress={(e) => { if (isMouseDownOnInput) { e?.stopPropagation(); // Preventing the click action @@ -92,8 +96,7 @@ function BaseListItem({ id={keyForList ?? ''} style={pressableStyle} onFocus={onFocus} - onMouseUp={handleMouseUp} - onMouseLeave={handleMouseUp} + onMouseLeave={handleMouseLeave} tabIndex={item.tabIndex} > diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 8b6ba790e6b0..eb2e66ad9a78 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -93,6 +93,8 @@ function BaseSelectionList( updateCellsBatchingPeriod = 50, removeClippedSubviews = true, shouldDelayFocus = true, + onLongPressRow, + isMobileSelectionModeActive, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -447,6 +449,8 @@ function BaseSelectionList( isDisabled={isDisabled} showTooltip={showTooltip} canSelectMultiple={canSelectMultiple} + onLongPressRow={onLongPressRow} + isMobileSelectionModeActive={isMobileSelectionModeActive} onSelectRow={() => selectRow(item)} onCheckboxPress={handleOnCheckboxPress()} onDismissError={() => onDismissError?.(item)} diff --git a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx index f634f84509b1..13e181264b25 100644 --- a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx +++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx @@ -2,15 +2,18 @@ import React, {memo} from 'react'; import {View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import {PressableWithFeedback} from '@components/Pressable'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import type {SearchAccountDetails, SearchTransactionAction} from '@src/types/onyx/SearchResults'; import ActionCell from './ActionCell'; import UserInfoCell from './UserInfoCell'; type ExpenseItemHeaderNarrowProps = { + text?: string; participantFrom: SearchAccountDetails; participantTo: SearchAccountDetails; participantFromDisplayName: string; @@ -18,9 +21,28 @@ type ExpenseItemHeaderNarrowProps = { action?: SearchTransactionAction; transactionID?: string; onButtonPress: () => void; + canSelectMultiple?: boolean; + isSelected?: boolean; + isDisabled?: boolean | null; + isDisabledCheckbox?: boolean; + handleCheckboxPress?: () => void; }; -function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, participantTo, participantToDisplayName, action, transactionID, onButtonPress}: ExpenseItemHeaderNarrowProps) { +function ExpenseItemHeaderNarrow({ + participantFrom, + participantFromDisplayName, + participantTo, + participantToDisplayName, + onButtonPress, + action, + canSelectMultiple, + isDisabledCheckbox, + isSelected, + isDisabled, + handleCheckboxPress, + text, + transactionID, +}: ExpenseItemHeaderNarrowProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const theme = useTheme(); @@ -28,6 +50,26 @@ function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, p return ( + {canSelectMultiple && ( + handleCheckboxPress?.()} + style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), isDisabledCheckbox && styles.cursorDisabled, styles.mr1]} + > + + {isSelected && ( + + )} + + + )} ({ onSelectRow, onDismissError, onFocus, + onLongPressRow, shouldSyncFocus, }: ReportListItemProps) { const reportItem = item as unknown as ReportListItemType; @@ -110,6 +111,7 @@ function ReportListItem({ onSelectRow={() => openReportInRHP(transactionItem)} onDismissError={onDismissError} onFocus={onFocus} + onLongPressRow={onLongPressRow} shouldSyncFocus={shouldSyncFocus} /> ); @@ -126,6 +128,7 @@ function ReportListItem({ showTooltip={showTooltip} canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} + onLongPressRow={onLongPressRow} onDismissError={onDismissError} errors={item.errors} pendingAction={item.pendingAction} @@ -155,7 +158,7 @@ function ReportListItem({ containerStyle={[StyleUtils.getCheckboxContainerStyle(20), StyleUtils.getMultiselectListStyles(!!item.isSelected, !!item.isDisabled)]} disabled={!!isDisabled || item.isDisabledCheckbox} accessibilityLabel={item.text ?? ''} - style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), item.isDisabledCheckbox && styles.cursorDisabled]} + style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), item.isDisabledCheckbox && styles.cursorDisabled, !isLargeScreenWidth && styles.mr3]} /> )} @@ -200,6 +203,7 @@ function ReportListItem({ isDisabled={!!isDisabled} canSelectMultiple={!!canSelectMultiple} isButtonSelected={item.isSelected} + shouldShowTransactionCheckbox /> ))} diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index 6db308831baa..a10552ca9ad8 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -15,6 +15,7 @@ function TransactionListItem({ onCheckboxPress, onDismissError, onFocus, + onLongPressRow, shouldSyncFocus, }: TransactionListItemProps) { const transactionItem = item as unknown as TransactionListItemType; @@ -46,6 +47,7 @@ function TransactionListItem({ pendingAction={item.pendingAction} keyForList={item.keyForList} onFocus={onFocus} + onLongPressRow={onLongPressRow} shouldSyncFocus={shouldSyncFocus} hoverStyle={item.isSelected && styles.activeComponentBG} > @@ -59,6 +61,7 @@ function TransactionListItem({ isDisabled={!!isDisabled} canSelectMultiple={!!canSelectMultiple} isButtonSelected={item.isSelected} + shouldShowTransactionCheckbox={false} /> ); diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx index f9ca70536e4b..6a8e4dc52bb7 100644 --- a/src/components/SelectionList/Search/TransactionListItemRow.tsx +++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx @@ -4,6 +4,7 @@ import {View} from 'react-native'; import Checkbox from '@components/Checkbox'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import {PressableWithFeedback} from '@components/Pressable'; import ReceiptImage from '@components/ReceiptImage'; import type {TransactionListItemType} from '@components/SelectionList/types'; import TextWithTooltip from '@components/TextWithTooltip'; @@ -52,6 +53,7 @@ type TransactionListItemRowProps = { isDisabled: boolean; canSelectMultiple: boolean; isButtonSelected?: boolean; + shouldShowTransactionCheckbox?: boolean; }; const getTypeIcon = (type?: SearchTransactionType) => { @@ -240,27 +242,55 @@ function TransactionListItemRow({ containerStyle, isChildListItem = false, isButtonSelected = false, + shouldShowTransactionCheckbox, }: TransactionListItemRowProps) { const styles = useThemeStyles(); const {isLargeScreenWidth} = useWindowDimensions(); const StyleUtils = useStyleUtils(); + const theme = useTheme(); if (!isLargeScreenWidth) { return ( {showItemHeaderOnNarrowLayout && ( )} - + + {canSelectMultiple && shouldShowTransactionCheckbox && ( + + + {item.isSelected && ( + + )} + + + )} = { /** Handles what to do when the item is focused */ onFocus?: () => void; + + /** Callback to fire when the item is long pressed */ + onLongPressRow?: (item: TItem) => void; + + /** Whether Selection Mode is active - used only on small screens */ + isMobileSelectionModeActive?: boolean; } & TRightHandSideComponent; type ListItem = { @@ -465,6 +471,12 @@ type BaseSelectionListProps = Partial & { * https://reactnative.dev/docs/optimizing-flatlist-configuration#windowsize */ windowSize?: number; + + /** Callback to fire when the item is long pressed */ + onLongPressRow?: (item: TItem) => void; + + /** Whether Selection Mode is active - used only on small screens */ + isMobileSelectionModeActive?: boolean; } & TRightHandSideComponent; type SelectionListHandle = { diff --git a/src/languages/en.ts b/src/languages/en.ts index 1ac9684ac22e..bfd0e1c8c37c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3436,6 +3436,7 @@ export default { screenShareRequest: 'Expensify is inviting you to a screen share', }, search: { + selectMultiple: 'Select multiple', resultsAreLimited: 'Search results are limited.', searchResults: { emptyResults: { diff --git a/src/languages/es.ts b/src/languages/es.ts index ea9186daee78..c9b9407e3ce9 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3485,6 +3485,7 @@ export default { screenShareRequest: 'Expensify te está invitando a compartir la pantalla', }, search: { + selectMultiple: 'Seleccionar múltiples', resultsAreLimited: 'Los resultados de búsqueda están limitados.', searchResults: { emptyResults: { diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index eb662fd49046..be38185926d7 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -1,6 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useMemo} from 'react'; +import React, {useMemo, useState} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Search from '@components/Search'; import useActiveRoute from '@hooks/useActiveRoute'; @@ -29,6 +30,7 @@ function SearchPageBottomTab() { const {isSmallScreenWidth} = useWindowDimensions(); const activeRoute = useActiveRoute(); const styles = useThemeStyles(); + const [isMobileSelectionModeActive, setIsMobileSelectionModeActive] = useState(false); const { query: rawQuery, @@ -59,18 +61,29 @@ function SearchPageBottomTab() { onBackButtonPress={handleOnBackButtonPress} shouldShowLink={false} > - - + {!isMobileSelectionModeActive ? ( + <> + + + + ) : ( + setIsMobileSelectionModeActive(false)} + /> + )} {isSmallScreenWidth && ( )} diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx new file mode 100644 index 000000000000..b90142ff5873 --- /dev/null +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -0,0 +1,57 @@ +import React, {useRef, useState} from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; +import MenuItem from '@components/MenuItem'; +import Modal from '@components/Modal'; +import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Expensicons from '@src/components/Icon/Expensicons'; +import CONST from '@src/CONST'; + +type SearchSelectedNarrowProps = {options: Array>; itemsLength: number}; + +function SearchSelectedNarrow({options, itemsLength}: SearchSelectedNarrowProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [isModalVisible, setIsModalVisible] = useState(false); + const buttonRef = useRef(null); + + const openMenu = () => setIsModalVisible(true); + const closeMenu = () => setIsModalVisible(false); + + return ( + +