diff --git a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/Examples.tsx index a7e8ae6d621..8da3852b187 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/Examples.tsx @@ -193,3 +193,29 @@ export const LayoutHorizontalFlexGrowItems = () => { ) } + +export const WrappedWithChildren = () => { + return ( + + {() => { + const Wrapper = Flex.withChildren(({ children }) => { + return
{children}
+ }) + + return ( + + FlexItem 1 + + FlexItem 2 + FlexItem 3 + + FlexItem 4 + + ) + }} +
+ ) +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/demos.mdx index dc660651479..a16b510a712 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/demos.mdx @@ -47,3 +47,7 @@ Will wrap on small screens. ### Vertical aligned Field.String + +### Flex.withChildren + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/info.mdx index 86aa40a8394..3d81bae1abd 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/info.mdx @@ -28,6 +28,27 @@ You may else wrap your custom component in a `Flex.Item` – this way, you still Technically, `Flex.Container` checks if a nested component has a property called `_supportsSpacingProps`. So if you have a component that supports the [spacing properties](/uilib/layout/space/), you can add this property `ComponentName._supportsSpacingProps = true`. +If the component is a wrapper component, and you want its children to support spacing, you can add this property `ComponentName._supportsSpacingProps = 'children'`. + +But for simplicity, you can use the HOC `Flex.withChildren`: + +```tsx +const Wrapper = Flex.withChildren(({ children }) => { + return
{children}
+}) + +render( + + + + + + + + , +) +``` + ### Horizontal and Vertical aliases For shortening the usage of `direction="..."`, you can use: diff --git a/packages/dnb-eufemia/src/components/flex/Container.tsx b/packages/dnb-eufemia/src/components/flex/Container.tsx index 55e4193bffc..6f1510d19a5 100644 --- a/packages/dnb-eufemia/src/components/flex/Container.tsx +++ b/packages/dnb-eufemia/src/components/flex/Container.tsx @@ -94,7 +94,7 @@ function FlexContainer(props: Props) { ...rest } = props - const childrenArray = React.Children.toArray(children) + const childrenArray = wrapChildren(props, children) const hasHeading = childrenArray.some((child, i) => { const previousChild = childrenArray?.[i - 1] return ( @@ -204,6 +204,23 @@ function FlexContainer(props: Props) { ) } +function wrapChildren(props: Props, children: React.ReactNode) { + return React.Children.toArray(children).map((child) => { + if ( + React.isValidElement(child) && + child.type['_supportsSpacingProps'] === 'children' + ) { + return React.cloneElement( + child, + child.props, + {child.props.children} + ) + } + + return child + }) +} + FlexContainer._supportsSpacingProps = true export default FlexContainer diff --git a/packages/dnb-eufemia/src/components/flex/__tests__/Container.screenshot.test.ts b/packages/dnb-eufemia/src/components/flex/__tests__/Container.screenshot.test.ts index 737cc3a94e0..c53e598834f 100644 --- a/packages/dnb-eufemia/src/components/flex/__tests__/Container.screenshot.test.ts +++ b/packages/dnb-eufemia/src/components/flex/__tests__/Container.screenshot.test.ts @@ -15,6 +15,15 @@ describe('Flex.Container', () => { expect(screenshot).toMatchImageSnapshot() }) + it('have to match with children', async () => { + const screenshot = await makeScreenshot({ + url: '/uilib/layout/flex/container/demos', + selector: + '[data-visual-test="flex-container-with-children"] .dnb-flex-container', + }) + expect(screenshot).toMatchImageSnapshot() + }) + it('have to match field on large viewport', async () => { const screenshot = await makeScreenshot({ url: '/uilib/layout/flex/container/demos', diff --git a/packages/dnb-eufemia/src/components/flex/__tests__/Container.test.tsx b/packages/dnb-eufemia/src/components/flex/__tests__/Container.test.tsx index 241f1d24fcf..87716ff7712 100644 --- a/packages/dnb-eufemia/src/components/flex/__tests__/Container.test.tsx +++ b/packages/dnb-eufemia/src/components/flex/__tests__/Container.test.tsx @@ -346,21 +346,8 @@ describe('Flex.Container', () => { expect(children[2].className).toContain('dnb-space__right--zero') }) - it('should set element', () => { - render(content) - - const element = document.querySelector('.dnb-flex-container') - - expect(element.tagName).toBe('SECTION') - }) - it('should not add a wrapper when _supportsSpacingProps is given', () => { - const { rerender } = render( - - content - content - - ) + const { rerender } = render(<>) const TestComponent = (props: SpaceProps) => { const cn = createSpacingClasses(props) @@ -417,6 +404,155 @@ describe('Flex.Container', () => { } }) + it('should transform children if _supportsSpacingProps="children" is given', () => { + const { rerender } = render(<>) + + const Wrapper = ({ children }) => { + return
{children}
+ } + + const TestComponent = (props: SpaceProps) => { + const cn = createSpacingClasses(props) + cn.push('test-item') + return
content
+ } + + { + rerender( + + + + + + + ) + + const elements = document.querySelectorAll( + '.dnb-flex-container > div' + ) + expect(elements).toHaveLength(1) + expect(elements[0].className).toBe( + 'dnb-space dnb-space__top--zero dnb-space__bottom--zero' + ) + expect((elements[0].firstChild as HTMLElement).className).toBe( + 'wrapper' + ) + } + + { + Wrapper._supportsSpacingProps = 'children' + + rerender( + + + + + + + ) + + { + const elements = Array.from( + document.querySelectorAll('.dnb-flex-container > div') + ) + + expect(elements).toHaveLength(3) + expect(elements[0]).toHaveClass( + 'dnb-space dnb-space__top--zero dnb-space__bottom--zero' + ) + expect(elements[1]).toHaveClass( + 'dnb-space dnb-space__top--zero dnb-space__bottom--zero' + ) + expect(elements[2]).toHaveClass( + 'dnb-space dnb-space__top--large dnb-space__bottom--zero' + ) + } + + { + const elements = Array.from( + document.querySelectorAll('.dnb-flex-container > div > div') + ) + + expect(elements).toHaveLength(3) + expect(elements[0]).toHaveClass('wrapper') + expect(elements[1]).toHaveClass('test-item') + expect(elements[2]).toHaveClass('test-item') + } + + { + const elements = Array.from( + document.querySelectorAll('.dnb-flex-container') + ) + + expect(elements).toHaveLength(2) + } + + { + const elements = Array.from( + document.querySelectorAll( + 'body > div > .dnb-flex-container > div' + ) + ) + + expect(elements).toHaveLength(1) + expect(elements[0]).toHaveClass( + 'dnb-space dnb-space__top--zero dnb-space__bottom--zero' + ) + } + } + + { + TestComponent._supportsSpacingProps = true + + rerender( + + + + + + + ) + + { + const elements = Array.from( + document.querySelectorAll( + 'body > div > .dnb-flex-container > div' + ) + ) + + expect(elements).toHaveLength(1) + expect(elements[0]).toHaveClass( + 'dnb-space dnb-space__top--zero dnb-space__bottom--zero' + ) + } + + { + const elements = Array.from( + document.querySelectorAll('.dnb-flex-container > div') + ) + + expect(elements).toHaveLength(3) + expect(elements[0]).toHaveClass( + 'dnb-space dnb-space__top--zero dnb-space__bottom--zero' + ) + expect(elements[1]).toHaveClass( + 'dnb-space__top--zero dnb-space__bottom--zero test-item' + ) + expect(elements[2]).toHaveClass( + 'dnb-space__top--x-large dnb-space__bottom--zero test-item' + ) + } + } + }) + + it('should set custom element', () => { + render(content) + + const element = document.querySelector('.dnb-flex-container') + + expect(element.tagName).toBe('SECTION') + }) + it('gets valid ref element', () => { let ref: React.RefObject diff --git a/packages/dnb-eufemia/src/components/flex/__tests__/__image_snapshots__/flexcontainer-have-to-match-with-children.snap.png b/packages/dnb-eufemia/src/components/flex/__tests__/__image_snapshots__/flexcontainer-have-to-match-with-children.snap.png new file mode 100644 index 00000000000..e2762c6f2b3 Binary files /dev/null and b/packages/dnb-eufemia/src/components/flex/__tests__/__image_snapshots__/flexcontainer-have-to-match-with-children.snap.png differ diff --git a/packages/dnb-eufemia/src/components/flex/export.ts b/packages/dnb-eufemia/src/components/flex/export.ts index 3d924cf3197..e2d17f2b69a 100644 --- a/packages/dnb-eufemia/src/components/flex/export.ts +++ b/packages/dnb-eufemia/src/components/flex/export.ts @@ -3,3 +3,4 @@ export { default as Item } from './Item' export { default as Stack } from './Stack' export { default as Horizontal } from './Horizontal' export { default as Vertical } from './Vertical' +export { default as withChildren } from './withChildren' diff --git a/packages/dnb-eufemia/src/components/flex/stories/Flex.stories.tsx b/packages/dnb-eufemia/src/components/flex/stories/Flex.stories.tsx new file mode 100644 index 00000000000..4c6dc806966 --- /dev/null +++ b/packages/dnb-eufemia/src/components/flex/stories/Flex.stories.tsx @@ -0,0 +1,28 @@ +/** + * @dnb/eufemia Component Story + * + */ + +import { TestElement } from '../../../extensions/forms' +import Flex from '../Flex' + +export default { + title: 'Eufemia/Components/Flex', +} + +const Wrapper = Flex.withChildren(({ children }) => { + return
{children}
+}) + +export function FlexWithChildren() { + return ( + + FlexItem 1 + + FlexItem 2 + FlexItem 3 + + FlexItem 4 + + ) +} diff --git a/packages/dnb-eufemia/src/components/flex/withChildren.tsx b/packages/dnb-eufemia/src/components/flex/withChildren.tsx new file mode 100644 index 00000000000..73b5933789f --- /dev/null +++ b/packages/dnb-eufemia/src/components/flex/withChildren.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +type WithChildrenProps = { + children?: React.ReactNode +} + +function withChildren( + Component: React.ComponentType +): React.ComponentType { + Component['_supportsSpacingProps'] = 'children' + return Component +} + +export default withChildren diff --git a/packages/dnb-eufemia/src/extensions/forms/utils/TestElement/TestElement.tsx b/packages/dnb-eufemia/src/extensions/forms/utils/TestElement/TestElement.tsx index 15649520bba..c5c924e5a1b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/utils/TestElement/TestElement.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/utils/TestElement/TestElement.tsx @@ -10,3 +10,5 @@ export default function TestElement({ className = null, ...props }) { /> ) } + +TestElement._supportsSpacingProps = true