Skip to content

Commit

Permalink
Add Form.Snapshot
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Oct 10, 2024
1 parent 068596a commit e5f3a01
Show file tree
Hide file tree
Showing 21 changed files with 809 additions and 264 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React from 'react'
import { Button, Card } from '@dnb/eufemia/src'
import ComponentBox from '../../../../../../shared/tags/ComponentBox'
import { Field, Form, Wizard } from '@dnb/eufemia/src/extensions/forms'
import {
Field,
Form,
Tools,
Wizard,
} from '@dnb/eufemia/src/extensions/forms'

export const InWizard = () => {
return (
Expand All @@ -10,87 +17,100 @@ export const InWizard = () => {
Form.useSnapshot('my-form')

return (
<Form.Handler
id="my-form"
defaultData={{ activeSteps: 'group-1' }}
>
<Form.Handler id="my-form">
<Wizard.Container
onStepChange={(index, mode, args) => {
if (mode === 'previous') {
revertSnapshot(args.id)
revertSnapshot(String(args.id), 'my-snapshot-slice')
} else {
createSnapshot(args.previousStep.id)
createSnapshot(
args.previousStep.id,
'my-snapshot-slice',
)
}
}}
>
<Wizard.Step
title="Step A"
id="step-a"
activeWhen={{
path: '/activeSteps',
hasValue: 'group-1',
}}
>
<Form.MainHeading>Step A</Form.MainHeading>
<Field.String path="/foo" label="Content" />
<Wizard.Step title="Step A" id="step-a">
<Form.Snapshot name="my-snapshot-slice">
<Field.String path="/foo" label="Will be reverted" />
</Form.Snapshot>
<Field.String path="/bar" label="Will stay" />
<Wizard.Buttons />
</Wizard.Step>

<Wizard.Step
title="Step B"
id="step-b"
activeWhen={{
path: '/activeSteps',
hasValue: 'group-1',
}}
>
<Form.MainHeading>Step B</Form.MainHeading>
<Field.String path="/foo" label="Content" />
<Wizard.Step title="Step B" id="step-b">
<Field.String path="/foo" label="Will be reverted" />
<Field.String path="/bar" label="Will stay" />
<Wizard.Buttons />
</Wizard.Step>
</Wizard.Container>
</Form.Handler>
)
}

<Wizard.Step
title="Step C"
id="step-c"
activeWhen={{
path: '/activeSteps',
hasValue: (value: string) =>
['group-1', 'group-2'].includes(value),
}}
>
<Form.MainHeading>Step C</Form.MainHeading>
<Field.String path="/foo" label="Content" />
<Wizard.Buttons />
</Wizard.Step>
return <MyForm />
}}
</ComponentBox>
)
}

<Wizard.Step
title="Step D"
id="step-d"
activeWhen={{
path: '/activeSteps',
hasValue: 'group-2',
}}
>
<Form.MainHeading>Step D</Form.MainHeading>
<Field.String path="/foo" label="Content" />
<Wizard.Buttons />
</Wizard.Step>
</Wizard.Container>
export const UndoRedo = () => {
return (
<ComponentBox scope={{ Tools }}>
{() => {
const MyComponent = () => {
const { createSnapshot, applySnapshot } = Form.useSnapshot()
const pointerRef = React.useRef(0)

<Field.Selection
path="/activeSteps"
variant="button"
optionsLayout="horizontal"
top
>
<Field.Option value="group-1" title="Group 1" />
<Field.Option value="group-2" title="Group 2" />
</Field.Selection>
</Form.Handler>
React.useEffect(() => {
createSnapshot(pointerRef.current, 'my-snapshot-slice')
}, [createSnapshot])

const changeHandler = React.useCallback(() => {
pointerRef.current += 1
createSnapshot(pointerRef.current, 'my-snapshot-slice')
}, [createSnapshot])
const undoHandler = React.useCallback(() => {
pointerRef.current -= 1
applySnapshot(pointerRef.current, 'my-snapshot-slice')
}, [applySnapshot])
const redoHandler = React.useCallback(() => {
pointerRef.current += 1
applySnapshot(pointerRef.current, 'my-snapshot-slice')
}, [applySnapshot])

return (
<>
<Card stack>
<Form.Snapshot name="my-snapshot-slice">
<Field.String
path="/foo"
label="Will be reverted"
onChange={changeHandler}
/>
</Form.Snapshot>
<Field.String path="/bar" label="Will stay" />
</Card>

<Form.ButtonRow>
<Button variant="secondary" onClick={undoHandler}>
Undo
</Button>
<Button variant="secondary" onClick={redoHandler}>
Redo
</Button>
</Form.ButtonRow>

<Tools.Log top />
</>
)
}

return <MyForm></MyForm>
return (
<Form.Handler>
<MyComponent />
</Form.Handler>
)
}}
</ComponentBox>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import * as Examples from './Examples'

## Demos

### Undo / Redo

<Examples.UndoRedo />

### Used in a Wizard

This example revers the form data to its previous state when the user navigates back to a previous step.
This example reverts the form data to its previous state when the user navigates back to a previous step.

<Examples.InWizard />
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ showTabs: true

The `Form.useSnapshot` hook lets you store data snapshots of your form data, either inside or outside of the form context.

The hook returns an object with the following properties:

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

function MyComponent() {
const { createSnapshot, revertSnapshot } = Form.useSnapshot()
const { createSnapshot, applySnapshot, revertSnapshot } =
Form.useSnapshot()

return <>MyComponent</>
}
Expand All @@ -24,16 +23,49 @@ render(
)
```

The hook returns an object with the following properties:

- `createSnapshot` will store the current data as a new snapshot with the given id.
- `applySnapshot` will revert the data to the snapshot with the given id (required).
- `revertSnapshot` will revert the data to the snapshot with the given id (required). A reverted snapshot gets deleted from the memory.

## Usage
## Partial data snapshots

In order to create and revert a snapshot for a specific part of the data context, you can use the `Form.Snapshot` component:

```tsx
import { Form, Field } from '@dnb/eufemia/extensions/forms'

function MyForm() {
return (
<Form.Handler>
<Form.Snapshot name="my-snapshot-slice-name">
<Field.String path="/foo" label="Will be reverted" />
<Field.String path="/bar" label="Me too" />
</Form.Snapshot>

<Field.String path="/baz" label="Will stay as before" />
</Form.Handler>
)
}
```

When calling the `createSnapshot` or `revertSnapshot` functions, you can pass inn your snapshot the `name` (my-snapshot-slice-name) as the second parameter. This will make sure that the snapshot is only applied to the given part of the form data.

```tsx
createSnapshot('my-snapshot-1', 'my-snapshot-slice-name')
revertSnapshot('my-snapshot-1', 'my-snapshot-slice-name')
```

You can check out examples in the demo section.

## Usage of the `Form.useSnapshot` hook

You can use the `Form.useSnapshot` hook with or without an `id` (string) property, which is optional and can be used to link the data to a specific [Form.Handler](/uilib/extensions/forms/Form/Handler/) component.

### Without an `id` property

Here "Component" is rendered inside the `Form.Handler` component and does not need an `id` property to access the form data:
Here "Component" is rendered inside the `Form.Handler` component and does not need an `id` property to access the snapshot:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
OnSubmitParams,
} from '../types'
import { Props as ProviderProps } from './Provider'
import { SnapshotName } from '../Form/Snapshot'

export type MountState = {
isPreMounted?: boolean
Expand Down Expand Up @@ -160,6 +161,9 @@ export interface ContextState {
valuePropsRef?: React.MutableRefObject<Record<string, ValueProps>>
fieldConnectionsRef?: React.RefObject<Record<Path, FieldConnections>>
mountedFieldsRef?: React.MutableRefObject<Record<Path, MountState>>
snapshotsRef?: React.MutableRefObject<
Map<SnapshotName, Map<Path, unknown>>
>
formElementRef?: React.MutableRefObject<HTMLFormElement>
showAllErrors: boolean
hasVisibleError: boolean
Expand Down
Loading

0 comments on commit e5f3a01

Please sign in to comment.