From c2910f9ac502ba1ea75c26c879ee39ad9b87f772 Mon Sep 17 00:00:00 2001
From: Cee Chen <549407+cee-chen@users.noreply.github.com>
Date: Tue, 28 Mar 2023 11:46:47 -0700
Subject: [PATCH] Add EuiSkipLink to Kibana header (#153810)
## Summary
This PR adds a "skip to main content" button, AKA a [skip
link](https://webaim.org/techniques/skipnav/) via
[EuiSkipLink](https://elastic.github.io/eui/#/utilities/accessibility#skip-link).
For initial behavior, this PR attempts to find a `main` tag (which will
succeed for all plugins using `KibanaPageTemplate`, followed by a
`.euiPageContent` className (which will succeed for plugins using the
old/deprecated `KibanaPageTemplate`), and as a worst case scenario for
plugins not using any EUI or Kibana page templates, targets the
`.kbnAppWrapper` app root which should be present on all pages.
At some point Kibana may want to consider adding a UI API for allowing
individual apps/plugins to set a custom `destinationId`.
## QA
- [x] Go to a plugin that uses the new KibanaPageTemplate, e.g.
Observability or Security, and confirm that the skip link correctly
targets the main content:
![1](https://user-images.githubusercontent.com/549407/228051255-0a94c95d-baf2-4ab4-8aef-e9b479de70ec.gif)
- [x] Go to a plugin that uses the old deprecated KibanaPageTemplate,
e.g. Canvas (http://localhost:5601/xyz/app/canvas), and confirm that the
skip link correctly targets the page content:
![2](https://user-images.githubusercontent.com/549407/228051148-6b0089e6-ba62-429f-90de-523f3e90e630.gif)
- [x] Go to a plugin that does not use any Kibana page template, e.g.
Maps (http://localhost:5601/xyz/app/maps) and confirm that the skip link
correctly targets the app wrapper on that plugin:
![3](https://user-images.githubusercontent.com/549407/228051161-bec401a3-e21a-41da-9911-dc95745b1d99.gif)
### Checklist
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---------
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../header/__snapshots__/header.test.tsx.snap | 15 +++++++++++
.../screen_reader_a11y.test.tsx.snap | 18 +++++++++++++
.../src/ui/header/header.tsx | 3 ++-
.../src/ui/header/screen_reader_a11y.test.tsx | 11 ++++++--
.../src/ui/header/screen_reader_a11y.tsx | 25 ++++++++++++++++++-
5 files changed, 68 insertions(+), 4 deletions(-)
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/header.test.tsx.snap b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/header.test.tsx.snap
index d86194ac7dbaf7..37aeb88feefa80 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/header.test.tsx.snap
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/header.test.tsx.snap
@@ -20,6 +20,21 @@ Array [
test - Elastic
,
+ ,
`;
+
+exports[`SkipToMainContent renders 1`] = `
+
+`;
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx
index 227a94a208ca34..5449ee34bb662b 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx
@@ -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, SkipToMainContent } from './screen_reader_a11y';
export interface HeaderProps {
kibanaVersion: string;
@@ -114,6 +114,7 @@ export function Header({
customBranding$={customBranding$}
appId$={application.currentAppId$}
/>
+
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.test.tsx
index 3dfc25c93c25ac..b468eefd51d795 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.test.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.test.tsx
@@ -9,8 +9,8 @@
import React from 'react';
import { BehaviorSubject } from 'rxjs';
import { mountWithIntl } from '@kbn/test-jest-helpers';
-import { ScreenReaderRouteAnnouncements } from './screen_reader_a11y';
-import { mount } from 'enzyme';
+import { ScreenReaderRouteAnnouncements, SkipToMainContent } from './screen_reader_a11y';
+import { mount, render } from 'enzyme';
describe('ScreenReaderRouteAnnouncements', () => {
it('renders', () => {
@@ -67,3 +67,10 @@ describe('ScreenReaderRouteAnnouncements', () => {
).toBeTruthy();
});
});
+
+describe('SkipToMainContent', () => {
+ it('renders', () => {
+ const component = render();
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.tsx
index 811da523414172..f879e896297dce 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.tsx
@@ -8,7 +8,8 @@
import React, { FC, useState, useEffect } from 'react';
import useObservable from 'react-use/lib/useObservable';
-import { EuiScreenReaderLive } from '@elastic/eui';
+import { EuiScreenReaderLive, EuiSkipLink } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import type { InternalApplicationStart } from '@kbn/core-application-browser-internal';
import type { HeaderProps } from './header';
@@ -56,3 +57,25 @@ export const ScreenReaderRouteAnnouncements: FC<{
);
};
+
+const fallbackContentQueries = [
+ 'main', // Ideal target for all plugins using KibanaPageTemplate
+ '[role="main"]', // Fallback for plugins using deprecated EuiPageContent
+ '.kbnAppWrapper', // Last-ditch fallback for all plugins regardless of page template
+];
+
+export const SkipToMainContent = () => {
+ return (
+
+ {i18n.translate('core.ui.skipToMainButton', {
+ defaultMessage: 'Skip to main content',
+ })}
+
+ );
+};