Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Forms): add autoPushWhen property to Iterate.PushContainer #3876

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,84 @@ export const InitiallyOpen = () => {
</ComponentBox>
)
}

export const AutoPush = () => {
return (
<ComponentBox scope={{ Iterate }}>
{() => {
const MyEditItemForm = () => {
return (
<Field.Composition>
<Field.SelectCountry
label="Land du er statsborger i"
itemPath="/"
/>
</Field.Composition>
)
}

const MyEditItem = (props) => {
return (
<Iterate.EditContainer {...props}>
<MyEditItemForm />
</Iterate.EditContainer>
)
}

const CreateNewEntry = () => {
return (
<Iterate.PushContainer
path="/countries"
openButton={
<Iterate.PushContainer.OpenButton text="Legg til flere statsborskap" />
}
showOpenButtonWhen={(list) => list.length > 0}
autoPushWhen={(list) => list.length === 0}
>
<MyEditItemForm />
</Iterate.PushContainer>
)
}

const MyViewItem = () => {
return (
<Iterate.ViewContainer>
<Value.SelectCountry
label="Land du er statsborger i"
itemPath="/"
/>
</Iterate.ViewContainer>
)
}

const MyForm = () => {
return (
<Form.Handler
data={{
countries: [],
}}
onSubmit={(data) => console.log('onSubmit', data)}
onSubmitRequest={() => console.log('onSubmitRequest')}
>
<Flex.Vertical>
<Form.MainHeading>Statsborgerskap</Form.MainHeading>

<Card align="stretch">
<Iterate.Array path="/countries">
<MyViewItem />
<MyEditItem />
</Iterate.Array>
<CreateNewEntry />
</Card>

<Form.SubmitButton variant="send" />
</Flex.Vertical>
</Form.Handler>
)
}

return <MyForm />
}}
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ import * as Examples from './Examples'
### With existing data

<Examples.ViewAndEditContainer />

### Auto push

This example makes use of the `autoPushWhen` prop. It automatically pushes a new item when a field inside the container is changed. You can use this feature only with a single toggleable field.

<Examples.AutoPush />
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ function SelectCountry(props: Props) {
label={label}
input_icon={false}
data={dataRef.current}
value={value}
value={typeof value === 'string' ? value : null}
disabled={disabled}
on_show={fillData}
on_focus={onFocusHandler}
Expand Down
20 changes: 17 additions & 3 deletions packages/dnb-eufemia/src/extensions/forms/Iterate/Array/Array.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ import IterateItemContext, {
} from '../IterateItemContext'
import SummaryListContext from '../../Value/SummaryList/SummaryListContext'
import ValueBlockContext from '../../ValueBlock/ValueBlockContext'
import DataContext from '../../DataContext/Context'
import FieldBoundaryProvider from '../../DataContext/FieldBoundary/FieldBoundaryProvider'
import useDataValue from '../../hooks/useDataValue'

import type { ContainerMode, ElementChild, Props, Value } from './types'
import type {
ContainerMode,
ContainerModeWhen,
ElementChild,
Props,
Value,
} from './types'
import type { Identifier, Path } from '../../types'

/**
Expand Down Expand Up @@ -107,6 +114,13 @@ function ArrayComponent(props: Props) {
valueCountRef.current = arrayValue || []
}, [arrayValue])

const { fieldPropsRef } = useContext(DataContext) || {}
const pathProxy = fieldPropsRef?.current?.[
String(path) + '/iterateProxy'
] as {
containerModeWhen?: ContainerModeWhen
}

const elementData = useMemo(() => {
return ((valueWhileClosingRef.current || arrayValue) ?? []).map(
(value, index) => {
Expand All @@ -123,8 +137,8 @@ function ArrayComponent(props: Props) {
const isNew = isNewRef.current[id] || false
if (!modesRef.current[id]) {
modesRef.current[id] =
value?.['__containerMode'] ?? (isNew ? 'edit' : 'view')
delete value?.['__containerMode']
pathProxy?.containerModeWhen?.(isNew) ??
(isNew ? 'edit' : 'view')
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ export type Props = Omit<
withoutFlex?: boolean
placeholder?: React.ReactNode
}
export type ContainerModeWhen = (
isNew: boolean
) => ContainerMode | undefined
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useRef } from 'react'
import React, { useCallback, useContext, useRef } from 'react'
import Isolation from '../../Form/Isolation'
import PushContainerContext from './PushContainerContext'
import IterateItemContext from '../IterateItemContext'
import DataContext from '../../DataContext/Context'
import useDataValue from '../../hooks/useDataValue'
import EditContainer from '../EditContainer'
import IterateArray, { ContainerMode } from '../Array'
import IterateArray, { ContainerMode, ContainerModeWhen } from '../Array'
import OpenButton from './OpenButton'
import { HeightAnimation } from '../../../../components'
import { Path } from '../../types'
Expand Down Expand Up @@ -32,11 +33,22 @@ export type Props = {
*/
showOpenButtonWhen?: (list: unknown[]) => boolean

/**
* Define if the container should auto push when e.g. the first item (list is empty).
* Should be a function that returns a boolean.
*/
autoPushWhen?: (list: unknown[]) => boolean

/**
* Prefilled data to add to the fields.
*/
data?: Record<string, unknown>

/**
* A custom toolbar to be shown below the container.
*/
toolbar?: React.ReactNode

/**
* The container contents.
*/
Expand All @@ -47,45 +59,67 @@ export type AllProps = Props & SpacingProps

function PushContainer(props: AllProps) {
const {
data = {},
data = null,
path,
title,
children,
openButton,
showOpenButtonWhen,
autoPushWhen,
...rest
} = props

const { setFieldProps } = useContext(DataContext) || {}
const commitHandleRef = useRef<() => void>()
const switchContainerModeRef = useRef<(mode: ContainerMode) => void>()
const { value: entries = [], moveValueToPath } = useDataValue<
Array<unknown>
>({ path })

const showOpenButton = showOpenButtonWhen?.(entries)
const autoPush = autoPushWhen?.(entries)

const newItemContextProps: PushContainerContext = {
path,
entries,
commitHandleRef,
switchContainerMode: switchContainerModeRef.current,
}

const switchArrayContainerToViewMode = useCallback(() => {
if (path) {
const opts: { containerModeWhen: ContainerModeWhen } = {
containerModeWhen: (isNew: boolean) => {
return isNew ? 'view' : undefined
},
}
setFieldProps?.(path + '/iterateProxy', opts)
}
}, [path, setFieldProps])

if (autoPush && !rest.toolbar) {
rest.toolbar = <></>
}

return (
<Isolation
commitHandleRef={commitHandleRef}
transformOnCommit={({ newItem }) => {
return moveValueToPath(path, [
...entries,
{ ...newItem[0], __containerMode: 'view' },
])
transformOnCommit={({ newItems }) => {
return moveValueToPath(path, [...entries, ...newItems])
}}
onCommit={(data, { clearData }) => {
clearData()
switchArrayContainerToViewMode()
switchContainerModeRef.current?.('view')
clearData()
}}
onPathChange={() => {
if (autoPush) {
commitHandleRef.current?.()
}
}}
>
<PushContainerContext.Provider value={newItemContextProps}>
<IterateArray value={[data]} path="/newItem">
<IterateArray value={[data]} path="/newItems">
<IterateItemContext.Consumer>
{({ containerMode, switchContainerMode }) => {
switchContainerModeRef.current = switchContainerMode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ export const PushContainerProperties: PropertiesTableProps = {
type: 'function',
status: 'optional',
},
autoPushWhen: {
doc: 'Define if the container should auto push when e.g. the first item (list is empty). Should be a function that returns a boolean.',
type: 'function',
status: 'optional',
},
toolbar: {
doc: 'A custom toolbar to be shown below the container.',
type: 'React.Node',
status: 'optional',
},
children: {
doc: 'The container contents.',
type: 'React.Node',
Expand Down
Loading
Loading