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', + })} + + ); +};