diff --git a/src/column-layout/flexible-column-layout/index.tsx b/src/column-layout/flexible-column-layout/index.tsx index c80c38f2d0..9c0fa1c725 100644 --- a/src/column-layout/flexible-column-layout/index.tsx +++ b/src/column-layout/flexible-column-layout/index.tsx @@ -1,11 +1,11 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React from 'react'; -import flattenChildren from 'react-keyed-flatten-children'; import clsx from 'clsx'; import { useContainerQuery } from '@cloudscape-design/component-toolkit'; +import { flattenChildren } from '../../internal/utils/flatten-children'; import { InternalColumnLayoutProps } from '../interfaces'; import styles from './styles.css.js'; diff --git a/src/column-layout/grid-column-layout.tsx b/src/column-layout/grid-column-layout.tsx index 067c725df8..c303e4181d 100644 --- a/src/column-layout/grid-column-layout.tsx +++ b/src/column-layout/grid-column-layout.tsx @@ -1,12 +1,12 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React from 'react'; -import flattenChildren from 'react-keyed-flatten-children'; import clsx from 'clsx'; import { GridProps } from '../grid/interfaces'; import InternalGrid from '../grid/internal'; import { useContainerBreakpoints } from '../internal/hooks/container-queries'; +import { flattenChildren } from '../internal/utils/flatten-children'; import { InternalColumnLayoutProps } from './interfaces'; import { COLUMN_TRIGGERS, ColumnLayoutBreakpoint } from './internal'; import { repeat } from './util'; diff --git a/src/grid/internal.tsx b/src/grid/internal.tsx index cc7c05881c..f5893669ec 100644 --- a/src/grid/internal.tsx +++ b/src/grid/internal.tsx @@ -1,7 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React from 'react'; -import flattenChildren from 'react-keyed-flatten-children'; import clsx, { ClassValue } from 'clsx'; import { useMergeRefs, warnOnce } from '@cloudscape-design/component-toolkit/internal'; @@ -11,6 +10,7 @@ import { Breakpoint, matchBreakpointMapping } from '../internal/breakpoints'; import { useContainerBreakpoints } from '../internal/hooks/container-queries'; import { InternalBaseComponentProps } from '../internal/hooks/use-base-component'; import { isDevelopment } from '../internal/is-development'; +import { flattenChildren } from '../internal/utils/flatten-children'; import { GridProps } from './interfaces'; import styles from './styles.css.js'; diff --git a/src/internal/utils/__tests__/flatten-children.test.tsx b/src/internal/utils/__tests__/flatten-children.test.tsx new file mode 100644 index 0000000000..8e4a13a173 --- /dev/null +++ b/src/internal/utils/__tests__/flatten-children.test.tsx @@ -0,0 +1,70 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { Fragment } from 'react'; + +import { flattenChildren } from '../flatten-children'; + +describe('flattenChildren', () => { + const nestedArrayChildren = [ +
Item 1
, + [
Item 2
,
Item 3
], +
Item 4
, + ]; + + const fragmentChildren = [ + + A + B + , + C, + ]; + + const singleFragment = ( + + A + B + + ); + + // Tests React 19+ behavior: uses Children.toArray() which does NOT flatten fragments + describe('React 19+', () => { + beforeEach(() => { + // Mock React.version to trigger Children.toArray() code path + Object.defineProperty(React, 'version', { + value: '19.0.0', + writable: true, + configurable: true, + }); + }); + + it('flattens nested arrays', () => { + expect(flattenChildren(nestedArrayChildren)).toHaveLength(4); + }); + + it('does NOT flatten fragments', () => { + expect(flattenChildren(fragmentChildren)).toHaveLength(2); + expect(flattenChildren(singleFragment)).toHaveLength(1); + }); + }); + + // Tests React 16-18 behavior: uses react-keyed-flatten-children which DOES flatten fragments + describe('React 16-18', () => { + beforeEach(() => { + // Mock React.version to trigger react-keyed-flatten-children code path + Object.defineProperty(React, 'version', { + value: '18.2.0', + writable: true, + configurable: true, + }); + }); + + it('flattens nested arrays', () => { + expect(flattenChildren(nestedArrayChildren)).toHaveLength(4); + }); + + it('flattens fragments', () => { + expect(flattenChildren(fragmentChildren)).toHaveLength(3); + expect(flattenChildren(singleFragment)).toHaveLength(2); + }); + }); +}); diff --git a/src/internal/utils/flatten-children.ts b/src/internal/utils/flatten-children.ts new file mode 100644 index 0000000000..d97279c2ec --- /dev/null +++ b/src/internal/utils/flatten-children.ts @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { ReactNode } from 'react'; +import flattenChildrenLegacy from 'react-keyed-flatten-children'; + +export function flattenChildren(children: ReactNode): ReactNode[] { + const majorVersion = parseInt(React.version.split('.')[0], 10); + + if (majorVersion >= 19) { + // React 19+: Uses Children.toArray() which doesn't flatten fragments. + // This also supports bigint which is not available in earlier React versions. + return React.Children.toArray(children); + } else { + // React 16-18: Use react-keyed-flatten-children for backward compatibility + return flattenChildrenLegacy(children); + } +} diff --git a/src/space-between/internal.tsx b/src/space-between/internal.tsx index f15e362fb2..8019045f74 100644 --- a/src/space-between/internal.tsx +++ b/src/space-between/internal.tsx @@ -1,13 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React, { forwardRef } from 'react'; -import flattenChildren from 'react-keyed-flatten-children'; import clsx from 'clsx'; import { useMergeRefs } from '@cloudscape-design/component-toolkit/internal'; import { getBaseProps } from '../internal/base-component'; import { InternalBaseComponentProps } from '../internal/hooks/use-base-component'; +import { flattenChildren } from '../internal/utils/flatten-children'; import WithNativeAttributes from '../internal/utils/with-native-attributes'; import { SpaceBetweenProps } from './interfaces'; @@ -52,10 +52,11 @@ const InternalSpaceBetween = forwardRef( ref={mergedRef} > {flattenedChildren.map(child => { - const key = typeof child === 'object' ? child.key : undefined; + // If this react child is a primitive value, the key will be undefined + const key = (child as Record<'key', unknown>).key; return ( -
+
{child}
);