Skip to content

Commit 2818eeb

Browse files
authored
fix: narrow react element and child types (#22011)
1 parent 585f191 commit 2818eeb

20 files changed

Lines changed: 165 additions & 149 deletions

File tree

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5347,6 +5347,9 @@ Map {
53475347
"onChange": {
53485348
"type": "func",
53495349
},
5350+
"readOnly": {
5351+
"type": "bool",
5352+
},
53505353
},
53515354
"render": [Function],
53525355
},
@@ -13363,6 +13366,9 @@ Map {
1336313366
"onChange": {
1336413367
"type": "func",
1336513368
},
13369+
"readOnly": {
13370+
"type": "bool",
13371+
},
1336613372
},
1336713373
"render": [Function],
1336813374
},
@@ -15343,6 +15349,9 @@ Map {
1534315349
"onChange": {
1534415350
"type": "func",
1534515351
},
15352+
"readOnly": {
15353+
"type": "bool",
15354+
},
1534615355
},
1534715356
"render": [Function],
1534815357
},

packages/react/src/components/Accordion/AccordionItem.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,15 @@ export interface AccordionItemProps {
7676
*/
7777
renderExpando?: (
7878
props: PropsWithChildren<AccordionToggleProps>
79-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
80-
) => ReactElement<any>;
79+
) => ReactElement;
8180

8281
/**
8382
* The callback function to render the expand button.
8483
* Can be a React component class.
8584
*/
8685
renderToggle?: (
8786
props: PropsWithChildren<AccordionToggleProps>
88-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
89-
) => ReactElement<any>;
87+
) => ReactElement;
9088

9189
/**
9290
* The accordion title.

packages/react/src/components/ComboBox/ComboBox.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,8 +1528,7 @@ type ComboboxComponentProps<ItemType> = PropsWithChildren<
15281528
RefAttributes<HTMLInputElement>;
15291529

15301530
export interface ComboBoxComponent {
1531-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
1532-
<ItemType>(props: ComboboxComponentProps<ItemType>): ReactElement<any> | null;
1531+
<ItemType>(props: ComboboxComponentProps<ItemType>): ReactElement | null;
15331532
}
15341533

15351534
export default ComboBox as ComboBoxComponent;

packages/react/src/components/ContentSwitcher/ContentSwitcher.tsx

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,28 @@ import PropTypes from 'prop-types';
99
import React, {
1010
Children,
1111
cloneElement,
12-
isValidElement,
1312
useContext,
1413
useEffect,
1514
useRef,
1615
useState,
1716
type HTMLAttributes,
18-
type KeyboardEvent,
19-
type MouseEvent,
2017
type ReactElement,
2118
} from 'react';
2219
import classNames from 'classnames';
2320
import { deprecate } from '../../prop-types/deprecate';
2421
import { LayoutConstraint } from '../Layout';
25-
import { composeEventHandlers } from '../../tools/events';
26-
import { getNextIndex, matches, keys } from '../../internal/keyboard';
22+
import { getNextIndex, match, keys } from '../../internal/keyboard';
2723
import { PrefixContext } from '../../internal/usePrefix';
2824
import { isComponentElement } from '../../internal';
29-
import { IconSwitch } from '../Switch';
25+
import { IconSwitch, Switch } from '../Switch';
3026
import type { SwitchEventHandlersParams } from '../Switch/Switch';
3127

3228
export interface ContentSwitcherProps
3329
extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
3430
/**
3531
* Pass in Switch components to be rendered in the ContentSwitcher
3632
*/
37-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
38-
children?: ReactElement<any>[];
33+
children?: ReactElement | ReactElement[];
3934

4035
/**
4136
* Specify an optional className to be added to the container node
@@ -118,23 +113,19 @@ export const ContentSwitcher = ({
118113
}
119114
};
120115

121-
const isKeyboardEvent = (
122-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
123-
event: any
124-
): event is KeyboardEvent<HTMLButtonElement> | globalThis.KeyboardEvent =>
125-
event && typeof event === 'object' && 'key' in event;
116+
const hasKey = (
117+
event: SwitchEventHandlersParams
118+
): event is SwitchEventHandlersParams & { key: string | number } =>
119+
typeof event === 'object' && event !== null && 'key' in event;
126120

127-
const handleChildChange = (
128-
event: SwitchEventHandlersParams &
129-
(KeyboardEvent<HTMLButtonElement> | MouseEvent<HTMLButtonElement>)
130-
) => {
121+
const handleChildChange = (event: SwitchEventHandlersParams) => {
131122
if (typeof event.index === 'undefined') return;
132123

133124
const { index } = event;
134125

135126
if (
136-
isKeyboardEvent(event) &&
137-
matches(event, [keys.ArrowRight, keys.ArrowLeft])
127+
hasKey(event) &&
128+
(match(event.key, keys.ArrowRight) || match(event.key, keys.ArrowLeft))
138129
) {
139130
const nextIndex = getNextIndex(event.key, index, childrenArray.length);
140131

@@ -147,7 +138,10 @@ export const ContentSwitcher = ({
147138

148139
setSelectedIndex(nextIndex);
149140

150-
if (isValidElement<SwitchEventHandlersParams>(child)) {
141+
if (
142+
isComponentElement(child, Switch) ||
143+
isComponentElement(child, IconSwitch)
144+
) {
151145
onChange({
152146
...event,
153147
index: nextIndex,
@@ -158,7 +152,9 @@ export const ContentSwitcher = ({
158152
}
159153
} else if (
160154
selectedIndex !== index &&
161-
(isKeyboardEvent(event) ? matches(event, [keys.Enter, keys.Space]) : true)
155+
(hasKey(event)
156+
? match(event.key, keys.Enter) || match(event.key, keys.Space)
157+
: true)
162158
) {
163159
setSelectedIndex(index);
164160
focusSwitch(index);
@@ -185,23 +181,32 @@ export const ContentSwitcher = ({
185181
className={classes}
186182
role="tablist"
187183
onChange={undefined}>
188-
{children &&
189-
Children.map(children, (child, index) =>
190-
cloneElement(child, {
191-
index,
192-
onClick: composeEventHandlers([
193-
handleChildChange,
194-
child.props.onClick,
195-
]),
196-
onKeyDown: composeEventHandlers([
197-
handleChildChange,
198-
child.props.onKeyDown,
199-
]),
200-
selected: index === selectedIndex,
201-
ref: handleItemRef(index),
202-
size,
203-
})
204-
)}
184+
{Children.map(children, (child, index) => {
185+
if (
186+
!isComponentElement(child, Switch) &&
187+
!isComponentElement(child, IconSwitch)
188+
)
189+
return child;
190+
191+
const sharedProps = {
192+
index,
193+
onClick: (event: SwitchEventHandlersParams) => {
194+
handleChildChange(event);
195+
child.props.onClick?.(event);
196+
},
197+
onKeyDown: (event: SwitchEventHandlersParams) => {
198+
handleChildChange(event);
199+
child.props.onKeyDown?.(event);
200+
},
201+
selected: index === selectedIndex,
202+
ref: handleItemRef(index),
203+
};
204+
205+
return cloneElement(child, {
206+
...sharedProps,
207+
...(isComponentElement(child, IconSwitch) ? { size } : {}),
208+
});
209+
})}
205210
</LayoutConstraint>
206211
);
207212
};

packages/react/src/components/DataTable/TableSlugRow.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2025
2+
* Copyright IBM Corp. 2016, 2026
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

88
import PropTypes from 'prop-types';
9-
import React, { ReactNode, useEffect } from 'react';
9+
import React, {
10+
cloneElement,
11+
isValidElement,
12+
useEffect,
13+
type ReactNode,
14+
} from 'react';
1015
import classNames from 'classnames';
1116
import { usePrefix } from '../../internal/usePrefix';
1217
import { deprecateComponent } from '../../prop-types/deprecateComponent';
@@ -39,13 +44,11 @@ const TableSlugRow = ({ className, slug }: TableSlugRowProps) => {
3944
});
4045

4146
// Slug is always size `mini`
42-
let normalizedSlug;
43-
if (slug) {
44-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
45-
normalizedSlug = React.cloneElement(slug as React.ReactElement<any>, {
46-
size: 'mini',
47-
});
48-
}
47+
const normalizedSlug = isValidElement<{ size?: string }>(slug)
48+
? cloneElement(slug, {
49+
size: 'mini',
50+
})
51+
: undefined;
4952

5053
return <td className={TableSlugRowClasses}>{normalizedSlug}</td>;
5154
};

packages/react/src/components/Dropdown/Dropdown.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -735,8 +735,7 @@ const Dropdown = React.forwardRef(
735735
interface DropdownComponent {
736736
<ItemType>(
737737
props: DropdownProps<ItemType> & { ref?: Ref<HTMLButtonElement> }
738-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
739-
): React.ReactElement<any> | null;
738+
): React.ReactElement | null;
740739
}
741740

742741
Dropdown.displayName = 'Dropdown';

packages/react/src/components/FluidTimePicker/FluidTimePicker.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
/**
2-
* Copyright IBM Corp. 2022
2+
* Copyright IBM Corp. 2022, 2026
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
7-
import React from 'react';
7+
import React, { cloneElement } from 'react';
88
import PropTypes from 'prop-types';
99
import classnames from 'classnames';
1010
import FluidTextInput, { FluidTextInputProps } from '../FluidTextInput';
11+
import FluidTimePickerSelect from '../FluidTimePickerSelect';
12+
import { isComponentElement } from '../../internal';
1113
import { usePrefix } from '../../internal/usePrefix';
1214
import { WarningFilled, WarningAltFilled } from '@carbon/icons-react';
1315

@@ -102,18 +104,20 @@ const FluidTimePicker = React.forwardRef<
102104
const childrenWithProps = () => {
103105
if (disabled) {
104106
return React.Children.toArray(children).map((child) =>
105-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
106-
React.cloneElement(child as React.ReactElement<any>, {
107-
disabled: true,
108-
})
107+
isComponentElement(child, FluidTimePickerSelect)
108+
? cloneElement(child, {
109+
disabled: true,
110+
})
111+
: child
109112
);
110113
}
111114
if (readOnly) {
112115
return React.Children.toArray(children).map((child) =>
113-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
114-
React.cloneElement(child as React.ReactElement<any>, {
115-
readOnly: true,
116-
})
116+
isComponentElement(child, FluidTimePickerSelect)
117+
? cloneElement(child, {
118+
readOnly: true,
119+
})
120+
: child
117121
);
118122
}
119123
return children;

packages/react/src/components/FluidTimePickerSelect/FluidTimePickerSelect.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ export interface FluidTimePickerSelectProps {
4646
* the underlying `<input>` changes
4747
*/
4848
onChange?: React.ChangeEventHandler<HTMLSelectElement>;
49+
50+
/**
51+
* Whether the component is read-only.
52+
*/
53+
readOnly?: boolean;
4954
}
5055

5156
// eslint-disable-next-line react/display-name -- https://github.com/carbon-design-system/carbon/issues/20452
@@ -97,6 +102,11 @@ FluidTimePickerSelect.propTypes = {
97102
* the underlying `<input>` changes
98103
*/
99104
onChange: PropTypes.func,
105+
106+
/**
107+
* Whether the component is read-only.
108+
*/
109+
readOnly: PropTypes.bool,
100110
};
101111

102112
export default FluidTimePickerSelect;

packages/react/src/components/Grid/Column.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { enabled } from '@carbon/feature-flags';
99
import cx from 'classnames';
1010
import PropTypes from 'prop-types';
11-
import React from 'react';
11+
import React, { type ReactElement } from 'react';
1212
import { usePrefix } from '../../internal/usePrefix';
1313
import { useGridSettings } from './GridContext';
1414
import { PolymorphicComponentPropWithRef } from '../../internal/PolymorphicProps';
@@ -95,8 +95,7 @@ export interface ColumnComponent {
9595
props: ColumnProps<T>,
9696
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
9797
context?: any
98-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
99-
): React.ReactElement<any, any> | null;
98+
): ReactElement | null;
10099
}
101100

102101
// eslint-disable-next-line react/display-name -- https://github.com/carbon-design-system/carbon/issues/20452

packages/react/src/components/Grid/ColumnHang.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2026
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

88
import cx from 'classnames';
99
import PropTypes from 'prop-types';
10-
import React from 'react';
10+
import React, { type ReactElement } from 'react';
1111
import { usePrefix } from '../../internal/usePrefix';
1212
import { PolymorphicProps } from '../../types/common';
1313

@@ -33,8 +33,7 @@ export interface ColumnHangComponent {
3333
props: ColumnHangProps<T>,
3434
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
3535
context?: any
36-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
37-
): React.ReactElement<any, any> | null;
36+
): ReactElement | null;
3837
}
3938

4039
/**

0 commit comments

Comments
 (0)