mirror of
https://github.com/Redocly/redoc.git
synced 2025-07-31 02:19:47 +03:00
DOP-3611: reverse options (#37)
This commit is contained in:
parent
a15a16cc00
commit
895fa179ee
15
options.json
15
options.json
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;`)}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -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]}`),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user