From aeb16fa2f89e24316eed6f1e89b6b09112b17617 Mon Sep 17 00:00:00 2001 From: Hab Date: Fri, 6 Dec 2024 15:12:03 +0100 Subject: [PATCH] feat(select): add an option to not allow deselecting a selected option --- .../BoemlyFormControl.stories.tsx | 11 ++ src/components/Select/Select.stories.tsx | 7 ++ src/components/Select/Select.test.tsx | 113 ++++++++++++++++++ src/components/Select/Select.tsx | 22 +++- 4 files changed, 150 insertions(+), 3 deletions(-) diff --git a/src/components/BoemlyFormControl/BoemlyFormControl.stories.tsx b/src/components/BoemlyFormControl/BoemlyFormControl.stories.tsx index 7092f42..188ec14 100644 --- a/src/components/BoemlyFormControl/BoemlyFormControl.stories.tsx +++ b/src/components/BoemlyFormControl/BoemlyFormControl.stories.tsx @@ -94,6 +94,17 @@ SelectWithDisabledOption.args = { ], }; +export const SelectWithPreventDeselection = Template.bind({}); +SelectWithPreventDeselection.args = { + id: 'select', + inputType: 'Select', + selectOptions: [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + ], + selectProps: { preventDeselection: true }, +}; + export const Checkbox = Template.bind({}); Checkbox.args = { id: 'checkbox', diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx index 67e87a6..7202680 100644 --- a/src/components/Select/Select.stories.tsx +++ b/src/components/Select/Select.stories.tsx @@ -67,6 +67,13 @@ WithPlaceholder.args = { options: commonOptions, }; +export const WithPreventDeselection = Template.bind({}); +WithPreventDeselection.args = { + color: 'black', + preventDeselection: true, + options: commonOptions, +}; + export const Searchable = Template.bind({}); Searchable.args = { isSearchable: true, diff --git a/src/components/Select/Select.test.tsx b/src/components/Select/Select.test.tsx index f294cb5..bc8810f 100644 --- a/src/components/Select/Select.test.tsx +++ b/src/components/Select/Select.test.tsx @@ -167,4 +167,117 @@ describe('The Select component', () => { // Check if onClose was called expect(onCloseMock).toHaveBeenCalledTimes(1); }); + + it('allows deselection when preventDeselection is false', () => { + const handleChange = jest.fn(); + + render( + + ); + + fireEvent.click(screen.getByRole('combobox')); + fireEvent.click(screen.getByText('Option 1')); + + expect(handleChange).toHaveBeenCalledWith(['1']); + + fireEvent.click(screen.getByRole('combobox')); + fireEvent.click(screen.getByRole('menuitemradio', { name: 'Option 1' })); // try to deselect + + // check that the text in the combobox doesn't change + expect(screen.getByRole('combobox')).toHaveTextContent('Option 1'); + }); + + it('allows switching between options when preventDeselection is true', () => { + const handleChange = jest.fn(); + + render( + + ); + + const toggleButton = screen.getByRole('combobox'); + fireEvent.click(toggleButton); + + const disabledOption = screen.getByText('Option 1'); + const enabledOption = screen.getByText('Option 2'); + + // try to select disabled option + fireEvent.click(disabledOption); + expect(handleChange).not.toHaveBeenCalled(); + + // Select enabled option + fireEvent.click(enabledOption); + expect(handleChange).toHaveBeenCalledWith(['2']); + }); }); diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 06c60f3..9a59e39 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -40,6 +40,7 @@ export interface BoemlySelectProps extends Omit = ({ isFullWidth = true, isSearchable = false, isMultiple = false, + preventDeselection = false, selectAllText = 'Select All', size = 'md', variant = 'outline', @@ -110,8 +112,22 @@ export const BoemlySelect: React.FC = ({ ? prevSelectedOptions.filter((val) => val !== optionValue) : [...prevSelectedOptions, optionValue]; } else { - const isOptionSelected = prevSelectedOptions.includes(optionValue); - newSelectedOptions = isOptionSelected ? [] : [optionValue]; // Deselect if selected, otherwise select + // If preventDeselection is true, don't allow deselection of the selected option + if (preventDeselection) { + if (prevSelectedOptions.length === 0) { + // If no option is selected, select the clicked option + newSelectedOptions = [optionValue]; + } else if (prevSelectedOptions[0] === optionValue) { + // If the clicked option is the same as the current selection, keep the selection and don't deselect + newSelectedOptions = prevSelectedOptions; + } else { + // If a different option is clicked, select the new option + newSelectedOptions = [optionValue]; + } + } else { + const isOptionSelected = prevSelectedOptions.includes(optionValue); + newSelectedOptions = isOptionSelected ? [] : [optionValue]; // Deselect if selected, otherwise select + } setIsOpen(false); // Close dropdown for single select } @@ -121,7 +137,7 @@ export const BoemlySelect: React.FC = ({ setSearchTerm(''); // Clear search term after selection }, - [isMultiple, onChange] + [isMultiple, onChange, preventDeselection] ); const onSelectAll = useCallback(() => {