Skip to content

Commit 1f8cc39

Browse files
Merge pull request #274 from NHSDigital/panel-component
Add panel component, prepare beta release
2 parents b28239c + dbb64be commit 1f8cc39

File tree

10 files changed

+250
-2
lines changed

10 files changed

+250
-2
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# NHS.UK React components
22

3+
## 6.0.0-beta.1 - 8 October 2025
4+
5+
This version adds the panel component from NHS.UK frontend v9.3.0 and supports React v19.
6+
7+
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).
8+
39
## 6.0.0-beta.0 - 30 September 2025
410

511
This version provides support for nhsuk-frontend version 10.

docs/upgrade-to-6.0.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ There are some breaking changes you'll need to be aware of when upgrading to v6.
66

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

9+
## New features
10+
11+
### New header component with account section
12+
13+
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:
14+
15+
- show currently active section or page in the navigation
16+
- align navigation items to the left by default
17+
- update navigation label from ’Primary navigation’ to ‘Menu’, and remove superfluous `role` and `id` attributes
18+
- update NHS logo in the header to have higher contrast when focused
19+
- refactor CSS classes and BEM naming, use hidden attributes instead of modifier classes, use generic search element
20+
21+
### Panel component
22+
23+
The [panel](https://service-manual.nhs.uk/design-system/components/panel) component from NHS.UK frontend v9.3.0 has been added:
24+
25+
```jsx
26+
<Panel>
27+
<Panel.Title>Booking complete</Panel.Title>
28+
We have sent you a confirmation email
29+
</Panel>
30+
```
31+
32+
This replaces the [list panel component](#list-panel) which was removed in NHS.UK frontend v6.0.0.
33+
934
## Breaking changes
1035

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

211236
The custom `autoSelectNext` prop is no longer supported.
212237

213-
### New header component with account section
238+
### Header
214239

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

@@ -464,3 +489,22 @@ To align with NHS.UK frontend, the warning callout `WarningCallout.Label` compon
464489
</p>
465490
</WarningCallout>
466491
```
492+
493+
## Fixes
494+
495+
- [#52: Expose header navigation open/close state (with setter)](https://github.com/NHSDigital/nhsuk-react-components/issues/52)
496+
- [#69: Unable to use ref attribute on some components](https://github.com/NHSDigital/nhsuk-react-components/issues/69)
497+
- [#71: Expose FormGroup component to consumers](https://github.com/NHSDigital/nhsuk-react-components/issues/71)
498+
- [#105: getHeadingsFromChildren forces use of string as table cell child](https://github.com/NHSDigital/nhsuk-react-components/issues/105)
499+
- [#166: SkipLink double jumps to first heading then #maincontent if disableDefaultBehaviour is not set](https://github.com/NHSDigital/nhsuk-react-components/issues/166)
500+
- [#174: Responsive tables and validation errors](https://github.com/NHSDigital/nhsuk-react-components/issues/174)
501+
- [#214: Hints and errors are not semantically associated with fieldsets](https://github.com/NHSDigital/nhsuk-react-components/issues/214)
502+
- [#215: Suggestion: remove all 'boolean' examples from storybook](https://github.com/NHSDigital/nhsuk-react-components/issues/215)
503+
- [#243: Use correct NHS.UK frontend JavaScript when rendered client-side](https://github.com/NHSDigital/nhsuk-react-components/issues/243)
504+
- [#244: Breaking change: remove default legend and label sizes or else change to l](https://github.com/NHSDigital/nhsuk-react-components/issues/244)
505+
- [#245: Fieldset incorrectly gets set in error when a child input is in error](https://github.com/NHSDigital/nhsuk-react-components/issues/245)
506+
- [#247: Date component uses label rather than fieldset with legend](https://github.com/NHSDigital/nhsuk-react-components/issues/247)
507+
- [#256: SkipLink does not work if intended target header is rerendered](https://github.com/NHSDigital/nhsuk-react-components/issues/256)
508+
- [#259: Remove pattern="[0-9]\*" from date inputs](https://github.com/NHSDigital/nhsuk-react-components/issues/259)
509+
- [#260: Allow custom component for button links](https://github.com/NHSDigital/nhsuk-react-components/issues/260)
510+
- [#265: Header logo is not labeled correctly when organisation info is provided](https://github.com/NHSDigital/nhsuk-react-components/issues/265)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nhsuk-react-components",
3-
"version": "6.0.0-beta.0",
3+
"version": "6.0.0-beta.1",
44
"license": "MIT",
55
"author": {
66
"name": "NHS England"

src/__tests__/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ describe('Index', () => {
5252
'Legend',
5353
'NavAZ',
5454
'Pagination',
55+
'Panel',
5556
'Radios',
5657
'RadiosContext',
5758
'ReadingWidth',

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 './panel/index.js';
78
export * from './summary-list/index.js';
89
export * from './table/index.js';
910
export * from './tabs/index.js';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import classNames from 'classnames';
2+
import { Children, forwardRef, type ComponentPropsWithoutRef, type FC } from 'react';
3+
import { HeadingLevel, type HeadingLevelProps } from '#components/utils/HeadingLevel.js';
4+
import { childIsOfComponentType } from '#util/types/TypeGuards.js';
5+
6+
export type PanelTitleProps = HeadingLevelProps;
7+
8+
const PanelTitle: FC<PanelTitleProps> = ({ children, headingLevel = 'h1', ...rest }) => (
9+
<HeadingLevel className="nhsuk-panel__title" headingLevel={headingLevel} {...rest}>
10+
{children}
11+
</HeadingLevel>
12+
);
13+
14+
export type PanelProps = ComponentPropsWithoutRef<'div'>;
15+
16+
const PanelComponent = forwardRef<HTMLDivElement, PanelProps>(
17+
({ children, className, ...rest }, forwardedRef) => {
18+
const items = Children.toArray(children);
19+
const title = items.find((child) => childIsOfComponentType(child, PanelTitle));
20+
const bodyItems = items.filter((child) => !childIsOfComponentType(child, PanelTitle));
21+
22+
return (
23+
<div className={classNames('nhsuk-panel', className)} ref={forwardedRef} {...rest}>
24+
{title}
25+
{bodyItems ? <div className="nhsuk-panel__body">{bodyItems}</div> : null}
26+
</div>
27+
);
28+
},
29+
);
30+
31+
PanelComponent.displayName = 'Panel';
32+
PanelComponent.displayName = 'Panel.Title';
33+
34+
export const Panel = Object.assign(PanelComponent, {
35+
Title: PanelTitle,
36+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { render } from '@testing-library/react';
2+
import { createRef } from 'react';
3+
import { Panel, type PanelTitleProps } from '..';
4+
import { renderClient, renderServer } from '#util/components';
5+
6+
describe('Panel', () => {
7+
it('matches snapshot', async () => {
8+
const { container } = await renderClient(
9+
<Panel>
10+
<Panel.Title>Booking complete</Panel.Title>
11+
We have sent you a confirmation email
12+
</Panel>,
13+
{ className: 'nhsuk-panel' },
14+
);
15+
16+
expect(container).toMatchSnapshot();
17+
});
18+
19+
it('matches snapshot (via server)', async () => {
20+
const { container, element } = await renderServer(
21+
<Panel>
22+
<Panel.Title>Booking complete</Panel.Title>
23+
We have sent you a confirmation email
24+
</Panel>,
25+
{ className: 'nhsuk-panel' },
26+
);
27+
28+
expect(container).toMatchSnapshot('server');
29+
30+
await renderClient(element, {
31+
className: 'nhsuk-panel',
32+
hydrate: true,
33+
container,
34+
});
35+
36+
expect(container).toMatchSnapshot('client');
37+
});
38+
39+
it('forwards refs', async () => {
40+
const ref = createRef<HTMLDivElement>();
41+
42+
const { modules } = await renderClient(
43+
<Panel ref={ref}>
44+
<Panel.Title>Booking complete</Panel.Title>
45+
We have sent you a confirmation email
46+
</Panel>,
47+
{ className: 'nhsuk-panel' },
48+
);
49+
50+
const [panelEl] = modules;
51+
52+
expect(ref.current).toBe(panelEl);
53+
expect(ref.current).toHaveClass('nhsuk-panel');
54+
});
55+
56+
it.each<PanelTitleProps | undefined>([
57+
undefined,
58+
{ headingLevel: 'h1' },
59+
{ headingLevel: 'h2' },
60+
{ headingLevel: 'h3' },
61+
{ headingLevel: 'h4' },
62+
])('renders heading level $headingLevel if specified', (props) => {
63+
const { container } = render(
64+
<Panel>
65+
<Panel.Title {...props}>Booking complete</Panel.Title>
66+
We have sent you a confirmation email
67+
</Panel>,
68+
);
69+
70+
const title = container.querySelector('.nhsuk-panel__title');
71+
72+
expect(title).toHaveProperty('tagName', props?.headingLevel?.toUpperCase() ?? 'H1');
73+
});
74+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`Panel matches snapshot (via server): client 1`] = `
4+
<div>
5+
<div
6+
class="nhsuk-panel"
7+
>
8+
<h1
9+
class="nhsuk-panel__title"
10+
>
11+
Booking complete
12+
</h1>
13+
<div
14+
class="nhsuk-panel__body"
15+
>
16+
We have sent you a confirmation email
17+
</div>
18+
</div>
19+
</div>
20+
`;
21+
22+
exports[`Panel matches snapshot (via server): server 1`] = `
23+
<div>
24+
<div
25+
class="nhsuk-panel"
26+
>
27+
<h1
28+
class="nhsuk-panel__title"
29+
>
30+
Booking complete
31+
</h1>
32+
<div
33+
class="nhsuk-panel__body"
34+
>
35+
We have sent you a confirmation email
36+
</div>
37+
</div>
38+
</div>
39+
`;
40+
41+
exports[`Panel matches snapshot 1`] = `
42+
<div>
43+
<div
44+
class="nhsuk-panel"
45+
>
46+
<h1
47+
class="nhsuk-panel__title"
48+
>
49+
Booking complete
50+
</h1>
51+
<div
52+
class="nhsuk-panel__body"
53+
>
54+
We have sent you a confirmation email
55+
</div>
56+
</div>
57+
</div>
58+
`;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Panel.js';
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { type Meta, type StoryObj } from '@storybook/react-vite';
2+
import { Panel } from '#components';
3+
4+
const meta: Meta<typeof Panel> = {
5+
title: 'Content Presentation/Panel',
6+
component: Panel,
7+
};
8+
export default meta;
9+
type Story = StoryObj<typeof Panel>;
10+
11+
export const StandardPanel: Story = {
12+
render: () => (
13+
<Panel>
14+
<Panel.Title>Booking complete</Panel.Title>
15+
We have sent you a confirmation email
16+
</Panel>
17+
),
18+
};
19+
20+
export const PanelWithCustomHeadingLevel: Story = {
21+
render: () => (
22+
<Panel>
23+
<Panel.Title headingLevel="h2">Booking complete</Panel.Title>
24+
We have sent you a confirmation email
25+
</Panel>
26+
),
27+
};

0 commit comments

Comments
 (0)