Skip to content

Commit 37641b4

Browse files
Merge pull request #284 from robkerrybjss/add-notification-banner-component
Add notification banner component
2 parents 9737c3c + abc9b8f commit 37641b4

File tree

12 files changed

+1512
-1
lines changed

12 files changed

+1512
-1
lines changed

docs/upgrade-to-6.0.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ The [panel](https://service-manual.nhs.uk/design-system/components/panel) compon
3131

3232
This replaces the [list panel component](#list-panel) which was removed in NHS.UK frontend v6.0.0.
3333

34+
### Notification banner component
35+
36+
The [notification banner](https://service-manual.nhs.uk/design-system/components/notification-banner) component from NHS.UK frontend v10 has been added:
37+
38+
```jsx
39+
<NotificationBanner>
40+
<NotificationBanner.Heading>Upcoming maintenance</NotificationBanner.Heading>
41+
<p>The service will be unavailable from 8pm to 9pm on Thursday 1 January 2025.</p>
42+
</NotificationBanner>
43+
```
44+
3445
### Support for React Server Components (RSC)
3546

3647
All components have been tested as React Server Components (RSC) but due to [multipart namespace component limitations](https://ivicabatinic.from.hr/posts/multipart-namespace-components-addressing-rsc-and-dot-notation-issues) an alternative syntax (without dot notation) can be used as a workaround:
@@ -440,8 +451,9 @@ You must rename the `Select` prop `selectRef` to `ref` for consistency with othe
440451
To align with NHS.UK frontend, the skip link component focuses the main content rather than the first heading on the page:
441452

442453
```html
443-
<main class="nhsuk-main-wrapper id="maincontent">
454+
<main class="nhsuk-main-wrapper" id="maincontent">
444455
<!-- // ... -->
456+
</main>
445457
```
446458

447459
For accessibility reasons, you must make the following changes:

src/__tests__/index.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ describe('Index', () => {
9191
'NavAZ',
9292
'NavAZDisabledItem',
9393
'NavAZLinkItem',
94+
'NotificationBanner',
95+
'NotificationBannerHeading',
96+
'NotificationBannerLink',
97+
'NotificationBannerTitle',
9498
'Pagination',
9599
'PaginationLink',
96100
'Panel',

src/components/content-presentation/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from './hero/index.js';
44
export * from './icons/index.js';
55
export * from './images/index.js';
66
export * from './inset-text/index.js';
7+
export * from './notification-banner/index.js';
78
export * from './panel/index.js';
89
export * from './summary-list/index.js';
910
export * from './table/index.js';
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use client';
2+
3+
import {
4+
Children,
5+
createRef,
6+
forwardRef,
7+
useEffect,
8+
useState,
9+
type ComponentPropsWithoutRef,
10+
} from 'react';
11+
import classNames from 'classnames';
12+
import {
13+
NotificationBannerHeading,
14+
NotificationBannerLink,
15+
NotificationBannerTitle,
16+
} from './components/index.js';
17+
import { type NotificationBanner as NotificationBannerModule } from 'nhsuk-frontend';
18+
import { childIsOfComponentType } from '#util/types/TypeGuards.js';
19+
20+
export interface NotificationBannerProps extends ComponentPropsWithoutRef<'div'> {
21+
success?: boolean;
22+
disableAutoFocus?: boolean;
23+
titleId?: string;
24+
}
25+
26+
const NotificationBannerComponent = forwardRef<HTMLDivElement, NotificationBannerProps>(
27+
(props, forwardedRef) => {
28+
const { children, className, title, titleId, success, role, disableAutoFocus, ...rest } = props;
29+
30+
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLDivElement>());
31+
const [instanceError, setInstanceError] = useState<Error>();
32+
const [instance, setInstance] = useState<NotificationBannerModule>();
33+
34+
useEffect(() => {
35+
if (!('current' in moduleRef) || !moduleRef.current || instance) {
36+
return;
37+
}
38+
39+
import('nhsuk-frontend')
40+
.then(({ NotificationBanner }) => setInstance(new NotificationBanner(moduleRef.current)))
41+
.catch(setInstanceError);
42+
}, [moduleRef, instance]);
43+
44+
const items = Children.toArray(children);
45+
46+
const titleElement = items.find((child) =>
47+
childIsOfComponentType(child, NotificationBannerTitle, {
48+
className: 'nhsuk-notification-banner__title',
49+
}),
50+
);
51+
52+
const titleElementId = titleElement?.props.id || titleId || 'nhsuk-notification-banner-title';
53+
54+
const contentItems = items.filter((child) => child !== titleElement);
55+
56+
if (instanceError) {
57+
throw instanceError;
58+
}
59+
60+
return (
61+
<div
62+
className={classNames(
63+
'nhsuk-notification-banner',
64+
{ 'nhsuk-notification-banner--success': success },
65+
className,
66+
)}
67+
aria-labelledby={titleElementId}
68+
data-module="nhsuk-notification-banner"
69+
data-disable-auto-focus={disableAutoFocus}
70+
ref={moduleRef}
71+
role={role || (success ? 'alert' : 'region')}
72+
{...rest}
73+
>
74+
<div className="nhsuk-notification-banner__header">
75+
{titleElement ? (
76+
<>{titleElement}</>
77+
) : (
78+
<NotificationBannerTitle id={titleElementId} success={success}>
79+
{title}
80+
</NotificationBannerTitle>
81+
)}
82+
</div>
83+
<div className="nhsuk-notification-banner__content">{contentItems}</div>
84+
</div>
85+
);
86+
},
87+
);
88+
89+
NotificationBannerComponent.displayName = 'NotificationBanner';
90+
91+
export const NotificationBanner = Object.assign(NotificationBannerComponent, {
92+
Title: NotificationBannerTitle,
93+
Heading: NotificationBannerHeading,
94+
Link: NotificationBannerLink,
95+
});

0 commit comments

Comments
 (0)