Skip to content

Commit

Permalink
Merge pull request Expensify#49513 from nkdengineer/fix/49439
Browse files Browse the repository at this point in the history
  • Loading branch information
luacmartins authored Sep 27, 2024
2 parents 33818a9 + 6e72e09 commit 4c76858
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 58 deletions.
2 changes: 1 addition & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ type OnyxValuesMapping = {

// ONYXKEYS.NVP_TRYNEWDOT is HybridApp onboarding data
[ONYXKEYS.NVP_TRYNEWDOT]: OnyxTypes.TryNewDot;
[ONYXKEYS.SAVED_SEARCHES]: OnyxTypes.SaveSearch[];
[ONYXKEYS.SAVED_SEARCHES]: OnyxTypes.SaveSearch;
[ONYXKEYS.RECENTLY_USED_CURRENCIES]: string[];
[ONYXKEYS.ACTIVE_CLIENTS]: string[];
[ONYXKEYS.DEVICE_ID]: string;
Expand Down
90 changes: 49 additions & 41 deletions src/components/PopoverMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import * as Browser from '@libs/Browser';
import * as Modal from '@userActions/Modal';
import CONST from '@src/CONST';
import type {AnchorPosition} from '@src/styles';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import FocusableMenuItem from './FocusableMenuItem';
import FocusTrapForModal from './FocusTrap/FocusTrapForModal';
import * as Expensicons from './Icon/Expensicons';
import type {MenuItemProps} from './MenuItem';
import MenuItem from './MenuItem';
import type BaseModalProps from './Modal/types';
import OfflineWithFeedback from './OfflineWithFeedback';
import PopoverWithMeasuredContent from './PopoverWithMeasuredContent';
import ScrollView from './ScrollView';
import Text from './Text';
Expand Down Expand Up @@ -48,6 +50,8 @@ type PopoverMenuItem = MenuItemProps & {

/** Whether to close all modals */
shouldCloseAllModals?: boolean;

pendingAction?: PendingAction;
};

type PopoverModalProps = Pick<ModalProps, 'animationIn' | 'animationOut' | 'animationInTiming'>;
Expand Down Expand Up @@ -262,49 +266,53 @@ function PopoverMenu({
{renderHeaderText()}
{enteredSubMenuIndexes.length > 0 && renderBackButtonItem()}
{currentMenuItems.map((item, menuIndex) => (
<FocusableMenuItem
<OfflineWithFeedback
// eslint-disable-next-line react/no-array-index-key
key={`${item.text}_${menuIndex}`}
icon={item.icon}
iconWidth={item.iconWidth}
iconHeight={item.iconHeight}
iconFill={item.iconFill}
contentFit={item.contentFit}
title={item.text}
shouldShowSelectedItemCheck={shouldShowSelectedItemCheck}
titleStyle={StyleSheet.flatten([styles.flex1, item.titleStyle])}
shouldCheckActionAllowedOnPress={false}
description={item.description}
numberOfLinesDescription={item.numberOfLinesDescription}
onPress={() => selectItem(menuIndex)}
focused={focusedIndex === menuIndex}
displayInDefaultIconColor={item.displayInDefaultIconColor}
shouldShowRightIcon={item.shouldShowRightIcon}
shouldShowRightComponent={item.shouldShowRightComponent}
iconRight={item.iconRight}
rightComponent={item.rightComponent}
shouldPutLeftPaddingWhenNoIcon={item.shouldPutLeftPaddingWhenNoIcon}
label={item.label}
style={{backgroundColor: item.isSelected ? theme.activeComponentBG : undefined}}
isLabelHoverable={item.isLabelHoverable}
floatRightAvatars={item.floatRightAvatars}
floatRightAvatarSize={item.floatRightAvatarSize}
shouldShowSubscriptRightAvatar={item.shouldShowSubscriptRightAvatar}
disabled={item.disabled}
onFocus={() => setFocusedIndex(menuIndex)}
success={item.success}
containerStyle={item.containerStyle}
shouldRenderTooltip={item.shouldRenderTooltip}
tooltipAnchorAlignment={item.tooltipAnchorAlignment}
tooltipShiftHorizontal={item.tooltipShiftHorizontal}
tooltipShiftVertical={item.tooltipShiftVertical}
tooltipWrapperStyle={item.tooltipWrapperStyle}
renderTooltipContent={item.renderTooltipContent}
numberOfLinesTitle={item.numberOfLinesTitle}
interactive={item.interactive}
isSelected={item.isSelected}
badgeText={item.badgeText}
/>
pendingAction={item.pendingAction}
>
<FocusableMenuItem
icon={item.icon}
iconWidth={item.iconWidth}
iconHeight={item.iconHeight}
iconFill={item.iconFill}
contentFit={item.contentFit}
title={item.text}
shouldShowSelectedItemCheck={shouldShowSelectedItemCheck}
titleStyle={StyleSheet.flatten([styles.flex1, item.titleStyle])}
shouldCheckActionAllowedOnPress={false}
description={item.description}
numberOfLinesDescription={item.numberOfLinesDescription}
onPress={() => selectItem(menuIndex)}
focused={focusedIndex === menuIndex}
displayInDefaultIconColor={item.displayInDefaultIconColor}
shouldShowRightIcon={item.shouldShowRightIcon}
shouldShowRightComponent={item.shouldShowRightComponent}
iconRight={item.iconRight}
rightComponent={item.rightComponent}
shouldPutLeftPaddingWhenNoIcon={item.shouldPutLeftPaddingWhenNoIcon}
label={item.label}
style={{backgroundColor: item.isSelected ? theme.activeComponentBG : undefined}}
isLabelHoverable={item.isLabelHoverable}
floatRightAvatars={item.floatRightAvatars}
floatRightAvatarSize={item.floatRightAvatarSize}
shouldShowSubscriptRightAvatar={item.shouldShowSubscriptRightAvatar}
disabled={item.disabled}
onFocus={() => setFocusedIndex(menuIndex)}
success={item.success}
containerStyle={item.containerStyle}
shouldRenderTooltip={item.shouldRenderTooltip}
tooltipAnchorAlignment={item.tooltipAnchorAlignment}
tooltipShiftHorizontal={item.tooltipShiftHorizontal}
tooltipShiftVertical={item.tooltipShiftVertical}
tooltipWrapperStyle={item.tooltipWrapperStyle}
renderTooltipContent={item.renderTooltipContent}
numberOfLinesTitle={item.numberOfLinesTitle}
interactive={item.interactive}
isSelected={item.isSelected}
badgeText={item.badgeText}
/>
</OfflineWithFeedback>
))}
</ScrollView>
</FocusTrapForModal>
Expand Down
71 changes: 69 additions & 2 deletions src/libs/actions/Search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,78 @@ function saveSearch({queryJSON, newName}: {queryJSON: SearchQueryJSON; newName?:
const saveSearchName = newName ?? queryJSON?.inputQuery ?? '';
const jsonQuery = JSON.stringify(queryJSON);

API.write(WRITE_COMMANDS.SAVE_SEARCH, {jsonQuery, newName: saveSearchName});
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.SAVED_SEARCHES}`,
value: {
[queryJSON.hash]: {
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
name: saveSearchName,
query: queryJSON.inputQuery,
},
},
},
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.SAVED_SEARCHES}`,
value: {
[queryJSON.hash]: null,
},
},
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.SAVED_SEARCHES}`,
value: {
[queryJSON.hash]: {
pendingAction: null,
},
},
},
];
API.write(WRITE_COMMANDS.SAVE_SEARCH, {jsonQuery, newName: saveSearchName}, {optimisticData, failureData, successData});
}

function deleteSavedSearch(hash: number) {
API.write(WRITE_COMMANDS.DELETE_SAVED_SEARCH, {hash});
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.SAVED_SEARCHES}`,
value: {
[hash]: {
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
},
},
},
];
const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.SAVED_SEARCHES}`,
value: {
[hash]: null,
},
},
];
const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.SAVED_SEARCHES}`,
value: {
[hash]: {
pendingAction: null,
},
},
},
];

API.write(WRITE_COMMANDS.DELETE_SAVED_SEARCH, {hash}, {optimisticData, failureData, successData});
}

function search({queryJSON, offset}: {queryJSON: SearchQueryJSON; offset?: number}) {
Expand Down
11 changes: 8 additions & 3 deletions src/pages/Search/SavedSearchItemThreeDotMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ import React, {useRef, useState} from 'react';
import {View} from 'react-native';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import ThreeDotsMenu from '@components/ThreeDotsMenu';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';

type SavedSearchItemThreeDotMenuProps = {
menuItems: PopoverMenuItem[];
isDisabledItem: boolean;
};

function SavedSearchItemThreeDotMenu({menuItems}: SavedSearchItemThreeDotMenuProps) {
function SavedSearchItemThreeDotMenu({menuItems, isDisabledItem}: SavedSearchItemThreeDotMenuProps) {
const threeDotsMenuContainerRef = useRef<View>(null);
const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState({horizontal: 0, vertical: 0});

const styles = useThemeStyles();
return (
<View ref={threeDotsMenuContainerRef}>
<View
ref={threeDotsMenuContainerRef}
style={[isDisabledItem && styles.pointerEventsNone]}
>
<ThreeDotsMenu
menuItems={menuItems}
onIconPress={() => {
Expand Down
1 change: 1 addition & 0 deletions src/pages/Search/SavedSearchRenamePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function SavedSearchRenamePage({route}: {route: {params: {q: string; name: strin
submitButtonText={translate('common.save')}
onSubmit={onSaveSearch}
style={[styles.mh5, styles.flex1]}
enabledWhenOffline
>
<InputWrapper
InputComponent={TextInput}
Expand Down
14 changes: 10 additions & 4 deletions src/pages/Search/SearchTypeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {View} from 'react-native';
// eslint-disable-next-line no-restricted-imports
import type {ScrollView as RNScrollView, ScrollViewProps, TextStyle, ViewStyle} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {MenuItemBaseProps} from '@components/MenuItem';
import MenuItem from '@components/MenuItem';
import MenuItemList from '@components/MenuItemList';
import type {MenuItemWithLink} from '@components/MenuItemList';
Expand Down Expand Up @@ -34,7 +33,7 @@ import type IconAsset from '@src/types/utils/IconAsset';
import SavedSearchItemThreeDotMenu from './SavedSearchItemThreeDotMenu';
import SearchTypeMenuNarrow from './SearchTypeMenuNarrow';

type SavedSearchMenuItem = MenuItemBaseProps & {
type SavedSearchMenuItem = MenuItemWithLink & {
key: string;
hash: string;
query: string;
Expand Down Expand Up @@ -118,8 +117,15 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
SearchActions.clearAllFilters();
Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: item?.query ?? '', name: item?.name}));
},
rightComponent: <SavedSearchItemThreeDotMenu menuItems={getOverflowMenu(title, Number(key), item.query)} />,
rightComponent: (
<SavedSearchItemThreeDotMenu
menuItems={getOverflowMenu(title, Number(key), item.query)}
isDisabledItem={item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}
/>
),
styles: [styles.alignItemsCenter],
pendingAction: item.pendingAction,
disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
};

if (!isNarrow) {
Expand Down Expand Up @@ -179,7 +185,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
if (!savedSearches) {
return [];
}
return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item as SaveSearchItem, key, shouldUseNarrowLayout, index));
return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item, key, shouldUseNarrowLayout, index));
};

const renderSavedSearchesSection = useCallback(
Expand Down
9 changes: 5 additions & 4 deletions src/pages/Search/SearchTypeMenuNarrow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Animated, View} from 'react-native';
import type {TextStyle, ViewStyle} from 'react-native';
import Button from '@components/Button';
import Icon from '@components/Icon';
import type {MenuItemBaseProps} from '@components/MenuItem';
import type {MenuItemWithLink} from '@components/MenuItemList';
import PopoverMenu from '@components/PopoverMenu';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
Expand All @@ -26,7 +26,7 @@ import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {SearchTypeMenuItem} from './SearchTypeMenu';

type SavedSearchMenuItem = MenuItemBaseProps & {
type SavedSearchMenuItem = MenuItemWithLink & {
key: string;
hash: string;
query: string;
Expand Down Expand Up @@ -120,11 +120,13 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title,
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
}}
disabled={item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}
/>
),
isSelected: currentSavedSearch?.hash === item.hash,
pendingAction: item.pendingAction,
disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
}));

const allMenuItems = [];
allMenuItems.push(...popoverMenuItems);

Expand All @@ -136,7 +138,6 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title,
});
allMenuItems.push(...savedSearchItems);
}

return (
<View style={[styles.pb4, styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween, styles.ph5, styles.gap2]}>
<PressableWithFeedback
Expand Down
8 changes: 5 additions & 3 deletions src/types/onyx/SaveSearch.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type * as OnyxCommon from './OnyxCommon';

/**
* Model of a single saved search
*/
type SaveSearchItem = {
type SaveSearchItem = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Name of the saved search */
name: string;

/** Query string for the saved search */
query: string;
};
}>;

/**
* Model of saved searches
*/
type SaveSearch = Record<number, SaveSearchItem | null>;
type SaveSearch = Record<number, SaveSearchItem>;

export type {SaveSearch, SaveSearchItem};

0 comments on commit 4c76858

Please sign in to comment.