diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/Examples.tsx index c3dcd8d075c..191a9e463be 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/Examples.tsx @@ -538,186 +538,6 @@ export const RadioNestingWithLogic = () => ( ) -export const RadioNestingAdvanced = () => ( - - - - - - - value === 'first', - }} - animate - > - - - - - - - value === 'first', - }} - animate - > - - - - - - - - value === 'second', - }} - animate - > - - - - - - - value === 'first', - }} - animate - > - - - - - - - - value === 'third', - }} - animate - > - - - - - - - value === 'first', - }} - animate - > - - - - - - - - - - - - - -) - // - Button export const ButtonEmpty = () => ( diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/demos.mdx index 3af45f23184..44af5827822 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/demos.mdx @@ -130,10 +130,6 @@ You can nest other fields and show them based on your desired logic. -#### Radio nesting advanced - - - #### Radio button with a path to populate the data 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 ef1bb5b14b0..57ba305ab9b 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 @@ -4,11 +4,11 @@ showTabs: true import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' -import { fieldBlockProperties } from '@dnb/eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs' +import { FieldBlockProperties } from '@dnb/eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs' ## Properties - + ## Translations diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/properties.mdx index cae26b66b72..2f6f1b63ac4 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/properties.mdx @@ -4,7 +4,7 @@ showTabs: true import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' -import { fieldBlockProperties } from '@dnb/eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs' +import { FieldBlockProperties } from '@dnb/eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs' import { PostalCodeAndCityProperties } from '@dnb/eufemia/src/extensions/forms/Field/PostalCodeAndCity/PostalCodeAndCityDocs' ## Properties @@ -15,7 +15,7 @@ import { PostalCodeAndCityProperties } from '@dnb/eufemia/src/extensions/forms/F ### General properties - + ## Translations diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/ArraySelection.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/ArraySelection.tsx index 7847100352e..80cb49daf8a 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/ArraySelection.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/ArraySelection.tsx @@ -108,6 +108,7 @@ function ArraySelection(props: Props) { ) : undefined} ), + disableStatusSummary: true, ...pickSpacingProps(props), } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/ArraySelection.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/ArraySelection.test.tsx index 4e8309e78c1..13a53f7aebb 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/ArraySelection.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/ArraySelection.test.tsx @@ -5,6 +5,9 @@ import { axeComponent } from '../../../../../core/jest/jestSetup' import DataContext from '../../../DataContext/Context' import { Field, FieldBlock, Form } from '../../..' +import nbNO from '../../../constants/locales/nb-NO' +const nb = nbNO['nb-NO'] + describe('ArraySelection', () => { describe('checkbox', () => { it('renders correctly', () => { @@ -381,6 +384,36 @@ describe('ArraySelection', () => { }) }) + it('should show errors in separate FormStatus components', () => { + render( + + + + + ) + + expect(document.querySelectorAll('.dnb-form-status')).toHaveLength(2) + const [first, second] = Array.from( + document.querySelectorAll('.dnb-form-status') + ) + + expect(first.textContent).toBe(nb.Field.errorRequired) + expect(second.textContent).toBe( + nb.NumberField.errorExclusiveMinimum.replace( + '{exclusiveMinimum}', + '900' + ) + ) + }) + describe('ARIA', () => { it('should validate with ARIA rules', async () => { const result = render( diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Composition/CompositionDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Field/Composition/CompositionDocs.ts index 78567d738e3..e040f710c86 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Composition/CompositionDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Composition/CompositionDocs.ts @@ -1,8 +1,8 @@ import { PropertiesTableProps } from '../../../../shared/types' -import { fieldBlockProperties } from '../../FieldBlock/FieldBlockDocs' +import { FieldBlockProperties } from '../../FieldBlock/FieldBlockDocs' export const CompositionProperties: PropertiesTableProps = { - ...fieldBlockProperties, + ...FieldBlockProperties, align: { doc: '`center` or `bottom` for aligning the contents vertically. Defaults to `bottom`.', type: ['string', 'false'], diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx index f1740fae264..1cc70a699fc 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx @@ -171,6 +171,7 @@ function Selection(props: Props) { const fieldBlockProps: FieldBlockProps = { forId: id, className: cn, + disableStatusSummary: true, ...pickSpacingProps(props), } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx index f42d0b3e806..b8def6d876b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx @@ -13,6 +13,9 @@ import DrawerListProvider from '../../../../../fragments/drawer-list/DrawerListP import { makeOptions } from '../Selection' import { Field, Form } from '../../..' +import nbNO from '../../../constants/locales/nb-NO' +const nb = nbNO['nb-NO'] + describe('Selection', () => { it('renders selected option', () => { render( @@ -1741,6 +1744,28 @@ describe('validation and error handling', () => { 'dnb-toggle-button__status--error' ) }) + + it('should show errors in separate FormStatus components', () => { + render( + + + + + ) + + expect(document.querySelectorAll('.dnb-form-status')).toHaveLength(2) + const [first, second] = Array.from( + document.querySelectorAll('.dnb-form-status') + ) + + expect(first.textContent).toBe(nb.Field.errorRequired) + expect(second.textContent).toBe( + nb.NumberField.errorExclusiveMinimum.replace( + '{exclusiveMinimum}', + '900' + ) + ) + }) }) describe('makeOptions', () => { diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx index 985c9f6e61f..0762a4322de 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx @@ -88,6 +88,14 @@ export type SharedFieldBlockProps = { * A more discreet text displayed beside the label */ labelDescription?: React.ReactNode + /** + * Width of outer block element + */ + width?: FieldBlockWidth + /** + * Width of contents block, while label etc can be wider if space is available + */ + contentWidth?: FieldBlockWidth } export type Props = SharedFieldBlockProps & @@ -103,10 +111,6 @@ export type Props = SharedFieldBlockProps & labelSrOnly?: boolean /** Defines the layout of nested fields */ composition?: FieldBlockContextProps['composition'] - /** Width of outer block element */ - width?: FieldBlockWidth - /** Width of contents block, while label etc can be wider if space is available */ - contentWidth?: FieldBlockWidth /** For composition only: Align the contents vertically */ align?: 'center' | 'bottom' /** Class name for the contents block */ @@ -117,6 +121,8 @@ export type Props = SharedFieldBlockProps & labelSize?: 'medium' | 'large' /** Defines the height of an component (size prop), so the label can be aligned correctly */ labelHeight?: FieldBlockHorizontalLabelHeight + /** Disable the error summary for this field block */ + disableStatusSummary?: boolean /** For internal use only */ required?: boolean children?: React.ReactNode @@ -124,7 +130,10 @@ export type Props = SharedFieldBlockProps & function FieldBlock(props: Props) { const dataContext = useContext(DataContext) - const nestedFieldBlockContext = useContext(FieldBlockContext) + const fieldBlockContext = useContext(FieldBlockContext) + const nestedFieldBlockContext = !fieldBlockContext?.disableStatusSummary + ? fieldBlockContext + : null const sharedData = createSharedState( 'field-block-props-' + (props.id ?? props.forId) @@ -144,6 +153,7 @@ function FieldBlock(props: Props) { info, warning, error: errorProp, + disableStatusSummary, fieldState, disabled, width, @@ -523,6 +533,7 @@ function FieldBlock(props: Props) { fieldStateIdsRef, mountedFieldsRef, composition, + disableStatusSummary, }} > void hasErrorProp?: boolean composition?: true + disableStatusSummary?: boolean fieldStateIdsRef?: React.MutableRefObject mountedFieldsRef?: React.MutableRefObject } diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs.ts b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs.ts index e1ba4b58e20..9832c1b02b7 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs.ts @@ -26,11 +26,6 @@ export const FieldBlockSharedProperties: PropertiesTableProps = { type: 'object', status: 'optional', }, - labelHeight: { - doc: 'Defines the height of an component (size prop), so the label can be aligned correctly. Can be `default`, `small`, `medium`, `large`.', - type: 'string', - status: 'optional', - }, width: { doc: 'Will set the width for the whole block. Use `small`, `medium`, `large` for predefined standard widths. You can also set a custom width `{number}rem` or use `stretch` or `false`.', type: ['string', 'false'], @@ -48,13 +43,19 @@ export const FieldBlockSharedProperties: PropertiesTableProps = { }, } -export const fieldBlockProperties: PropertiesTableProps = { +/** For internal use only */ +export const FieldBlockProperties: PropertiesTableProps = { ...FieldBlockSharedProperties, labelSize: { doc: 'Define one of the following [heading sizes](/uilib/elements/heading/): `medium` or `large`.', type: ['string', 'false'], status: 'optional', }, + labelHeight: { + doc: 'Defines the height of an component (size prop), so the label can be aligned correctly. Can be `default`, `small`, `medium`, `large`.', + type: 'string', + status: 'optional', + }, asFieldset: { doc: 'Use `true` when you have several form elements. This way a `fieldset` with a `legend` is used.', type: 'boolean', @@ -65,6 +66,11 @@ export const fieldBlockProperties: PropertiesTableProps = { type: ['string', 'false'], status: 'optional', }, + disableStatusSummary: { + doc: 'Use `true` to disable the error summary.', + type: 'boolean', + status: 'optional', + }, composition: { doc: 'Use `true` for when you have more than one field wrapped.', type: 'string', 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 48e459e1f1b..371823e6a18 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 @@ -880,6 +880,81 @@ describe('FieldBlock', () => { log.mockRestore() }) + + it('should summarize errors in one FormStatus components', () => { + const MockComponent = () => { + useFieldProps({ + required: true, + validateInitially: true, + }) + + return null + } + + render( + + + + ) + + expect(document.querySelectorAll('.dnb-form-status')).toHaveLength(1) + expect(document.querySelector('.dnb-form-status').textContent).toBe( + nb.Field.errorSummary + 'Error message' + nb.Field.errorRequired + ) + }) + + it('should summarize errors for nested FieldBlocks', () => { + const nested = new Error('Nested') + const outer = new Error('Outer') + + const MockComponent = () => { + useFieldProps({ + id: 'unique', + error: nested, + }) + + return content + } + + render( + + + + ) + + expect(document.querySelectorAll('.dnb-form-status')).toHaveLength(1) + expect(document.querySelector('.dnb-form-status').textContent).toBe( + nb.Field.errorSummary + 'Outer' + 'Nested' + ) + }) + + it('should not summarize errors when "disableStatusSummary" is true', () => { + const nested = new Error('Nested') + const outer = new Error('Outer') + + const MockComponent = () => { + useFieldProps({ + id: 'unique', + error: nested, + }) + + return content + } + + render( + + + + ) + + expect(document.querySelectorAll('.dnb-form-status')).toHaveLength(2) + expect( + document.querySelectorAll('.dnb-form-status')[0].textContent + ).toBe('Outer') + expect( + document.querySelectorAll('.dnb-form-status')[1].textContent + ).toBe('Nested') + }) }) function MockComponent({ label = null, id = null }) { diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts index b1b97cceee5..8d64bac152e 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts @@ -208,7 +208,9 @@ export default function useFieldProps( const onChangeContext = dataContext?.props?.onChange const disabled = disabledProp ?? props.readOnly - const inFieldBlock = Boolean(fieldBlockContext) + const inFieldBlock = Boolean( + fieldBlockContext && fieldBlockContext.disableStatusSummary !== true + ) const { setFieldState: setFieldStateFieldBlock, showFieldError: showFieldErrorFieldBlock,