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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# NHS.UK React components

## 6.0.0-beta.1 - 8 October 2025

This version adds the panel component from NHS.UK frontend v9.3.0 and supports React v19.

For a full list of changes in this release please refer to the [migration doc](https://github.com/NHSDigital/nhsuk-react-components/blob/main/docs/upgrade-to-6.0.md).

## 6.0.0-beta.0 - 30 September 2025

This version provides support for nhsuk-frontend version 10.
Expand Down
46 changes: 45 additions & 1 deletion docs/upgrade-to-6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ There are some breaking changes you'll need to be aware of when upgrading to v6.

You must read and apply these updates carefully to make sure your service does not break.

## New features

### New header component with account section

The updated [header](https://service-manual.nhs.uk/design-system/components/header) component from NHS.UK frontend v10.x has been added, including support for account information and links. As part of this work we’ve also made some other improvements to the header:

- show currently active section or page in the navigation
- align navigation items to the left by default
- update navigation label from ’Primary navigation’ to ‘Menu’, and remove superfluous `role` and `id` attributes
- update NHS logo in the header to have higher contrast when focused
- refactor CSS classes and BEM naming, use hidden attributes instead of modifier classes, use generic search element

### Panel component

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

```jsx
<Panel>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>
```

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

## Breaking changes

### Update the JavaScript supported script snippet
Expand Down Expand Up @@ -210,7 +235,7 @@ To align with NHS.UK frontend, the date input component automatically renders it

The custom `autoSelectNext` prop is no longer supported.

### New header component with account section
### Header

The updated header component from NHS.UK frontend v10.x has been added. You will need to make the following changes:

Expand Down Expand Up @@ -464,3 +489,22 @@ To align with NHS.UK frontend, the warning callout `WarningCallout.Label` compon
</p>
</WarningCallout>
```

## Fixes

- [#52: Expose header navigation open/close state (with setter)](https://github.com/NHSDigital/nhsuk-react-components/issues/52)
- [#69: Unable to use ref attribute on some components](https://github.com/NHSDigital/nhsuk-react-components/issues/69)
- [#71: Expose FormGroup component to consumers](https://github.com/NHSDigital/nhsuk-react-components/issues/71)
- [#105: getHeadingsFromChildren forces use of string as table cell child](https://github.com/NHSDigital/nhsuk-react-components/issues/105)
- [#166: SkipLink double jumps to first heading then #maincontent if disableDefaultBehaviour is not set](https://github.com/NHSDigital/nhsuk-react-components/issues/166)
- [#174: Responsive tables and validation errors](https://github.com/NHSDigital/nhsuk-react-components/issues/174)
- [#214: Hints and errors are not semantically associated with fieldsets](https://github.com/NHSDigital/nhsuk-react-components/issues/214)
- [#215: Suggestion: remove all 'boolean' examples from storybook](https://github.com/NHSDigital/nhsuk-react-components/issues/215)
- [#243: Use correct NHS.UK frontend JavaScript when rendered client-side](https://github.com/NHSDigital/nhsuk-react-components/issues/243)
- [#244: Breaking change: remove default legend and label sizes or else change to l](https://github.com/NHSDigital/nhsuk-react-components/issues/244)
- [#245: Fieldset incorrectly gets set in error when a child input is in error](https://github.com/NHSDigital/nhsuk-react-components/issues/245)
- [#247: Date component uses label rather than fieldset with legend](https://github.com/NHSDigital/nhsuk-react-components/issues/247)
- [#256: SkipLink does not work if intended target header is rerendered](https://github.com/NHSDigital/nhsuk-react-components/issues/256)
- [#259: Remove pattern="[0-9]\*" from date inputs](https://github.com/NHSDigital/nhsuk-react-components/issues/259)
- [#260: Allow custom component for button links](https://github.com/NHSDigital/nhsuk-react-components/issues/260)
- [#265: Header logo is not labeled correctly when organisation info is provided](https://github.com/NHSDigital/nhsuk-react-components/issues/265)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nhsuk-react-components",
"version": "6.0.0-beta.0",
"version": "6.0.0-beta.1",
"license": "MIT",
"author": {
"name": "NHS England"
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('Index', () => {
'Legend',
'NavAZ',
'Pagination',
'Panel',
'Radios',
'RadiosContext',
'ReadingWidth',
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 './panel/index.js';
export * from './summary-list/index.js';
export * from './table/index.js';
export * from './tabs/index.js';
Expand Down
36 changes: 36 additions & 0 deletions src/components/content-presentation/panel/Panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import classNames from 'classnames';
import { Children, forwardRef, type ComponentPropsWithoutRef, type FC } from 'react';
import { HeadingLevel, type HeadingLevelProps } from '#components/utils/HeadingLevel.js';
import { childIsOfComponentType } from '#util/types/TypeGuards.js';

export type PanelTitleProps = HeadingLevelProps;

const PanelTitle: FC<PanelTitleProps> = ({ children, headingLevel = 'h1', ...rest }) => (
<HeadingLevel className="nhsuk-panel__title" headingLevel={headingLevel} {...rest}>
{children}
</HeadingLevel>
);

export type PanelProps = ComponentPropsWithoutRef<'div'>;

const PanelComponent = forwardRef<HTMLDivElement, PanelProps>(
({ children, className, ...rest }, forwardedRef) => {
const items = Children.toArray(children);
const title = items.find((child) => childIsOfComponentType(child, PanelTitle));
const bodyItems = items.filter((child) => !childIsOfComponentType(child, PanelTitle));

return (
<div className={classNames('nhsuk-panel', className)} ref={forwardedRef} {...rest}>
{title}
{bodyItems ? <div className="nhsuk-panel__body">{bodyItems}</div> : null}
</div>
);
},
);

PanelComponent.displayName = 'Panel';
PanelComponent.displayName = 'Panel.Title';

export const Panel = Object.assign(PanelComponent, {
Title: PanelTitle,
});
74 changes: 74 additions & 0 deletions src/components/content-presentation/panel/__tests__/Panel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { Panel, type PanelTitleProps } from '..';
import { renderClient, renderServer } from '#util/components';

describe('Panel', () => {
it('matches snapshot', async () => {
const { container } = await renderClient(
<Panel>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>,
{ className: 'nhsuk-panel' },
);

expect(container).toMatchSnapshot();
});

it('matches snapshot (via server)', async () => {
const { container, element } = await renderServer(
<Panel>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>,
{ className: 'nhsuk-panel' },
);

expect(container).toMatchSnapshot('server');

await renderClient(element, {
className: 'nhsuk-panel',
hydrate: true,
container,
});

expect(container).toMatchSnapshot('client');
});

it('forwards refs', async () => {
const ref = createRef<HTMLDivElement>();

const { modules } = await renderClient(
<Panel ref={ref}>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>,
{ className: 'nhsuk-panel' },
);

const [panelEl] = modules;

expect(ref.current).toBe(panelEl);
expect(ref.current).toHaveClass('nhsuk-panel');
});

it.each<PanelTitleProps | undefined>([
undefined,
{ headingLevel: 'h1' },
{ headingLevel: 'h2' },
{ headingLevel: 'h3' },
{ headingLevel: 'h4' },
])('renders heading level $headingLevel if specified', (props) => {
const { container } = render(
<Panel>
<Panel.Title {...props}>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>,
);

const title = container.querySelector('.nhsuk-panel__title');

expect(title).toHaveProperty('tagName', props?.headingLevel?.toUpperCase() ?? 'H1');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`Panel matches snapshot (via server): client 1`] = `
<div>
<div
class="nhsuk-panel"
>
<h1
class="nhsuk-panel__title"
>
Booking complete
</h1>
<div
class="nhsuk-panel__body"
>
We have sent you a confirmation email
</div>
</div>
</div>
`;

exports[`Panel matches snapshot (via server): server 1`] = `
<div>
<div
class="nhsuk-panel"
>
<h1
class="nhsuk-panel__title"
>
Booking complete
</h1>
<div
class="nhsuk-panel__body"
>
We have sent you a confirmation email
</div>
</div>
</div>
`;

exports[`Panel matches snapshot 1`] = `
<div>
<div
class="nhsuk-panel"
>
<h1
class="nhsuk-panel__title"
>
Booking complete
</h1>
<div
class="nhsuk-panel__body"
>
We have sent you a confirmation email
</div>
</div>
</div>
`;
1 change: 1 addition & 0 deletions src/components/content-presentation/panel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Panel.js';
27 changes: 27 additions & 0 deletions stories/Content Presentation/Panel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { Panel } from '#components';

const meta: Meta<typeof Panel> = {
title: 'Content Presentation/Panel',
component: Panel,
};
export default meta;
type Story = StoryObj<typeof Panel>;

export const StandardPanel: Story = {
render: () => (
<Panel>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>
),
};

export const PanelWithCustomHeadingLevel: Story = {
render: () => (
<Panel>
<Panel.Title headingLevel="h2">Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>
),
};