Skip to content

Commit

Permalink
fix(Forms): ensure fields inside composition share submit indicator (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker authored Nov 20, 2024
1 parent 54cd00d commit e726e20
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ function FieldBlock(props: Props) {
const blockId = useId(props.id)
const [wasUpdated, forceUpdate] = useReducer(() => ({}), {})
const mountedFieldsRef = useRef<MountedFieldsRef>({})
const fieldStateRef = useRef<SubmitState>(null)
const stateRecordRef = useRef<StateRecord>({})
const fieldStateIdsRef = useRef<FieldErrorIdsRef>(null)
const contentsRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -254,11 +255,11 @@ function FieldBlock(props: Props) {
}
}, [])

const setFieldState = useCallback(
const setBlockRecord = useCallback(
(props: StateBasis) => {
if (nestedFieldBlockContext) {
// If this FieldBlock is inside another one, forward the call to the outer one
nestedFieldBlockContext.setFieldState(props)
nestedFieldBlockContext.setBlockRecord(props)
return
}

Expand All @@ -269,6 +270,17 @@ function FieldBlock(props: Props) {
[nestedFieldBlockContext, setInternalRecord]
)

const setFieldState = useCallback(
(identifier: Identifier, fieldState: SubmitState) => {
if (fieldState !== fieldStateRef.current) {
fieldStateRef.current = fieldState

forceUpdate()
}
},
[]
)

const showFieldError = useCallback(
(identifier: Identifier, show: boolean) => {
if (nestedFieldBlockContext) {
Expand Down Expand Up @@ -465,6 +477,11 @@ function FieldBlock(props: Props) {
hasCustomContentWidth ? 'custom' : contentWidth
}`,
labelHeight && `dnb-forms-field-block--label-height-${labelHeight}`,
composition && 'dnb-forms-field-block__composition',
composition &&
`dnb-forms-field-block__composition--${
composition === true ? 'horizontal' : composition
}`,
className
)
const gridClasses = classnames(
Expand Down Expand Up @@ -534,6 +551,7 @@ function FieldBlock(props: Props) {
return (
<FieldBlockContext.Provider
value={{
setBlockRecord,
setFieldState,
showFieldError,
hasErrorProp: Boolean(errorProp),
Expand Down Expand Up @@ -611,10 +629,6 @@ function FieldBlock(props: Props) {
hasCustomContentWidth ? 'custom' : contentWidth
}`,
align && `dnb-forms-field-block__contents--align-${align}`,
composition &&
`dnb-forms-field-block__contents__composition--${
composition === true ? 'horizontal' : composition
}`,
contentClassName
)}
ref={contentsRef}
Expand All @@ -623,7 +637,7 @@ function FieldBlock(props: Props) {
</div>

<SubmitIndicator
state={fieldState}
state={fieldState ?? fieldStateRef.current}
className="dnb-forms-field-block__indicator dnb-forms-submit-indicator--inline-wrap"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import type { FieldProps, Identifier } from '../types'
import type { FieldProps, Identifier, SubmitState } from '../types'

export type FieldErrorIdsRef = Record<StateTypes, string>
export type MountedFieldsRef = Record<Identifier, boolean>
Expand Down Expand Up @@ -35,14 +35,15 @@ export type StatusContent = {
}

export type FieldBlockContextProps = {
setFieldState?: ({
setBlockRecord?: ({
identifier,
type,
stateId,
content,
showInitially,
show,
}: StateBasis) => void
setFieldState?: (identifier: Identifier, fieldState: SubmitState) => void
showFieldError?: (identifier: Identifier, showError: boolean) => void
hasErrorProp?: boolean
composition?: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react'
import { wait } from '../../../../core/jest/jestSetup'
import { fireEvent, render, waitFor } from '@testing-library/react'
import { Input } from '../../../../components'
import FieldBlock from '../FieldBlock'
Expand Down Expand Up @@ -957,6 +958,99 @@ describe('FieldBlock', () => {
).toBe('Nested')
})

describe('fieldState', () => {
it('should show indicator when fieldState is set to pending', async () => {
render(
<FieldBlock fieldState="pending">
<MockComponent />
</FieldBlock>
)

const elements = document.querySelectorAll(
'.dnb-forms-submit-indicator'
)
expect(elements).toHaveLength(1)
expect(elements[0]).toHaveClass(
'dnb-forms-submit-indicator--state-pending'
)
})

it('should show indicator two (2) times when nested', async () => {
render(
<FieldBlock fieldState="pending">
<FieldBlock fieldState="pending">content</FieldBlock>
</FieldBlock>
)

const elements = document.querySelectorAll(
'.dnb-forms-submit-indicator'
)
expect(elements).toHaveLength(2)
expect(elements[0]).toHaveClass(
'dnb-forms-submit-indicator--state-pending'
)
expect(elements[1]).toHaveClass(
'dnb-forms-submit-indicator--state-pending'
)
})

it('should show indicator two (2) times when nested with useFieldProps', async () => {
const onChange = jest.fn(async () => {
await wait(10)
return null
})
const MockComponent = () => {
const { id, handleChange } = useFieldProps({
onChange,
})

return (
<FieldBlock id={id}>
<input type="text" onChange={handleChange} />
</FieldBlock>
)
}

render(
<FieldBlock>
<MockComponent />
</FieldBlock>
)

const elements = document.querySelectorAll(
'.dnb-forms-submit-indicator'
)
expect(elements).toHaveLength(2)

expect(elements[0].className).not.toContain(
'dnb-forms-submit-indicator--state-'
)
expect(elements[1].className).not.toContain(
'dnb-forms-submit-indicator--state-'
)

await userEvent.type(document.querySelector('input'), '1')

expect(elements[0]).toHaveClass(
'dnb-forms-submit-indicator--state-pending'
)
expect(elements[1]).toHaveClass(
'dnb-forms-submit-indicator--state-pending'
)

await waitFor(() => {
expect(elements[0]).toHaveClass(
'dnb-forms-submit-indicator--state-complete'
)
})
await waitFor(() => {
expect(elements[1]).toHaveClass(
'dnb-forms-submit-indicator--state-complete'
)
})
})
})

describe('help', () => {
it('should render content when open is true', async () => {
render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ fieldset.dnb-forms-field-block {

&__status {
grid-area: status;

.dnb-form-status__shell {
margin-top: 0.5rem; // set margin-top on shell, so we can animate the height
max-width: 60ch; // to enhance readability
Expand Down Expand Up @@ -204,31 +205,42 @@ fieldset.dnb-forms-field-block {
}
}
}
}

&__composition {
&--vertical {
display: flex;
flex-flow: column;
row-gap: var(--spacing-small);
}
&__composition--vertical &__contents {
display: flex;
flex-flow: column;
row-gap: var(--spacing-x-small);
}

&--horizontal {
display: flex;
flex-flow: row;
column-gap: var(--spacing-small);
&__composition--horizontal &__contents {
display: flex;
flex-flow: row;
column-gap: var(--spacing-small);

@include allAbove(x-small) {
align-items: flex-end; // To support fields with labels of different size
@include allAbove(x-small) {
align-items: flex-end; // To support fields with labels of different size

&[class*='align-center'] {
align-items: center; // To support fields without labels, but different heights
}
}
@include allBelow(x-small) {
row-gap: var(--spacing-x-small);
flex-flow: column;
}
&[class*='align-center'] {
align-items: center; // To support fields without labels, but different heights
}
}
@include allBelow(x-small) {
row-gap: var(--spacing-x-small);
flex-flow: column;
}
}

// Because we want to hide / show the indicator responsively,
// we render both, but hide the one we don't want to show.
@include allBelow(x-small) {
&__composition > &__grid > .dnb-forms-submit-indicator {
display: none;
}
}
@include allAbove(x-small) {
&__composition > &__grid > &__contents .dnb-forms-submit-indicator {
display: none;
}
}
}
25 changes: 16 additions & 9 deletions packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export default function useFieldProps<Value, EmptyValue, Props>(
fieldBlockContext && fieldBlockContext.disableStatusSummary !== true
)
const {
setBlockRecord,
setFieldState: setFieldStateFieldBlock,
showFieldError: showFieldErrorFieldBlock,
mountedFieldsRef: mountedFieldsRefFieldBlock,
Expand Down Expand Up @@ -402,13 +403,12 @@ export default function useFieldProps<Value, EmptyValue, Props>(

const runPool = useCallback(async (cb = null) => {
for (const key in eventPool.current) {
if (!eventPool.current[key] || eventPool.current[key].pending) {
if (!eventPool.current[key]) {
continue
}

const { fn, runAsync } = eventPool.current[key] || {}
if (fn) {
eventPool.current[key].pending = true
eventPool.current[key] = null

if (runAsync) {
Expand All @@ -429,11 +429,18 @@ export default function useFieldProps<Value, EmptyValue, Props>(
(state: SubmitStateWithValidating) => {
fieldStateRef.current = state
setFieldStateDataContext?.(identifier, resolveValidatingState(state))
setFieldStateFieldBlock?.(identifier, resolveValidatingState(state))

if (!validateInitially) {
forceUpdate()
}
},
[setFieldStateDataContext, identifier, validateInitially]
[
setFieldStateDataContext,
identifier,
setFieldStateFieldBlock,
validateInitially,
]
)

const revealError = useCallback(() => {
Expand Down Expand Up @@ -766,7 +773,7 @@ export default function useFieldProps<Value, EmptyValue, Props>(
setFieldErrorBoundary?.(identifier, error)

// Set the visual states
setFieldStateFieldBlock?.({
setBlockRecord?.({
stateId,
identifier,
type: 'error',
Expand All @@ -784,7 +791,7 @@ export default function useFieldProps<Value, EmptyValue, Props>(
setFieldErrorBoundary,
setFieldErrorDataContext,
setFieldStateDataContext,
setFieldStateFieldBlock,
setBlockRecord,
stateId,
validateInitially,
]
Expand Down Expand Up @@ -2072,21 +2079,21 @@ export default function useFieldProps<Value, EmptyValue, Props>(
// Set the error in the field block context if this field is inside a field block
useEffect(() => {
if (inFieldBlock) {
setFieldStateFieldBlock?.({
setBlockRecord?.({
identifier,
type: 'error',
content: errorProp,
showInitially: true,
show: true,
})
setFieldStateFieldBlock?.({
setBlockRecord?.({
identifier,
type: 'warning',
content: warning,
showInitially: true,
show: true,
})
setFieldStateFieldBlock?.({
setBlockRecord?.({
identifier,
type: 'info',
content: info,
Expand All @@ -2108,7 +2115,7 @@ export default function useFieldProps<Value, EmptyValue, Props>(
inFieldBlock,
info,
mountedFieldsRefFieldBlock,
setFieldStateFieldBlock,
setBlockRecord,
warning,
])

Expand Down

0 comments on commit e726e20

Please sign in to comment.