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
4 changes: 4 additions & 0 deletions docs/upgrade-to-6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ The updated [header](https://service-manual.nhs.uk/design-system/components/head
- 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

#### Smaller versions of radio buttons and checkboxes

You can now use smaller versions of the [radios](https://service-manual.nhs.uk/design-system/components/radios) and [checkboxes](https://service-manual.nhs.uk/design-system/components/checkboxes) components by adding the `small` prop.

### Panel component

The [panel](https://service-manual.nhs.uk/design-system/components/panel) component from NHS.UK frontend v9.3.0 has been added:
Expand Down
11 changes: 11 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ export default defineConfig([
'import/no-named-as-default-member': 'off',
'import/no-unresolved': 'off',
'import/no-unused-modules': 'off',

// Prefer rules that are type aware
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
},
settings: {
'import/resolver': {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"jest-axe": "^10.0.0",
"jest-environment-jsdom": "^30.2.0",
"lodash": "^4.17.21",
"nhsuk-frontend": "^10.0.0",
"nhsuk-frontend": "^10.1.0",
"outdent": "^0.8.0",
"prettier": "^3.6.2",
"react": "^19.2.0",
Expand All @@ -124,7 +124,7 @@
},
"peerDependencies": {
"classnames": ">=2.5.0",
"nhsuk-frontend": ">=10.0.0 <11.0.0",
"nhsuk-frontend": ">=10.1.0 <11.0.0",
"react": ">=18.2.0",
"react-dom": ">=18.2.0",
"tslib": ">=2.8.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export const CharacterCount = forwardRef<HTMLTextAreaElement, CharacterCountProp
...formGroupProps,
'className': classNames('nhsuk-character-count', formGroupProps?.className),
'data-module': 'nhsuk-character-count',
'data-maxlength': maxLength,
'data-maxwords': maxWords,
'data-threshold': threshold,
'data-maxlength': maxLength?.toString(),
'data-maxwords': maxWords?.toString(),
'data-threshold': threshold?.toString(),
'ref': moduleRef,
}}
{...rest}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('Character Count', () => {
const fieldRef = createRef<HTMLTextAreaElement>();

const { container } = await renderClient(
<CharacterCount formGroupProps={{ ref: groupRef }} ref={fieldRef} />,
<CharacterCount formGroupProps={{ ref: groupRef }} ref={fieldRef} maxLength={200} />,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This change seems unrelated but it was necessary for the NHS.UK frontend v10.1 update for smaller radios/checkboxes. Character counts now validate their minimum required config

{ moduleName: 'nhsuk-character-count' },
);

Expand Down
10 changes: 7 additions & 3 deletions src/components/form-elements/checkboxes/Checkboxes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface CheckboxesProps
extends ComponentPropsWithoutRef<'div'>,
Omit<FormElementProps, 'label' | 'labelProps'> {
idPrefix?: string;
small?: boolean;
}

const CheckboxesComponent = forwardRef<HTMLDivElement, CheckboxesProps>((props, forwardedRef) => {
Expand Down Expand Up @@ -71,8 +72,7 @@ const CheckboxesComponent = forwardRef<HTMLDivElement, CheckboxesProps>((props,

return (
<FormGroup<CheckboxesProps> inputType="checkboxes" {...rest}>
{/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
{({ className, name, id, idPrefix, error, ...restRenderProps }) => {
{({ className, small, name, id, idPrefix, error, ...restRenderProps }) => {
resetCheckboxIds();
const contextValue: ICheckboxesContext = {
name,
Expand All @@ -82,7 +82,11 @@ const CheckboxesComponent = forwardRef<HTMLDivElement, CheckboxesProps>((props,
};
return (
<div
className={classNames('nhsuk-checkboxes', className)}
className={classNames(
'nhsuk-checkboxes',
{ 'nhsuk-checkboxes--small': small },
className,
)}
data-module="nhsuk-checkboxes"
id={id}
ref={moduleRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { CheckboxesContext, type ICheckboxesContext } from '../CheckboxesContext.js';
import { HintText } from '#components/form-elements/hint-text/index.js';
import { Label } from '#components/form-elements/label/index.js';
import { type FormElementProps, type HTMLAttributesWithData } from '#util/types/index.js';
import { type ComponentPropsWithDataAttributes, type FormElementProps } from '#util/types/index.js';

export interface CheckboxesItemProps
extends ComponentPropsWithoutRef<'input'>,
Expand Down Expand Up @@ -54,10 +54,10 @@ export const CheckboxesItem = forwardRef<HTMLInputElement, CheckboxesItemProps>(

useEffect(() => () => unleaseReference(checkboxReference));

const inputProps: HTMLAttributesWithData<HTMLInputElement> = rest;
const inputProps: ComponentPropsWithDataAttributes<'input'> = rest;

if (exclusive) {
inputProps['data-checkbox-exclusive'] = true;
inputProps['data-checkbox-exclusive'] = 'true';
}

return (
Expand Down
15 changes: 11 additions & 4 deletions src/components/form-elements/radios/Radios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { type FormElementProps } from '#util/types/FormTypes.js';
export interface RadiosProps
extends ComponentPropsWithoutRef<'div'>,
Omit<FormElementProps, 'label' | 'labelProps'> {
inline?: boolean;
idPrefix?: string;
inline?: boolean;
small?: boolean;
}

const RadiosComponent = forwardRef<HTMLDivElement, RadiosProps>((props, forwardedRef) => {
Expand Down Expand Up @@ -78,8 +79,7 @@ const RadiosComponent = forwardRef<HTMLDivElement, RadiosProps>((props, forwarde

return (
<FormGroup<RadiosProps> inputType="radios" {...rest}>
{/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
{({ className, inline, name, id, error, ...restRenderProps }) => {
{({ className, inline, small, name, id, error, ...restRenderProps }) => {
resetRadioIds();
const contextValue: IRadiosContext = {
getRadioId: (reference) => getRadioId(id, reference),
Expand All @@ -92,7 +92,14 @@ const RadiosComponent = forwardRef<HTMLDivElement, RadiosProps>((props, forwarde

return (
<div
className={classNames('nhsuk-radios', { 'nhsuk-radios--inline': inline }, className)}
className={classNames(
'nhsuk-radios',
{
'nhsuk-radios--inline': inline,
'nhsuk-radios--small': small,
},
className,
)}
data-module="nhsuk-radios"
id={id}
ref={moduleRef}
Expand Down
4 changes: 0 additions & 4 deletions src/components/utils/__tests__/FormGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ describe('FormGroup', () => {
it('string component', async () => {
const { container } = await renderClient(
<FormGroup<TextInputProps> inputType="input" error="Oh no there's an error!">
{/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
{({ error, ...rest }) => <input {...rest} />}
</FormGroup>,
{ className: 'nhsuk-form-group' },
Expand All @@ -245,7 +244,6 @@ describe('FormGroup', () => {
hint="This is a hint"
label="Form Label"
>
{/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
{({ error, ...rest }) => <input {...rest} />}
</FormGroup>
</main>,
Expand All @@ -264,7 +262,6 @@ describe('FormGroup', () => {
error="This is an error"
hint="This is a hint"
>
{/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
{({ error, ...rest }) => <input {...rest} />}
</FormGroup>,
{ className: 'nhsuk-form-group' },
Expand All @@ -279,7 +276,6 @@ describe('FormGroup', () => {
it('should have no aria-describedby when there is no hint or label', async () => {
const { container } = await renderClient(
<FormGroup<TextInputProps> inputType="input">
{/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
{({ error, ...rest }) => <input {...rest} />}
</FormGroup>,
{ className: 'nhsuk-form-group' },
Expand Down
10 changes: 2 additions & 8 deletions src/util/types/FormTypes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { type ComponentPropsWithRef } from 'react';

import { type ErrorMessageProps } from '#components/form-elements/error-message/index.js';
import { type FieldsetProps } from '#components/form-elements/fieldset/index.js';
import { type HintTextProps } from '#components/form-elements/hint-text/index.js';
import { type LabelProps } from '#components/form-elements/label/index.js';
import { type LegendProps } from '#components/form-elements/legend/index.js';
import { type ComponentPropsWithDataAttributes } from '#util/types/NHSUKTypes.js';

export interface FormElementProps {
'fieldsetProps'?: FieldsetProps;
Expand All @@ -16,12 +15,7 @@ export interface FormElementProps {
'errorProps'?: ErrorMessageProps;
'hint'?: string;
'hintProps'?: HintTextProps;
'formGroupProps'?: ComponentPropsWithRef<'div'> & {
'data-module'?: string;
'data-maxlength'?: number;
'data-maxwords'?: number;
'data-threshold'?: number;
};
'formGroupProps'?: ComponentPropsWithDataAttributes<'div'>;
'id'?: string;
'name'?: string;
'aria-describedby'?: string;
Expand Down
6 changes: 3 additions & 3 deletions src/util/types/NHSUKTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type HTMLAttributes } from 'react';
import { type ComponentProps, type ElementType } from 'react';

export type NHSUKSize = 's' | 'm' | 'l' | 'xl';

Expand All @@ -16,6 +16,6 @@ export type ColWidth =
| 'one-third'
| 'one-quarter';

export type HTMLAttributesWithData<T> = HTMLAttributes<T> & {
[key: `data-${string}`]: unknown;
export type ComponentPropsWithDataAttributes<T extends ElementType> = ComponentProps<T> & {
[key: `data-${string}`]: string | undefined;
};
18 changes: 18 additions & 0 deletions stories/Form Elements/Checkboxes.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ export const WithHintText: Story = {
),
};

export const Small: Story = {
args: {
...Standard.args,
legendProps: { isPageHeading: true, size: 'm' },
small: true,
},
render: Standard.render,
};

export const SmallWithHintText: Story = {
args: {
...WithHintText.args,
legendProps: { isPageHeading: true, size: 'm' },
small: true,
},
render: WithHintText.render,
};

export const WithDisabledItem: Story = {
render: (args) => (
<Checkboxes {...args}>
Expand Down
49 changes: 49 additions & 0 deletions stories/Form Elements/Radios.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,55 @@ export const RadiosWithHintsOnItems: Story = {
),
};

export const SmallRadios: Story = {
args: {
...StandardRadios.args,
legendProps: { isPageHeading: true, size: 'm' },
idPrefix: 'small',
small: true,
},
render: StandardRadios.render,
};

export const SmallInlineRadios: Story = {
args: {
...InlineRadios.args,
legendProps: { isPageHeading: true, size: 'm' },
small: true,
},
render: InlineRadios.render,
};

export const SmallRadiosWithConditionalContent: Story = {
args: {
...RadiosWithConditionalContent.args,
legendProps: { isPageHeading: true, size: 'm' },
idPrefix: 'small-conditional',
small: true,
},
render: RadiosWithConditionalContent.render,
};

export const SmallRadiosWithADivider: Story = {
args: {
...RadiosWithADivider.args,
legendProps: { isPageHeading: true, size: 'm' },
idPrefix: 'small-divider',
small: true,
},
render: RadiosWithADivider.render,
};

export const SmallRadiosWithHintsOnItems: Story = {
args: {
...RadiosWithHintsOnItems.args,
legendProps: { isPageHeading: true, size: 'm' },
idPrefix: 'small-hints',
small: true,
},
render: RadiosWithHintsOnItems.render,
};

export const DisabledRadios: Story = {
args: {
idPrefix: 'disabled',
Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8464,10 +8464,10 @@ __metadata:
languageName: node
linkType: hard

"nhsuk-frontend@npm:^10.0.0":
version: 10.0.0
resolution: "nhsuk-frontend@npm:10.0.0"
checksum: 10c0/5a24cc7b56188d18bbd86c040e39b420546451f1b955eed176f4acd5f3b75d6ec5eef86b8ab970cff1b4741f2f599debae03bf4be75c3f6a903cfce205e88501
"nhsuk-frontend@npm:^10.1.0":
version: 10.1.0
resolution: "nhsuk-frontend@npm:10.1.0"
checksum: 10c0/adf8ccc240c3514b865ae361b4e29f7ad11051f5c4585970cbd2580f61dbe5f1818d53f3b8eef3825aaf72f9ea3df647f4b0cc57d60092a3edc7583665374f91
languageName: node
linkType: hard

Expand Down Expand Up @@ -8514,7 +8514,7 @@ __metadata:
jest-axe: "npm:^10.0.0"
jest-environment-jsdom: "npm:^30.2.0"
lodash: "npm:^4.17.21"
nhsuk-frontend: "npm:^10.0.0"
nhsuk-frontend: "npm:^10.1.0"
outdent: "npm:^0.8.0"
prettier: "npm:^3.6.2"
react: "npm:^19.2.0"
Expand All @@ -8530,7 +8530,7 @@ __metadata:
vite-tsconfig-paths: "npm:^5.1.4"
peerDependencies:
classnames: ">=2.5.0"
nhsuk-frontend: ">=10.0.0 <11.0.0"
nhsuk-frontend: ">=10.1.0 <11.0.0"
react: ">=18.2.0"
react-dom: ">=18.2.0"
tslib: ">=2.8.0"
Expand Down