From 4832073e5349b437d3979895da73a0891947598f Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Thu, 4 Jul 2024 14:11:43 +0200 Subject: [PATCH 01/11] Add Selection Mode for small screens in Search Page --- ios/Podfile.lock | 2 +- .../Search/SearchListWithHeader.tsx | 106 ++++++++++++++---- src/components/Search/SearchPageHeader.tsx | 50 +++++---- src/components/Search/index.tsx | 33 +++--- src/components/SelectionList/BaseListItem.tsx | 4 + .../SelectionList/BaseSelectionList.tsx | 4 + .../Search/ExpenseItemHeaderNarrow.tsx | 45 +++++++- .../SelectionList/Search/ReportListItem.tsx | 7 +- .../Search/TransactionListItem.tsx | 3 + .../Search/TransactionListItemRow.tsx | 33 +++++- src/components/SelectionList/types.ts | 12 ++ src/pages/Search/SearchFilters.tsx | 7 +- src/pages/Search/SearchPageBottomTab.tsx | 10 +- src/pages/Search/SearchSelectedNarrow.tsx | 72 ++++++++++++ 14 files changed, 327 insertions(+), 61 deletions(-) create mode 100644 src/pages/Search/SearchSelectedNarrow.tsx diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a5ffdcb4b63c..f2ee4e6a390f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2631,7 +2631,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: d5e281e5370cb0211a104efd90eb5fa7af936e14 diff --git a/src/components/Search/SearchListWithHeader.tsx b/src/components/Search/SearchListWithHeader.tsx index 48d9a2b4ae3a..ba798f33b915 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 (Object.keys(selectedItems).length !== 0) { return; } - setSelectedItems({ - ...selectedItems, - ...Object.fromEntries(item.transactions.map(mapTransactionItemToSelectedEntry)), - }); - }; + setIsMobileSelectionModeActive?.(false); + }); const toggleAllTransactions = () => { const areItemsOfReportType = searchType === CONST.SEARCH.DATA_TYPES.REPORT; @@ -104,6 +151,7 @@ function SearchListWithHeader({ListItem, onSelectRow, query, hash, data, searchT clearSelectedItems={clearSelectedItems} query={query} hash={hash} + isMobileSelectionModeActive={isMobileSelectionModeActive} /> // eslint-disable-next-line react/jsx-props-no-spreading @@ -111,10 +159,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..2828c018eaf7 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,12 @@ type SearchHeaderProps = { selectedItems?: SelectedTransactions; clearSelectedItems?: () => void; hash: number; + isMobileSelectionModeActive?: boolean; }; type SearchHeaderOptionValue = DeepValueOf | undefined; -function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems}: SearchHeaderProps) { +function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems, isMobileSelectionModeActive}: SearchHeaderProps) { const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); @@ -39,12 +41,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); @@ -107,21 +110,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]); if (isSmallScreenWidth) { + if (isMobileSelectionModeActive) { + return ( + + ); + } return null; } @@ -131,11 +131,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 6414501fb06d..63e416e53fb5 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>(); @@ -163,6 +164,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. @@ -203,6 +208,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(); @@ -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 diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 503f7d11d2da..af23f8124159 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 8f46a5388da8..1b3a30a81c6c 100644 --- a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx +++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx @@ -2,24 +2,47 @@ 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} from '@src/types/onyx/SearchResults'; import ActionCell from './ActionCell'; import UserInfoCell from './UserInfoCell'; type ExpenseItemHeaderNarrowProps = { + id: string; + text?: string; participantFrom: SearchAccountDetails; participantTo: SearchAccountDetails; participantFromDisplayName: string; participantToDisplayName: string; onButtonPress: () => void; action?: string; + canSelectMultiple?: boolean; + isSelected?: boolean; + isDisabled?: boolean | null; + isDisabledCheckbox?: boolean; + handleCheckboxPress?: (id: string) => void; }; -function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, participantTo, participantToDisplayName, onButtonPress, action}: ExpenseItemHeaderNarrowProps) { +function ExpenseItemHeaderNarrow({ + participantFrom, + participantFromDisplayName, + participantTo, + participantToDisplayName, + onButtonPress, + action, + canSelectMultiple, + isDisabledCheckbox, + isSelected, + isDisabled, + handleCheckboxPress, + id, + text, +}: ExpenseItemHeaderNarrowProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const theme = useTheme(); @@ -27,6 +50,26 @@ function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, p return ( + {canSelectMultiple && ( + handleCheckboxPress?.(id)} + style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), isDisabledCheckbox && styles.cursorDisabled, styles.mr3]} + > + + {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} @@ -137,6 +140,7 @@ function ReportListItem({ {!isLargeScreenWidth && ( ({ 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]} /> )} @@ -201,6 +205,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 b00ae0703c2e..0e1b218f26d0 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 9f0799143373..6c6c44df82d2 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'; @@ -51,6 +52,7 @@ type TransactionListItemRowProps = { isDisabled: boolean; canSelectMultiple: boolean; isButtonSelected?: boolean; + shouldShowTransactionCheckbox?: boolean; }; const getTypeIcon = (type?: SearchTransactionType) => { @@ -225,26 +227,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 = { @@ -462,6 +468,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/pages/Search/SearchFilters.tsx b/src/pages/Search/SearchFilters.tsx index bbb861364f00..9ea2089e8865 100644 --- a/src/pages/Search/SearchFilters.tsx +++ b/src/pages/Search/SearchFilters.tsx @@ -16,6 +16,7 @@ import SearchFiltersNarrow from './SearchFiltersNarrow'; type SearchFiltersProps = { query: string; + isMobileSelectionModeActive: boolean; }; type SearchMenuFilterItem = { @@ -25,7 +26,7 @@ type SearchMenuFilterItem = { route: Route; }; -function SearchFilters({query}: SearchFiltersProps) { +function SearchFilters({query, isMobileSelectionModeActive}: SearchFiltersProps) { const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const {singleExecution} = useSingleExecution(); @@ -59,6 +60,10 @@ function SearchFilters({query}: SearchFiltersProps) { ]; const activeItemIndex = filterItems.findIndex((item) => item.query === query); + if (isMobileSelectionModeActive) { + return null; + } + if (isSmallScreenWidth) { return ( - + {isSmallScreenWidth && ( )} diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx new file mode 100644 index 000000000000..cdbadac0c602 --- /dev/null +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -0,0 +1,72 @@ +import React, {useRef, useState} from 'react'; +import {Animated, View} from 'react-native'; +import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; +import Icon from '@components/Icon'; +import MenuItem from '@components/MenuItem'; +import Modal from '@components/Modal'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; +import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +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 theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [isModalVisible, setIsModalVisible] = useState(false); + const buttonRef = useRef(null); + + const openMenu = () => setIsModalVisible(true); + const closeMenu = () => setIsModalVisible(false); + + return ( + + + {({hovered}) => ( + + + {translate('workspace.common.selected', {selectedNumber: itemsLength})} + + + + )} + + + + {options.map((option) => ( + + ))} + + + ); +} + +SearchSelectedNarrow.displayName = 'SearchSelectedNarrow'; + +export default SearchSelectedNarrow; From 57ed2ba7a9a4f10328dc652c7822fe19732443e1 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Thu, 4 Jul 2024 14:12:56 +0200 Subject: [PATCH 02/11] revert podfile changes --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f2ee4e6a390f..a5ffdcb4b63c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2631,7 +2631,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 + Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 PODFILE CHECKSUM: d5e281e5370cb0211a104efd90eb5fa7af936e14 From 3b3b2b78e3389cb760d35ef4956593e0ece3ea85 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 5 Jul 2024 14:21:16 +0200 Subject: [PATCH 03/11] Fix icon for selected button --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a5ffdcb4b63c..f2ee4e6a390f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2631,7 +2631,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: d5e281e5370cb0211a104efd90eb5fa7af936e14 From d3a7f9edd5749b66e5454dcc7a77cf85dd9f211b Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 5 Jul 2024 14:23:13 +0200 Subject: [PATCH 04/11] Fix icon --- ios/Podfile.lock | 2 +- src/pages/Search/SearchSelectedNarrow.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f2ee4e6a390f..a5ffdcb4b63c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2631,7 +2631,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 + Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 PODFILE CHECKSUM: d5e281e5370cb0211a104efd90eb5fa7af936e14 diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx index cdbadac0c602..e3ba8e89e603 100644 --- a/src/pages/Search/SearchSelectedNarrow.tsx +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -10,6 +10,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; import * as Expensicons from '@src/components/Icon/Expensicons'; import CONST from '@src/CONST'; @@ -40,8 +41,9 @@ function SearchSelectedNarrow({options, itemsLength}: SearchSelectedNarrowProps) {translate('workspace.common.selected', {selectedNumber: itemsLength})} From 6cf4194ea8676d944908518bed13e36a36dca3ca Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Mon, 8 Jul 2024 08:57:12 +0200 Subject: [PATCH 05/11] Use Button Component --- src/components/Button/index.tsx | 6 ++++- src/pages/Search/SearchSelectedNarrow.tsx | 29 +++++++---------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 88ae8d48a871..a6473faabb57 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 */ + centeredContent?: boolean; }; type KeyboardShortcutComponentProps = Pick; @@ -202,6 +205,7 @@ function Button( id = '', accessibilityLabel = '', isSplitButton = false, + centeredContent = 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/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx index e3ba8e89e603..8a1af3191b4d 100644 --- a/src/pages/Search/SearchSelectedNarrow.tsx +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -1,23 +1,19 @@ import React, {useRef, useState} from 'react'; -import {Animated, View} from 'react-native'; +import {View} from 'react-native'; +import Button from '@components/Button'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; -import Icon from '@components/Icon'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader'; -import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import variables from '@styles/variables'; 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 theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -35,20 +31,13 @@ function SearchSelectedNarrow({options, itemsLength}: SearchSelectedNarrowProps) style={[styles.tabSelectorButton, styles.ph5]} onPress={openMenu} > - {({hovered}) => ( - - - {translate('workspace.common.selected', {selectedNumber: itemsLength})} - - - - )} +