Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/column-layout/flexible-column-layout/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -68,7 +68,7 @@ export default function FlexibleColumnLayout({
>
{flattenedChildren.map((child, i) => {
// If this react child is a primitive value, the key will be undefined
const key = (child as Record<'key', unknown>).key;
const key = child && typeof child === 'object' ? (child as Record<'key', unknown>).key : undefined;

return (
<div
Expand Down
2 changes: 1 addition & 1 deletion src/column-layout/grid-column-layout.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
4 changes: 2 additions & 2 deletions src/grid/internal.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -82,7 +82,7 @@ const InternalGrid = React.forwardRef(
>
{flattenedChildren.map((child, i) => {
// If this react child is a primitive value, the key will be undefined
const key = (child as Record<'key', unknown>).key;
const key = child && typeof child === 'object' ? (child as Record<'key', unknown>).key : undefined;

return (
<div
Expand Down
70 changes: 70 additions & 0 deletions src/internal/utils/__tests__/flatten-children.test.tsx
Original file line number Diff line number Diff line change
@@ -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 = [
<div key="1">Item 1</div>,
[<div key="2">Item 2</div>, <div key="3">Item 3</div>],
<div key="4">Item 4</div>,
];

const fragmentChildren = [
<Fragment key="group">
<span key="a">A</span>
<span key="b">B</span>
</Fragment>,
<span key="c">C</span>,
];

const singleFragment = (
<Fragment>
<span>A</span>
<span>B</span>
</Fragment>
);

// 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);
});
});
});
17 changes: 17 additions & 0 deletions src/internal/utils/flatten-children.ts
Original file line number Diff line number Diff line change
@@ -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 versionString = React.version?.split('.')[0];
const majorVersion = versionString ? parseInt(versionString, 10) : NaN;

if (!Number.isNaN(majorVersion) && majorVersion < 19) {
// React 16-18: Use react-keyed-flatten-children for backward compatibility
return flattenChildrenLegacy(children);
}
// 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);
}
7 changes: 4 additions & 3 deletions src/space-between/internal.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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 && typeof child === 'object' ? (child as Record<'key', unknown>).key : undefined;

return (
<div key={key} className={styles.child}>
<div key={key ? String(key) : undefined} className={styles.child}>
{child}
</div>
);
Expand Down
Loading