From ee90a0bf22d4c2360c93fbc693cad8c32fc301c5 Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Tue, 11 Oct 2022 21:14:02 +0200 Subject: [PATCH 1/6] fix(theme-classic): Use native href for skip to content #6411. --- .../docusaurus-theme-classic/src/theme/Layout/index.tsx | 1 + .../src/theme/SkipToContent/index.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx index 26308f7eb26c..520805fb78dc 100644 --- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx @@ -42,6 +42,7 @@ export default function Layout(props: Props): JSX.Element {
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - + From fdf3bd8c372d04c0d68800d8cb3ce263157b482d Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Thu, 13 Oct 2022 17:54:00 +0200 Subject: [PATCH 2/6] chore: Address review comments. --- .../docusaurus-theme-classic/src/theme/Layout/index.tsx | 8 ++++++-- .../src/theme/SkipToContent/index.tsx | 3 ++- .../docusaurus-theme-common/src/hooks/useSkipToContent.ts | 4 ++-- packages/docusaurus-theme-common/src/index.ts | 2 ++ .../docusaurus-theme-common/src/utils/layoutUtils.tsx | 7 +++++++ 5 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 packages/docusaurus-theme-common/src/utils/layoutUtils.tsx diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx index 520805fb78dc..31480ddc5a43 100644 --- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx @@ -8,7 +8,11 @@ import React from 'react'; import clsx from 'clsx'; import ErrorBoundary from '@docusaurus/ErrorBoundary'; -import {PageMetadata, ThemeClassNames} from '@docusaurus/theme-common'; +import { + PageMetadata, + SkipToContentId, + ThemeClassNames, +} from '@docusaurus/theme-common'; import {useKeyboardNavigation} from '@docusaurus/theme-common/internal'; import SkipToContent from '@theme/SkipToContent'; import AnnouncementBar from '@theme/AnnouncementBar'; @@ -42,7 +46,7 @@ export default function Layout(props: Props): JSX.Element {
Date: Wed, 19 Oct 2022 17:05:53 +0200 Subject: [PATCH 3/6] more skipToContent refactor --- .../src/theme/Layout/index.tsx | 4 +- .../src/theme/SkipToContent/index.tsx | 23 +--- .../src/hooks/useSkipToContent.ts | 58 ---------- packages/docusaurus-theme-common/src/index.ts | 5 +- .../docusaurus-theme-common/src/internal.ts | 1 - .../src/utils/layoutUtils.tsx | 7 -- .../src/utils/skipToContentUtils.tsx | 109 ++++++++++++++++++ 7 files changed, 117 insertions(+), 90 deletions(-) delete mode 100644 packages/docusaurus-theme-common/src/hooks/useSkipToContent.ts delete mode 100644 packages/docusaurus-theme-common/src/utils/layoutUtils.tsx create mode 100644 packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx index 31480ddc5a43..cf460364766f 100644 --- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx @@ -10,7 +10,7 @@ import clsx from 'clsx'; import ErrorBoundary from '@docusaurus/ErrorBoundary'; import { PageMetadata, - SkipToContentId, + SkipToContentFallbackId, ThemeClassNames, } from '@docusaurus/theme-common'; import {useKeyboardNavigation} from '@docusaurus/theme-common/internal'; @@ -46,7 +46,7 @@ export default function Layout(props: Props): JSX.Element { - ); + return ; } diff --git a/packages/docusaurus-theme-common/src/hooks/useSkipToContent.ts b/packages/docusaurus-theme-common/src/hooks/useSkipToContent.ts deleted file mode 100644 index a2222253c72f..000000000000 --- a/packages/docusaurus-theme-common/src/hooks/useSkipToContent.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type React from 'react'; -import {useCallback, useRef} from 'react'; -import {useHistory} from '@docusaurus/router'; -import {useLocationChange} from '../utils/useLocationChange'; -import {SkipToContentId} from '../utils/layoutUtils'; - -function programmaticFocus(el: HTMLElement) { - el.setAttribute('tabindex', '-1'); - el.focus(); - el.removeAttribute('tabindex'); -} - -/** This hook wires the logic for a skip-to-content link. */ -export function useSkipToContent(): { - /** - * The ref to the container. On page transition, the container will be focused - * so that keyboard navigators can instantly interact with the link and jump - * to content. **Note:** the type is `RefObject` only because - * the typing for refs don't reflect that the `ref` prop is contravariant, so - * using `HTMLElement` causes type-checking to fail. You can plug the ref into - * any HTML element, as long as it can be focused. - */ - containerRef: React.RefObject; - /** - * Callback fired when the skip to content link has been interacted with. It - * will programmatically focus the main content. - */ - handleSkip: (e: React.MouseEvent) => void; -} { - const containerRef = useRef(null); - const {action} = useHistory(); - const handleSkip = useCallback((e: React.MouseEvent) => { - e.preventDefault(); - - const targetElement: HTMLElement | null = - document.querySelector('main:first-of-type') ?? - document.getElementById(SkipToContentId); - - if (targetElement) { - programmaticFocus(targetElement); - } - }, []); - - useLocationChange(({location}) => { - if (containerRef.current && !location.hash && action === 'PUSH') { - programmaticFocus(containerRef.current); - } - }); - - return {containerRef, handleSkip}; -} diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index 722ec17e581d..844fa25cfd0c 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -81,4 +81,7 @@ export {useDocsPreferredVersion} from './contexts/docsPreferredVersion'; export {processAdmonitionProps} from './utils/admonitionUtils'; -export {SkipToContentId} from './utils/layoutUtils'; +export { + SkipToContentFallbackId, + SkipToContentLink, +} from './utils/skipToContentUtils'; diff --git a/packages/docusaurus-theme-common/src/internal.ts b/packages/docusaurus-theme-common/src/internal.ts index 8415cc967bfe..aefaa2ddbabf 100644 --- a/packages/docusaurus-theme-common/src/internal.ts +++ b/packages/docusaurus-theme-common/src/internal.ts @@ -117,6 +117,5 @@ export { export {useLockBodyScroll} from './hooks/useLockBodyScroll'; export {useSearchPage} from './hooks/useSearchPage'; export {useCodeWordWrap} from './hooks/useCodeWordWrap'; -export {useSkipToContent} from './hooks/useSkipToContent'; export {getPrismCssVariables} from './utils/codeBlockUtils'; export {useBackToTopButton} from './hooks/useBackToTopButton'; diff --git a/packages/docusaurus-theme-common/src/utils/layoutUtils.tsx b/packages/docusaurus-theme-common/src/utils/layoutUtils.tsx deleted file mode 100644 index 97006599d3b4..000000000000 --- a/packages/docusaurus-theme-common/src/utils/layoutUtils.tsx +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -export const SkipToContentId = 'docusaurus_skipToContent'; diff --git a/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx b/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx new file mode 100644 index 000000000000..e55587f95e42 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx @@ -0,0 +1,109 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {useCallback, useRef, type ComponentProps} from 'react'; +import {useHistory} from '@docusaurus/router'; +import Translate, {translate} from '@docusaurus/Translate'; +import {useLocationChange} from './useLocationChange'; + +/** + * The id of the element that should become focused on a page + * that does not have a
html tag. + * Focusing the Docusaurus Layout children is a reasonable fallback. + */ +export const SkipToContentFallbackId = 'docusaurus_skipToContent_fallback'; + +/** + * Returns the skip to content element to focus when the link is clicked. + */ +function getSkipToContentTarget(): HTMLElement | null { + return ( + // Try to focus the
in priority + // Note: this will only work if JS is enabled + // See https://github.com/facebook/docusaurus/issues/6411#issuecomment-1284136069 + document.querySelector('main:first-of-type') ?? + // Then try to focus the fallback element (usually the Layout children) + document.getElementById(SkipToContentFallbackId) + ); +} + +function programmaticFocus(el: HTMLElement) { + el.setAttribute('tabindex', '-1'); + el.focus(); + el.removeAttribute('tabindex'); +} + +/** This hook wires the logic for a skip-to-content link. */ +function useSkipToContent(): { + /** + * The ref to the container. On page transition, the container will be focused + * so that keyboard navigators can instantly interact with the link and jump + * to content. + */ + containerRef: React.RefObject; + /** + * Callback fired when the skip to content link has been clicked. + * It will programmatically focus the main content. + */ + onClick: (e: React.MouseEvent) => void; +} { + const containerRef = useRef(null); + const {action} = useHistory(); + + const onSkipToContentClick = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + const targetElement = getSkipToContentTarget(); + if (targetElement) { + programmaticFocus(targetElement); + } + }, + [], + ); + + // "Reset" focus when navigating. + // See https://github.com/facebook/docusaurus/pull/8204#issuecomment-1276547558 + useLocationChange(({location}) => { + if (containerRef.current && !location.hash && action === 'PUSH') { + programmaticFocus(containerRef.current); + } + }); + + return {containerRef, onClick: onSkipToContentClick}; +} + +function SkipToContentLabel() { + return ( + + Skip to main content + + ); +} + +type SkipToContentLinkProps = Omit, 'href' | 'onClick'>; + +export function SkipToContentLink(props: SkipToContentLinkProps): JSX.Element { + const linkLabel = props.children ?? ; + const {containerRef, onClick} = useSkipToContent(); + return ( + + ); +} From d135d8a85e8d1d4ba59b390cce446d5c4bad3257 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Wed, 19 Oct 2022 17:13:48 +0200 Subject: [PATCH 4/6] mark SkipToContent as safe to swizzle --- packages/docusaurus-theme-classic/src/getSwizzleConfig.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts index c0378ab47e42..c5f83bb3c114 100644 --- a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts +++ b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts @@ -377,6 +377,14 @@ export default function getSwizzleConfig(): SwizzleConfig { description: 'The search bar component of your site, appearing in the navbar.', }, + SkipToContent: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component responsible for implementing the accessibility "skip to content" link (https://www.w3.org/TR/WCAG20-TECHS/G1.html)', + }, 'prism-include-languages': { actions: { eject: 'safe', From 95abdbed305c9e4d5250816b99b2c4d498071ad9 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Wed, 19 Oct 2022 17:19:47 +0200 Subject: [PATCH 5/6] fix duplicate translationId in theme-common --- .../src/utils/skipToContentUtils.tsx | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx b/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx index e55587f95e42..bfca4dc800a1 100644 --- a/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx @@ -7,7 +7,7 @@ import React, {useCallback, useRef, type ComponentProps} from 'react'; import {useHistory} from '@docusaurus/router'; -import Translate, {translate} from '@docusaurus/Translate'; +import {translate} from '@docusaurus/Translate'; import {useLocationChange} from './useLocationChange'; /** @@ -76,26 +76,23 @@ function useSkipToContent(): { return {containerRef, onClick: onSkipToContentClick}; } -function SkipToContentLabel() { - return ( - - Skip to main content - - ); -} +const DefaultSkipToContentLabel = translate({ + id: 'theme.common.skipToMainContent', + description: + 'The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation', + message: 'Skip to main content', +}); type SkipToContentLinkProps = Omit, 'href' | 'onClick'>; export function SkipToContentLink(props: SkipToContentLinkProps): JSX.Element { - const linkLabel = props.children ?? ; + const linkLabel = props.children ?? DefaultSkipToContentLabel; const {containerRef, onClick} = useSkipToContent(); return (
+ aria-label={DefaultSkipToContentLabel}> Date: Wed, 19 Oct 2022 17:56:56 +0200 Subject: [PATCH 6/6] typo --- .../src/utils/skipToContentUtils.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx b/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx index bfca4dc800a1..fbdf862a8c89 100644 --- a/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx @@ -54,16 +54,13 @@ function useSkipToContent(): { const containerRef = useRef(null); const {action} = useHistory(); - const onSkipToContentClick = useCallback( - (e: React.MouseEvent) => { - e.preventDefault(); - const targetElement = getSkipToContentTarget(); - if (targetElement) { - programmaticFocus(targetElement); - } - }, - [], - ); + const onClick = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + const targetElement = getSkipToContentTarget(); + if (targetElement) { + programmaticFocus(targetElement); + } + }, []); // "Reset" focus when navigating. // See https://github.com/facebook/docusaurus/pull/8204#issuecomment-1276547558 @@ -73,7 +70,7 @@ function useSkipToContent(): { } }); - return {containerRef, onClick: onSkipToContentClick}; + return {containerRef, onClick}; } const DefaultSkipToContentLabel = translate({