Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/upgrade-to-6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ The [panel](https://service-manual.nhs.uk/design-system/components/panel) compon

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

### Notification banner component

The [notification banner](https://service-manual.nhs.uk/design-system/components/notification-banner) component from NHS.UK frontend v10 has been added:

```jsx
<NotificationBanner>
<NotificationBanner.Heading>Upcoming maintenance</NotificationBanner.Heading>
<p>The service will be unavailable from 8pm to 9pm on Thursday 1 January 2025.</p>
</NotificationBanner>
```

### Support for React Server Components (RSC)

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:
Expand Down Expand Up @@ -440,8 +451,9 @@ You must rename the `Select` prop `selectRef` to `ref` for consistency with othe
To align with NHS.UK frontend, the skip link component focuses the main content rather than the first heading on the page:

```html
<main class="nhsuk-main-wrapper id="maincontent">
<main class="nhsuk-main-wrapper" id="maincontent">
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for spotting this!

<!-- // ... -->
</main>
```

For accessibility reasons, you must make the following changes:
Expand Down
4 changes: 4 additions & 0 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ describe('Index', () => {
'NavAZ',
'NavAZDisabledItem',
'NavAZLinkItem',
'NotificationBanner',
'NotificationBannerHeading',
'NotificationBannerLink',
'NotificationBannerTitle',
'Pagination',
'PaginationLink',
'Panel',
Expand Down
1 change: 1 addition & 0 deletions src/components/content-presentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './hero/index.js';
export * from './icons/index.js';
export * from './images/index.js';
export * from './inset-text/index.js';
export * from './notification-banner/index.js';
export * from './panel/index.js';
export * from './summary-list/index.js';
export * from './table/index.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use client';

import {
Children,
createRef,
forwardRef,
useEffect,
useState,
type ComponentPropsWithoutRef,
} from 'react';
import classNames from 'classnames';
import {
NotificationBannerHeading,
NotificationBannerLink,
NotificationBannerTitle,
} from './components/index.js';
import { type NotificationBanner as NotificationBannerModule } from 'nhsuk-frontend';
import { childIsOfComponentType } from '#util/types/TypeGuards.js';

export interface NotificationBannerProps extends ComponentPropsWithoutRef<'div'> {
success?: boolean;
disableAutoFocus?: boolean;
titleId?: string;
}

const NotificationBannerComponent = forwardRef<HTMLDivElement, NotificationBannerProps>(
(props, forwardedRef) => {
const { children, className, title, titleId, success, role, disableAutoFocus, ...rest } = props;

const [moduleRef] = useState(() => forwardedRef || createRef<HTMLDivElement>());
const [instanceError, setInstanceError] = useState<Error>();
const [instance, setInstance] = useState<NotificationBannerModule>();

useEffect(() => {
if (!('current' in moduleRef) || !moduleRef.current || instance) {
return;
}

import('nhsuk-frontend')
.then(({ NotificationBanner }) => setInstance(new NotificationBanner(moduleRef.current)))
.catch(setInstanceError);
}, [moduleRef, instance]);

const items = Children.toArray(children);

const titleElement = items.find((child) =>
childIsOfComponentType(child, NotificationBannerTitle, {
className: 'nhsuk-notification-banner__title',
}),
);

const titleElementId = titleElement?.props.id || titleId || 'nhsuk-notification-banner-title';

const contentItems = items.filter((child) => child !== titleElement);

if (instanceError) {
throw instanceError;
}

return (
<div
className={classNames(
'nhsuk-notification-banner',
{ 'nhsuk-notification-banner--success': success },
className,
)}
aria-labelledby={titleElementId}
data-module="nhsuk-notification-banner"
data-disable-auto-focus={disableAutoFocus}
ref={moduleRef}
role={role || (success ? 'alert' : 'region')}
{...rest}
>
<div className="nhsuk-notification-banner__header">
{titleElement ? (
<>{titleElement}</>
) : (
<NotificationBannerTitle id={titleElementId} success={success}>
{title}
</NotificationBannerTitle>
)}
</div>
<div className="nhsuk-notification-banner__content">{contentItems}</div>
</div>
);
},
);

NotificationBannerComponent.displayName = 'NotificationBanner';

export const NotificationBanner = Object.assign(NotificationBannerComponent, {
Title: NotificationBannerTitle,
Heading: NotificationBannerHeading,
Link: NotificationBannerLink,
});
Loading