diff --git a/CHANGELOG.md b/CHANGELOG.md index 5141e92b8..9c08600f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/docs/upgrade-to-6.0.md b/docs/upgrade-to-6.0.md index b16402fff..8c09dc05b 100644 --- a/docs/upgrade-to-6.0.md +++ b/docs/upgrade-to-6.0.md @@ -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 + + Booking complete + We have sent you a confirmation email + +``` + +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 @@ -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: @@ -464,3 +489,22 @@ To align with NHS.UK frontend, the warning callout `WarningCallout.Label` compon

``` + +## 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) diff --git a/package.json b/package.json index 439fe4e1a..d0cc1bd23 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 1e30bc1fa..f20617293 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -52,6 +52,7 @@ describe('Index', () => { 'Legend', 'NavAZ', 'Pagination', + 'Panel', 'Radios', 'RadiosContext', 'ReadingWidth', diff --git a/src/components/content-presentation/index.ts b/src/components/content-presentation/index.ts index 973fd2744..55a38c078 100644 --- a/src/components/content-presentation/index.ts +++ b/src/components/content-presentation/index.ts @@ -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'; diff --git a/src/components/content-presentation/panel/Panel.tsx b/src/components/content-presentation/panel/Panel.tsx new file mode 100644 index 000000000..ce2d1f352 --- /dev/null +++ b/src/components/content-presentation/panel/Panel.tsx @@ -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 = ({ children, headingLevel = 'h1', ...rest }) => ( + + {children} + +); + +export type PanelProps = ComponentPropsWithoutRef<'div'>; + +const PanelComponent = forwardRef( + ({ 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 ( +
+ {title} + {bodyItems ?
{bodyItems}
: null} +
+ ); + }, +); + +PanelComponent.displayName = 'Panel'; +PanelComponent.displayName = 'Panel.Title'; + +export const Panel = Object.assign(PanelComponent, { + Title: PanelTitle, +}); diff --git a/src/components/content-presentation/panel/__tests__/Panel.test.tsx b/src/components/content-presentation/panel/__tests__/Panel.test.tsx new file mode 100644 index 000000000..1cfde1a6e --- /dev/null +++ b/src/components/content-presentation/panel/__tests__/Panel.test.tsx @@ -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( + + Booking complete + We have sent you a confirmation email + , + { className: 'nhsuk-panel' }, + ); + + expect(container).toMatchSnapshot(); + }); + + it('matches snapshot (via server)', async () => { + const { container, element } = await renderServer( + + Booking complete + We have sent you a confirmation email + , + { 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(); + + const { modules } = await renderClient( + + Booking complete + We have sent you a confirmation email + , + { className: 'nhsuk-panel' }, + ); + + const [panelEl] = modules; + + expect(ref.current).toBe(panelEl); + expect(ref.current).toHaveClass('nhsuk-panel'); + }); + + it.each([ + undefined, + { headingLevel: 'h1' }, + { headingLevel: 'h2' }, + { headingLevel: 'h3' }, + { headingLevel: 'h4' }, + ])('renders heading level $headingLevel if specified', (props) => { + const { container } = render( + + Booking complete + We have sent you a confirmation email + , + ); + + const title = container.querySelector('.nhsuk-panel__title'); + + expect(title).toHaveProperty('tagName', props?.headingLevel?.toUpperCase() ?? 'H1'); + }); +}); diff --git a/src/components/content-presentation/panel/__tests__/__snapshots__/Panel.test.tsx.snap b/src/components/content-presentation/panel/__tests__/__snapshots__/Panel.test.tsx.snap new file mode 100644 index 000000000..a2ae20ed3 --- /dev/null +++ b/src/components/content-presentation/panel/__tests__/__snapshots__/Panel.test.tsx.snap @@ -0,0 +1,58 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`Panel matches snapshot (via server): client 1`] = ` +
+
+

+ Booking complete +

+
+ We have sent you a confirmation email +
+
+
+`; + +exports[`Panel matches snapshot (via server): server 1`] = ` +
+
+

+ Booking complete +

+
+ We have sent you a confirmation email +
+
+
+`; + +exports[`Panel matches snapshot 1`] = ` +
+
+

+ Booking complete +

+
+ We have sent you a confirmation email +
+
+
+`; diff --git a/src/components/content-presentation/panel/index.ts b/src/components/content-presentation/panel/index.ts new file mode 100644 index 000000000..81c82d413 --- /dev/null +++ b/src/components/content-presentation/panel/index.ts @@ -0,0 +1 @@ +export * from './Panel.js'; diff --git a/stories/Content Presentation/Panel.stories.tsx b/stories/Content Presentation/Panel.stories.tsx new file mode 100644 index 000000000..d8f289cd7 --- /dev/null +++ b/stories/Content Presentation/Panel.stories.tsx @@ -0,0 +1,27 @@ +import { type Meta, type StoryObj } from '@storybook/react-vite'; +import { Panel } from '#components'; + +const meta: Meta = { + title: 'Content Presentation/Panel', + component: Panel, +}; +export default meta; +type Story = StoryObj; + +export const StandardPanel: Story = { + render: () => ( + + Booking complete + We have sent you a confirmation email + + ), +}; + +export const PanelWithCustomHeadingLevel: Story = { + render: () => ( + + Booking complete + We have sent you a confirmation email + + ), +};