From 8488edf2bb44a5855bb5e307fd3343ca18e41616 Mon Sep 17 00:00:00 2001 From: Joey Connor Date: Sun, 2 Feb 2025 17:14:47 +0000 Subject: [PATCH] Header: Support custom logos (#1281) * feat: add logo prop to the Header component to allow for custom logo to be added chore: add test cases to the Header component to support testing the logo prop fix: remove unused import * fix: update code style to use the _logo variable name, and update the test case to mock the and components * fix: improve the logo props to allow for null to be passed in to represent no logo * fix: show the custom logo even for GovUK branded pages * feat: update storybook for Header component to reflect new options * Update components/header/src/Header.tsx Co-authored-by: Daniel A.C. Martin * Update components/header/src/Header.tsx Co-authored-by: Daniel A.C. Martin * Update components/header/src/Header.tsx Co-authored-by: Daniel A.C. Martin * Update components/header/src/Header.tsx Co-authored-by: Daniel A.C. Martin --------- Co-authored-by: Joey Connor Co-authored-by: Daniel A.C. Martin --- components/header/spec/Header.stories.mdx | 38 ++++++++++++ components/header/spec/Header.ts | 75 +++++++++++++++++++++++ components/header/src/Header.tsx | 26 +++++--- 3 files changed, 131 insertions(+), 8 deletions(-) diff --git a/components/header/spec/Header.stories.mdx b/components/header/spec/Header.stories.mdx index 3d737dca0..ae2bffdd0 100644 --- a/components/header/spec/Header.stories.mdx +++ b/components/header/spec/Header.stories.mdx @@ -1,3 +1,4 @@ +import { Fragment } from 'react' import { Meta, Preview, Props, Story } from '@storybook/addon-docs'; import { Header } from '../src/Header'; import readMe from '../README.md'; @@ -286,3 +287,40 @@ your department in order to use their colours. /> + +#### Remove the logo + + + +
+ + + +#### Customise the logo + + +##### GovUK Header + + + +
Logo} + /> + + + +##### NotGovUK Header + + + +
Logo} + /> + + + diff --git a/components/header/spec/Header.ts b/components/header/spec/Header.ts index 215f04cf3..3d78eb3e5 100644 --- a/components/header/spec/Header.ts +++ b/components/header/spec/Header.ts @@ -2,12 +2,31 @@ import { createElement as h } from 'react'; import { render, screen } from '@not-govuk/component-test-helpers'; import Header from '../src/Header'; +jest.mock('../src/CrownLogo', () => ({ + CrownLogo: () => h('svg', { + 'data-testid': 'crownLogo', + children: h('title', { + children: 'GOV.UK' + }) + }) +})) + +jest.mock('../src/CoatLogo', () => ({ + CoatLogo: () => h('svg', { + 'data-testid': 'coatLogo' + }) +})) + describe('Header', () => { describe('when given valid props', () => { beforeEach(async () => { render(h(Header, {})); }); + afterEach(() => { + jest.clearAllMocks() + }) + it('renders an element', async () => expect(screen.getByRole('banner')).toBeInTheDocument()); it('is NOT GOV.UK branded', async () => expect(screen.getByRole('banner')).not.toHaveTextContent('GOV.UK')); it('does NOT contain the sign-out link', async () => expect(screen.getByRole('banner')).not.toHaveTextContent('Sign out')); @@ -51,4 +70,60 @@ describe('Header', () => { it('contains the navigation links', async () => expect(screen.getByRole('banner')).toHaveTextContent('Navigation item 2')); it('contains the sign-out link', async () => expect(screen.getByRole('banner')).toHaveTextContent('Log out')); }); + + describe('header logo behaviour', () => { + it('displays the crown logo', async () => { + const props = { + govUK: true + }; + render(h(Header, props, 'Child')); + + expect(screen.getByTestId('crownLogo')).toBeInTheDocument() + }) + + it('displays the custom logo if the govUK prop is true and logo prop is provided', async () => { + const props = { + govUK: true, + logo: h('div', { 'data-testid': 'custom-logo' }) + }; + render(h(Header, props, 'Child')); + + expect(screen.getByTestId('custom-logo')).toBeInTheDocument() + expect(screen.queryByTestId('crownLogo')).not.toBeInTheDocument() + }) + + it('displays the coat logo', () => { + const props = { + govUK: false + }; + + render(h(Header, props, 'Child')); + + expect(screen.getByTestId('coatLogo')).toBeInTheDocument() + }) + + it('displays a custom ReactNode', () => { + const props = { + govUK: false, + logo: h('div', { 'data-testid': 'custom-logo'}) + }; + + render(h(Header, props, 'Child')); + + expect(screen.getByTestId('custom-logo')).toBeInTheDocument() + }) + + it('displays no logo', () => { + const props = { + govUK: false, + logo: null + }; + + render(h(Header, props, 'Child')); + + expect(screen.queryByTestId('custom-logo')).not.toBeInTheDocument() + expect(screen.queryByTestId('coatLogo')).not.toBeInTheDocument() + expect(screen.queryByTestId('crownLogo')).not.toBeInTheDocument() + }) + }) }); diff --git a/components/header/src/Header.tsx b/components/header/src/Header.tsx index 816823bd0..de2c1981b 100644 --- a/components/header/src/Header.tsx +++ b/components/header/src/Header.tsx @@ -1,4 +1,4 @@ -import { FC, Fragment, createElement as h } from 'react'; +import { FC, Fragment, createElement as h, ReactNode, isValidElement } from 'react'; import { StandardProps, classBuilder } from '@not-govuk/component-helpers'; import { Link, LinkProps } from '@not-govuk/link'; import { WidthContainer } from '@not-govuk/width-container'; @@ -35,6 +35,8 @@ export type HeaderProps = StandardProps & { signOutHref?: string /** Sign out link text */ signOutText?: string + /** Custom logo, use null to remove */ + logo?: ReactNode }; const departmentMap: Record = { @@ -94,6 +96,7 @@ export const Header: FC = ({ serviceName, signOutHref, signOutText = 'Sign out', + logo: _logo, ...attrs }) => { const classes = classBuilder('govuk-header', classBlock, classModifiers, className); @@ -106,6 +109,15 @@ export const Header: FC = ({ forceExternal: true }]; + // Use the CrownLogo or CoatLogo by default. + const crownLogo: ReactNode = + const coatLogo: ReactNode =