Skip to content

Commit

Permalink
Move SR focus region to top of page + add skip to content link
Browse files Browse the repository at this point in the history
- unfortunately, some skip link behavior isn't working as expected due to how Kibana dynamically rerender's `main`s - EUI may need to fix this in the future

- a few apps that aren't using EuiPageTemplate (Maps, Monitoring) or `main` tags will be missing this UX - they'll either need to update their layout, or a custom `skipLinkDestination` chrome will need to be added as a Kibana API
  • Loading branch information
cee-chen committed Feb 21, 2023
1 parent 0b39f1b commit 5267bc4
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import { HeaderActionMenu } from './header_action_menu';
import { HeaderExtension } from './header_extension';
import { HeaderTopBanner } from './header_top_banner';
import { HeaderMenuButton } from './header_menu_button';
import { ScreenReaderRouteAnnouncements } from './screen_reader_a11y';
import { ScreenReaderRouteAnnouncements, SkipToContent } from './screen_reader_a11y';

export interface HeaderProps {
kibanaVersion: string;
Expand Down Expand Up @@ -108,6 +108,9 @@ export function Header({

return (
<>
<ScreenReaderRouteAnnouncements breadcrumbs$={observables.breadcrumbs$} />
<SkipToContent />

<HeaderTopBanner headerBanner$={observables.headerBanner$} />
<header className={className} data-test-subj="headerGlobalNav">
<div id="globalHeaderBars" className="header__bars">
Expand Down Expand Up @@ -166,7 +169,6 @@ export function Header({
/>

<EuiHeader position="fixed" className="header__secondBar">
<ScreenReaderRouteAnnouncements breadcrumbs$={observables.breadcrumbs$} />
<EuiHeaderSection grow={false}>
<EuiHeaderSectionItem border="right" className="header__toggleNavButtonSection">
<CollapsibleNav
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
* Side Public License, v 1.
*/

import React, { FC, useState, useEffect } from 'react';
import React, { FC, useState, useEffect, useRef } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { EuiScreenReaderLive } from '@elastic/eui';
import { EuiScreenReaderLive, EuiSkipLink, useMutationObserver } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import type { HeaderProps } from './header';

Expand Down Expand Up @@ -40,3 +41,48 @@ export const ScreenReaderRouteAnnouncements: FC<{

return <EuiScreenReaderLive focusRegionOnTextChange>{routeTitle}</EuiScreenReaderLive>;
};

export const SkipToContent = () => {
const [applicationRef, setApplicationRef] = useState<HTMLElement | null>(null);
useEffect(() => {
setApplicationRef(document.querySelector<HTMLElement>('.kbnAppWrapper'));
}, []);

// Ensure pages are done loading and `main` is available
const [shouldRender, setShouldRender] = useState(false);
const [forceRerender, setForceRerender] = useState(1);
const prevMainId = useRef<string | undefined>();
useMutationObserver(
applicationRef,
() => {
const main = document.querySelector('main'); // TODO: This should use `destinationId` once configurable
if (main) {
setShouldRender(true);
// Ensure that rerendered `main`s are targetable between in-app navigation
if (main.id !== prevMainId.current) {
setForceRerender(forceRerender + 1);
prevMainId.current = main.id;
}
} else {
// Don't display a skip link if there's no content found
setShouldRender(false);
prevMainId.current = undefined;
}
},
{ subtree: true, childList: true }
);

return shouldRender ? (
<EuiSkipLink
position="fixed"
overrideLinkBehavior
destinationId="" // TODO: Allow plugins to configure this - Maps will likely need this
href={undefined}
key={forceRerender}
>
{i18n.translate('core.ui.primaryNav.skipToContent', {
defaultMessage: 'Skip to content',
})}
</EuiSkipLink>
) : null;
};

0 comments on commit 5267bc4

Please sign in to comment.