Skip to content

Commit 818e4a6

Browse files
authored
Merge pull request Expensify#65959 from mehrozone/65501_Update_Dropdow_Button
65501 update dropdown to show only button in case of single option
2 parents 929701d + 8d7e864 commit 818e4a6

6 files changed

Lines changed: 94 additions & 11 deletions

File tree

src/CONST/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5137,6 +5137,7 @@ const CONST = {
51375137
},
51385138
SELECTION_LIST_WITH_MODAL_TEST_ID: 'selectionListWithModalMenuItem',
51395139

5140+
ICON_TEST_ID: 'Icon',
51405141
IMAGE_TEST_ID: 'Image',
51415142
IMAGE_SVG_TEST_ID: 'ImageSVG',
51425143
VIDEO_PLAYER_TEST_ID: 'VideoPlayer',

src/components/ButtonWithDropdownMenu/index.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,25 @@ function ButtonWithDropdownMenuInner<IValueType>(props: ButtonWithDropdownMenuPr
109109
}
110110
}, [windowWidth, windowHeight, isMenuVisible, anchorAlignment.vertical, popoverHorizontalOffsetType]);
111111

112+
const handleSingleOptionPress = useCallback(
113+
(event: GestureResponderEvent | KeyboardEvent | undefined) => {
114+
const option = options.at(0);
115+
if (!option) {
116+
return;
117+
}
118+
119+
if (option.onSelected) {
120+
option.onSelected();
121+
} else {
122+
onOptionSelected?.(option);
123+
onPress(event, option.value);
124+
}
125+
126+
onSubItemSelected?.(option, 0, event);
127+
},
128+
[options, onPress, onOptionSelected, onSubItemSelected],
129+
);
130+
112131
useKeyboardShortcut(
113132
CONST.KEYBOARD_SHORTCUTS.CTRL_ENTER,
114133
(e) => {
@@ -121,10 +140,7 @@ function ButtonWithDropdownMenuInner<IValueType>(props: ButtonWithDropdownMenuPr
121140
onPress(e, selectedItem.value);
122141
}
123142
} else {
124-
const option = options.at(0);
125-
if (option?.value) {
126-
onPress(e, option.value);
127-
}
143+
handleSingleOptionPress(e);
128144
}
129145
},
130146
{
@@ -215,10 +231,7 @@ function ButtonWithDropdownMenuInner<IValueType>(props: ButtonWithDropdownMenuPr
215231
disabledStyle={disabledStyle}
216232
isLoading={isLoading}
217233
text={selectedItem?.text}
218-
onPress={(event) => {
219-
const option = options.at(0);
220-
return option ? onPress(event, option.value) : undefined;
221-
}}
234+
onPress={handleSingleOptionPress}
222235
large={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE}
223236
medium={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
224237
small={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.SMALL}
@@ -229,6 +242,7 @@ function ButtonWithDropdownMenuInner<IValueType>(props: ButtonWithDropdownMenuPr
229242
icon={shouldUseOptionIcon && !shouldShowButtonRightIcon ? options.at(0)?.icon : icon}
230243
iconRight={shouldShowButtonRightIcon ? options.at(0)?.icon : undefined}
231244
shouldShowRightIcon={shouldShowButtonRightIcon}
245+
testID={testID}
232246
/>
233247
)}
234248
{(shouldAlwaysShowDropdownMenu || options.length > 1) && !!popoverAnchorPosition && (

src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS
134134
<ButtonWithDropdownMenu
135135
success={false}
136136
onPress={() => {}}
137-
shouldAlwaysShowDropdownMenu
137+
shouldUseOptionIcon
138138
customText={translate('common.more')}
139139
options={secondaryActions}
140140
isSplitButton={false}

src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ function PolicyDistanceRatesPage({
337337
<ButtonWithDropdownMenu
338338
success={false}
339339
onPress={() => {}}
340-
shouldAlwaysShowDropdownMenu
340+
shouldUseOptionIcon
341341
customText={translate('common.more')}
342342
options={secondaryActions}
343343
isSplitButton={false}

src/pages/workspace/taxes/WorkspaceTaxesPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ function WorkspaceTaxesPage({
335335
<ButtonWithDropdownMenu
336336
success={false}
337337
onPress={() => {}}
338-
shouldAlwaysShowDropdownMenu
338+
shouldUseOptionIcon
339339
customText={translate('common.more')}
340340
options={secondaryActions}
341341
isSplitButton={false}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import {fireEvent, render, screen} from '@testing-library/react-native';
2+
import React from 'react';
3+
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
4+
import * as Expensicons from '@components/Icon/Expensicons';
5+
import CONST from '@src/CONST';
6+
7+
describe('ButtonWithDropdownMenu (single option)', () => {
8+
const mockOnSelected = jest.fn();
9+
const mockOnOptionSelected = jest.fn();
10+
const mockOnSubItemSelected = jest.fn();
11+
const mockOnPress = jest.fn();
12+
const option = {
13+
value: 'test',
14+
text: 'Test Option',
15+
icon: Expensicons.Checkbox,
16+
onSelected: mockOnSelected,
17+
};
18+
19+
afterEach(() => {
20+
jest.clearAllMocks();
21+
});
22+
23+
it('renders a button (not a dropdown) when only one option is present', () => {
24+
render(
25+
<ButtonWithDropdownMenu
26+
options={[option]}
27+
onPress={mockOnPress}
28+
onOptionSelected={mockOnOptionSelected}
29+
onSubItemSelected={mockOnSubItemSelected}
30+
shouldUseOptionIcon
31+
/>,
32+
);
33+
expect(screen.getByText('Test Option')).toBeTruthy();
34+
});
35+
36+
it('calls all relevant callbacks when the button is pressed', () => {
37+
render(
38+
<ButtonWithDropdownMenu
39+
options={[option]}
40+
onPress={mockOnPress}
41+
onOptionSelected={mockOnOptionSelected}
42+
onSubItemSelected={mockOnSubItemSelected}
43+
shouldUseOptionIcon
44+
/>,
45+
);
46+
fireEvent.press(screen.getByText('Test Option'));
47+
expect(mockOnSelected).toHaveBeenCalled();
48+
expect(mockOnOptionSelected).not.toHaveBeenCalled(); // onSelected takes precedence
49+
expect(mockOnSubItemSelected).toHaveBeenCalledWith(expect.objectContaining(option), 0, undefined);
50+
expect(mockOnPress).not.toHaveBeenCalled(); // onPress should not be called when onSelected exists to prevent double execution
51+
});
52+
53+
it('renders the icon from the option along with the text', () => {
54+
render(
55+
<ButtonWithDropdownMenu
56+
options={[option]}
57+
onPress={mockOnPress}
58+
onOptionSelected={mockOnOptionSelected}
59+
onSubItemSelected={mockOnSubItemSelected}
60+
shouldUseOptionIcon
61+
testID={CONST.ICON_TEST_ID}
62+
/>,
63+
);
64+
const iconNodes = screen.getAllByTestId(CONST.ICON_TEST_ID);
65+
expect(iconNodes.length).toBeGreaterThan(0);
66+
expect(screen.getByText('Test Option')).toBeTruthy();
67+
});
68+
});

0 commit comments

Comments
 (0)