[DOP-3497]: Add arrow key functionality (#32)

This commit is contained in:
Brandon Ly 2023-04-05 09:03:26 -05:00 committed by GitHub
parent 1612c0ca89
commit 34a854b765
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 44 deletions

View File

@ -3,9 +3,9 @@ import { StyledLi, StyledOptionText, StyledPlaceholder } from './styled.elements
import Checkmark from './CheckmarkSvg';
import { OptionProps } from './types';
export const Option = ({ option, value, selected, onClick }: OptionProps) => {
const KEY_ENTER = 'ENTER';
const KEY_SPACE = 'SPACE';
export const Option = ({ option, value, selected, onClick, focused }: OptionProps) => {
const KEY_ENTER = 'Enter';
const KEY_SPACE = ' ';
const handleKeyPress = (event: React.KeyboardEvent) => {
if (event.key === KEY_ENTER || event.key === KEY_SPACE) {
@ -14,7 +14,12 @@ export const Option = ({ option, value, selected, onClick }: OptionProps) => {
};
return (
<StyledLi onClick={() => onClick(value)} onKeyPress={handleKeyPress} selected={selected}>
<StyledLi
onClick={() => onClick(value)}
onKeyPress={handleKeyPress}
selected={selected}
focused={focused}
>
{selected ? <Checkmark /> : <StyledPlaceholder />}
<StyledOptionText>{option}</StyledOptionText>
</StyledLi>

View File

@ -21,16 +21,32 @@ import { useOutsideClick } from './use-outside-click';
const VersionSelectorComponent = ({
resourceVersions,
active,
rootUrl,
description,
rootUrl,
}: VersionSelectorProps): JSX.Element => {
const [selectedIdx, setSelectedIdx] = React.useState(
resourceVersions.indexOf(active.resourceVersion),
);
const initialSelectedIdx = resourceVersions.indexOf(active.resourceVersion);
const [open, setOpen] = React.useState<boolean>(false);
const [focusedIdx, setFocusedIdx] = React.useState<number>(-1);
const [selectedIdx, setSelectedIdx] = React.useState<number>(initialSelectedIdx);
const menuListRef = React.useRef(null);
const options = resourceVersions.map((option, i) => {
return (
<Option
key={`option-${i}`}
selected={i === selectedIdx}
value={option}
option={`${option}${i === resourceVersions.length - 1 ? ' (latest)' : ''}`}
onClick={option => handleClick(i, option)}
focused={i === focusedIdx}
/>
);
});
useOutsideClick(menuListRef, () => {
if (open) setOpen(false);
setFocusedIdx(0);
});
const handleClick = (idx: number, resourceVersion: string) => {
@ -47,8 +63,43 @@ const VersionSelectorComponent = ({
return setOpen(false);
};
const handleFocusChange = (event: React.KeyboardEvent<HTMLDivElement>) => {
const { key, shiftKey } = event;
if (key === 'ArrowDown' || (key === 'Tab' && !shiftKey)) {
// if we go down when we are already past the end, don't do anything
if (focusedIdx === resourceVersions.length) return;
if (focusedIdx === -1) {
// when first entering the dropdown via the down arrow key or tab,
// we want to open the modal
setOpen(true);
} else if (focusedIdx === resourceVersions.length - 1) {
// if we are at the last element of the dropdown, and attempt to go
// down again, we want to close the dropdown
setOpen(false);
}
setFocusedIdx(focusedIdx + 1);
} else if (key === 'ArrowUp' || (key === 'Tab' && shiftKey)) {
// if we go down when we are already past the end, don't do anything
if (focusedIdx === -1) return;
if (focusedIdx === resourceVersions.length) {
// in this scenario, we are entering the dropdown from below
// we open the dropdown and start from the bottom
setOpen(true);
} else if (focusedIdx === 0) {
// if we reach the first element in the drop down, and we attempt to go up again,
// we want to close the dropdown
setOpen(false);
}
setFocusedIdx(focusedIdx - 1);
}
};
return (
<StyledWrapper ref={menuListRef}>
<StyledWrapper onKeyDown={handleFocusChange} ref={menuListRef}>
<StyledSelectWrapper>
<StyledLabel>Resource Version:</StyledLabel>
{description && <StyledDescription>{description}</StyledDescription>}
@ -62,17 +113,7 @@ const VersionSelectorComponent = ({
<StyledDropdown open={open}>
<div>
<StyledMenuList>
{resourceVersions.map((option, i) => (
<Option
key={`option-${i}`}
selected={i === selectedIdx}
value={option}
option={`${option}${i === resourceVersions.length - 1 ? ' (latest)' : ''}`}
onClick={option => handleClick(i, option)}
/>
))}
</StyledMenuList>
<StyledMenuList>{options}</StyledMenuList>
</div>
</StyledDropdown>
</StyledWrapper>

View File

@ -121,26 +121,17 @@ export const enabledOptionStyle = css`
&:hover {
background-color: ${palette.gray.light2};
}
&:focus-visible {
color: ${palette.blue.dark2};
background-color: ${palette.blue.light3};
&:before {
opacity: 1;
transform: scaleY(1);
background-color: ${palette.blue.base};
}
}
`;
export const StyledLi = styled.li.attrs<{ selected: boolean; disabled?: boolean }>(
({ selected }) => ({
role: 'option',
'aria-selected': selected,
tabIndex: '0',
}),
)<{ selected: boolean; disabled?: boolean }>`
export const StyledLi = styled.li.attrs<{
selected: boolean;
disabled?: boolean;
focused?: boolean;
}>(({ selected }) => ({
role: 'option',
'aria-selected': selected,
tabIndex: '0',
}))<{ selected: boolean; disabled?: boolean; focused?: boolean }>`
display: flex;
width: 100%;
outline: none;
@ -150,9 +141,11 @@ export const StyledLi = styled.li.attrs<{ selected: boolean; disabled?: boolean
padding: 8px 12px;
cursor: pointer;
color: ${palette.gray.dark3};
${props =>
props.focused &&
`color: ${palette.blue.dark2};
background-color: ${palette.blue.light3};`}
font-weight: ${props => (props.selected ? `bold` : `normal`)};
&:before {
content: '';
position: absolute;
@ -165,8 +158,14 @@ export const StyledLi = styled.li.attrs<{ selected: boolean; disabled?: boolean
border-radius: 0px 4px 4px 0px;
opacity: 0;
transition: all ${transitionDuration.default}ms ease-in-out;
${props =>
props.focused &&
`
opacity: 1;
transform: scaleY(1);
background-color: ${palette.blue.base};
`}
}
${props => (props.disabled ? disabledOptionStyle : enabledOptionStyle)}
`;

View File

@ -10,6 +10,7 @@ export interface VersionSelectorProps {
}
export interface OptionProps {
focused: boolean;
option: string;
value: string;
selected: boolean;

View File

@ -54,7 +54,7 @@ exports[`VersionSelector should correctly render VersionSelector 1`] = `
>
<li
aria-selected="false"
class="sc-pNWdM glxGCd"
class="sc-pNWdM kSTcTW"
role="option"
tabindex="0"
>
@ -69,7 +69,7 @@ exports[`VersionSelector should correctly render VersionSelector 1`] = `
</li>
<li
aria-selected="false"
class="sc-pNWdM glxGCd"
class="sc-pNWdM kSTcTW"
role="option"
tabindex="0"
>
@ -84,7 +84,7 @@ exports[`VersionSelector should correctly render VersionSelector 1`] = `
</li>
<li
aria-selected="true"
class="sc-pNWdM jlRKTH"
class="sc-pNWdM derVNM"
role="option"
selected=""
tabindex="0"