From ad673e8f489bb0f2a50de474d1cbad2c3f3e9cbd 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 | 119 ++++++++++++++++++ src/components/Select/Select.tsx | 23 +++- 4 files changed, 157 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..a204385 100644 --- a/src/components/Select/Select.test.tsx +++ b/src/components/Select/Select.test.tsx @@ -167,4 +167,123 @@ 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( + + ); + + const toggleButton = screen.getByRole('combobox'); + fireEvent.click(toggleButton); + + const option1 = screen.getByText('Option 1'); + fireEvent.click(option1); + + expect(handleChange).toHaveBeenCalledWith(['1']); + + // Click the same option again - should not deselect + fireEvent.click(option1); + + // Verify that the change handler was not called again + // and the selection remains the same + expect(handleChange).toHaveBeenCalledTimes(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..dca8704 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,23 @@ 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) { + // If preventDeselection is true, don't allow deselection of the selected option + 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 + newSelectedOptions = prevSelectedOptions; + } else { + // If a different option is clicked, select the new option + newSelectedOptions = [optionValue]; + } + } else { + // Existing deselection logic + const isOptionSelected = prevSelectedOptions.includes(optionValue); + newSelectedOptions = isOptionSelected ? [] : [optionValue]; // Deselect if selected, otherwise select + } setIsOpen(false); // Close dropdown for single select } @@ -121,7 +138,7 @@ export const BoemlySelect: React.FC = ({ setSearchTerm(''); // Clear search term after selection }, - [isMultiple, onChange] + [isMultiple, onChange, preventDeselection] ); const onSelectAll = useCallback(() => {