Skip to content

Commit 424bfbe

Browse files
Update header for NHS.UK frontend v10.0.0
1 parent bc82c35 commit 424bfbe

File tree

20 files changed

+802
-836
lines changed

20 files changed

+802
-836
lines changed
Lines changed: 99 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,149 @@
11
'use client';
2-
import React, { FC, HTMLProps, useContext, useState, useEffect, useMemo, useRef } from 'react';
2+
import React, {
3+
ComponentPropsWithoutRef,
4+
useState,
5+
useEffect,
6+
useMemo,
7+
useRef,
8+
Children,
9+
} from 'react';
310
import classNames from 'classnames';
4-
import NHSLogo, { NHSLogoNavProps } from './components/NHSLogo';
5-
import OrganisationalLogo, { OrganisationalLogoProps } from './components/OrganisationalLogo';
11+
import { Container } from '@components/layout';
12+
import { childIsOfComponentType } from '@util/types/TypeGuards';
613
import HeaderContext, { IHeaderContext } from './HeaderContext';
14+
import Account from './components/Account';
15+
import AccountItem from './components/AccountItem';
16+
import Logo from './components/Logo';
17+
import Navigation from './components/Navigation';
18+
import NavigationItem from './components/NavigationItem';
719
import Search from './components/Search';
8-
import Nav from './components/Nav';
9-
import NavItem from './components/NavItem';
10-
import NavDropdownMenu from './components/NavDropdownMenu';
11-
import { Container } from '@components/layout';
12-
import Content from './components/Content';
13-
import TransactionalServiceName from './components/TransactionalServiceName';
20+
import ServiceName from './components/ServiceName';
1421
import { Header } from 'nhsuk-frontend';
1522

16-
const HeaderLogo: FC<OrganisationalLogoProps & NHSLogoNavProps> = (props) => {
17-
const { orgName } = useContext<IHeaderContext>(HeaderContext);
18-
if (orgName) {
19-
return <OrganisationalLogo {...props} />;
20-
}
21-
return <NHSLogo {...props} />;
22-
};
23-
24-
const HeaderContainer: FC<HTMLProps<HTMLDivElement>> = ({ className, ...rest }) => (
25-
<Container className={classNames('nhsuk-header__container', className)} {...rest} />
26-
);
27-
28-
interface HeaderProps extends HTMLProps<HTMLDivElement> {
29-
transactional?: boolean;
30-
orgName?: string;
31-
orgSplit?: string;
32-
orgDescriptor?: string;
33-
serviceName?: string;
23+
interface HeaderProps extends ComponentPropsWithoutRef<'div'> {
24+
containerClasses?: string;
25+
logo?: IHeaderContext['logoProps'];
26+
service?: IHeaderContext['serviceProps'];
27+
organisation?: IHeaderContext['organisationProps'];
3428
white?: boolean;
3529
}
3630

3731
const HeaderComponent = ({
3832
className,
33+
containerClasses,
3934
children,
40-
transactional,
41-
orgName,
42-
orgSplit,
43-
orgDescriptor,
44-
role = 'banner',
45-
serviceName,
35+
logo,
36+
service,
37+
organisation,
4638
white,
4739
...rest
4840
}: HeaderProps) => {
4941
const moduleRef = useRef<HTMLDivElement>(null);
5042

51-
const [hasMenuToggle, setHasMenuToggle] = useState(false);
52-
const [hasSearch, setHasSearch] = useState(false);
53-
const [hasServiceName, setHasServiceName] = useState(false);
43+
const [logoProps, setLogoProps] = useState(logo);
44+
const [serviceProps, setServiceProps] = useState(service);
45+
const [organisationProps, setOrganisationProps] = useState(organisation);
5446
const [instance, setInstance] = useState<Header>();
5547
const [menuOpen, setMenuOpen] = useState(false);
5648

5749
useEffect(() => {
58-
if (!moduleRef.current || instance) {
50+
if (!logo) {
5951
return;
6052
}
6153

62-
setInstance(new Header(moduleRef.current));
63-
}, [moduleRef, instance]);
54+
setLogoProps(logo);
55+
return () => setLogoProps(undefined);
56+
}, [logo]);
57+
58+
useEffect(() => {
59+
if (!service) {
60+
return;
61+
}
62+
63+
setServiceProps(service);
64+
return () => setServiceProps(undefined);
65+
}, [service]);
6466

65-
const setMenuToggle = (toggle: boolean): void => {
66-
setHasMenuToggle(toggle);
67-
};
67+
useEffect(() => {
68+
if (!organisation) {
69+
return;
70+
}
71+
72+
setOrganisationProps(organisation);
73+
return () => setOrganisationProps(undefined);
74+
}, [organisation]);
75+
76+
useEffect(() => {
77+
if (!moduleRef.current || instance) {
78+
if (!instance) {
79+
return;
80+
}
81+
82+
// Sync menu open state
83+
if (menuOpen && !instance.menuIsOpen) {
84+
instance.openMenu();
85+
}
6886

69-
const setSearch = (toggle: boolean): void => {
70-
setHasSearch(toggle);
71-
};
87+
// Sync menu close state
88+
if (!menuOpen && instance.menuIsOpen) {
89+
instance.closeMenu();
90+
}
7291

73-
const toggleMenu = (): void => {
74-
setMenuOpen(!menuOpen);
75-
};
92+
return;
93+
}
7694

77-
const setServiceName = (toggle: boolean): void => {
78-
setHasServiceName(toggle);
79-
};
95+
setInstance(new Header(moduleRef.current));
96+
}, [moduleRef, instance, menuOpen]);
8097

8198
const contextValue: IHeaderContext = useMemo(() => {
8299
return {
83-
orgName,
84-
orgSplit,
85-
orgDescriptor,
86-
serviceName,
87-
hasSearch,
88-
hasMenuToggle,
89-
hasServiceName,
90-
setMenuToggle,
91-
setSearch,
92-
setServiceName,
93-
toggleMenu,
100+
logoProps,
101+
serviceProps,
102+
organisationProps,
94103
menuOpen,
95-
transactional: transactional ?? false,
104+
setMenuOpen,
105+
setLogoProps,
106+
setServiceProps,
107+
setOrganisationProps,
96108
};
97-
}, [
98-
orgName,
99-
orgSplit,
100-
orgDescriptor,
101-
serviceName,
102-
hasSearch,
103-
hasMenuToggle,
104-
hasServiceName,
105-
setMenuToggle,
106-
setSearch,
107-
setServiceName,
108-
toggleMenu,
109-
menuOpen,
110-
transactional,
111-
]);
109+
}, [logoProps, serviceProps, organisationProps]);
110+
111+
const items = Children.toArray(children);
112+
const [childLogo] = items.filter((child) => childIsOfComponentType(child, Logo));
113+
const [childSearch] = items.filter((child) => childIsOfComponentType(child, Search));
114+
const [childNavigation] = items.filter((child) => childIsOfComponentType(child, Navigation));
115+
const [childAccount] = items.filter((child) => childIsOfComponentType(child, Account));
112116

113117
return (
114118
<header
115119
className={classNames(
116120
'nhsuk-header',
117-
{ 'nhsuk-header__transactional': transactional },
118-
{ 'nhsuk-header--organisation': orgName },
119-
{ 'nhsuk-header--white': white },
121+
{ 'nhsuk-header--organisation': !!organisationProps },
122+
{ 'nhsuk-header--white': !!white },
120123
className,
121124
)}
122-
role={role}
125+
data-module="nhsuk-header"
126+
role="banner"
123127
ref={moduleRef}
124128
{...rest}
125129
>
126-
<HeaderContext.Provider value={contextValue}>{children}</HeaderContext.Provider>
130+
<HeaderContext.Provider value={contextValue}>
131+
<Container className={classNames('nhsuk-header__container', containerClasses)}>
132+
<ServiceName {...serviceProps}>{childLogo}</ServiceName>
133+
{childSearch}
134+
{childAccount}
135+
</Container>
136+
{childNavigation}
137+
</HeaderContext.Provider>
127138
</header>
128139
);
129140
};
130141

131-
HeaderComponent.Logo = HeaderLogo;
142+
HeaderComponent.Account = Account;
143+
HeaderComponent.AccountItem = AccountItem;
144+
HeaderComponent.Logo = Logo;
132145
HeaderComponent.Search = Search;
133-
HeaderComponent.Nav = Nav;
134-
HeaderComponent.NavItem = NavItem;
135-
HeaderComponent.NavDropdownMenu = NavDropdownMenu;
136-
HeaderComponent.Container = HeaderContainer;
137-
HeaderComponent.Content = Content;
138-
HeaderComponent.ServiceName = TransactionalServiceName;
146+
HeaderComponent.Navigation = Navigation;
147+
HeaderComponent.NavigationItem = NavigationItem;
139148

140149
export default HeaderComponent;
Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,36 @@
1-
import { createContext } from 'react';
1+
import { Dispatch, SetStateAction, createContext } from 'react';
22

33
export interface IHeaderContext {
4-
orgName: string | undefined;
5-
serviceName: string | undefined;
6-
orgSplit: string | undefined;
7-
orgDescriptor: string | undefined;
8-
setSearch: (toggle: boolean) => void;
9-
setMenuToggle: (toggle: boolean) => void;
10-
setServiceName: (toggle: boolean) => void;
11-
toggleMenu: () => void;
12-
hasSearch: boolean;
13-
hasMenuToggle: boolean;
14-
hasServiceName: boolean;
4+
logoProps?: {
5+
href?: string;
6+
src?: string;
7+
alt?: string;
8+
'aria-label'?: string;
9+
};
10+
serviceProps?: {
11+
href?: string;
12+
text?: string;
13+
};
14+
organisationProps?: {
15+
name?: string;
16+
split?: string;
17+
descriptor?: string;
18+
};
1519
menuOpen: boolean;
16-
transactional: boolean;
20+
setMenuOpen: Dispatch<SetStateAction<IHeaderContext['menuOpen']>>;
21+
setLogoProps: Dispatch<SetStateAction<IHeaderContext['logoProps']>>;
22+
setServiceProps: Dispatch<SetStateAction<IHeaderContext['serviceProps']>>;
23+
setOrganisationProps: Dispatch<SetStateAction<IHeaderContext['organisationProps']>>;
1724
}
1825

1926
export default createContext<IHeaderContext>({
2027
/* eslint-disable @typescript-eslint/no-empty-function */
21-
orgName: undefined,
22-
serviceName: undefined,
23-
orgSplit: undefined,
24-
orgDescriptor: undefined,
25-
setSearch: () => {},
26-
setMenuToggle: () => {},
27-
setServiceName: () => {},
28-
hasSearch: false,
29-
hasMenuToggle: false,
30-
hasServiceName: false,
31-
toggleMenu: () => {},
28+
logoProps: undefined,
29+
serviceProps: undefined,
30+
organisationProps: undefined,
3231
menuOpen: false,
33-
transactional: false,
32+
setMenuOpen: () => {},
33+
setLogoProps: () => {},
34+
setServiceProps: () => {},
35+
setOrganisationProps: () => {},
3436
});

0 commit comments

Comments
 (0)