From e8d301c4709dd849fb81125ce4bb6dacc020abe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Wed, 6 Dec 2023 10:50:12 +0100 Subject: [PATCH] fix(forms): add support for disabled states --- .../components/form-label/properties.mdx | 1 + .../FieldBlock/properties.mdx | 1 + .../src/components/form-label/FormLabel.tsx | 4 +- .../extensions/forms/Field/Number/Number.tsx | 1 + .../Field/Number/__tests__/Number.test.tsx | 14 ++ .../forms/Field/PhoneNumber/PhoneNumber.tsx | 1 + .../__tests__/PhoneNumber.test.tsx | 14 ++ .../extensions/forms/Field/String/String.tsx | 1 + .../Field/String/__tests__/String.test.tsx | 20 +- .../extensions/forms/Field/Toggle/Toggle.tsx | 2 + .../Field/Toggle/__tests__/Toggle.test.tsx | 172 +++++++++++++++++- .../forms/FieldBlock/FieldBlock.tsx | 3 + .../FieldBlock/__tests__/FieldBlock.test.tsx | 16 ++ 13 files changed, 242 insertions(+), 8 deletions(-) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/form-label/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/form-label/properties.mdx index faf150697ee..4a79af5f30b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/form-label/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/form-label/properties.mdx @@ -12,6 +12,7 @@ showTabs: true | `vertical` | _(optional)_ if set to `true`, will do the same as `label_direction` when set to **vertical**. | | `size` | _(optional)_ define one of the following [heading size](/uilib/elements/heading/): `medium` or `large`. | | `skeleton` | _(optional)_ if set to `true`, an overlaying skeleton with animation will be shown. | +| `disabled` | _(optional)_ if set to `true`, the label will behave as not interactive. | | `element` | _(optional)_ defines the HTML element used. Defaults to `label`. | | `innerRef` | _(optional)_ attach a React Ref to the inner label `element`. | | [Space](/uilib/layout/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. | diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/properties.mdx index b5cf81152c9..9bd65849e8a 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/properties.mdx @@ -10,4 +10,5 @@ showTabs: true | `contentsWidth` | `string` or `false` | _(optional)_ `small`, `medium`, `large`, `stretch` or `false` for predefined standard widths. | | `size` | `string` or `false` | _(optional)_ define one of the following [heading size](/uilib/elements/heading/): `medium` or `large`. | | `asFieldset` | `boolean` | _(optional)_ use `true` when you have several form elements. This way a `fieldset` with a `legend` is used. | +| `disabled` | `boolean` | _(optional)_ set `true` to make the inner [FormLabel](/uilib/components/form-label/) behave as disabled. | | `FieldProps` such as [Value.String-properties](/uilib/extensions/forms/create-component/Value/String/properties) | Various | _(optional)_ `FieldProps` properties. | diff --git a/packages/dnb-eufemia/src/components/form-label/FormLabel.tsx b/packages/dnb-eufemia/src/components/form-label/FormLabel.tsx index 15688a046e1..56d139b2960 100644 --- a/packages/dnb-eufemia/src/components/form-label/FormLabel.tsx +++ b/packages/dnb-eufemia/src/components/form-label/FormLabel.tsx @@ -31,14 +31,12 @@ export type FormLabelProps = { size?: 'basis' | 'medium' | 'large' id?: string skeleton?: boolean + disabled?: boolean label?: React.ReactNode vertical?: boolean srOnly?: boolean innerRef?: React.RefObject - /** Is not a part of HTMLLabelElement and not documented as of now */ - disabled?: boolean - /** @deprecated use forId instead */ for_id?: string /** @deprecated use srOnly instead */ diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Number/Number.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Number/Number.tsx index ca9c217b1ff..1cf5b28ba66 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Number/Number.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Number/Number.tsx @@ -209,6 +209,7 @@ function NumberComponent(props: Props) { info={info} warning={warning} error={error} + disabled={disabled} width={width === 'stretch' ? width : undefined} contentsWidth={width !== false ? width : undefined} {...pickSpacingProps(props)} diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx index fc448a2c737..1eda5fc1050 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx @@ -24,6 +24,20 @@ describe('Field.Number', () => { expect(screen.getByLabelText('Number label')).toBeInTheDocument() }) + it('should support disabled prop', () => { + const { rerender } = render( + + ) + + const labelElement = () => document.querySelector('label') + + expect(labelElement()).toHaveAttribute('disabled') + + rerender() + + expect(labelElement()).not.toHaveAttribute('disabled') + }) + it('renders autoComplete', () => { const { rerender } = render( diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx index 463b68ecb31..db5fa1a483e 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx @@ -238,6 +238,7 @@ function PhoneNumber(props: Props) { info={info} warning={warning} error={error} + disabled={disabled} {...pickSpacingProps(props)} > diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx index 7f9ee428e56..68cdf39facd 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx @@ -39,6 +39,20 @@ describe('Field.PhoneNumber', () => { expect(selectedItemElement.textContent).toBe('+47 Norge') }) + it('should support disabled prop', () => { + const { rerender } = render( + + ) + + const labelElement = () => document.querySelector('label') + + expect(labelElement()).toHaveAttribute('disabled') + + rerender() + + expect(labelElement()).not.toHaveAttribute('disabled') + }) + it('should change locale', () => { const { rerender } = render( diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/String/String.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/String/String.tsx index 44f63907079..680af07ea98 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/String/String.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/String/String.tsx @@ -159,6 +159,7 @@ function StringComponent(props: Props) { labelSecondary={labelSecondary ?? characterCounterElement} info={info} warning={warning} + disabled={disabled} error={error} width={width === 'stretch' ? width : undefined} contentsWidth={width !== false ? width : undefined} diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx index ea66cbd2a84..37b113de3a1 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx @@ -41,6 +41,11 @@ describe('Field.String', () => { ).toBeInTheDocument() }) + it('renders label', () => { + render() + expect(screen.getByLabelText('The label')).toBeInTheDocument() + }) + it('does not render placeholder when value is given', () => { render( @@ -50,9 +55,18 @@ describe('Field.String', () => { ).not.toBeInTheDocument() }) - it('renders label', () => { - render() - expect(screen.getByLabelText('The label')).toBeInTheDocument() + it('should support disabled prop', () => { + const { rerender } = render( + + ) + + const labelElement = () => document.querySelector('label') + + expect(labelElement()).toHaveAttribute('disabled') + + rerender() + + expect(labelElement()).not.toHaveAttribute('disabled') }) it('input is connected to label', () => { diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Toggle/Toggle.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Toggle/Toggle.tsx index e6e04f50a1d..7dbf8cbd3f6 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Toggle/Toggle.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Toggle/Toggle.tsx @@ -61,6 +61,7 @@ function Toggle(props: Props) { info, warning, error, + disabled, } const fieldBlockProps = { @@ -69,6 +70,7 @@ function Toggle(props: Props) { label, labelDescription, labelSecondary, + disabled, } const isOn = value === valueOn diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Toggle/__tests__/Toggle.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Toggle/__tests__/Toggle.test.tsx index 1501fbb4bb5..6161cac413d 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Toggle/__tests__/Toggle.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Toggle/__tests__/Toggle.test.tsx @@ -1,10 +1,178 @@ import React from 'react' -import { render } from '@testing-library/react' -import Toggle, { Props } from '..' +import { fireEvent, render } from '@testing-library/react' +import Toggle, { Props } from '../Toggle' describe('Field.Toggle', () => { it('should render with props', () => { const props: Props = { valueOn: 'checked', valueOff: 'unchecked' } render() }) + + it('should support disabled prop', () => { + const { rerender } = render( + + ) + + const labelElement = () => document.querySelector('label') + + expect(labelElement()).toHaveAttribute('disabled') + + rerender( + + ) + + expect(labelElement()).not.toHaveAttribute('disabled') + }) + + describe('variants', () => { + describe('button', () => { + it('should render correct HTML', () => { + const onChange = jest.fn() + + render( + + ) + + const element = document.querySelector( + '.dnb-toggle-button__button' + ) + + expect(element).toBeInTheDocument() + expect(element).toHaveAttribute('aria-pressed', 'true') + + fireEvent.click(element) + + expect(element).toHaveAttribute('aria-pressed', 'false') + expect(onChange).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenLastCalledWith('off') + + fireEvent.click(element) + + expect(element).toHaveAttribute('aria-pressed', 'true') + expect(onChange).toHaveBeenCalledTimes(2) + expect(onChange).toHaveBeenLastCalledWith('on') + }) + }) + + describe('buttons', () => { + it('should render correct HTML', () => { + const onChange = jest.fn() + + render( + + ) + + const [yesElement, noElement]: Array = + Array.from( + document.querySelectorAll('.dnb-toggle-button__button') + ) + + expect(yesElement).toHaveAttribute('aria-pressed', 'true') + expect(noElement).toHaveAttribute('aria-pressed', 'false') + + fireEvent.click(noElement) + + expect(yesElement).toHaveAttribute('aria-pressed', 'false') + expect(noElement).toHaveAttribute('aria-pressed', 'true') + expect(onChange).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenLastCalledWith('off') + + fireEvent.click(yesElement) + + expect(yesElement).toHaveAttribute('aria-pressed', 'true') + expect(noElement).toHaveAttribute('aria-pressed', 'false') + expect(onChange).toHaveBeenCalledTimes(2) + expect(onChange).toHaveBeenLastCalledWith('on') + }) + }) + + describe('checkbox-button', () => { + it('should render correct HTML', () => { + const onChange = jest.fn() + + render( + + ) + + const element = document.querySelector( + '.dnb-toggle-button__button .dnb-checkbox__input' + ) + + expect(element).toBeInTheDocument() + expect(element).toHaveAttribute('data-checked', 'true') + + fireEvent.click(element) + + expect(element).toHaveAttribute('data-checked', 'false') + expect(onChange).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenLastCalledWith('off') + + fireEvent.click(element) + + expect(element).toHaveAttribute('data-checked', 'true') + expect(onChange).toHaveBeenCalledTimes(2) + expect(onChange).toHaveBeenLastCalledWith('on') + }) + }) + + describe('checkbox', () => { + it('should render correct HTML', () => { + const onChange = jest.fn() + + render( + + ) + + const element = document.querySelector('.dnb-checkbox__input') + + expect(element).toBeInTheDocument() + expect(element).toBeChecked() + + fireEvent.click(element) + + expect(element).not.toBeChecked() + expect(onChange).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenLastCalledWith('off') + + fireEvent.click(element) + + expect(element).toBeChecked() + expect(onChange).toHaveBeenCalledTimes(2) + expect(onChange).toHaveBeenLastCalledWith('on') + }) + }) + }) }) diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx index db8c53fb2b6..432f55e3ace 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx @@ -15,6 +15,7 @@ export type Props = Pick< | 'info' | 'warning' | 'error' + | 'disabled' > & { forId?: string contentClassName?: string @@ -43,6 +44,7 @@ function FieldBlock(props: Props) { info, warning, error: errorProp, + disabled, width, contentsWidth, size, @@ -151,6 +153,7 @@ function FieldBlock(props: Props) { forId={enableFieldset ? undefined : forId} space={{ top: 0, bottom: 'x-small' }} size={size} + disabled={disabled} > {children} diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx index 449c0b73988..88d0558271e 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx @@ -31,6 +31,22 @@ describe('FieldBlock', () => { expect(element.classList).toContain('dnb-space__top--x-large') }) + it('should support disabled prop', () => { + const { rerender } = render( + + content + + ) + + const labelElement = () => document.querySelector('label') + + expect(labelElement()).toHaveAttribute('disabled') + + rerender(content) + + expect(labelElement()).not.toHaveAttribute('disabled') + }) + it('should support heading size prop', () => { const { rerender } = render(