Skip to content

Commit

Permalink
feat: up and down arrow navigation for filter items (#5673)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Dec 18, 2023
1 parent 75bdd73 commit e380d28
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 6 deletions.
30 changes: 29 additions & 1 deletion frontend/src/component/filter/FilterItem/FilterItem.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { screen } from '@testing-library/react';
import { screen, fireEvent } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { FilterItem, FilterItemParams, IFilterItemProps } from './FilterItem';

Expand Down Expand Up @@ -163,4 +163,32 @@ describe('FilterItem Component', () => {
},
]);
});

it('navigates between items with arrow keys', async () => {
setup(null);

const searchInput = await screen.findByPlaceholderText('Search');
fireEvent.keyDown(searchInput, { key: 'ArrowDown' });

const firstOption = screen.getByText('Option 1').closest('li')!;
expect(document.activeElement).toBe(firstOption);

fireEvent.keyDown(firstOption, { key: 'ArrowUp' });
expect(document.activeElement).toBe(searchInput);
});

it('selects an item with the Enter key', async () => {
const recordedChanges = setup(null);

const searchInput = await screen.findByPlaceholderText('Search');
fireEvent.keyDown(searchInput, { key: 'ArrowDown' });

const firstOption = screen.getByText('Option 1').closest('li')!;
fireEvent.keyDown(firstOption, { key: 'Enter' });

expect(recordedChanges).toContainEqual({
operator: 'IS',
values: ['1'],
});
});
});
52 changes: 47 additions & 5 deletions frontend/src/component/filter/FilterItem/FilterItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
StyledTextField,
} from './FilterItem.styles';
import { FilterItemChip } from './FilterItemChip/FilterItemChip';
import { onEnter } from '../../common/Search/SearchSuggestions/onEnter';

export interface IFilterItemProps {
name: string;
Expand All @@ -27,6 +26,37 @@ export type FilterItemParams = {
values: string[];
};

interface UseSelectionManagementProps {
options: Array<{ label: string; value: string }>;
handleToggle: (value: string) => () => void;
}

const useSelectionManagement = ({
options,
handleToggle,
}: UseSelectionManagementProps) => {
const listRefs = useRef<Array<HTMLInputElement | HTMLLIElement | null>>([]);

const handleSelection = (event: React.KeyboardEvent, index: number) => {
// we have to be careful not to prevent other keys e.g tab
if (event.key === 'ArrowDown' && index < listRefs.current.length - 1) {
event.preventDefault();
listRefs.current[index + 1]?.focus();
} else if (event.key === 'ArrowUp' && index > 0) {
event.preventDefault();
listRefs.current[index - 1]?.focus();
} else if (event.key === 'Enter') {
event.preventDefault();
if (index > 0) {
const listItemIndex = index - 1;
handleToggle(options[listItemIndex].value)();
}
}
};

return { listRefs, handleSelection };
};

export const FilterItem: FC<IFilterItemProps> = ({
name,
label,
Expand Down Expand Up @@ -92,6 +122,11 @@ export const FilterItem: FC<IFilterItemProps> = ({
}
};

const { listRefs, handleSelection } = useSelectionManagement({
options,
handleToggle,
});

useEffect(() => {
if (state && !currentOperators.includes(state.operator)) {
onChange({
Expand Down Expand Up @@ -144,6 +179,10 @@ export const FilterItem: FC<IFilterItemProps> = ({
</InputAdornment>
),
}}
inputRef={(el) => {
listRefs.current[0] = el;
}}
onKeyDown={(event) => handleSelection(event, 0)}
/>
<List sx={{ overflowY: 'auto' }} disablePadding>
{options
Expand All @@ -152,7 +191,7 @@ export const FilterItem: FC<IFilterItemProps> = ({
.toLowerCase()
.includes(searchText.toLowerCase()),
)
.map((option) => {
.map((option, index) => {
const labelId = `checkbox-list-label-${option.value}`;

return (
Expand All @@ -161,10 +200,13 @@ export const FilterItem: FC<IFilterItemProps> = ({
dense
disablePadding
tabIndex={0}
onKeyDown={onEnter(
handleToggle(option.value),
)}
onClick={handleToggle(option.value)}
ref={(el) => {
listRefs.current[index + 1] = el;
}}
onKeyDown={(event) =>
handleSelection(event, index + 1)
}
>
<StyledCheckbox
edge='start'
Expand Down

0 comments on commit e380d28

Please sign in to comment.