DOP-3611: reverse options (#37)

This commit is contained in:
mmeigs 2023-04-10 09:42:32 -04:00 committed by GitHub
parent a15a16cc00
commit 895fa179ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 119 additions and 97 deletions

View File

@ -5,7 +5,20 @@
"resourceVersion": "2023-02-14" "resourceVersion": "2023-02-14"
}, },
"rootUrl": "https://mongodb.com/docs/atlas/reference/api-resources-spec/v2", "rootUrl": "https://mongodb.com/docs/atlas/reference/api-resources-spec/v2",
"resourceVersions": ["2022-09-09", "2022-10-18", "2023-02-14"] "resourceVersions": [
"2022-01-01",
"2022-01-18",
"2022-02-01",
"2022-03-09",
"2022-04-18",
"2022-05-14",
"2022-06-09",
"2022-07-18",
"2022-08-14",
"2022-09-09",
"2022-10-18",
"2023-02-14"
]
}, },
"backNavigationPath": "https://www.mongodb.com/docs/atlas/", "backNavigationPath": "https://www.mongodb.com/docs/atlas/",
"siteTitle": "MongoDB Atlas", "siteTitle": "MongoDB Atlas",

View File

@ -4,22 +4,8 @@ import Checkmark from './CheckmarkSvg';
import { OptionProps } from './types'; import { OptionProps } from './types';
export const Option = ({ option, value, selected, onClick, focused }: OptionProps) => { 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) {
onClick(value);
}
};
return ( return (
<StyledLi <StyledLi onClick={() => onClick(value)} selected={selected} focused={focused}>
onClick={() => onClick(value)}
onKeyPress={handleKeyPress}
selected={selected}
focused={focused}
>
{selected ? <Checkmark /> : <StyledPlaceholder />} {selected ? <Checkmark /> : <StyledPlaceholder />}
<StyledOptionText>{option}</StyledOptionText> <StyledOptionText>{option}</StyledOptionText>
</StyledLi> </StyledLi>

View File

@ -24,33 +24,37 @@ const VersionSelectorComponent = ({
description, description,
rootUrl, rootUrl,
}: VersionSelectorProps): JSX.Element => { }: VersionSelectorProps): JSX.Element => {
const initialSelectedIdx = resourceVersions.indexOf(active.resourceVersion); const descendingResourceVersions = resourceVersions.slice().reverse();
const initialSelectedIdx = descendingResourceVersions.indexOf(active.resourceVersion);
const [open, setOpen] = React.useState<boolean>(false); const [open, setOpen] = React.useState<boolean>(false);
const [focusedIdx, setFocusedIdx] = React.useState<number>(-1); const [focusedIdx, setFocusedIdx] = React.useState<number>(-1);
const [selectedIdx, setSelectedIdx] = React.useState<number>(initialSelectedIdx); const [selectedIdx, setSelectedIdx] = React.useState<number>(initialSelectedIdx);
const menuListRef = React.useRef(null); 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, () => { useOutsideClick(menuListRef, () => {
if (open) setOpen(false); if (open) setOpen(false);
setFocusedIdx(0); setFocusedIdx(0);
}); });
const handleKeyDownSelect = React.useCallback(() => {
if (focusedIdx < 0) return;
if (focusedIdx === selectedIdx) return setOpen(false);
const resourceVersionReal = descendingResourceVersions[focusedIdx];
// navigate to resource version spec
let selectedResourceVersionUrl = `${rootUrl}/${resourceVersionReal}`;
const anchorTagIdx = window.location.href.indexOf('#tag');
if (anchorTagIdx > -1) {
selectedResourceVersionUrl += window.location.href.slice(anchorTagIdx);
}
window.location.href = selectedResourceVersionUrl;
setSelectedIdx(focusedIdx);
return setOpen(false);
}, [selectedIdx, rootUrl, focusedIdx, descendingResourceVersions]);
const handleClick = (idx: number, resourceVersion: string) => { const handleClick = (idx: number, resourceVersion: string) => {
if (idx === selectedIdx) return setOpen(false); if (idx === selectedIdx) return setOpen(false);
if (resourceVersion === undefined) return setOpen(false);
// navigate to resource version spec // navigate to resource version spec
let selectedResourceVersionUrl = `${rootUrl}/${resourceVersion}`; let selectedResourceVersionUrl = `${rootUrl}/${resourceVersion}`;
@ -59,44 +63,63 @@ const VersionSelectorComponent = ({
selectedResourceVersionUrl += window.location.href.slice(anchorTagIdx); selectedResourceVersionUrl += window.location.href.slice(anchorTagIdx);
} }
window.location.href = selectedResourceVersionUrl; window.location.href = selectedResourceVersionUrl;
setFocusedIdx(idx);
setSelectedIdx(idx); setSelectedIdx(idx);
return setOpen(false); return setOpen(false);
}; };
const handleFocusChange = (event: React.KeyboardEvent<HTMLDivElement>) => { const handleFocusChange = React.useCallback(
const { key, shiftKey } = event; (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 === descendingResourceVersions.length) return;
if (key === 'ArrowDown' || (key === 'Tab' && !shiftKey)) { if (focusedIdx === -1) {
// if we go down when we are already past the end, don't do anything // when first entering the dropdown via the down arrow key or tab,
if (focusedIdx === resourceVersions.length) return; // we want to open the modal
setOpen(true);
} else if (focusedIdx === descendingResourceVersions.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);
}
if (focusedIdx === -1) { setFocusedIdx(focusedIdx + 1);
// when first entering the dropdown via the down arrow key or tab, } else if (key === 'ArrowUp' || (key === 'Tab' && shiftKey)) {
// we want to open the modal // if we go down when we are already past the end, don't do anything
setOpen(true); if (focusedIdx === -1) return;
} else if (focusedIdx === resourceVersions.length - 1) {
// if we are at the last element of the dropdown, and attempt to go if (focusedIdx === descendingResourceVersions.length) {
// down again, we want to close the dropdown // in this scenario, we are entering the dropdown from below
setOpen(false); // 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);
} else if (key === 'Enter' || key === ' ') {
handleKeyDownSelect();
} }
setFocusedIdx(focusedIdx + 1); },
} else if (key === 'ArrowUp' || (key === 'Tab' && shiftKey)) { [focusedIdx, descendingResourceVersions, handleKeyDownSelect],
// if we go down when we are already past the end, don't do anything );
if (focusedIdx === -1) return;
if (focusedIdx === resourceVersions.length) { const options = descendingResourceVersions.map((option, i) => {
// in this scenario, we are entering the dropdown from below return (
// we open the dropdown and start from the bottom <Option
setOpen(true); key={`option-${i}`}
} else if (focusedIdx === 0) { selected={i === selectedIdx}
// if we reach the first element in the drop down, and we attempt to go up again, value={option}
// we want to close the dropdown option={`${option}${i === 0 ? ' (latest)' : ''}`}
setOpen(false); onClick={() => handleClick(i, option)}
} focused={i === focusedIdx}
/>
setFocusedIdx(focusedIdx - 1); );
} });
};
return ( return (
<StyledWrapper onKeyDown={handleFocusChange} ref={menuListRef}> <StyledWrapper onKeyDown={handleFocusChange} ref={menuListRef}>
@ -105,7 +128,7 @@ const VersionSelectorComponent = ({
{description && <StyledDescription>{description}</StyledDescription>} {description && <StyledDescription>{description}</StyledDescription>}
<StyledButton onClick={() => setOpen(!open)}> <StyledButton onClick={() => setOpen(!open)}>
<StyledDisplay> <StyledDisplay>
<StyledSelected>{resourceVersions[selectedIdx]}</StyledSelected> <StyledSelected>{descendingResourceVersions[selectedIdx]}</StyledSelected>
<ArrowIcon open={open} /> <ArrowIcon open={open} />
</StyledDisplay> </StyledDisplay>
</StyledButton> </StyledButton>

View File

@ -188,7 +188,7 @@ export const openDropdownStyle = css`
export const StyledDropdown = styled.div.attrs<{ open: boolean }>({ export const StyledDropdown = styled.div.attrs<{ open: boolean }>({
role: 'listbox', role: 'listbox',
'aria-labelledby': 'View a different version of documentation.', 'aria-labelledby': 'View a different version of documentation.',
tabIndex: '-1', tabIndex: '0',
})<{ open: boolean }>` })<{ open: boolean }>`
${props => (props.open ? openDropdownStyle : `display: none;`)} ${props => (props.open ? openDropdownStyle : `display: none;`)}
`; `;

View File

@ -31,9 +31,9 @@ describe('VersionSelector', () => {
wrapper.find('button').simulate('click'); wrapper.find('button').simulate('click');
expect(wrapper.find('li')).toHaveLength(3); expect(wrapper.find('li')).toHaveLength(3);
wrapper.find('li').at(0).simulate('click'); wrapper.find('li').at(1).simulate('click');
expect(JSON.stringify(window.location)).toBe( expect(JSON.stringify(window.location)).toBe(
JSON.stringify(`${versionData.rootUrl}/${versionData.resourceVersions[0]}`), JSON.stringify(`${versionData.rootUrl}/${versionData.resourceVersions[1]}`),
); );
}); });
@ -42,7 +42,7 @@ describe('VersionSelector', () => {
wrapper.find('button').simulate('click'); wrapper.find('button').simulate('click');
expect(wrapper.find('li')).toHaveLength(3); expect(wrapper.find('li')).toHaveLength(3);
wrapper.find('li').at(2).simulate('click'); wrapper.find('li').at(0).simulate('click');
expect(JSON.stringify(window.location)).not.toBe( expect(JSON.stringify(window.location)).not.toBe(
JSON.stringify(`${versionData.rootUrl}/${versionData.resourceVersions[2]}`), JSON.stringify(`${versionData.rootUrl}/${versionData.resourceVersions[2]}`),
); );

View File

@ -46,42 +46,12 @@ exports[`VersionSelector should correctly render VersionSelector 1`] = `
aria-labelledby="View a different version of documentation." aria-labelledby="View a different version of documentation."
class="sc-kEqXSa litKaz" class="sc-kEqXSa litKaz"
role="listbox" role="listbox"
tabindex="-1" tabindex="0"
> >
<div> <div>
<ul <ul
class="sc-gKAaRy iQghMf" class="sc-gKAaRy iQghMf"
> >
<li
aria-selected="false"
class="sc-pNWdM kSTcTW"
role="option"
tabindex="0"
>
<span
class="sc-iqAclL dXUzZL"
/>
<span
class="sc-jrsJWt hDLwjI"
>
2021-09-09
</span>
</li>
<li
aria-selected="false"
class="sc-pNWdM kSTcTW"
role="option"
tabindex="0"
>
<span
class="sc-iqAclL dXUzZL"
/>
<span
class="sc-jrsJWt hDLwjI"
>
2022-10-18
</span>
</li>
<li <li
aria-selected="true" aria-selected="true"
class="sc-pNWdM derVNM" class="sc-pNWdM derVNM"
@ -110,6 +80,36 @@ exports[`VersionSelector should correctly render VersionSelector 1`] = `
2023-01-01 (latest) 2023-01-01 (latest)
</span> </span>
</li> </li>
<li
aria-selected="false"
class="sc-pNWdM kSTcTW"
role="option"
tabindex="0"
>
<span
class="sc-iqAclL dXUzZL"
/>
<span
class="sc-jrsJWt hDLwjI"
>
2022-10-18
</span>
</li>
<li
aria-selected="false"
class="sc-pNWdM kSTcTW"
role="option"
tabindex="0"
>
<span
class="sc-iqAclL dXUzZL"
/>
<span
class="sc-jrsJWt hDLwjI"
>
2021-09-09
</span>
</li>
</ul> </ul>
</div> </div>
</div> </div>