Skip to content

Commit

Permalink
feat(Forms): add Form.SubmitConfirmation to confirm a submit during…
Browse files Browse the repository at this point in the history
… or before sent (#4019)
  • Loading branch information
tujoworker authored Sep 29, 2024
1 parent c3f10e3 commit dfce4e4
Show file tree
Hide file tree
Showing 16 changed files with 1,461 additions and 127 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: 'SubmitConfirmation'
description: '`Form.SubmitConfirmation` can be used to prevent the `Form.Handler` from submitting, and makes it possible to show a confirmation dialog in different scenarios.'
showTabs: true
tabs:
- title: Info
key: '/info'
- title: Demos
key: '/demos'
- title: Properties
key: '/properties'
breadcrumb:
- text: Forms
href: /uilib/extensions/forms/
- text: Form
href: /uilib/extensions/forms/Form
- text: SubmitConfirmation
href: /uilib/extensions/forms/Form/SubmitConfirmation/
---

import Info from 'Docs/uilib/extensions/forms/Form/SubmitConfirmation/info'
import Demos from 'Docs/uilib/extensions/forms/Form/SubmitConfirmation/demos'

<Info />
<Demos />
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Dialog, Flex, Section } from '@dnb/eufemia/src'
import ComponentBox from '../../../../../../shared/tags/ComponentBox'
import { Field, Form } from '@dnb/eufemia/src/extensions/forms'

export const WithDialog = () => {
return (
<ComponentBox>
<Form.Handler
locale="en-GB"
onSubmit={async () => {
await new Promise((resolve) => setTimeout(resolve, 2000))
}}
>
<Flex.Stack>
<Field.String label="Label" path="/foo" defaultValue="foo" />
<Form.SubmitButton />
</Flex.Stack>

<Form.SubmitConfirmation
preventSubmitWhen={() => true}
renderWithState={({ connectWithDialog }) => {
return (
<Dialog
variant="confirmation"
title="Dialog confirmation title"
description="Some content describing the situation."
{...connectWithDialog}
/>
)
}}
/>
</Form.Handler>
</ComponentBox>
)
}

export const WithStateContent = () => {
return (
<ComponentBox>
<Form.Handler
locale="en-GB"
onSubmit={async () => {
await new Promise((resolve) => setTimeout(resolve, 2000))
}}
>
<Form.SubmitConfirmation
preventSubmitWhen={() => true}
onStateChange={({ confirmationState }) => {
console.log('onStateChange', confirmationState)
}}
renderWithState={({ confirmationState, connectWithDialog }) => {
let content = null

switch (confirmationState) {
case 'readyToBeSubmitted':
content = <>Is waiting ...</>
break
case 'submitInProgress':
content = <>Submitting...</>
break
case 'submissionComplete':
content = <>Complete!</>
break
default:
content = (
<Flex.Stack>
<Field.String
label="Label"
path="/foo"
defaultValue="foo"
/>
<Form.SubmitButton />
</Flex.Stack>
)
break
}

return (
<>
{content}
<Dialog
variant="confirmation"
title="Dialog confirmation title"
description="Some content describing the situation."
{...connectWithDialog}
/>
</>
)
}}
/>
</Form.Handler>
</ComponentBox>
)
}

export const WithCustomReturnStatus = () => {
return (
<ComponentBox>
<Form.Handler
onSubmit={async () => {
await new Promise((resolve) => setTimeout(resolve, 2000))
return {
customStatus: 'My custom status',
}
}}
>
<Flex.Stack>
<Field.String label="Label" path="/foo" defaultValue="foo" />
<Form.SubmitButton />
</Flex.Stack>

<Form.SubmitConfirmation
onSubmitResult={({ submitState, setConfirmationState }) => {
if (submitState && submitState.customStatus) {
setConfirmationState('readyToBeSubmitted')
}
}}
renderWithState={({ connectWithDialog, submitState }) => {
return (
<Dialog
variant="confirmation"
title="Dialog confirmation title"
description="Some content describing the situation."
{...connectWithDialog}
>
<Section
variant="info"
innerSpace={{ top: true, bottom: true }}
top
>
<Flex.Stack>
<Field.String label="Inside the dialog" path="/foo" />
<Form.Isolation
onChange={console.log}
data={{
bar: submitState
? submitState.customStatus
: 'bar',
}}
>
<Field.String label="Isolated" path="/bar" />
</Form.Isolation>
</Flex.Stack>
</Section>
</Dialog>
)
}}
/>
</Form.Handler>
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
showTabs: true
---

import * as Examples from './Examples'

## Demos

### With confirmation dialog

<Examples.WithDialog />

### Enable and disable the confirmation mechanism

This example makes first an ordinary submit request. But when the custom status is returned, the dialog component will be shown.

<Examples.WithCustomReturnStatus />

### Render different content based on the submit state

<Examples.WithStateContent />
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
showTabs: true
---

## Description

`Form.SubmitConfirmation` can be used to prevent the [Form.Handler](/uilib/extensions/forms/Form/Handler/) from submitting, and makes it possible to show a confirmation dialog in different scenarios.

```jsx
import { Dialog } from '@dnb/eufemia'
import { Form } from '@dnb/eufemia/extensions/forms'
render(
<Form.Handler
onSubmit={async () => {
// Your submit request
}}
>
Content...
<Form.SubmitButton />
<Form.SubmitConfirmation
preventSubmitWhen={(submitState) => {
// Your preventSubmitWhen logic
}}
onStateChange={(parameters) => {
// Your onStateChange logic
}}
renderWithState={(parameters) => {
return 'Your content'
}}
/>
</Form.Handler>,
)
```

The `renderWithState` function is called whenever the submit confirmation state changes. It receives an object as the first parameter, which contains:

- `connectWithDialog` lets you connect the submit confirmation with a [Dialog](/uilib/components/dialog).
- `submitHandler` is a function that can be called to submit the form.
- `cancelHandler` is a function that can be called to cancel the form.
- `setConfirmationState` is a function that can be called to update the submit state.
- `confirmationState` is a string that can be used to determine the current state of the submit confirmation:
- `idle`
- `readyToBeSubmitted`
- `submitInProgress`
- `submissionComplete`
- `submitState` is the state of the `onSubmit` form event:
- `error`
- `info`
- `warning`
- `success`
- `customStatus` Your custom status.
- `data` is the data that was submitted.

## Connect with a Dialog

You can connect the submit confirmation with a [Dialog](/uilib/components/dialog) by using the `connectWithDialog` property. This property is an object that contains the `openState`, `onConfirm`, `onDecline`, and `onClose` properties, which you can spread to the Dialog component.

```jsx
import { Dialog } from '@dnb/eufemia'
import { Form } from '@dnb/eufemia/extensions/forms'

render(
<Form.Handler>
<Form.SubmitConfirmation
renderWithState={({ connectWithDialog }) => {
return (
<Dialog
variant="confirmation"
title="Dialog confirmation title"
description="Some content describing the situation."
{...connectWithDialog}
/>
)
}}
/>
</Form.Handler>,
)
```

## Using the submitHandler and cancelHandler

In addition to `connectWithDialog`, there are the `submitHandler` and `cancelHandler` functions, available to handle the submission and cancellation processes:

```jsx
<Form.SubmitConfirmation
renderWithState={({ submitHandler, cancelHandler }) => {
return (
<>
<Button onClick={submitHandler} text="Submit" />
<Button onClick={cancelHandler} text="Cancel" />
</>
)
}}
/>
```

## Accessibility

When the `cancelHandler` is called or the `onSubmit` event is completed, the [Form.SubmitButton](/uilib/extensions/forms/Form/SubmitButton/) will regain focus.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
showTabs: true
---

import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable'
import { SubmitConfirmationProperties } from '@dnb/eufemia/src/extensions/forms/Form/SubmitConfirmation/SubmitConfirmationDocs'

## Properties

<PropertiesTable props={SubmitConfirmationProperties} />

This file was deleted.

22 changes: 14 additions & 8 deletions packages/dnb-eufemia/src/extensions/forms/DataContext/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ import {
FormError,
ValueProps,
OnChange,
OnSubmitParams,
} from '../types'
import { Props as ProviderProps } from './Provider'

type HandleSubmitProps = {
formElement?: HTMLFormElement
}

export type MountState = {
isPreMounted?: boolean
isMounted?: boolean
Expand Down Expand Up @@ -109,7 +106,7 @@ export interface ContextState {
filterDataHandler?: FilterDataHandler<unknown>
visibleDataHandler?: VisibleDataHandler<unknown>
validateData: () => void
handleSubmit: (props?: HandleSubmitProps) => void
handleSubmit: () => Promise<EventStateObject | undefined>
scrollToTop: () => void
setShowAllErrors: (showAllErrors: boolean) => void
hasErrors: () => boolean
Expand All @@ -118,7 +115,10 @@ export interface ContextState {
setFieldState: (path: Path, fieldState: SubmitState) => void
setFieldError: (path: Path, error: Error | FormError) => void
setMountedFieldState: (path: Path, options: MountState) => void
setFormState?: (state: SubmitState) => void
setFormState?: (
state: SubmitState,
options?: { keepPending?: boolean }
) => void
setSubmitState?: (state: EventStateObject) => void
addOnChangeHandler?: (callback: OnChange) => void
handleSubmitCall: ({
Expand All @@ -134,7 +134,9 @@ export interface ContextState {
enableAsyncBehavior: boolean
skipFieldValidation?: boolean
skipErrorCheck?: boolean
}) => void
}) => Promise<EventStateObject | undefined>
getSubmitData?: () => unknown
getSubmitOptions?: () => OnSubmitParams
setFieldEventListener?: (
path: EventListenerCall['path'],
type: EventListenerCall['type'],
Expand All @@ -143,12 +145,16 @@ export interface ContextState {
setVisibleError?: (path: Path, hasError: boolean) => void
setFieldProps?: (path: Path, props: unknown) => void
setValueProps?: (path: Path, props: unknown) => void
setHandleSubmit?: (callback: HandleSubmitCallback) => void
setHandleSubmit?: (
callback: HandleSubmitCallback,
params?: { remove?: boolean }
) => void
setFieldConnection?: (path: Path, connections: FieldConnections) => void
fieldPropsRef?: React.MutableRefObject<Record<string, FieldProps>>
valuePropsRef?: React.MutableRefObject<Record<string, ValueProps>>
fieldConnectionsRef?: React.RefObject<Record<Path, FieldConnections>>
mountedFieldsRef?: React.MutableRefObject<Record<Path, MountState>>
formElementRef?: React.MutableRefObject<HTMLFormElement>
showAllErrors: boolean
hasVisibleError: boolean
formState: SubmitState
Expand Down
Loading

0 comments on commit dfce4e4

Please sign in to comment.