Skip to content

Commit

Permalink
Add validateInitially prop to Section and rename "openWhenFieldVa…
Browse files Browse the repository at this point in the history
…lidationError" to "auto"
  • Loading branch information
tujoworker committed Sep 11, 2024
1 parent cf0bf55 commit 7517098
Show file tree
Hide file tree
Showing 15 changed files with 87 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const ViewAndEditContainer = () => {
defaultData={{
nestedPath: {
firstName: 'Nora',
lastName: 'Mørk',
},
}}
>
Expand All @@ -100,7 +101,7 @@ export const ViewAndEditContainer = () => {
)
}

export const ViewAndEditContainerValidateInitially = () => {
export const ViewAndEditContainerValidation = () => {
return (
<ComponentBox hideCode>
{() => {
Expand Down Expand Up @@ -130,16 +131,13 @@ export const ViewAndEditContainerValidateInitially = () => {
defaultData={{
nestedPath: {
firstName: 'Nora',
lastName: undefined, // initiate error
},
}}
>
<Card stack>
<Form.SubHeading>Your account</Form.SubHeading>
<Form.Section
path="/nestedPath"
required
containerMode="openWhenFieldValidationError"
>
<Form.Section path="/nestedPath" required validateInitially>
<MyEditContainer />
<MyViewContainer />
</Form.Section>
Expand Down Expand Up @@ -185,6 +183,7 @@ export const BasicViewAndEditContainer = () => {
defaultData={{
nestedPath: {
firstName: 'Nora',
lastName: 'Mørk',
},
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ This example uses the [EditContainer](/uilib/extensions/forms/Form/Section/EditC

### Show errors on the whole section

When a field in the section has an error and the section has `containerMode="openWhenFieldValidationError"` set to true, the whole section will switch to edit mode. The errors will be shown.
When a field in the section has an error and the section has `containerMode` set to `auto` (default), the whole section will switch to edit mode. The errors will be shown when `validateInitially` is set to `true`.

<Examples.ViewAndEditContainerValidateInitially />
<Examples.ViewAndEditContainerValidation />

Using `variant="basic"` will render the view and edit container without the additional Card `outline`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,21 @@ export type AllProps = Props & SectionContainerProps & FlexContainerProps
function EditContainer(props: AllProps) {
const { children, className, title, ...restProps } = props || {}
const ariaLabel = useMemo(() => convertJsxToString(title), [title])
const { switchContainerMode, initialContainerMode } =
const { validateInitially, switchContainerMode, initialContainerMode } =
useContext(SectionContainerContext) || {}

const onPathError = useCallback(
(path: Path, error: Error) => {
if (
initialContainerMode === 'openWhenFieldValidationError' &&
error instanceof Error
) {
if (initialContainerMode === 'auto' && error instanceof Error) {
switchContainerMode?.('edit')
}
},
[switchContainerMode, initialContainerMode]
[initialContainerMode, switchContainerMode]
)

return (
<FieldBoundaryProvider
showErrors={initialContainerMode === 'openWhenFieldValidationError'}
showErrors={validateInitially && initialContainerMode === 'auto'}
onPathError={onPathError}
>
<SectionContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ export default function EditToolbarTools() {
const [showError, setShowError] = useState(false)

const cancelHandler = useCallback(() => {
if (
hasSubmitError ||
(initialContainerMode === 'openWhenFieldValidationError' && hasError)
) {
if (hasSubmitError || (initialContainerMode === 'auto' && hasError)) {
setShowBoundaryErrors?.(true)
setShowError(true)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,13 @@ describe('EditContainer and ViewContainer', () => {
<Form.Handler>
<Form.Section>
<Form.Section.EditContainer>
<Field.String path="/" required />
<Field.String
path="/"
label="Label"
onBlurValidator={() => {
return new Error('Error message')
}}
/>
</Form.Section.EditContainer>

<Form.Section.ViewContainer>content</Form.Section.ViewContainer>
Expand All @@ -77,13 +83,17 @@ describe('EditContainer and ViewContainer', () => {
)

expect(containerMode).toBe('view')
expect(
document.querySelector('.dnb-form-status')
).not.toBeInTheDocument()

const input = document.querySelector('input')
await userEvent.type(input, 'x{Backspace}')

const form = document.querySelector('form')
fireEvent.submit(form)

expect(document.querySelector('.dnb-form-status')).toBeInTheDocument()
expect(containerMode).toBe('edit')
})

Expand Down Expand Up @@ -128,32 +138,32 @@ describe('EditContainer and ViewContainer', () => {
expect(input).toHaveValue('bar')
})

describe('containerMode="openWhenFieldValidationError"', () => {
it('should open in view mode when there are no errors', async () => {
let containerMode = null
it('should open in view mode when there are no errors', async () => {
let containerMode = null

const ContextConsumer = () => {
const context = React.useContext(SectionContainerContext)
containerMode = context.containerMode
const ContextConsumer = () => {
const context = React.useContext(SectionContainerContext)
containerMode = context.containerMode

return null
}
return null
}

render(
<Form.Section containerMode="openWhenFieldValidationError">
<Form.Section.EditContainer>
<Field.String path="/foo" />
</Form.Section.EditContainer>
render(
<Form.Section containerMode="auto">
<Form.Section.EditContainer>
<Field.String path="/foo" />
</Form.Section.EditContainer>

<Form.Section.ViewContainer>content</Form.Section.ViewContainer>
<Form.Section.ViewContainer>content</Form.Section.ViewContainer>

<ContextConsumer />
</Form.Section>
)
<ContextConsumer />
</Form.Section>
)

expect(containerMode).toBe('view')
})
expect(containerMode).toBe('view')
})

describe('validateInitially', () => {
it('fields open in edit mode and show errors when there are errors', async () => {
let containerMode = null

Expand All @@ -165,7 +175,7 @@ describe('EditContainer and ViewContainer', () => {
}

render(
<Form.Section containerMode="openWhenFieldValidationError">
<Form.Section validateInitially>
<Form.Section.EditContainer>
<Field.String path="/foo" required />
</Form.Section.EditContainer>
Expand Down Expand Up @@ -210,10 +220,7 @@ describe('EditContainer and ViewContainer', () => {
}

render(
<Form.Section
containerMode="openWhenFieldValidationError"
required
>
<Form.Section validateInitially required>
<Form.Section.EditContainer>
<Field.String path="/foo" />
<Field.String path="/bar" />
Expand Down Expand Up @@ -245,7 +252,7 @@ describe('EditContainer and ViewContainer', () => {

it('should not set focus on initially opened section', async () => {
render(
<Form.Section containerMode="openWhenFieldValidationError">
<Form.Section>
<Form.Section.ViewContainer>
View Content
</Form.Section.ViewContainer>
Expand All @@ -271,7 +278,7 @@ describe('EditContainer and ViewContainer', () => {

render(
<Form.Handler>
<Form.Section containerMode="openWhenFieldValidationError">
<Form.Section>
<Form.Section.ViewContainer>
View Content
</Form.Section.ViewContainer>
Expand Down Expand Up @@ -533,19 +540,20 @@ describe('EditContainer and ViewContainer', () => {
await userEvent.click(doneButton)

expect(document.querySelector('.dnb-form-status')).toBeInTheDocument()
expect(document.querySelectorAll('.dnb-form-status')).toHaveLength(1)

await userEvent.click(cancelButton)
await userEvent.click(editButton)

expect(
document.querySelector('.dnb-form-status')
).not.toBeInTheDocument()
expect(document.querySelectorAll('.dnb-form-status')).toHaveLength(2)

await userEvent.click(doneButton)
await userEvent.type(document.querySelector('input'), 'foo')

expect(document.querySelector('.dnb-form-status')).toBeInTheDocument()
await waitFor(() => {
expect(
document.querySelector('.dnb-form-status')
).not.toBeInTheDocument()
})

await userEvent.type(document.querySelector('input'), 'foo')
await userEvent.click(doneButton)

expect(
Expand Down
20 changes: 14 additions & 6 deletions packages/dnb-eufemia/src/extensions/forms/Form/Section/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ export type SectionProps<overwriteProps = OverwritePropsDefaults> = {
required?: boolean

/**
* Defines the container mode. Can be `view`, `edit` or `openWhenFieldValidationError`.
* When set to `openWhenFieldValidationError`, the mode will initially be "edit" if fields contain errors.
* Fields will then automatically get `validateInitially` and show their error messages.
* Defaults to `view`.
* If set to `true`, the whole section will be validated initially. All fields will then automatically get `validateInitially` and show their error messages. Can be useful in combination with `containerMode="auto"`.
*/
validateInitially?: boolean

/**
* Defines the container mode. Can be `view`, `edit` or `auto`.
* When set to `auto`, the mode will initially be "edit" if fields contain errors.
* Defaults to `auto`.
*/
containerMode?: ContainerMode

Expand All @@ -67,7 +71,8 @@ function SectionComponent(props: LocalProps) {
required,
data,
defaultData,
containerMode = 'view',
validateInitially,
containerMode = 'auto',
onChange,
errorPrioritization = ['contextSchema'],
children,
Expand Down Expand Up @@ -111,7 +116,10 @@ function SectionComponent(props: LocalProps) {
props,
}}
>
<SectionContainerProvider containerMode={containerMode}>
<SectionContainerProvider
validateInitially={validateInitially}
containerMode={containerMode}
>
<FieldPropsProvider
overwriteProps={{
...overwriteProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export const SectionProperties: PropertiesTableProps = {
type: 'boolean',
status: 'optional',
},
validateInitially: {
doc: 'If set to `true`, the whole section will be validated initially. All fields will then automatically get `validateInitially` and show their error messages. Can be useful in combination with `containerMode="auto"`.',
type: 'boolean',
status: 'optional',
},
defaultData: {
doc: 'Provide default data to the section fields and values, in case the data context (Form.Handler) is not available.',
type: 'object',
Expand All @@ -32,7 +37,7 @@ export const SectionProperties: PropertiesTableProps = {
status: 'optional',
},
containerMode: {
doc: 'Defines the container mode. Can be `view`, `edit` or `openWhenFieldValidationError`. When set to `openWhenFieldValidationError`, the mode will initially be "edit" if fields contain errors. Fields will then automatically get `validateInitially` and show their error messages. Defaults to `view`.',
doc: 'Defines the container mode. Can be `view`, `edit` or `auto`. When set to `auto`, the mode will initially be "edit" if fields contain errors. Defaults to `auto`.',
type: 'string',
status: 'optional',
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import SectionContainerContext, {
import { Props as FlexContainerProps } from '../../../../../components/flex/Container'
import FieldBoundaryContext from '../../../DataContext/FieldBoundary/FieldBoundaryContext'

export type ContainerMode =
| 'view'
| 'edit'
| 'openWhenFieldValidationError'
export type ContainerMode = 'view' | 'edit' | 'auto'
export type SectionContainerProps = {
/**
* Defines the variant of the ViewContainer or EditContainer. Can be `outline`.
Expand Down Expand Up @@ -53,8 +50,7 @@ function SectionContainer(props: Props & FlexContainerProps) {
contextRef.current.containerMode = 'edit'
}

const { switchContainerMode, containerMode, initialContainerMode } =
contextRef.current
const { containerMode, initialContainerMode } = contextRef.current

const {
mode,
Expand Down Expand Up @@ -88,15 +84,10 @@ function SectionContainer(props: Props & FlexContainerProps) {
// - Remove the block with animation, if it's in the right mode
const handleAnimationEnd = useCallback(
(state) => {
// - Keep the block open if we have an error
if (contextRef.current.hasSubmitError) {
switchContainerMode?.('edit')
}

if (state === 'opened') {
if (
!contextRef.current.hasSubmitError &&
initialContainerMode === 'openWhenFieldValidationError'
initialContainerMode === 'auto'
? !contextRef.current.hasError
: true
) {
Expand All @@ -106,15 +97,17 @@ function SectionContainer(props: Props & FlexContainerProps) {

onAnimationEnd?.(state)
},
[initialContainerMode, onAnimationEnd, switchContainerMode]
[initialContainerMode, onAnimationEnd]
)

const preventAnimationRef = useRef(true)
useEffect(() => {
setTimeout(() => {
const timeout = setTimeout(() => {
preventAnimationRef.current = false
forceUpdate()
}, 1000) // Initially, we don't want to animate

return () => clearTimeout(timeout)
}, [])

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import { ContainerMode } from './SectionContainer'

export interface SectionContainerContextState {
validateInitially?: boolean
containerMode?: ContainerMode
initialContainerMode?: ContainerMode
switchContainerMode?: (mode: ContainerMode) => void
Expand Down
Loading

0 comments on commit 7517098

Please sign in to comment.