Skip to content

Commit

Permalink
fix(Flex): enhance handling of React fragments (#3892)
Browse files Browse the repository at this point in the history
  • Loading branch information
langz authored Sep 4, 2024
1 parent ee4796d commit 156c805
Show file tree
Hide file tree
Showing 4 changed files with 493 additions and 4 deletions.
16 changes: 13 additions & 3 deletions packages/dnb-eufemia/src/components/flex/Container.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react'
import React, { Fragment, useCallback } from 'react'
import classnames from 'classnames'
import Space, { SpaceProps } from '../space/Space'
import { Hr } from '../../elements'
Expand Down Expand Up @@ -105,8 +105,7 @@ function FlexContainer(props: Props) {
} = props

const spacing = spacingProp ?? gap ?? 'small'

const childrenArray = wrapChildren(props, children)
const childrenArray = replaceRootFragment(wrapChildren(props, children))
const hasHeading = childrenArray.some((child, i) => {
const previousChild = childrenArray?.[i - 1]
return (
Expand Down Expand Up @@ -272,6 +271,17 @@ function wrapChildren(props: Props, children: React.ReactNode) {
})
}

function replaceRootFragment(children) {
const firstChild = children[0]
if (
React.Children.count(children) === 1 &&
firstChild?.type === Fragment
) {
return React.Children.toArray(firstChild?.props?.children)
}
return children
}

FlexContainer._supportsSpacingProps = true

export default FlexContainer
179 changes: 179 additions & 0 deletions packages/dnb-eufemia/src/components/flex/__tests__/Container.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { setMedia, matchMedia } from 'mock-match-media'
import Flex from '../Flex'
import { createSpacingClasses } from '../../space/SpacingUtils'
import { SpaceProps } from '../../Space'
import { Form } from '../../../extensions/forms'
import H1 from '../../../elements/H1'
import P from '../../../elements/P'

describe('Flex.Container', () => {
it('should forward HTML attributes', () => {
Expand Down Expand Up @@ -699,6 +701,57 @@ describe('Flex.Container', () => {
>
Content A
</div>
<div
class="dnb-space dnb-space__top--small dnb-space__bottom--zero"
>
<p>
Content B
</p>
</div>
<div
class="dnb-space__top--large dnb-space__bottom--zero test-item"
>
content
</div>
</div>
`)

expect(
document.querySelectorAll('.dnb-flex-container')
).toHaveLength(1)
expect(
document.querySelectorAll('[class*="dnb-space__"]')
).toHaveLength(3)
})

it('should handle nested fragments like _supportsSpacingProps=children', () => {
const { rerender, Wrapper, TestComponent } = getMocks()

Wrapper._supportsSpacingProps = 'children'

rerender(
<Flex.Vertical>
<>
<>
Content A<p>Content B</p>
</>
<>
<TestComponent top="large" />
</>
</>
</Flex.Vertical>
)

const container = document.querySelector('.dnb-flex-container')
expect(container).toMatchInlineSnapshot(`
<div
class="dnb-space dnb-flex-container dnb-flex-container--direction-vertical dnb-flex-container--justify-flex-start dnb-flex-container--align-flex-start dnb-flex-container--spacing-small dnb-flex-container--wrap dnb-flex-container--divider-space"
>
<div
class="dnb-space dnb-space__top--zero dnb-space__bottom--zero"
>
Content A
</div>
<div
class="dnb-space dnb-space__top--zero dnb-space__bottom--zero"
>
Expand All @@ -721,6 +774,132 @@ describe('Flex.Container', () => {
document.querySelectorAll('[class*="dnb-space__"]')
).toHaveLength(3)
})

it('should handle Form.Visibility', () => {
const { rerender, Wrapper } = getMocks()

Wrapper._supportsSpacingProps = 'children'

rerender(
<Form.Handler
id="unique-id"
data={{
visible: false,
}}
>
<Flex.Vertical>
<Form.SubHeading>Heading</Form.SubHeading>
<Form.Visibility
visibleWhen={{ path: '/visible', hasValue: true }}
>
<P>text</P>
</Form.Visibility>
<Form.Visibility
visibleWhen={{ path: '/visible', hasValue: true }}
>
<P>text</P>
</Form.Visibility>
<Form.Visibility
visibleWhen={{ path: '/visible', hasValue: true }}
>
<P>text</P>
</Form.Visibility>
<Form.Visibility
visibleWhen={{ path: '/visible', hasValue: true }}
>
<P>text</P>
</Form.Visibility>
</Flex.Vertical>
</Form.Handler>
)

const container = document.querySelector('.dnb-flex-container')
expect(container).toMatchInlineSnapshot(`
<div
class="dnb-space dnb-flex-container dnb-flex-container--direction-vertical dnb-flex-container--justify-flex-start dnb-flex-container--align-flex-start dnb-flex-container--spacing-small dnb-flex-container--wrap dnb-flex-container--divider-space"
>
<h3
class="dnb-heading dnb-h--medium dnb-forms-sub-heading dnb-space__top--zero dnb-space__bottom--zero"
>
Heading
</h3>
</div>
`)

expect(
document.querySelectorAll('.dnb-flex-container')
).toHaveLength(1)
expect(
document.querySelectorAll('[class*="dnb-space__"]')
).toHaveLength(1)
})

it('should handle Form.Visibility nested in fragments', () => {
const { rerender, Wrapper } = getMocks()

Wrapper._supportsSpacingProps = 'children'

rerender(
<Form.Handler
id="unique-id"
data={{
visible: false,
}}
>
<Flex.Vertical>
<Form.SubHeading>Heading</Form.SubHeading>
<>
<>
<Form.Visibility
visibleWhen={{ path: '/visible', hasValue: true }}
>
<P>text</P>
</Form.Visibility>
<Form.Visibility
visibleWhen={{ path: '/visible', hasValue: true }}
>
<P>text</P>
</Form.Visibility>
</>
<Form.Visibility
visibleWhen={{ path: '/visible', hasValue: true }}
>
<P>text</P>
</Form.Visibility>
</>
<>
<>
<Form.Visibility
visibleWhen={{ path: '/visible', hasValue: true }}
>
<P>text</P>
</Form.Visibility>
</>
</>
</Flex.Vertical>
</Form.Handler>
)

const container = document.querySelector('.dnb-flex-container')
expect(container).toMatchInlineSnapshot(`
<div
class="dnb-space dnb-flex-container dnb-flex-container--direction-vertical dnb-flex-container--justify-flex-start dnb-flex-container--align-flex-start dnb-flex-container--spacing-small dnb-flex-container--wrap dnb-flex-container--divider-space"
>
<h3
class="dnb-heading dnb-h--medium dnb-forms-sub-heading dnb-space__top--zero dnb-space__bottom--zero"
>
Heading
</h3>
</div>
`)

expect(
document.querySelectorAll('.dnb-flex-container')
).toHaveLength(1)
expect(
document.querySelectorAll('[class*="dnb-space__"]')
).toHaveLength(1)
})
})

it('should set custom element', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/dnb-eufemia/src/components/flex/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export function renderWithSpacing(
function wrapWithSpace({ element, spaceProps, variant = null }) {
return variant ?? getSpaceVariant(element) === true ? (
React.cloneElement(element as React.ReactElement, spaceProps)
) : getSpaceVariant(element) === 'children' ? (
renderWithSpacing(element, spaceProps)
) : (
<Space {...spaceProps}>{element}</Space>
)
Expand Down
Loading

0 comments on commit 156c805

Please sign in to comment.