From 05848a459287eed709ba1a587bda9bf7ce990236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Thu, 26 Sep 2024 07:15:33 +0200 Subject: [PATCH 1/2] feat(Forms): add Value.Provider to propagate `inheritVisibility` down to Value.* components --- .../forms/Form/Visibility/Examples.tsx | 28 +++++++++ .../forms/Form/Visibility/demos.mdx | 4 ++ .../extensions/forms/Form/Visibility/info.mdx | 17 ++++++ .../uilib/extensions/forms/Value/info.mdx | 2 + .../forms/Form/Visibility/Visibility.tsx | 2 + .../Form/Visibility/VisibilityContext.ts | 1 + .../Form/Visibility/VisibilityProvider.tsx | 20 +++++++ .../Visibility/VisibilityProviderContext.ts | 10 ++++ .../__tests__/VisibilityProvider.test.tsx | 57 +++++++++++++++++++ .../extensions/forms/hooks/useValueProps.ts | 8 ++- 10 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProvider.tsx create mode 100644 packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProviderContext.ts create mode 100644 packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/VisibilityProvider.test.tsx diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx index 301460c1893..5f4b33468ef 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx @@ -280,3 +280,31 @@ export const FilterData = () => { ) } + +export function InheritVisibility() { + return ( + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/demos.mdx index d352ad7dfd5..8f740308f40 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/demos.mdx @@ -52,3 +52,7 @@ In this example we filter out all fields that have the `data-exclude-field` attr **Note:** This example uses `filterData` with `pathDefined` on a Visibility component along, which is a declarative way to describe the data to be shown. + +### Inherit visibility + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/info.mdx index d70b1a31986..368c6c2876f 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/info.mdx @@ -79,3 +79,20 @@ Check out the [Nested visibility example](#nested-visibility-example) to see how ### Why is this useful? In some cases, you want to keep the content in the DOM, even if it's not visible. This can be useful for fields that still needs to run validation. + +## Inherit visibility + +By using the provider component `Form.Visibility.Provider`, you can propagate the visibility (with the `inheritVisibility` property) of the parent to all nested values. + +```tsx +import { Form, Value } from '@dnb/eufemia/extensions/forms' + +render( + + + + + + , +) +``` diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/info.mdx index 78a6f55bba0..a5baacccc80 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/info.mdx @@ -52,6 +52,8 @@ const MyForm = () => { It's recommended to use [Form.Visibility](/uilib/extensions/forms/Form/Visibility/) because it can animate and describes the UI in a clear, declarative way. However, `inheritVisibility` will also work with other methods, such as React's `useState` hook. +You can also propagate the `inheritVisibility` property down to all nested values with the [Form.Visibility.Provider](/uilib/extensions/forms/Form/Visibility/). + ## Inherit labels from fields to values You can use `inheritLabel={true}` to inherit the label from the field with the same path. diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx index f9b8cbca37e..23a9db9985a 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx @@ -12,6 +12,7 @@ import type { Path, UseFieldProps } from '../../types' import type { DataAttributes } from '../../hooks/useFieldProps' import { FilterData } from '../../DataContext' import VisibilityContext from './VisibilityContext' +import VisibilityProvider from './VisibilityProvider' export type VisibleWhen = | { @@ -159,5 +160,6 @@ function Visibility({ return <>{open ? content : null} } +Visibility.Provider = VisibilityProvider Visibility._supportsSpacingProps = 'children' export default Visibility diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityContext.ts b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityContext.ts index a2876ed6e02..279364f000c 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityContext.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityContext.ts @@ -1,6 +1,7 @@ import { createContext } from 'react' type VisibilityContext = { + inheritVisibility?: boolean isVisible?: boolean } diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProvider.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProvider.tsx new file mode 100644 index 00000000000..26df6d60b69 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProvider.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import VisibilityProviderContext from './VisibilityProviderContext' + +export type Props = { + inheritVisibility?: boolean + children: React.ReactNode +} + +function VisibilityProvider(props: Props) { + const { inheritVisibility, children } = props + return ( + + {children} + + ) +} + +VisibilityProvider._supportsSpacingProps = 'children' + +export default VisibilityProvider diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProviderContext.ts b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProviderContext.ts new file mode 100644 index 00000000000..c3180fbce63 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProviderContext.ts @@ -0,0 +1,10 @@ +import { createContext } from 'react' + +type VisibilityProviderContext = { + inheritVisibility?: boolean +} + +const VisibilityProviderContext = + createContext(null) + +export default VisibilityProviderContext diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/VisibilityProvider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/VisibilityProvider.test.tsx new file mode 100644 index 00000000000..0a6bc503b9e --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/VisibilityProvider.test.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import { render, waitFor } from '@testing-library/react' +import { Field, Form, Value } from '../../..' +import userEvent from '@testing-library/user-event' + +describe('Visibility.Provider', () => { + describe('inheritVisibility', () => { + it('renders value when visibility of field is initially true', async () => { + render( + + + + + + + + + + + + + + + + ) + + expect(document.querySelectorAll('input')).toHaveLength(2) + expect(document.querySelectorAll('dd')).toHaveLength(2) + + const [valueFoo, valueBar] = Array.from( + document.querySelectorAll('dd') + ) + + expect(valueFoo).toHaveTextContent('Foo') + expect(valueBar).toHaveTextContent('Bar') + + const button = document.querySelector('.dnb-toggle-button__button') + await userEvent.click(button) + + await waitFor(() => { + expect(document.querySelectorAll('input')).toHaveLength(0) + expect(document.querySelectorAll('dd')).toHaveLength(0) + }) + + await userEvent.click(button) + + await waitFor(() => { + expect(document.querySelectorAll('input')).toHaveLength(2) + expect(document.querySelectorAll('dd')).toHaveLength(2) + }) + }) + }) +}) diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useValueProps.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useValueProps.ts index 9a3003646e0..ca278afd624 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useValueProps.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useValueProps.ts @@ -10,6 +10,7 @@ import useExternalValue from './useExternalValue' import usePath from './usePath' import DataContext from '../DataContext/Context' import SummaryListContext from '../Value/SummaryList/SummaryListContext' +import VisibilityProviderContext from '../Form/Visibility/VisibilityProviderContext' export type Props = ValueProps @@ -51,8 +52,13 @@ export default function useValueProps< inheritVisibility: inheritVisibilitySummaryList, inheritLabel: inheritLabelSummaryList, } = useContext(SummaryListContext) || {} + const { inheritVisibility: inheritVisibilityFromProvider } = + useContext(VisibilityProviderContext) || {} + const inheritVisibility = - inheritVisibilityProp ?? inheritVisibilitySummaryList + inheritVisibilityProp ?? + inheritVisibilityFromProvider ?? + inheritVisibilitySummaryList const inheritLabel = inheritLabelProp ?? inheritLabelSummaryList From 6904feb4940aa4be0ca41ae68bfea5f500169b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Thu, 26 Sep 2024 14:17:48 +0200 Subject: [PATCH 2/2] Move to `Value.Provider` --- .../forms/Form/Visibility/Examples.tsx | 16 +- .../extensions/forms/Form/Visibility/info.mdx | 6 +- .../uilib/extensions/forms/Value/Provider.mdx | 27 +++ .../forms/Value/Provider/Examples.tsx | 32 ++++ .../extensions/forms/Value/Provider/demos.mdx | 11 ++ .../extensions/forms/Value/Provider/info.mdx | 20 +++ .../forms/Value/Provider/properties.mdx | 10 ++ .../uilib/extensions/forms/Value/info.mdx | 2 +- .../forms/Form/Visibility/Visibility.tsx | 2 - .../Form/Visibility/VisibilityProvider.tsx | 20 --- .../Visibility/VisibilityProviderContext.ts | 10 -- .../__tests__/VisibilityProvider.test.tsx | 57 ------ .../forms/Value/Provider/ValueProvider.tsx | 25 +++ .../Value/Provider/ValueProviderContext.ts | 16 ++ .../forms/Value/Provider/ValueProviderDocs.ts | 13 ++ .../Provider/__tests__/ValueProvider.test.tsx | 166 ++++++++++++++++++ .../__tests__/useValueProvider.test.tsx | 89 ++++++++++ .../extensions/forms/Value/Provider/index.ts | 2 + .../stories/ValueProvider.stories.tsx | 34 ++++ .../forms/Value/Provider/useValueProvider.ts | 49 ++++++ .../forms/Value/SummaryList/SummaryList.tsx | 14 +- .../Value/SummaryList/SummaryListContext.tsx | 2 - .../src/extensions/forms/Value/index.ts | 5 +- .../extensions/forms/hooks/useValueProps.ts | 26 +-- .../shared/helpers/extendPropsWithContext.ts | 6 +- 25 files changed, 530 insertions(+), 130 deletions(-) create mode 100644 packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider.mdx create mode 100644 packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/Examples.tsx create mode 100644 packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/demos.mdx create mode 100644 packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/info.mdx create mode 100644 packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/properties.mdx delete mode 100644 packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProvider.tsx delete mode 100644 packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProviderContext.ts delete mode 100644 packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/VisibilityProvider.test.tsx create mode 100644 packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProvider.tsx create mode 100644 packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProviderContext.ts create mode 100644 packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProviderDocs.ts create mode 100644 packages/dnb-eufemia/src/extensions/forms/Value/Provider/__tests__/ValueProvider.test.tsx create mode 100644 packages/dnb-eufemia/src/extensions/forms/Value/Provider/__tests__/useValueProvider.test.tsx create mode 100644 packages/dnb-eufemia/src/extensions/forms/Value/Provider/index.ts create mode 100644 packages/dnb-eufemia/src/extensions/forms/Value/Provider/stories/ValueProvider.stories.tsx create mode 100644 packages/dnb-eufemia/src/extensions/forms/Value/Provider/useValueProvider.ts diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx index 5f4b33468ef..e30f7ddcc97 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx @@ -1,6 +1,6 @@ import React from 'react' import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Card, Flex, P, Section } from '@dnb/eufemia/src' +import { Card, Flex, HeightAnimation, P, Section } from '@dnb/eufemia/src' import { Field, Form, @@ -297,12 +297,14 @@ export function InheritVisibility() { - - - - - - + + + + + + + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/info.mdx index 368c6c2876f..a693a8948ab 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/info.mdx @@ -82,17 +82,17 @@ In some cases, you want to keep the content in the DOM, even if it's not visible ## Inherit visibility -By using the provider component `Form.Visibility.Provider`, you can propagate the visibility (with the `inheritVisibility` property) of the parent to all nested values. +By using the provider component `Value.Provider`, you can propagate the visibility (with the `inheritVisibility` property) of the parent to all nested values. ```tsx import { Form, Value } from '@dnb/eufemia/extensions/forms' render( - + - + , ) ``` diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider.mdx new file mode 100644 index 00000000000..3e5454638b9 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider.mdx @@ -0,0 +1,27 @@ +--- +title: 'Provider' +description: 'The `Value.Provider` lets you pass generic properties to all nested Value.* components.' +componentType: 'base-value' +hideInMenu: true +showTabs: true +tabs: + - title: Info + key: '/info' + - title: Demos + key: '/demos' + - title: Properties + key: '/properties' +breadcrumb: + - text: Forms + href: /uilib/extensions/forms/ + - text: Value + href: /uilib/extensions/forms/Value/ + - text: Provider + href: /uilib/extensions/forms/Value/Provider/ +--- + +import Info from 'Docs/uilib/extensions/forms/Value/Provider/info' +import Demos from 'Docs/uilib/extensions/forms/Value/Provider/demos' + + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/Examples.tsx new file mode 100644 index 00000000000..da661b658e6 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/Examples.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import ComponentBox from '../../../../../../shared/tags/ComponentBox' +import { Card } from '@dnb/eufemia/src' +import { Field, Form, Value } from '@dnb/eufemia/src/extensions/forms' + +export function InheritVisibility() { + return ( + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/demos.mdx new file mode 100644 index 00000000000..1f0133db7a4 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/demos.mdx @@ -0,0 +1,11 @@ +--- +showTabs: true +--- + +import * as Examples from './Examples' + +## Demos + +### Inherit visibility + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/info.mdx new file mode 100644 index 00000000000..858da138e12 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/info.mdx @@ -0,0 +1,20 @@ +--- +showTabs: true +--- + +## Description + +The `Value.Provider` lets you pass generic properties to all nested Value.\* components. + +```tsx +import { Field, Value } from '@dnb/eufemia/extensions/forms' +render( + <> + + + + + + , +) +``` diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/properties.mdx new file mode 100644 index 00000000000..ddd12e554b7 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Provider/properties.mdx @@ -0,0 +1,10 @@ +--- +showTabs: true +--- + +import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' +import { ValueProviderProperties } from '@dnb/eufemia/src/extensions/forms/Value/Provider/ValueProviderDocs' + +## Properties + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/info.mdx index a5baacccc80..1f7538e33d1 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/info.mdx @@ -52,7 +52,7 @@ const MyForm = () => { It's recommended to use [Form.Visibility](/uilib/extensions/forms/Form/Visibility/) because it can animate and describes the UI in a clear, declarative way. However, `inheritVisibility` will also work with other methods, such as React's `useState` hook. -You can also propagate the `inheritVisibility` property down to all nested values with the [Form.Visibility.Provider](/uilib/extensions/forms/Form/Visibility/). +You can also propagate the `inheritVisibility` property down to all nested values with the [Value.Provider](/uilib/extensions/forms/Form/Visibility/). ## Inherit labels from fields to values diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx index 23a9db9985a..f9b8cbca37e 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx @@ -12,7 +12,6 @@ import type { Path, UseFieldProps } from '../../types' import type { DataAttributes } from '../../hooks/useFieldProps' import { FilterData } from '../../DataContext' import VisibilityContext from './VisibilityContext' -import VisibilityProvider from './VisibilityProvider' export type VisibleWhen = | { @@ -160,6 +159,5 @@ function Visibility({ return <>{open ? content : null} } -Visibility.Provider = VisibilityProvider Visibility._supportsSpacingProps = 'children' export default Visibility diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProvider.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProvider.tsx deleted file mode 100644 index 26df6d60b69..00000000000 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProvider.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import VisibilityProviderContext from './VisibilityProviderContext' - -export type Props = { - inheritVisibility?: boolean - children: React.ReactNode -} - -function VisibilityProvider(props: Props) { - const { inheritVisibility, children } = props - return ( - - {children} - - ) -} - -VisibilityProvider._supportsSpacingProps = 'children' - -export default VisibilityProvider diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProviderContext.ts b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProviderContext.ts deleted file mode 100644 index c3180fbce63..00000000000 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/VisibilityProviderContext.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createContext } from 'react' - -type VisibilityProviderContext = { - inheritVisibility?: boolean -} - -const VisibilityProviderContext = - createContext(null) - -export default VisibilityProviderContext diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/VisibilityProvider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/VisibilityProvider.test.tsx deleted file mode 100644 index 0a6bc503b9e..00000000000 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/VisibilityProvider.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import { render, waitFor } from '@testing-library/react' -import { Field, Form, Value } from '../../..' -import userEvent from '@testing-library/user-event' - -describe('Visibility.Provider', () => { - describe('inheritVisibility', () => { - it('renders value when visibility of field is initially true', async () => { - render( - - - - - - - - - - - - - - - - ) - - expect(document.querySelectorAll('input')).toHaveLength(2) - expect(document.querySelectorAll('dd')).toHaveLength(2) - - const [valueFoo, valueBar] = Array.from( - document.querySelectorAll('dd') - ) - - expect(valueFoo).toHaveTextContent('Foo') - expect(valueBar).toHaveTextContent('Bar') - - const button = document.querySelector('.dnb-toggle-button__button') - await userEvent.click(button) - - await waitFor(() => { - expect(document.querySelectorAll('input')).toHaveLength(0) - expect(document.querySelectorAll('dd')).toHaveLength(0) - }) - - await userEvent.click(button) - - await waitFor(() => { - expect(document.querySelectorAll('input')).toHaveLength(2) - expect(document.querySelectorAll('dd')).toHaveLength(2) - }) - }) - }) -}) diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProvider.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProvider.tsx new file mode 100644 index 00000000000..fbc7c3fa1eb --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProvider.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import ValueProviderContext from './ValueProviderContext' +import type { Path, ValueProps } from '../../types' +import useValueProvider from './useValueProvider' + +export type ValueProviderProps = { + children: React.ReactNode + overwriteProps?: { + [key: Path]: ValueProps + } +} & ValueProps + +function ValueProviderProvider(props: ValueProviderProps) { + const { children, ...restProps } = props + const providerValue = useValueProvider(restProps) + + return ( + + {children} + + ) +} + +ValueProviderProvider._supportsSpacingProps = 'children' +export default ValueProviderProvider diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProviderContext.ts b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProviderContext.ts new file mode 100644 index 00000000000..6157cd55250 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProviderContext.ts @@ -0,0 +1,16 @@ +import React from 'react' +import { ValueProps } from '../../types' + +export type ValueProviderContextProps = { + extend: (props: T) => T + inheritedProps?: ValueProps + inheritedContext?: ValueProps +} + +const extend: ValueProviderContextProps['extend'] = (props) => props +const ValueProviderContext = + React.createContext({ + extend, + }) + +export default ValueProviderContext diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProviderDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProviderDocs.ts new file mode 100644 index 00000000000..e8a204cc262 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/ValueProviderDocs.ts @@ -0,0 +1,13 @@ +import { PropertiesTableProps } from '../../../../shared/types' +import { ValueProperties } from '../ValueDocs' + +export const ValueProviderProperties: PropertiesTableProps = { + ...ValueProperties, + children: { + doc: 'Contents.', + type: 'React.Node', + status: 'required', + }, +} + +export const StepEvents: PropertiesTableProps = {} diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/__tests__/ValueProvider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/__tests__/ValueProvider.test.tsx new file mode 100644 index 00000000000..61827f62bc4 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/__tests__/ValueProvider.test.tsx @@ -0,0 +1,166 @@ +import React from 'react' +import { render, waitFor } from '@testing-library/react' +import { Field, Form, Value } from '../../..' +import userEvent from '@testing-library/user-event' +import ValueProviderContext from '../ValueProviderContext' + +describe('Value.Provider', () => { + it('should merge inheritedContext with props passed to extend', () => { + let collectedProps = null + + const Collector = (props) => { + return ( + + {({ extend }) => { + collectedProps = extend(props) + return null + }} + + ) + } + + render( + + + + ) + + expect(collectedProps).toEqual({ + inheritVisibility: true, + myProp: 'value', + }) + }) + + it('props passed to extend should override inheritedContext', () => { + let collectedProps = null + + const Collector = (props) => { + return ( + + {({ extend }) => { + collectedProps = extend(props) + return null + }} + + ) + } + + render( + + + + ) + + expect(collectedProps).toEqual({ + inheritVisibility: false, + myProp: 'value', + }) + }) + + it('props passed to extend should override nested inheritedContext', () => { + let collectedProps = null + + const Collector = (props) => { + return ( + + {({ extend }) => { + collectedProps = extend(props) + return null + }} + + ) + } + + render( + + + + + + ) + + expect(collectedProps).toEqual({ + inheritVisibility: true, + myProp: 'value', + }) + }) + + it('second provider should override nested inheritedContext', () => { + let collectedProps = null + + const Collector = (props) => { + return ( + + {({ extend }) => { + collectedProps = extend(props) + return null + }} + + ) + } + + render( + + + + + + ) + + expect(collectedProps).toEqual({ + inheritVisibility: false, + myProp: 'value', + }) + }) + + describe('inheritVisibility', () => { + it('renders value when visibility of field is initially true', async () => { + render( + + + + + + + + + + + + + + + + ) + + expect(document.querySelectorAll('input')).toHaveLength(2) + expect(document.querySelectorAll('dd')).toHaveLength(2) + + const [valueFoo, valueBar] = Array.from( + document.querySelectorAll('dd') + ) + + expect(valueFoo).toHaveTextContent('Foo') + expect(valueBar).toHaveTextContent('Bar') + + const button = document.querySelector('.dnb-toggle-button__button') + await userEvent.click(button) + + await waitFor(() => { + expect(document.querySelectorAll('input')).toHaveLength(0) + expect(document.querySelectorAll('dd')).toHaveLength(0) + }) + + await userEvent.click(button) + + await waitFor(() => { + expect(document.querySelectorAll('input')).toHaveLength(2) + expect(document.querySelectorAll('dd')).toHaveLength(2) + }) + }) + }) +}) diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/__tests__/useValueProvider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/__tests__/useValueProvider.test.tsx new file mode 100644 index 00000000000..0c226a98bc2 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/__tests__/useValueProvider.test.tsx @@ -0,0 +1,89 @@ +import React from 'react' +import { renderHook } from '@testing-library/react' +import useValueProvider from '../useValueProvider' +import ValueProviderContext from '../ValueProviderContext' + +describe('useValueProvider', () => { + it('should return extend function and inherited props', () => { + const props = { overwriteProps: {}, test: 'propValue' } + const { result } = renderHook(useValueProvider, { + initialProps: props, + }) + + expect(result.current.extend).toBeInstanceOf(Function) + expect(result.current.inheritedProps).toEqual({ test: 'propValue' }) + expect(result.current.inheritedContext).toEqual({ + test: 'propValue', + }) + }) + + it('extend function should merge overwriteProps correctly', () => { + const props = { + overwriteProps: { path: { value: 'overwriteValue' } }, + test: 'propValue', + } + const { result } = renderHook(useValueProvider, { + initialProps: props, + }) + + const valueProps = { path: '/test/path', value: 'valueProps' } + + expect(result.current.extend(valueProps)).toEqual({ + path: '/test/path', + test: 'propValue', + value: 'overwriteValue', + }) + }) + + it('should pass inheritedContext to extend function', () => { + const props = { + overwriteProps: {}, + } + const inheritedContext = { inheritLabel: true } + const inheritedProps = null + const extend = () => null + + const { result } = renderHook(useValueProvider, { + initialProps: props, + wrapper: ({ children }) => ( + + {children} + + ), + }) + + const valueProps = {} + + expect(result.current.extend(valueProps)).toEqual({ + inheritLabel: true, + }) + }) + + it('props passed to extend should override inheritedContext', () => { + const props = { + overwriteProps: {}, + } + const inheritedContext = { inheritLabel: true } + const inheritedProps = null + const extend = () => null + + const { result } = renderHook(useValueProvider, { + initialProps: props, + wrapper: ({ children }) => ( + + {children} + + ), + }) + + const valueProps = { inheritLabel: false } + + expect(result.current.extend(valueProps)).toEqual({ + inheritLabel: false, + }) + }) +}) diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/index.ts b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/index.ts new file mode 100644 index 00000000000..0534ec90446 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/index.ts @@ -0,0 +1,2 @@ +export { default } from './ValueProvider' +export * from './ValueProvider' diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/stories/ValueProvider.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/stories/ValueProvider.stories.tsx new file mode 100644 index 00000000000..b4ad11af359 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/stories/ValueProvider.stories.tsx @@ -0,0 +1,34 @@ +import { Field, Form, Value } from '../../..' +import { Card, HeightAnimation } from '../../../../../components' + +export default { + title: 'Eufemia/Extensions/Forms/ValueProvider', +} + +export function ValueProvider() { + return ( + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/useValueProvider.ts b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/useValueProvider.ts new file mode 100644 index 00000000000..591b0fdff2f --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/useValueProvider.ts @@ -0,0 +1,49 @@ +import { useCallback, useContext, useMemo } from 'react' +import { assignPropsWithContext } from '../../../../shared/component-helper' +import ValueProviderContext from './ValueProviderContext' +import type { ValueProps } from '../../types' +import { ValueProviderProps } from './ValueProvider' + +function useValueProvider(props?: Omit) { + const { overwriteProps, ...restProps } = props || {} + const nestedContext = useContext(ValueProviderContext) + const inheritedProps = nestedContext?.inheritedContext + + const nestedValueProps = useMemo(() => { + if (inheritedProps && Object.keys(inheritedProps).length > 0) { + return { ...inheritedProps, ...restProps } as ValueProps + } + + return restProps + }, [inheritedProps, restProps]) + + const extend = useCallback( + (valueProps: T) => { + // Extract props from overwriteProps to be used in values + const key = overwriteProps && valueProps?.path?.split('/')?.pop() + const overwrite = overwriteProps?.[key] + const props = overwrite + ? { ...valueProps, ...overwrite } + : valueProps + + const value = + Object.keys(nestedValueProps).length > 0 + ? assignPropsWithContext( + props, + nestedValueProps as Record + ) + : props + + return value as T + }, + [nestedValueProps, overwriteProps] + ) + + return { + extend, + inheritedProps: restProps, + inheritedContext: nestedValueProps, + } +} + +export default useValueProvider diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryList.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryList.tsx index 526066eb8a2..d4b9d252a41 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryList.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryList.tsx @@ -1,7 +1,9 @@ import React from 'react' import classnames from 'classnames' +import { removeUndefinedProps } from '../../../../shared/component-helper' import SummaryListContext from './SummaryListContext' import Dl, { DlAllProps } from '../../../../elements/Dl' +import ValueProvider from '../Provider/ValueProvider' export type Props = Omit & { inheritVisibility?: boolean @@ -17,16 +19,20 @@ function SummaryList(props: Props) { inheritLabel, ...rest } = props + + const valueProviderProps = removeUndefinedProps({ + inheritVisibility, + inheritLabel, + }) + return ( - +
- {children} + {children}
) diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryListContext.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryListContext.tsx index 3d78f558feb..c1132103691 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryListContext.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryListContext.tsx @@ -4,8 +4,6 @@ import { DlProps } from '../../../../elements/Dl' export type SummaryListContextProps = { layout?: DlProps['layout'] isNested?: boolean - inheritVisibility?: boolean - inheritLabel?: boolean } const SummaryListContext = React.createContext< diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/index.ts b/packages/dnb-eufemia/src/extensions/forms/Value/index.ts index ea148918129..05e14cc75f4 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/index.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Value/index.ts @@ -1,3 +1,6 @@ +export { default as Provider } from './Provider' +export { default as SummaryList } from './SummaryList' +export { default as Composition } from './Composition' export { default as String } from './String' export { default as Number } from './Number' export { default as Boolean } from './Boolean' @@ -10,8 +13,6 @@ export { default as PostalCodeAndCity } from './PostalCodeAndCity' export { default as PhoneNumber } from './PhoneNumber' export { default as BankAccountNumber } from './BankAccountNumber' export { default as OrganizationNumber } from './OrganizationNumber' -export { default as SummaryList } from './SummaryList' -export { default as Composition } from './Composition' export { default as SelectCountry } from './SelectCountry' export { default as ArraySelection } from './ArraySelection' export { default as Selection } from './Selection' diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useValueProps.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useValueProps.ts index ca278afd624..b21b7e3fede 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useValueProps.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useValueProps.ts @@ -9,24 +9,26 @@ import { Path, ValueProps } from '../types' import useExternalValue from './useExternalValue' import usePath from './usePath' import DataContext from '../DataContext/Context' -import SummaryListContext from '../Value/SummaryList/SummaryListContext' -import VisibilityProviderContext from '../Form/Visibility/VisibilityProviderContext' +import ValueProviderContext from '../Value/Provider/ValueProviderContext' export type Props = ValueProps export default function useValueProps< Value = unknown, Props extends ValueProps = ValueProps, ->(props: Props): Props & ValueProps { +>(localeProps: Props): Props & ValueProps { const [, forceUpdate] = useReducer(() => ({}), {}) + const { extend } = useContext(ValueProviderContext) + const props = extend(localeProps) + const { path: pathProp, value: valueProp, itemPath, defaultValue, - inheritVisibility: inheritVisibilityProp, - inheritLabel: inheritLabelProp, + inheritVisibility, + inheritLabel, transformIn = (value: Value) => value, toInput = (value: Value) => value, fromExternal = (value: Value) => value, @@ -48,20 +50,6 @@ export default function useValueProps< transformers, }) ?? defaultValue - const { - inheritVisibility: inheritVisibilitySummaryList, - inheritLabel: inheritLabelSummaryList, - } = useContext(SummaryListContext) || {} - const { inheritVisibility: inheritVisibilityFromProvider } = - useContext(VisibilityProviderContext) || {} - - const inheritVisibility = - inheritVisibilityProp ?? - inheritVisibilityFromProvider ?? - inheritVisibilitySummaryList - - const inheritLabel = inheritLabelProp ?? inheritLabelSummaryList - const { fieldPropsRef, mountedFieldsRef, diff --git a/packages/dnb-eufemia/src/shared/helpers/extendPropsWithContext.ts b/packages/dnb-eufemia/src/shared/helpers/extendPropsWithContext.ts index 9250efa5daf..c0771416b35 100644 --- a/packages/dnb-eufemia/src/shared/helpers/extendPropsWithContext.ts +++ b/packages/dnb-eufemia/src/shared/helpers/extendPropsWithContext.ts @@ -20,7 +20,7 @@ export function extendPropsWithContext( props = { ...defaults, ...props } return { ...props, - ...reduceContexthasValue(props, defaults, contexts), + ...reduceContextHasValue(props, defaults, contexts), } } @@ -31,7 +31,7 @@ export function extendPropsWithContextInClassComponent( ) { return { ...props, - ...reduceContexthasValue(props, defaults, contexts, { + ...reduceContextHasValue(props, defaults, contexts, { onlyMergeExistingProps: true, }), } @@ -46,7 +46,7 @@ export function reduceContext(contexts: Contexts) { }, {}) } -function reduceContexthasValue( +function reduceContextHasValue( props: Props, defaults: DefaultsProps, contexts: Contexts,