Skip to content

Commit

Permalink
Header: Support custom logos (#1281)
Browse files Browse the repository at this point in the history
* 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 <CrownLogo> and <CoatLogo> 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 <daniel-ac-martin@users.noreply.github.com>

* Update components/header/src/Header.tsx

Co-authored-by: Daniel A.C. Martin <daniel-ac-martin@users.noreply.github.com>

* Update components/header/src/Header.tsx

Co-authored-by: Daniel A.C. Martin <daniel-ac-martin@users.noreply.github.com>

* Update components/header/src/Header.tsx

Co-authored-by: Daniel A.C. Martin <daniel-ac-martin@users.noreply.github.com>

---------

Co-authored-by: Joey Connor <joey.connor@engineering.digital.dwp.gov.uk>
Co-authored-by: Daniel A.C. Martin <daniel-ac-martin@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 2, 2025
1 parent 063f5a6 commit 8488edf
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 8 deletions.
38 changes: 38 additions & 0 deletions components/header/spec/Header.stories.mdx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -286,3 +287,40 @@ your department in order to use their colours.
/>
</Story>
</Preview>

#### Remove the logo

<Preview>
<Story name="Remove the logo">
<Header
department="department-for-work-pensions"
logo={null}
/>
</Story>
</Preview>

#### Customise the logo


##### GovUK Header

<Preview>
<Story name="GovUK Header with custom logo">
<Header
govUK={true}
logo={<span>Logo</span>}
/>
</Story>
</Preview>

##### NotGovUK Header

<Preview>
<Story name="NotGovUK Header with custom logo">
<Header
department="department-for-work-pensions"
logo={<span>Logo</span>}
/>
</Story>
</Preview>

75 changes: 75 additions & 0 deletions components/header/spec/Header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down Expand Up @@ -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()
})
})
});
26 changes: 18 additions & 8 deletions components/header/src/Header.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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<string, string> = {
Expand Down Expand Up @@ -94,6 +96,7 @@ export const Header: FC<HeaderProps> = ({
serviceName,
signOutHref,
signOutText = 'Sign out',
logo: _logo,
...attrs
}) => {
const classes = classBuilder('govuk-header', classBlock, classModifiers, className);
Expand All @@ -106,22 +109,29 @@ export const Header: FC<HeaderProps> = ({
forceExternal: true
}];

// Use the CrownLogo or CoatLogo by default.
const crownLogo: ReactNode = <CrownLogo focusable="false" className={classes('logotype')} height="30" width="148" />
const coatLogo: ReactNode = <CoatLogo aria-hidden="true" focusable="false" className={classes('logotype', ['coat'])} height="30" width="36" />
const logo = (
_logo !== undefined
? _logo
: (govUK ? crownLogo : coatLogo)
);

return (
<header {...attrs} className={classes()} data-module="govuk-header">
<WidthContainer maxWidth={maxContentsWidth} className={classes('container', department)}>
<div className={classes('logo')}>
<A href={orgHref} classModifiers={[ 'homepage', (orgText && orgText.length > 9) ? 'small' : undefined ]}>
{
govUK
? (
<CrownLogo focusable="false" className={classes('logotype')} height="30" width="148" />
)
? logo
: (
<Fragment>
<CoatLogo aria-hidden="true" focusable="false" className={classes('logotype', ['coat'])} height="30" width="36" />
<span className={classes('logotype-text')}>
{orgText}
</span>
{logo}
<span className={classes('logotype-text')}>
{orgText}
</span>
</Fragment>
)
}
Expand Down

1 comment on commit 8488edf

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Published on https://not-gov.uk as production
🚀 Deployed on https://679fa892c52595080d80baf8--notgovuk.netlify.app

Please sign in to comment.