Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Screen reader is not announcing the index numbers for the items in V2 dropdown #23613

Merged
merged 9 commits into from
Jan 23, 2025
138 changes: 120 additions & 18 deletions docs/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,141 @@
* Licensed under the MIT License.
*/

import { useActiveDocContext } from "@docusaurus/plugin-content-docs/client";
import type { WrapperProps } from "@docusaurus/types";
import type DocsVersionDropdownNavbarItemType from "@theme/NavbarItem/DocsVersionDropdownNavbarItem";
import DocsVersionDropdownNavbarItem from "@theme-original/NavbarItem/DocsVersionDropdownNavbarItem";
import { translate } from "@docusaurus/Translate";
import {
useVersions,
useActiveDocContext,
useDocsVersionCandidates,
useDocsPreferredVersion,
} from "@docusaurus/plugin-content-docs/client";
import type {
GlobalVersion,
GlobalDoc,
ActiveDocContext,
} from "@docusaurus/plugin-content-docs/client";
import { useLocation } from "@docusaurus/router";
import type { LinkLikeNavbarItemProps } from "@theme/NavbarItem";
import DefaultNavbarItem from "@theme/NavbarItem/DefaultNavbarItem";
import type { Props } from "@theme/NavbarItem/DocsVersionDropdownNavbarItem";
import DropdownNavbarItem from "@theme/NavbarItem/DropdownNavbarItem";
import React from "react";

/**
* Note: this module was generated by `swizzling` the Docusaurus "classic" theme's Footer component.
* Gets the documentation page marked as the main/landing page for a version,
* identified by the mainDocId property.
*
* Its inner layout and contents were updated to better suite our needs, but the underlying styling and inner
* components remain unchanged.
*
* See {@link https://docusaurus.io/docs/swizzling/ | here} for more information on swizzling.
* @param version - The version object to get the main doc from
* @returns The main documentation page for this version
*/
function getVersionMainDoc(version: GlobalVersion): GlobalDoc {
RishhiB marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a non-null assertion on the next line. Is this needed?

return version.docs.find((doc) => doc.id === version.mainDocId);
}

/**
* {@link DocsVersionDropdownNavbarItemWrapper} component props.
* When navigating between versions, attempts to keep the user on the same page.
* If the current page doesn't exist in the target version, falls back to that version's main page.
*
* @param version - The version being navigated to
* @param activeDocContext - Information about the currently viewed documentation
* @returns Either the equivalent page in the target version, or that version's main page
*/
type Props = WrapperProps<typeof DocsVersionDropdownNavbarItemType>;
function getVersionTargetDoc(
RishhiB marked this conversation as resolved.
Show resolved Hide resolved
version: GlobalVersion,
activeDocContext: ActiveDocContext,
): GlobalDoc {
return activeDocContext.alternateDocVersions[version.name] ?? getVersionMainDoc(version);
}

type AccessibleLinkProps = LinkLikeNavbarItemProps & {
"aria-label"?: string;
"role"?: string;
"aria-setsize"?: number;
"aria-posinset"?: number;
"className"?: string;
};

/**
* Wraps the default DocsVersionDropdownNavbarItem to omit the drop-down on non-versioned pages.
* This module provides a custom implementation of the DropdownNavbarItem to enhance accessibility of the dropdown menu for navigation between doc versions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this isn't a module, it's a function.

*
* @remarks
* Suggested workaround for lack of version dropdown customization.
* See {@link https://github.com/facebook/docusaurus/issues/4389}.
* Context:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: since this isn't part of the semantic description of the function, it really belongs under the @remarks block.

* - The default Docusaurus component does not allow modification of version items for accessibility or customization.
* - This implementation uses internal hooks (e.g., `useVersions`, `useDocsPreferredVersion`) to overcome that limitation.
*
* @remarks this module was generated by `swizzling` the Docusaurus "classic" theme's Footer component.
* See {@link https://docusaurus.io/docs/swizzling/ | here} for more information on swizzling.
*/
export default function DocsVersionDropdownNavbarItemWrapper(props: Props): JSX.Element {
export default function DocsVersionDropdownNavbarItem({
mobile,
docsPluginId,
dropdownItemsBefore,
dropdownItemsAfter,
...props
}: Props): JSX.Element {
const { search, hash } = useLocation();
const activeDocContext = useActiveDocContext(docsPluginId);
const versions = useVersions(docsPluginId);
const { savePreferredVersionName } = useDocsPreferredVersion(docsPluginId);

function versionToAccessibleLink(version: GlobalVersion, index: number): AccessibleLinkProps {
const targetDoc = getVersionTargetDoc(version, activeDocContext);
return {
"label": version.label,
"to": `${targetDoc.path}${search}${hash}`,
"isActive": () => version === activeDocContext.activeVersion,
"onClick": () => savePreferredVersionName(version.name),
"aria-label": `Version ${version.label}, item ${index + 1} of ${versions.length}`,
"role": "menuitem",
"aria-setsize": versions.length,
"aria-posinset": index + 1,
"className": "version-dropdown__item",
};
}

const items: AccessibleLinkProps[] = [
...dropdownItemsBefore,
...versions.map((version, index) => versionToAccessibleLink(version, index)),
...dropdownItemsAfter,
];

const dropdownVersion = useDocsVersionCandidates(docsPluginId)?.[0];

const dropdownLabel =
mobile === true && items.length > 1
? translate({
id: "theme.navbar.mobileVersionsDropdown.label",
message: "Versions",
description: "The label for the navbar versions dropdown on mobile view",
})
: dropdownVersion.label;
const dropdownTo =
mobile === true && items.length > 1
? undefined
: getVersionTargetDoc(dropdownVersion, activeDocContext).path;

// Do not display this navbar item if current page is not a doc
const activeDocContext = useActiveDocContext(props.docsPluginId);
if (!activeDocContext.activeDoc) {
return <></>;
}

return <DocsVersionDropdownNavbarItem {...props} />;
if (items.length <= 1) {
return (
<DefaultNavbarItem {...props} mobile={mobile} label={dropdownLabel} to={dropdownTo} />
);
}

return (
<div className="version-dropdown-wrapper">
<DropdownNavbarItem
{...props}
mobile={mobile}
label={dropdownLabel}
to={dropdownTo}
items={items}
aria-label="Select documentation version"
role="combobox"
aria-haspopup="listbox"
/>
</div>
);
}
Loading