Skip to content

Commit fb3b653

Browse files
authored
[material-ui][Collapse] Add slots and slotProps props (#47168)
1 parent 3896cb1 commit fb3b653

File tree

5 files changed

+175
-58
lines changed

5 files changed

+175
-58
lines changed

docs/pages/material-ui/api/collapse.json

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@
1919
"type": { "name": "enum", "description": "'horizontal'<br>&#124;&nbsp;'vertical'" },
2020
"default": "'vertical'"
2121
},
22+
"slotProps": {
23+
"type": {
24+
"name": "shape",
25+
"description": "{ root?: func<br>&#124;&nbsp;object, wrapper?: func<br>&#124;&nbsp;object, wrapperInner?: func<br>&#124;&nbsp;object }"
26+
},
27+
"default": "{}"
28+
},
29+
"slots": {
30+
"type": {
31+
"name": "shape",
32+
"description": "{ root?: elementType, wrapper?: elementType, wrapperInner?: elementType }"
33+
},
34+
"default": "{}"
35+
},
2236
"sx": {
2337
"type": {
2438
"name": "union",
@@ -39,6 +53,26 @@
3953
"import Collapse from '@mui/material/Collapse';",
4054
"import { Collapse } from '@mui/material';"
4155
],
56+
"slots": [
57+
{
58+
"name": "root",
59+
"description": "The component that renders the root.",
60+
"default": "'div'",
61+
"class": "MuiCollapse-root"
62+
},
63+
{
64+
"name": "wrapper",
65+
"description": "The component that renders the wrapper.",
66+
"default": "'div'",
67+
"class": "MuiCollapse-wrapper"
68+
},
69+
{
70+
"name": "wrapperInner",
71+
"description": "The component that renders the inner wrapper.",
72+
"default": "'div'",
73+
"class": "MuiCollapse-wrapperInner"
74+
}
75+
],
4276
"classes": [
4377
{
4478
"key": "entered",
@@ -57,24 +91,6 @@
5791
"className": "MuiCollapse-horizontal",
5892
"description": "State class applied to the root element if `orientation=\"horizontal\"`.",
5993
"isGlobal": false
60-
},
61-
{
62-
"key": "root",
63-
"className": "MuiCollapse-root",
64-
"description": "Styles applied to the root element.",
65-
"isGlobal": false
66-
},
67-
{
68-
"key": "wrapper",
69-
"className": "MuiCollapse-wrapper",
70-
"description": "Styles applied to the outer wrapper element.",
71-
"isGlobal": false
72-
},
73-
{
74-
"key": "wrapperInner",
75-
"className": "MuiCollapse-wrapperInner",
76-
"description": "Styles applied to the inner wrapper element.",
77-
"isGlobal": false
7894
}
7995
],
8096
"spread": true,

docs/translations/api-docs/collapse/collapse.json

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
},
1919
"in": { "description": "If <code>true</code>, the component will transition in." },
2020
"orientation": { "description": "The transition orientation." },
21+
"slotProps": { "description": "The props used for each slot inside." },
22+
"slots": { "description": "The components used for each slot inside." },
2123
"sx": {
2224
"description": "The system prop that allows defining system overrides as well as additional CSS styles."
2325
},
@@ -40,15 +42,11 @@
4042
"description": "State class applied to {{nodeName}} if {{conditions}}.",
4143
"nodeName": "the root element",
4244
"conditions": "<code>orientation=\"horizontal\"</code>"
43-
},
44-
"root": { "description": "Styles applied to the root element." },
45-
"wrapper": {
46-
"description": "Styles applied to {{nodeName}}.",
47-
"nodeName": "the outer wrapper element"
48-
},
49-
"wrapperInner": {
50-
"description": "Styles applied to {{nodeName}}.",
51-
"nodeName": "the inner wrapper element"
5245
}
46+
},
47+
"slotDescriptions": {
48+
"root": "The component that renders the root.",
49+
"wrapper": "The component that renders the wrapper.",
50+
"wrapperInner": "The component that renders the inner wrapper."
5351
}
5452
}

packages/mui-material/src/Collapse/Collapse.d.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,48 @@
11
import * as React from 'react';
22
import { SxProps } from '@mui/system';
3+
import { TransitionStatus } from 'react-transition-group';
34
import { Theme } from '../styles';
45
import { InternalStandardProps as StandardProps } from '../internal';
56
import { TransitionProps } from '../transitions/transition';
67
import { CollapseClasses } from './collapseClasses';
8+
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
79

8-
export interface CollapseProps extends StandardProps<TransitionProps, 'timeout'> {
10+
export interface CollapseSlots {
11+
/**
12+
* The component that renders the root.
13+
* @default 'div'
14+
*/
15+
root?: React.ElementType;
16+
/**
17+
* The component that renders the wrapper.
18+
* @default 'div'
19+
*/
20+
wrapper?: React.ElementType;
21+
/**
22+
* The component that renders the inner wrapper.
23+
* @default 'div'
24+
*/
25+
wrapperInner?: React.ElementType;
26+
}
27+
28+
export interface CollapseRootSlotPropsOverrides {}
29+
30+
export interface CollapseWrapperSlotPropsOverrides {}
31+
32+
export interface CollapseWrapperInnerSlotPropsOverrides {}
33+
34+
export type CollapseSlotsAndSlotProps = CreateSlotsAndSlotProps<
35+
CollapseSlots,
36+
{
37+
root: SlotProps<'div', CollapseRootSlotPropsOverrides, CollapseOwnerState>;
38+
wrapper: SlotProps<'div', CollapseWrapperSlotPropsOverrides, CollapseOwnerState>;
39+
wrapperInner: SlotProps<'div', CollapseWrapperInnerSlotPropsOverrides, CollapseOwnerState>;
40+
}
41+
>;
42+
43+
export interface CollapseProps
44+
extends StandardProps<TransitionProps, 'timeout'>,
45+
CollapseSlotsAndSlotProps {
946
/**
1047
* The content node to be collapsed.
1148
*/
@@ -53,6 +90,10 @@ export interface CollapseProps extends StandardProps<TransitionProps, 'timeout'>
5390
sx?: SxProps<Theme>;
5491
}
5592

93+
export interface CollapseOwnerState extends CollapseProps {
94+
state: TransitionStatus;
95+
}
96+
5697
/**
5798
* The Collapse transition is used by the
5899
* [Vertical Stepper](https://mui.com/material-ui/react-stepper/#vertical-stepper) StepContent component.

packages/mui-material/src/Collapse/Collapse.js

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useDefaultProps } from '../DefaultPropsProvider';
1212
import { duration } from '../styles/createTransitions';
1313
import { getTransitionProps } from '../transitions/utils';
1414
import { useForkRef } from '../utils';
15+
import useSlot from '../utils/useSlot';
1516
import { getCollapseUtilityClass } from './collapseClasses';
1617

1718
const useUtilityClasses = (ownerState) => {
@@ -149,6 +150,8 @@ const Collapse = React.forwardRef(function Collapse(inProps, ref) {
149150
onExited,
150151
onExiting,
151152
orientation = 'vertical',
153+
slots = {},
154+
slotProps = {},
152155
style,
153156
timeout = duration.standard,
154157
// eslint-disable-next-line react/prop-types
@@ -292,6 +295,41 @@ const Collapse = React.forwardRef(function Collapse(inProps, ref) {
292295
}
293296
};
294297

298+
const externalForwardedProps = {
299+
slots,
300+
slotProps,
301+
component,
302+
};
303+
304+
const [RootSlot, rootSlotProps] = useSlot('root', {
305+
ref: handleRef,
306+
className: clsx(classes.root, className),
307+
elementType: CollapseRoot,
308+
externalForwardedProps,
309+
ownerState,
310+
additionalProps: {
311+
style: {
312+
[isHorizontal ? 'minWidth' : 'minHeight']: collapsedSize,
313+
...style,
314+
},
315+
},
316+
});
317+
318+
const [WrapperSlot, wrapperSlotProps] = useSlot('wrapper', {
319+
ref: wrapperRef,
320+
className: classes.wrapper,
321+
elementType: CollapseWrapper,
322+
externalForwardedProps,
323+
ownerState,
324+
});
325+
326+
const [WrapperInnerSlot, wrapperInnerSlotProps] = useSlot('wrapperInner', {
327+
className: classes.wrapperInner,
328+
elementType: CollapseWrapperInner,
329+
externalForwardedProps,
330+
ownerState,
331+
});
332+
295333
return (
296334
<TransitionComponent
297335
in={inProp}
@@ -307,39 +345,26 @@ const Collapse = React.forwardRef(function Collapse(inProps, ref) {
307345
{...other}
308346
>
309347
{/* Destructure child props to prevent the component's "ownerState" from being overridden by incomingOwnerState. */}
310-
{(state, { ownerState: incomingOwnerState, ...restChildProps }) => (
311-
<CollapseRoot
312-
as={component}
313-
className={clsx(
314-
classes.root,
315-
{
348+
{(state, { ownerState: incomingOwnerState, ...restChildProps }) => {
349+
const stateOwnerState = { ...ownerState, state };
350+
return (
351+
<RootSlot
352+
{...rootSlotProps}
353+
className={clsx(rootSlotProps.className, {
316354
[classes.entered]: state === 'entered',
317355
[classes.hidden]: state === 'exited' && !inProp && collapsedSize === '0px',
318-
},
319-
className,
320-
)}
321-
style={{
322-
[isHorizontal ? 'minWidth' : 'minHeight']: collapsedSize,
323-
...style,
324-
}}
325-
ref={handleRef}
326-
ownerState={{ ...ownerState, state }}
327-
{...restChildProps}
328-
>
329-
<CollapseWrapper
330-
ownerState={{ ...ownerState, state }}
331-
className={classes.wrapper}
332-
ref={wrapperRef}
356+
})}
357+
ownerState={stateOwnerState}
358+
{...restChildProps}
333359
>
334-
<CollapseWrapperInner
335-
ownerState={{ ...ownerState, state }}
336-
className={classes.wrapperInner}
337-
>
338-
{children}
339-
</CollapseWrapperInner>
340-
</CollapseWrapper>
341-
</CollapseRoot>
342-
)}
360+
<WrapperSlot {...wrapperSlotProps} ownerState={stateOwnerState}>
361+
<WrapperInnerSlot {...wrapperInnerSlotProps} ownerState={stateOwnerState}>
362+
{children}
363+
</WrapperInnerSlot>
364+
</WrapperSlot>
365+
</RootSlot>
366+
);
367+
}}
343368
</TransitionComponent>
344369
);
345370
});
@@ -421,6 +446,24 @@ Collapse.propTypes /* remove-proptypes */ = {
421446
* @default 'vertical'
422447
*/
423448
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
449+
/**
450+
* The props used for each slot inside.
451+
* @default {}
452+
*/
453+
slotProps: PropTypes.shape({
454+
root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
455+
wrapper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
456+
wrapperInner: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
457+
}),
458+
/**
459+
* The components used for each slot inside.
460+
* @default {}
461+
*/
462+
slots: PropTypes.shape({
463+
root: PropTypes.elementType,
464+
wrapper: PropTypes.elementType,
465+
wrapperInner: PropTypes.elementType,
466+
}),
424467
/**
425468
* @ignore
426469
*/

packages/mui-material/src/Collapse/Collapse.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as React from 'react';
12
import { expect } from 'chai';
23
import { spy, stub } from 'sinon';
34
import { act, createRenderer } from '@mui/internal-test-utils';
@@ -6,6 +7,16 @@ import { ThemeProvider, createTheme } from '@mui/material/styles';
67
import Collapse, { collapseClasses as classes } from '@mui/material/Collapse';
78
import describeConformance from '../../test/describeConformance';
89

10+
const CustomCollapse = React.forwardRef(({ ownerState, ...props }, ref) => (
11+
<div ref={ref} {...props} />
12+
));
13+
const CustomWrapper = React.forwardRef(({ ownerState, ...props }, ref) => (
14+
<div ref={ref} {...props} />
15+
));
16+
const CustomWrapperInner = React.forwardRef(({ ownerState, ...props }, ref) => (
17+
<div ref={ref} {...props} />
18+
));
19+
920
describe('<Collapse />', () => {
1021
const { clock, render } = createRenderer();
1122

@@ -22,6 +33,14 @@ describe('<Collapse />', () => {
2233
muiName: 'MuiCollapse',
2334
testVariantProps: { orientation: 'horizontal' },
2435
testDeepOverrides: { slotName: 'wrapper', slotClassName: classes.wrapper },
36+
slots: {
37+
root: { expectedClassName: classes.root, testWithElement: CustomCollapse },
38+
wrapper: { expectedClassName: classes.wrapper, testWithElement: CustomWrapper },
39+
wrapperInner: {
40+
expectedClassName: classes.wrapperInner,
41+
testWithElement: CustomWrapperInner,
42+
},
43+
},
2544
skip: ['componentsProp'],
2645
}));
2746

0 commit comments

Comments
 (0)