Skip to content

Commit

Permalink
feat(select): add an option to not allow deselecting a selected option (
Browse files Browse the repository at this point in the history
  • Loading branch information
HabRonan authored Dec 9, 2024
1 parent 46187a3 commit ee6f383
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 3 deletions.
11 changes: 11 additions & 0 deletions src/components/BoemlyFormControl/BoemlyFormControl.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
7 changes: 7 additions & 0 deletions src/components/Select/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
113 changes: 113 additions & 0 deletions src/components/Select/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<Select color="black" options={mockOptions} isMultiple={false} onChange={handleChange} />
);

const toggleButton = screen.getByRole('combobox');
fireEvent.click(toggleButton);

const option1 = screen.getByText('Option 1');
fireEvent.click(option1);

expect(handleChange).toHaveBeenCalledWith(['1']);
fireEvent.click(toggleButton);
// Click the same option again to deselect
fireEvent.click(option1);

expect(handleChange).toHaveBeenCalledWith(['1']);
});

it('prevents deselection when preventDeselection is true', () => {
const handleChange = jest.fn();

render(
<Select
color="black"
options={mockOptions}
isMultiple={false}
preventDeselection={true}
onChange={handleChange}
/>
);

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(
<Select
color="black"
options={mockOptions}
isMultiple={false}
preventDeselection={true}
onChange={handleChange}
/>
);

const toggleButton = screen.getByRole('combobox');
fireEvent.click(toggleButton);

const option1 = screen.getByText('Option 1');

fireEvent.click(option1);
expect(handleChange).toHaveBeenCalledWith(['1']);

expect(screen.getByRole('combobox')).toHaveTextContent('Option 1');

// re open dropdown and select another option
fireEvent.click(toggleButton);

const option2 = screen.getByText('Option 2');

fireEvent.click(option2);
expect(handleChange).toHaveBeenCalledWith(['2']);
// check that the text in the combobox did change
expect(screen.getByRole('combobox')).toHaveTextContent('Option 2');
});

it('handles disabled options correctly', () => {
const handleChange = jest.fn();

const optionsWithDisabled = [
{ label: 'Option 1', value: '1', disabled: true },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
];

render(
<Select
color="black"
options={optionsWithDisabled}
isMultiple={false}
onChange={handleChange}
/>
);

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']);
});
});
22 changes: 19 additions & 3 deletions src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface BoemlySelectProps extends Omit<SelectProps, 'onChange' | 'value
isFullWidth?: boolean;
isSearchable?: boolean;
isMultiple?: boolean;
preventDeselection?: boolean;
selectAllText?: string;
value?: string[];
size?: 'xs' | 'sm' | 'md' | 'lg';
Expand All @@ -60,6 +61,7 @@ export const BoemlySelect: React.FC<BoemlySelectProps> = ({
isFullWidth = true,
isSearchable = false,
isMultiple = false,
preventDeselection = false,
selectAllText = 'Select All',
size = 'md',
variant = 'outline',
Expand Down Expand Up @@ -110,8 +112,22 @@ export const BoemlySelect: React.FC<BoemlySelectProps> = ({
? 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
}

Expand All @@ -121,7 +137,7 @@ export const BoemlySelect: React.FC<BoemlySelectProps> = ({

setSearchTerm(''); // Clear search term after selection
},
[isMultiple, onChange]
[isMultiple, onChange, preventDeselection]
);

const onSelectAll = useCallback(() => {
Expand Down

0 comments on commit ee6f383

Please sign in to comment.