Skip to content

Commit

Permalink
[Stateful sidenav] Custom branding logo (#184035)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebelga committed May 24, 2024
1 parent 9bafd06 commit e754403
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ export class ChromeService {
globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$}
actionMenu$={application.currentActionMenu$}
breadcrumbs$={currentProjectBreadcrumbs$}
customBranding$={customBranding$}
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
helpMenuLinks$={helpMenuLinks$}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,7 @@ export class ProjectNavigationService {
);

return {
setProjectHome: (homeHref: string) => {
this.projectHome$.next(homeHref);
},
setProjectHome: this.setProjectHome.bind(this),
getProjectHome$: () => {
return this.projectHome$.asObservable();
},
Expand Down Expand Up @@ -408,14 +406,25 @@ export class ProjectNavigationService {
return;
}

const { sideNavComponent } = definition;
const { sideNavComponent, homePage = '' } = definition;
const homePageLink = this.navLinksService?.get(homePage);

if (sideNavComponent) {
this.setSideNavComponent(sideNavComponent);
}

if (homePageLink) {
this.setProjectHome(homePageLink.href);
}

this.initNavigation(nextId, definition.navigationTree$);
});
}

private setProjectHome(homeHref: string) {
this.projectHome$.next(homeHref);
}

private goToSolutionHome(id: string) {
const definitions = this.solutionNavDefinitions$.getValue();
const definition = definitions[id];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('Header', () => {
navControlsLeft$: Rx.of([]),
navControlsCenter$: Rx.of([]),
navControlsRight$: Rx.of([]),
customBranding$: Rx.of({}),
prependBasePath: (str) => `hello/world/${str}`,
toggleSideNav: jest.fn(),
};
Expand All @@ -46,5 +47,16 @@ describe('Header', () => {

expect(await screen.findByTestId('euiCollapsibleNavButton')).toBeVisible();
expect(await screen.findByText('Hello, world!')).toBeVisible();
expect(screen.queryByTestId(/customLogo/)).toBeNull();
});

it('renders custom branding logo', async () => {
const { queryByTestId } = render(
<ProjectHeader {...mockProps} customBranding$={Rx.of({ logo: 'foo.jpg' })}>
<EuiHeader>Hello, world!</EuiHeader>
</ProjectHeader>
);

expect(queryByTestId(/customLogo/)).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EuiLoadingSpinner,
useEuiTheme,
EuiThemeComputed,
EuiImage,
} from '@elastic/eui';
import { css } from '@emotion/react';
import type { InternalApplicationStart } from '@kbn/core-application-browser-internal';
Expand All @@ -33,7 +34,9 @@ import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { Router } from '@kbn/shared-ux-router';
import React, { useCallback } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { debounceTime, Observable, of } from 'rxjs';
import { debounceTime, Observable } from 'rxjs';
import type { CustomBranding } from '@kbn/core-custom-branding-common';

import { useHeaderActionMenuMounter } from '../header/header_action_menu';
import { Breadcrumbs } from './breadcrumbs';
import { HeaderHelpMenu } from '../header/header_help_menu';
Expand All @@ -46,11 +49,11 @@ import { ProjectNavigation } from './navigation';
const getHeaderCss = ({ size, colors }: EuiThemeComputed) => ({
logo: {
container: css`
display: inline-block;
display: flex;
align-items: center;
justify-content: center;
min-width: 56px; /* 56 = 40 + 8 + 8 */
padding: 0 ${size.s};
cursor: pointer;
margin-left: -${size.s}; // to get equal spacing between .euiCollapsibleNavButtonWrapper, logo and breadcrumbs
`,
logo: css`
min-width: 0; /* overrides min-width: 40px */
Expand Down Expand Up @@ -113,6 +116,7 @@ export interface Props {
actionMenu$: Observable<MountPoint | undefined>;
docLinks: DocLinksStart;
children: React.ReactNode;
customBranding$: Observable<CustomBranding>;
globalHelpExtensionMenuLinks$: Observable<ChromeGlobalHelpExtensionMenuLink[]>;
helpExtension$: Observable<ChromeHelpExtension | undefined>;
helpSupportUrl$: Observable<string>;
Expand All @@ -130,46 +134,77 @@ export interface Props {

const LOADING_DEBOUNCE_TIME = 80;

type LogoProps = Pick<Props, 'application' | 'homeHref$' | 'loadingCount$' | 'prependBasePath'> & {
type LogoProps = Pick<
Props,
'application' | 'homeHref$' | 'loadingCount$' | 'prependBasePath' | 'customBranding$'
> & {
logoCss: HeaderCss['logo'];
};

const Logo = (props: LogoProps) => {
const loadingCount = useObservable(
props.loadingCount$.pipe(debounceTime(LOADING_DEBOUNCE_TIME)),
0
);

const homeHref = useObservable(props.homeHref$, '/app/home');
const Logo = ({
loadingCount$,
homeHref$,
prependBasePath,
application,
logoCss,
customBranding$,
}: LogoProps) => {
const loadingCount = useObservable(loadingCount$.pipe(debounceTime(LOADING_DEBOUNCE_TIME)), 0);
const homeHref = useObservable(homeHref$, '/app/home');
const customBranding = useObservable(customBranding$, {});
const { logo } = customBranding;

let fullHref: string | undefined;
if (homeHref) {
fullHref = props.prependBasePath(homeHref);
fullHref = prependBasePath(homeHref);
}

const navigateHome = useCallback(
(event: React.MouseEvent) => {
if (fullHref) {
props.application.navigateToUrl(fullHref);
application.navigateToUrl(fullHref);
}
event.preventDefault();
},
[fullHref, props.application]
[fullHref, application]
);

const renderLogo = () => {
if (logo) {
return (
<a href={fullHref} onClick={navigateHome} data-test-subj="globalLoadingIndicator-hidden">
<EuiImage
src={logo}
css={logoCss}
data-test-subj="globalLoadingIndicator-hidden customLogo"
size={24}
alt="logo"
aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.customLogoAriaLabel', {
defaultMessage: 'User logo',
})}
/>
</a>
);
}

return (
<EuiHeaderLogo
iconType="logoElastic"
onClick={navigateHome}
href={fullHref}
css={logoCss}
data-test-subj="globalLoadingIndicator-hidden"
aria-label={headerStrings.logo.ariaLabel}
/>
);
};

return (
<span css={props.logoCss.container} data-test-subj="nav-header-logo">
<span css={logoCss.container} data-test-subj="nav-header-logo">
{loadingCount === 0 ? (
<EuiHeaderLogo
iconType="logoElastic"
onClick={navigateHome}
href={fullHref}
css={props.logoCss}
data-test-subj="globalLoadingIndicator-hidden"
aria-label={headerStrings.logo.ariaLabel}
/>
renderLogo()
) : (
<a onClick={navigateHome} href={fullHref} css={props.logoCss.spinner}>
<a onClick={navigateHome} href={fullHref} css={logoCss.spinner}>
<EuiLoadingSpinner
size="l"
aria-hidden={false}
Expand All @@ -189,6 +224,7 @@ export const ProjectHeader = ({
prependBasePath,
docLinks,
toggleSideNav,
customBranding$,
...observables
}: Props) => {
const headerActionMenuMounter = useHeaderActionMenuMounter(observables.actionMenu$);
Expand All @@ -200,7 +236,7 @@ export const ProjectHeader = ({
<>
<ScreenReaderRouteAnnouncements
breadcrumbs$={observables.breadcrumbs$}
customBranding$={of()}
customBranding$={customBranding$}
appId$={application.currentAppId$}
/>
<SkipToMainContent />
Expand All @@ -220,6 +256,7 @@ export const ProjectHeader = ({
application={application}
homeHref$={observables.homeHref$}
loadingCount$={observables.loadingCount$}
customBranding$={customBranding$}
logoCss={logoCss}
/>
</EuiHeaderSectionItem>
Expand Down

0 comments on commit e754403

Please sign in to comment.