mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: up and down arrow navigation for filter items (#5673)
This commit is contained in:
parent
75bdd73c15
commit
e380d28924
@ -1,4 +1,4 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen, fireEvent } from '@testing-library/react';
|
||||||
import { render } from 'utils/testRenderer';
|
import { render } from 'utils/testRenderer';
|
||||||
import { FilterItem, FilterItemParams, IFilterItemProps } from './FilterItem';
|
import { FilterItem, FilterItemParams, IFilterItemProps } from './FilterItem';
|
||||||
|
|
||||||
@ -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'],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
StyledTextField,
|
StyledTextField,
|
||||||
} from './FilterItem.styles';
|
} from './FilterItem.styles';
|
||||||
import { FilterItemChip } from './FilterItemChip/FilterItemChip';
|
import { FilterItemChip } from './FilterItemChip/FilterItemChip';
|
||||||
import { onEnter } from '../../common/Search/SearchSuggestions/onEnter';
|
|
||||||
|
|
||||||
export interface IFilterItemProps {
|
export interface IFilterItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -27,6 +26,37 @@ export type FilterItemParams = {
|
|||||||
values: string[];
|
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> = ({
|
export const FilterItem: FC<IFilterItemProps> = ({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
@ -92,6 +122,11 @@ export const FilterItem: FC<IFilterItemProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { listRefs, handleSelection } = useSelectionManagement({
|
||||||
|
options,
|
||||||
|
handleToggle,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state && !currentOperators.includes(state.operator)) {
|
if (state && !currentOperators.includes(state.operator)) {
|
||||||
onChange({
|
onChange({
|
||||||
@ -144,6 +179,10 @@ export const FilterItem: FC<IFilterItemProps> = ({
|
|||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
inputRef={(el) => {
|
||||||
|
listRefs.current[0] = el;
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => handleSelection(event, 0)}
|
||||||
/>
|
/>
|
||||||
<List sx={{ overflowY: 'auto' }} disablePadding>
|
<List sx={{ overflowY: 'auto' }} disablePadding>
|
||||||
{options
|
{options
|
||||||
@ -152,7 +191,7 @@ export const FilterItem: FC<IFilterItemProps> = ({
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(searchText.toLowerCase()),
|
.includes(searchText.toLowerCase()),
|
||||||
)
|
)
|
||||||
.map((option) => {
|
.map((option, index) => {
|
||||||
const labelId = `checkbox-list-label-${option.value}`;
|
const labelId = `checkbox-list-label-${option.value}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -161,10 +200,13 @@ export const FilterItem: FC<IFilterItemProps> = ({
|
|||||||
dense
|
dense
|
||||||
disablePadding
|
disablePadding
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={onEnter(
|
|
||||||
handleToggle(option.value),
|
|
||||||
)}
|
|
||||||
onClick={handleToggle(option.value)}
|
onClick={handleToggle(option.value)}
|
||||||
|
ref={(el) => {
|
||||||
|
listRefs.current[index + 1] = el;
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) =>
|
||||||
|
handleSelection(event, index + 1)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
edge='start'
|
edge='start'
|
||||||
|
Loading…
Reference in New Issue
Block a user