Skip to content

Commit

Permalink
Allow consumers to disable fieldset rendering for single inputs withi…
Browse files Browse the repository at this point in the history
…n a group
  • Loading branch information
jakeb-nhs committed Oct 7, 2024
1 parent 6d09861 commit ab1d9c4
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 52 deletions.
46 changes: 26 additions & 20 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,66 @@
# NHS.UK React components

## Unreleased
## 4.1.4 - 07 October 2024

:new: **New features**

- Add ability to disable rendering of fieldset elements if they contain single inputs. PR [#257](https://github.com/NHSDigital/nhsuk-react-components/pull/257)

## 4.1.3 - 23 September 2024

:wrench: **Fixes**
* Remove the unnecessary aria-labelledby tags from radio items. PR [#253](https://github.com/NHSDigital/nhsuk-react-components/pull/253)

- Remove the unnecessary aria-labelledby tags from radio items. PR [#253](https://github.com/NHSDigital/nhsuk-react-components/pull/253)

## 4.1.2 - 3 September 2024

:wrench: **Fixes**

* Fix issues with SkipLink (always set the href) and bring into line with NHSUK frontend. PR [#248](https://github.com/NHSDigital/nhsuk-react-components/pull/248)
- Fix issues with SkipLink (always set the href) and bring into line with NHSUK frontend. PR [#248](https://github.com/NHSDigital/nhsuk-react-components/pull/248)

## 4.1.1 - 9 August 2024

:wrench: **Fixes**

* Remove the unnecessary aria-labelledby tags from DateInput fields. PR [#246](https://github.com/NHSDigital/nhsuk-react-components/pull/246)
- Remove the unnecessary aria-labelledby tags from DateInput fields. PR [#246](https://github.com/NHSDigital/nhsuk-react-components/pull/246)

## 4.1.0 - 11 June 2024

:wrench: **Fixes**

* Add js shims for buttons. PR [#231](https://github.com/NHSDigital/nhsuk-react-components/pull/231), Fixes [#218](https://github.com/NHSDigital/nhsuk-react-components/issues/218)
* Fix errors not being linked to inputs. PR [#230](https://github.com/NHSDigital/nhsuk-react-components/pull/230), Fixes [#227](https://github.com/NHSDigital/nhsuk-react-components/issues/227)
* Fix inputs incorrectly using `aria-labelledby`. PR [#230](https://github.com/NHSDigital/nhsuk-react-components/pull/230), Fixes [#212](https://github.com/NHSDigital/nhsuk-react-components/issues/212)
* Update Storybook docs for several components.
- Add js shims for buttons. PR [#231](https://github.com/NHSDigital/nhsuk-react-components/pull/231), Fixes [#218](https://github.com/NHSDigital/nhsuk-react-components/issues/218)
- Fix errors not being linked to inputs. PR [#230](https://github.com/NHSDigital/nhsuk-react-components/pull/230), Fixes [#227](https://github.com/NHSDigital/nhsuk-react-components/issues/227)
- Fix inputs incorrectly using `aria-labelledby`. PR [#230](https://github.com/NHSDigital/nhsuk-react-components/pull/230), Fixes [#212](https://github.com/NHSDigital/nhsuk-react-components/issues/212)
- Update Storybook docs for several components.

:new: **New features**

* Added a CHANGELOG to keep track of changes between releases. [Keep a changelog](https://keepachangelog.com)
* Added support for `preventDoubleClick` debouncing on buttons. PR [#231](https://github.com/NHSDigital/nhsuk-react-components/pull/231)
* Error summaries now automatically set role, tabindex, and aria-labelledby. PR [#229](https://github.com/NHSDigital/nhsuk-react-components/pull/237), Fixes [#228](https://github.com/NHSDigital/nhsuk-react-components/issues/229)
* Storybook link in readme now points to latest version. PR [#226](https://github.com/NHSDigital/nhsuk-react-components/pull/226)
- Added a CHANGELOG to keep track of changes between releases. [Keep a changelog](https://keepachangelog.com)
- Added support for `preventDoubleClick` debouncing on buttons. PR [#231](https://github.com/NHSDigital/nhsuk-react-components/pull/231)
- Error summaries now automatically set role, tabindex, and aria-labelledby. PR [#229](https://github.com/NHSDigital/nhsuk-react-components/pull/237), Fixes [#228](https://github.com/NHSDigital/nhsuk-react-components/issues/229)
- Storybook link in readme now points to latest version. PR [#226](https://github.com/NHSDigital/nhsuk-react-components/pull/226)

## 4.0.2 - 21 May 2024

:wrench: **Fixes**

* Fix error message role by @edwardhorsford in [#219](https://github.com/NHSDigital/nhsuk-react-components/pull/219)
- Fix error message role by @edwardhorsford in [#219](https://github.com/NHSDigital/nhsuk-react-components/pull/219)

## 4.0.1 - 20 May 2024

:wrench: **Fixes**

* Fix issue with the footer copyright not being rendered in the correct location if there are multiple link columns by @jakeb-nhs in [#223](https://github.com/NHSDigital/nhsuk-react-components/pull/223)
- Fix issue with the footer copyright not being rendered in the correct location if there are multiple link columns by @jakeb-nhs in [#223](https://github.com/NHSDigital/nhsuk-react-components/pull/223)

## 4.0.0 - 15 May 2024

This version updates nhsuk-frontend to version 8.

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

* Migrate enzyme to react-testing-library by @JoshuaBates-NHS in [#198](https://github.com/NHSDigital/nhsuk-react-components/pull/198)
* Allow support for module directives in build process by @JoshuaBates-NHS in [#199](https://github.com/NHSDigital/nhsuk-react-components/pull/199)
* Update modified components since NHS UK frontend v5 by @jakeb-nhs in [#197](https://github.com/NHSDigital/nhsuk-react-components/pull/197)
* Add new components since NHS UK frontend v5 by @jakeb-nhs in [#202](https://github.com/NHSDigital/nhsuk-react-components/pull/202)
* Migrate some patterns to components, rework removed components from frontend v8 by @jakeb-nhs in [#203](https://github.com/NHSDigital/nhsuk-react-components/pull/203)
* Improve unit test coverage by @jakeb-nhs in [#204](https://github.com/NHSDigital/nhsuk-react-components/pull/204)
- Migrate enzyme to react-testing-library by @JoshuaBates-NHS in [#198](https://github.com/NHSDigital/nhsuk-react-components/pull/198)
- Allow support for module directives in build process by @JoshuaBates-NHS in [#199](https://github.com/NHSDigital/nhsuk-react-components/pull/199)
- Update modified components since NHS UK frontend v5 by @jakeb-nhs in [#197](https://github.com/NHSDigital/nhsuk-react-components/pull/197)
- Add new components since NHS UK frontend v5 by @jakeb-nhs in [#202](https://github.com/NHSDigital/nhsuk-react-components/pull/202)
- Migrate some patterns to components, rework removed components from frontend v8 by @jakeb-nhs in [#203](https://github.com/NHSDigital/nhsuk-react-components/pull/203)
- Improve unit test coverage by @jakeb-nhs in [#204](https://github.com/NHSDigital/nhsuk-react-components/pull/204)
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ class GetStartedButton extends PureComponent {

## Upgrading

* [Upgrading to 1.0](/docs/upgrade-to-1.0.md)
* [Upgrading to 2.0](/docs/upgrade-to-2.0.md)
* [Upgrading to 3.0](/docs/upgrade-to-3.0.md)
* [Upgrading to 4.0](/docs/upgrade-to-4.0.md)
- [Upgrading to 1.0](/docs/upgrade-to-1.0.md)
- [Upgrading to 2.0](/docs/upgrade-to-2.0.md)
- [Upgrading to 3.0](/docs/upgrade-to-3.0.md)
- [Upgrading to 4.0](/docs/upgrade-to-4.0.md)

## Maintainers

Expand All @@ -53,7 +53,7 @@ class GetStartedButton extends PureComponent {
- Kevin Kuszyk ([GitHub](https://github.com/kevinkuszyk))
- Kai Spencer ([GitHub](https://github.com/KaiSpencer))
- Ed Horsford ([GitHub](https://github.com/edwardhorsford))
- Jake Barton ([GitHub](https://github.com/jakeb-nhs))
- Jake Barron ([GitHub](https://github.com/jakeb-nhs))

## Preparing releases

Expand Down
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": "4.1.2",
"version": "4.1.4",
"author": {
"name": "NHS England"
},
Expand Down
54 changes: 39 additions & 15 deletions src/components/form-elements/fieldset/Fieldset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classNames from 'classnames';
import { NHSUKSize } from '@util/types/NHSUKTypes';
import HeadingLevel, { HeadingLevelType } from '@util/HeadingLevel';
import FieldsetContext, { IFieldsetContext } from './FieldsetContext';
import { InputType, RegisteredComponent } from '@util/types/FormTypes';

interface LegendProps extends Omit<HTMLProps<HTMLLegendElement>, 'size'> {
isPageHeading?: boolean;
Expand Down Expand Up @@ -42,10 +43,17 @@ const Legend: FC<LegendProps> = ({
interface FieldsetProps extends HTMLProps<HTMLFieldSetElement> {
fieldsetRef?: MutableRefObject<HTMLFieldSetElement | null>;
disableErrorLine?: boolean;
disableFieldsetRenderWithSingleFormElements?: boolean;
}

const FieldSet = ({ className, disableErrorLine, fieldsetRef, ...rest }: FieldsetProps) => {
const [registeredComponents, setRegisteredComponents] = useState<string[]>([]);
const FieldSet = ({
className,
disableErrorLine,
fieldsetRef,
disableFieldsetRenderWithSingleFormElements,
...rest
}: FieldsetProps) => {
const [registeredComponents, setRegisteredComponents] = useState<RegisteredComponent[]>([]);
const [erroredComponents, setErroredComponents] = useState<string[]>([]);

const passError = (componentId: string, error: boolean): void => {
Expand All @@ -59,14 +67,20 @@ const FieldSet = ({ className, disableErrorLine, fieldsetRef, ...rest }: Fieldse
}
};

const registerComponent = (componentId: string, deregister = false): void => {
let newComponents = [...registeredComponents];
if (deregister) {
newComponents = newComponents.filter((id) => id !== componentId);
} else if (!registeredComponents.includes(componentId)) {
newComponents = [...newComponents, componentId];
}
setRegisteredComponents(newComponents);
const registerComponent = (
componentId: string,
inputType: InputType,
deregister = false,
): void => {
setRegisteredComponents((prevComponents) => {
let newComponents = [...prevComponents];
if (deregister) {
newComponents = newComponents.filter((component) => component.elementId !== componentId);
} else if (!prevComponents.map((c) => c.elementId).includes(componentId)) {
newComponents = [...newComponents, { elementId: componentId, inputType }];
}
return newComponents;
});
};

const contextValue: IFieldsetContext = useMemo(() => {
Expand All @@ -78,6 +92,12 @@ const FieldSet = ({ className, disableErrorLine, fieldsetRef, ...rest }: Fieldse
}, [registerComponent, passError]);

const containsFormElements = registeredComponents.length > 0;
const containsMultipleFormElements =
registeredComponents.length > 1 ||
registeredComponents.some(
(rc) =>
rc.inputType === 'checkboxes' || rc.inputType === 'dateinput' || rc.inputType === 'radios',
);
const containsError = erroredComponents.length > 0;

return (
Expand All @@ -88,11 +108,15 @@ const FieldSet = ({ className, disableErrorLine, fieldsetRef, ...rest }: Fieldse
'nhsuk-form-group--error': disableErrorLine ? false : containsError,
})}
>
<fieldset
className={classNames('nhsuk-fieldset', className)}
ref={fieldsetRef}
{...rest}
/>
{containsMultipleFormElements || !disableFieldsetRenderWithSingleFormElements ? (
<fieldset
className={classNames('nhsuk-fieldset', className)}
ref={fieldsetRef}
{...rest}
/>
) : (
rest.children
)}
</div>
) : (
<fieldset className={classNames('nhsuk-fieldset', className)} ref={fieldsetRef} {...rest} />
Expand Down
3 changes: 2 additions & 1 deletion src/components/form-elements/fieldset/FieldsetContext.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { InputType } from '@util/types/FormTypes';
import { createContext } from 'react';

export type IFieldsetContext = {
isFieldset: boolean;
passError: (componentId: string, error: boolean) => void;
registerComponent: (componentId: string, deregister?: boolean) => void;
registerComponent: (componentId: string, inputType: InputType, deregister?: boolean) => void;
};

const FieldsetContext = createContext<IFieldsetContext>({
Expand Down
54 changes: 54 additions & 0 deletions src/components/form-elements/fieldset/__tests__/Fieldset.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import React from 'react';
import { render } from '@testing-library/react';
import Fieldset from '../';
import TextInput from '@components/form-elements/text-input';
import Radios from '@components/form-elements/radios';
import Checkboxes from '@components/form-elements/checkboxes';
import DateInput from '@components/form-elements/date-input';
import Textarea from '@components/form-elements/textarea';
import Select from '@components/form-elements/select';

describe('Fieldset', () => {
it('matches snapshot', () => {
Expand All @@ -26,6 +31,55 @@ describe('Fieldset', () => {
expect(container.firstChild).toHaveClass('nhsuk-form-group');
});

it.each`
disableFieldsetRenderProp | formElements | expectFieldset
${false} | ${[TextInput]} | ${true}
${true} | ${[TextInput]} | ${false}
${false} | ${[TextInput, TextInput]} | ${true}
${true} | ${[TextInput, TextInput]} | ${true}
${false} | ${[Textarea]} | ${true}
${true} | ${[Textarea]} | ${false}
${false} | ${[Textarea, Textarea]} | ${true}
${true} | ${[Textarea, Textarea]} | ${true}
${false} | ${[Select]} | ${true}
${true} | ${[Select]} | ${false}
${false} | ${[Select, Select]} | ${true}
${true} | ${[Select, Select]} | ${true}
${false} | ${[Radios]} | ${true}
${true} | ${[Radios]} | ${true}
${false} | ${[Checkboxes]} | ${true}
${true} | ${[Checkboxes]} | ${true}
${false} | ${[DateInput]} | ${true}
${true} | ${[DateInput]} | ${true}
`(
'When the disableFieldsetRender prop is $disableFieldsetRenderProp and form elements rendered are $formElements, then whether to expect the fieldset being rendered is $expectFieldset',
({
disableFieldsetRenderProp,
formElements,
expectFieldset,
}: {
disableFieldsetRenderProp: boolean;
formElements: React.FC[];
expectFieldset: boolean;
}) => {
const { container } = render(
<Fieldset disableFieldsetRenderWithSingleFormElements={disableFieldsetRenderProp}>
{formElements.map((FormElement, index) => (
<FormElement key={index} />
))}
</Fieldset>,
);

expect(container.firstChild).toHaveClass('nhsuk-form-group');

if (expectFieldset) {
expect(container.firstChild?.firstChild).toHaveClass('nhsuk-fieldset');
} else {
expect(container.firstChild?.firstChild).not.toHaveClass('nhsuk-fieldset');
}
},
);

describe('Fieldset.Legend', () => {
it('matches snapshot', () => {
const { container } = render(<Fieldset.Legend>Text</Fieldset.Legend>);
Expand Down
13 changes: 5 additions & 8 deletions src/util/FormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import HintText from '../components/form-elements/hint-text/HintText';
import ErrorMessage from '../components/form-elements/error-message/ErrorMessage';
import { generateRandomID } from './RandomID';
import Label from '../components/form-elements/label/Label';
import { FormElementProps } from './types/FormTypes';
import { FormElementProps, InputType } from './types/FormTypes';
import FieldsetContext, {
IFieldsetContext,
} from '../components/form-elements/fieldset/FieldsetContext';
Expand Down Expand Up @@ -33,7 +33,7 @@ type FormElementRenderProps<T> = Omit<T, ExcludedProps> & {

export type FormGroupProps<T> = FormElementProps & {
children: (props: FormElementRenderProps<T>) => ReactNode;
inputType: 'input' | 'radios' | 'select' | 'checkboxes' | 'dateinput' | 'textarea';
inputType: InputType;
};

const FormGroup = <T extends BaseFormElementRenderProps>(props: FormGroupProps<T>): JSX.Element => {
Expand Down Expand Up @@ -62,10 +62,7 @@ const FormGroup = <T extends BaseFormElementRenderProps>(props: FormGroupProps<T
const errorID = `${elementID}--error-message`;
const hintID = `${elementID}--hint`;

const ariaDescribedBy = [
hint ? hintID : undefined,
error ? errorID : undefined,
].filter(Boolean);
const ariaDescribedBy = [hint ? hintID : undefined, error ? errorID : undefined].filter(Boolean);

const childProps = {
'aria-describedby': ariaDescribedBy.join(' ') || undefined,
Expand All @@ -82,8 +79,8 @@ const FormGroup = <T extends BaseFormElementRenderProps>(props: FormGroupProps<T
}, [elementID, error, isFieldset]);

useEffect(() => {
registerComponent(elementID);
return () => registerComponent(elementID, true);
registerComponent(elementID, inputType);
return () => registerComponent(elementID, inputType, true);
}, []);

const { className: formGroupClassName, ...formGroupRestProps } = formGroupProps || {};
Expand Down
7 changes: 7 additions & 0 deletions src/util/types/FormTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ export interface FormElementProps {
id?: string;
name?: string;
}

export type InputType = 'input' | 'radios' | 'select' | 'checkboxes' | 'dateinput' | 'textarea';

export type RegisteredComponent = {
inputType: InputType;
elementId: string;
};
7 changes: 5 additions & 2 deletions stories/Form Elements/Fieldset.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const meta: Meta<typeof Fieldset> = {
component: Fieldset,
args: {
children: 'What is your address?',
disableFieldsetRenderWithSingleFormElements: false,
},
};
export default meta;
Expand All @@ -58,8 +59,10 @@ export const WithCustomLegendSize: Story = {
};

export const WithFormElement: Story = {
render: () => (
<Fieldset>
render: (args) => (
<Fieldset
disableFieldsetRenderWithSingleFormElements={args.disableFieldsetRenderWithSingleFormElements}
>
<Fieldset.Legend size="m">Input below</Fieldset.Legend>
<TextInput id="test-input" />
</Fieldset>
Expand Down

0 comments on commit ab1d9c4

Please sign in to comment.