[Serverless] Support banner header (#160912)

## Summary

Closes https://github.com/elastic/kibana/issues/160052

This PR adds support for the header banner in serverless projects. 
<img width="1437" alt="image"
src="c950bbe7-46bb-4b82-b9ee-406aef7748dc">

**NOTE:** This PR adds support for the header banner by applying the
same techniques as in the "classic" layout: special class names of
`header__bar`/`header_firstBar`/`header__secondBar`. The effect is the
"header action menu" bar, which is an optional 2nd bar in the chrome
header, works better after this change when it is always visible. This
is a temporary issue that unblocks the header banner, and fixes other
overlapping issues with the current project layout stylings.

_As followup work_, the @elastic/appex-sharedux team will investigate
converting the Kibana page template to the new
[EuiPageTemplate](https://elastic.github.io/eui/#/templates/page-template/examples)
which supports horizontal sections of a page that have dynamic height --
we need this for the dynamic presence of the header banner, and the
intended dynamic presence of the header action menu.

### Checklist

Delete any items that are not applicable to this PR.

- [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)
This commit is contained in:
Tim Sullivan 2023-07-06 14:43:26 -07:00 committed by GitHub
parent 1d783110b3
commit f82588ba5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 106 additions and 100 deletions

View file

@ -310,6 +310,7 @@ export class ChromeService {
navControlsCenter$={navControls.getCenter$()}
navControlsRight$={navControls.getRight$()}
loadingCount$={http.getLoadingCount$()}
headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))}
homeHref$={projectNavigation.getProjectHome$()}
docLinks={docLinks}
kibanaVersion={injectedMetadata.getKibanaVersion()}

View file

@ -23,6 +23,7 @@ describe('Header', () => {
actionMenu$: Rx.of(undefined),
docLinks: docLinksServiceMock.createStartContract(),
globalHelpExtensionMenuLinks$: Rx.of([]),
headerBanner$: Rx.of(),
helpExtension$: Rx.of(undefined),
helpSupportUrl$: Rx.of('app/help'),
helpMenuLinks$: Rx.of([]),

View file

@ -19,28 +19,30 @@ import {
} from '@elastic/eui';
import { css } from '@emotion/react';
import type { InternalApplicationStart } from '@kbn/core-application-browser-internal';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import {
ChromeBreadcrumb,
ChromeGlobalHelpExtensionMenuLink,
ChromeHelpExtension,
ChromeHelpMenuLink,
ChromeNavControl,
ChromeUserBanner,
} from '@kbn/core-chrome-browser/src';
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
import type { HttpStart } from '@kbn/core-http-browser';
import { MountPoint } from '@kbn/core-mount-utils-browser';
import { i18n } from '@kbn/i18n';
import React, { createRef, useCallback, useState } from 'react';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { Router } from '@kbn/shared-ux-router';
import React, { createRef, useCallback, useState } from 'react';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import useObservable from 'react-use/lib/useObservable';
import { Observable, debounceTime } from 'rxjs';
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
import { debounceTime, Observable, of } from 'rxjs';
import { HeaderActionMenu, useHeaderActionMenuMounter } from '../header/header_action_menu';
import { HeaderBreadcrumbs } from '../header/header_breadcrumbs';
import { HeaderHelpMenu } from '../header/header_help_menu';
import { HeaderNavControls } from '../header/header_nav_controls';
import { HeaderTopBanner } from '../header/header_top_banner';
import { ScreenReaderRouteAnnouncements, SkipToMainContent } from '../header/screen_reader_a11y';
import { ProjectNavigation } from './navigation';
const headerCss = {
@ -88,6 +90,7 @@ const headerStrings = {
};
export interface Props {
headerBanner$: Observable<ChromeUserBanner | undefined>;
breadcrumbs$: Observable<ChromeBreadcrumb[]>;
actionMenu$: Observable<MountPoint | undefined>;
docLinks: DocLinksStart;
@ -175,94 +178,110 @@ export const ProjectHeader = ({
return (
<>
<EuiHeader position="fixed" data-test-subj="kibanaProjectHeader">
<EuiHeaderSection grow={false}>
<EuiHeaderSectionItem css={headerCss.nav.toggleNavButton}>
<Router history={application.history}>
<ProjectNavigation
isOpen={isOpen!}
closeNav={() => {
setIsOpen(false);
if (toggleCollapsibleNavRef.current) {
toggleCollapsibleNavRef.current.focus();
}
}}
button={
<EuiHeaderSectionItemButton
data-test-subj="toggleNavButton"
aria-label={headerStrings.nav.closeNavAriaLabel}
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen!}
aria-pressed={isOpen!}
aria-controls={navId}
ref={toggleCollapsibleNavRef}
<ScreenReaderRouteAnnouncements
breadcrumbs$={observables.breadcrumbs$}
customBranding$={of()}
appId$={application.currentAppId$}
/>
<SkipToMainContent />
<HeaderTopBanner headerBanner$={observables.headerBanner$} />
<header data-test-subj="kibanaProjectHeader">
<div id="globalHeaderBars" data-test-subj="headerGlobalNav" className="header__bars">
<EuiHeader position="fixed" className="header__firstBar">
<EuiHeaderSection grow={false}>
<EuiHeaderSectionItem css={headerCss.nav.toggleNavButton}>
<Router history={application.history}>
<ProjectNavigation
isOpen={isOpen!}
closeNav={() => {
setIsOpen(false);
if (toggleCollapsibleNavRef.current) {
toggleCollapsibleNavRef.current.focus();
}
}}
button={
<EuiHeaderSectionItemButton
data-test-subj="toggleNavButton"
aria-label={headerStrings.nav.closeNavAriaLabel}
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen!}
aria-pressed={isOpen!}
aria-controls={navId}
ref={toggleCollapsibleNavRef}
>
<EuiIcon type={isOpen ? 'menuLeft' : 'menuRight'} size="m" />
</EuiHeaderSectionItemButton>
}
>
<EuiIcon type={isOpen ? 'menuLeft' : 'menuRight'} size="m" />
</EuiHeaderSectionItemButton>
}
>
{children}
</ProjectNavigation>
</Router>
</EuiHeaderSectionItem>
{children}
</ProjectNavigation>
</Router>
</EuiHeaderSectionItem>
<EuiHeaderSectionItem>
<Logo
prependBasePath={prependBasePath}
application={application}
homeHref$={observables.homeHref$}
loadingCount$={observables.loadingCount$}
/>
</EuiHeaderSectionItem>
<EuiHeaderSectionItem>
<Logo
prependBasePath={prependBasePath}
application={application}
homeHref$={observables.homeHref$}
loadingCount$={observables.loadingCount$}
/>
</EuiHeaderSectionItem>
<EuiHeaderSectionItem>
<HeaderNavControls navControls$={observables.navControlsLeft$} />
</EuiHeaderSectionItem>
<EuiHeaderSectionItem>
<HeaderNavControls navControls$={observables.navControlsLeft$} />
</EuiHeaderSectionItem>
<EuiHeaderSectionItem>
<EuiHeaderLink href="https://cloud.elastic.co/deployments">
{headerStrings.cloud.linkToDeployments}
</EuiHeaderLink>
</EuiHeaderSectionItem>
<EuiHeaderSectionItem>
<EuiHeaderLink href="https://cloud.elastic.co/deployments">
{headerStrings.cloud.linkToDeployments}
</EuiHeaderLink>
</EuiHeaderSectionItem>
<EuiHeaderSectionItem>
<RedirectAppLinks coreStart={{ application }}>
<HeaderBreadcrumbs breadcrumbs$={observables.breadcrumbs$} />
</RedirectAppLinks>
</EuiHeaderSectionItem>
</EuiHeaderSection>
<EuiHeaderSectionItem>
<RedirectAppLinks coreStart={{ application }}>
<HeaderBreadcrumbs breadcrumbs$={observables.breadcrumbs$} />
</RedirectAppLinks>
</EuiHeaderSectionItem>
</EuiHeaderSection>
<EuiHeaderSection side="right">
<EuiHeaderSectionItem>
<HeaderNavControls navControls$={observables.navControlsCenter$} />
<HeaderNavControls navControls$={observables.navControlsRight$} />
</EuiHeaderSectionItem>
<EuiHeaderSection side="right">
<EuiHeaderSectionItem>
<HeaderNavControls navControls$={observables.navControlsCenter$} />
<HeaderNavControls navControls$={observables.navControlsRight$} />
</EuiHeaderSectionItem>
<EuiHeaderSectionItem>
<HeaderHelpMenu
globalHelpExtensionMenuLinks$={observables.globalHelpExtensionMenuLinks$}
helpExtension$={observables.helpExtension$}
helpSupportUrl$={observables.helpSupportUrl$}
defaultContentLinks$={observables.helpMenuLinks$}
kibanaDocLink={docLinks.links.elasticStackGetStarted}
docLinks={docLinks}
kibanaVersion={kibanaVersion}
navigateToUrl={application.navigateToUrl}
/>
</EuiHeaderSectionItem>
</EuiHeaderSection>
</EuiHeader>
<EuiHeaderSectionItem>
<HeaderHelpMenu
globalHelpExtensionMenuLinks$={observables.globalHelpExtensionMenuLinks$}
helpExtension$={observables.helpExtension$}
helpSupportUrl$={observables.helpSupportUrl$}
defaultContentLinks$={observables.helpMenuLinks$}
kibanaDocLink={docLinks.links.elasticStackGetStarted}
docLinks={docLinks}
kibanaVersion={kibanaVersion}
navigateToUrl={application.navigateToUrl}
/>
</EuiHeaderSectionItem>
</EuiHeaderSection>
</EuiHeader>
{headerActionMenuMounter.mount && (
<EuiHeader data-test-subj="kibanaProjectHeaderActionMenu">
<EuiHeaderSection />
<EuiHeaderSection side="right">
<EuiHeaderSectionItem>
<HeaderActionMenu mounter={headerActionMenuMounter} />
</EuiHeaderSectionItem>
</EuiHeaderSection>
</EuiHeader>
)}
<EuiHeader
position="fixed"
className="header__secondBar"
data-test-subj="kibanaProjectHeaderActionMenu"
>
<EuiHeaderSection />
{headerActionMenuMounter.mount && (
<EuiHeaderSection side="right">
<EuiHeaderSectionItem>
<HeaderActionMenu mounter={headerActionMenuMounter} />
</EuiHeaderSectionItem>
</EuiHeaderSection>
)}
</EuiHeader>
</div>
</header>
</>
);
};

View file

@ -75,9 +75,6 @@
&.kbnBody--chromeHidden {
@include kbnAffordForHeader(0);
}
&.kbnBody--projectLayout {
@include kbnAffordForHeader($euiHeaderHeightCompensation);
}
&.kbnBody--chromeHidden.kbnBody--hasHeaderBanner {
@include kbnAffordForHeader($kbnHeaderBannerHeight);
}

View file

@ -89,18 +89,6 @@ const PageOverlayGlobalStyles = createGlobalStyle<{ theme: EuiTheme }>`
body.${PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME} {
${FULL_SCREEN_CONTENT_OVERRIDES_CSS_STYLESHEET}
}
//-------------------------------------------------------------------------------------------
// Style overrides for when Page Overlay is displayed in serverless project
//-------------------------------------------------------------------------------------------
// With serverless, there is 1 less header displayed, thus the display of the page overlay
// need to be adjusted slightly so that it still display below the header
//-------------------------------------------------------------------------------------------
body.kbnBody.kbnBody--projectLayout:not(.${PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME}) .${PAGE_OVERLAY_CSS_CLASSNAME} {
top: ${({ theme: { eui } }) => eui.euiHeaderHeightCompensation};
height: calc(100% - (${({ theme: { eui } }) => eui.euiHeaderHeightCompensation}));
}
`;
const setDocumentBodyOverlayIsVisible = () => {