diff --git a/packages/@react-aria/test-utils/src/select.ts b/packages/@react-aria/test-utils/src/select.ts index 57dda4173c7..0e908d048b1 100644 --- a/packages/@react-aria/test-utils/src/select.ts +++ b/packages/@react-aria/test-utils/src/select.ts @@ -24,7 +24,9 @@ interface SelectTriggerOptionOpts extends SelectOpenOpts { /** * The index, text, or node of the option to select. Option nodes can be sourced via `options()`. */ - option: number | string | HTMLElement + option: number | string | HTMLElement, + + shouldStayOpen?: boolean } export class SelectTester { @@ -164,6 +166,7 @@ export class SelectTester { async selectOption(opts: SelectTriggerOptionOpts): Promise { let { option, + shouldStayOpen = false, interactionType = this._interactionType } = opts || {}; let trigger = this.trigger; @@ -203,7 +206,7 @@ export class SelectTester { } } - if (option?.getAttribute('href') == null) { + if (option?.getAttribute('href') == null && !shouldStayOpen) { await waitFor(() => { if (document.activeElement !== this._trigger) { throw new Error(`Expected the document.activeElement after selecting an option to be the select component trigger but got ${document.activeElement}`); diff --git a/packages/@react-stately/select/src/useSelectState.ts b/packages/@react-stately/select/src/useSelectState.ts index cdab4312f40..4f76ca480fd 100644 --- a/packages/@react-stately/select/src/useSelectState.ts +++ b/packages/@react-stately/select/src/useSelectState.ts @@ -54,7 +54,9 @@ export function useSelectState(props: SelectStateOptions): props.onSelectionChange(key); } - triggerState.close(); + if (props.shouldCloseOnSelect ?? true) { + triggerState.close(); + } validationState.commitValidation(); } }); diff --git a/packages/@react-types/select/src/index.d.ts b/packages/@react-types/select/src/index.d.ts index 22b6f976801..eba3cedb482 100644 --- a/packages/@react-types/select/src/index.d.ts +++ b/packages/@react-types/select/src/index.d.ts @@ -36,7 +36,9 @@ export interface SelectProps extends CollectionBase, Omit void + onOpenChange?: (isOpen: boolean) => void, + /** Should the menu be closed when an item is selected? */ + shouldCloseOnSelect?: boolean } export interface AriaSelectProps extends SelectProps, DOMProps, AriaLabelingProps, FocusableDOMProps { diff --git a/packages/react-aria-components/test/Select.test.js b/packages/react-aria-components/test/Select.test.js index 67c93fcd993..e14b5692351 100644 --- a/packages/react-aria-components/test/Select.test.js +++ b/packages/react-aria-components/test/Select.test.js @@ -192,6 +192,34 @@ describe('Select', () => { expect(trigger).toHaveTextContent('close'); }); + it('should stay open on selecting an option if shouldCloseOnSelect is false', async () => { + let {getByTestId} = render( + + ); + + let selectTester = testUtilUser.createTester('Select', {root: getByTestId('select')}); + let trigger = selectTester.trigger; + + await selectTester.open(); + expect(trigger).toHaveAttribute('data-pressed', 'true'); + + await selectTester.selectOption({option: 'Dog', shouldStayOpen: true}); + expect(trigger).toHaveTextContent('Dog'); + expect(trigger).toHaveAttribute('data-pressed', 'true'); + }); + it('should send disabled prop to the hidden field', () => { render(