From 77b487ea46769d0a9a412c7a9ade48e6f46447a0 Mon Sep 17 00:00:00 2001 From: Dariusz Rzepka Date: Tue, 23 Jun 2020 23:07:04 +0200 Subject: [PATCH 1/3] [Collapse] Add orientation and horizontal support * BREAKING CHANGE: Remove collapsedHeight property - use collapsedSize (#10051) --- docs/pages/api-docs/collapse.md | 6 +- .../components/transitions/SimpleCollapse.js | 36 ++++++- .../components/transitions/SimpleCollapse.tsx | 36 ++++++- .../components/transitions/transitions.md | 5 +- .../material-ui/src/Collapse/Collapse.d.ts | 8 +- packages/material-ui/src/Collapse/Collapse.js | 96 ++++++++++++++----- .../material-ui/src/Collapse/Collapse.test.js | 14 ++- 7 files changed, 161 insertions(+), 40 deletions(-) diff --git a/docs/pages/api-docs/collapse.md b/docs/pages/api-docs/collapse.md index 4a327190175b83..9eda019acb7385 100644 --- a/docs/pages/api-docs/collapse.md +++ b/docs/pages/api-docs/collapse.md @@ -32,10 +32,11 @@ The `MuiCollapse` name can be used for providing [default props](/customization/ |:-----|:-----|:--------|:------------| | children | node | | The content node to be collapsed. | | classes | object | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. | -| collapsedHeight | number
| string
| '0px' | The height of the container when collapsed. | +| collapsedSize | number
| string
| '0px' | The width (horizontal) or height (vertical) of the container when collapsed. | | component | elementType | 'div' | The component used for the root node. Either a string to use a HTML element or a component. | | disableStrictModeCompat | bool | false | Enable this prop if you encounter 'Function components cannot be given refs', use `unstable_createStrictModeTheme`, and can't forward the ref in the passed `Component`. | | in | bool | | If `true`, the component will transition in. | +| orientation | 'horizontal'
| 'vertical'
| 'vertical' | The collapse transition orientation. | | timeout | 'auto'
| number
| { appear?: number, enter?: number, exit?: number }
| duration.standard | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object.
Set to 'auto' to automatically calculate transition time based on height. | The `ref` is forwarded to the root element. @@ -47,8 +48,9 @@ Any other props supplied will be provided to the root element ([Transition](http | Rule name | Global class | Description | |:-----|:-------------|:------------| | container | .MuiCollapse-container | Styles applied to the container element. +| horizontal | .MuiCollapse-horizontal | Pseudo-class applied to the root element if `orientation="horizontal"`. | entered | .MuiCollapse-entered | Styles applied to the container element when the transition has entered. -| hidden | .MuiCollapse-hidden | Styles applied to the container element when the transition has exited and `collapsedHeight` != 0px. +| hidden | .MuiCollapse-hidden | Styles applied to the container element when the transition has exited and `collapsedSize` != 0px. | wrapper | .MuiCollapse-wrapper | Styles applied to the outer wrapper element. | wrapperInner | .MuiCollapse-wrapperInner | Styles applied to the inner wrapper element. diff --git a/docs/src/pages/components/transitions/SimpleCollapse.js b/docs/src/pages/components/transitions/SimpleCollapse.js index 363b4342aa9f0d..4fab1cd9a4685f 100644 --- a/docs/src/pages/components/transitions/SimpleCollapse.js +++ b/docs/src/pages/components/transitions/SimpleCollapse.js @@ -7,10 +7,16 @@ import FormControlLabel from '@material-ui/core/FormControlLabel'; const useStyles = makeStyles((theme) => ({ root: { - height: 180, + height: 300, }, container: { display: 'flex', + justifyContent: 'space-around', + height: 120, + width: 250, + }, + halfWidth: { + width: '50%', }, paper: { margin: theme.spacing(1), @@ -51,7 +57,7 @@ export default function SimpleCollapse() { - + +
+
+ + + + + + + +
+
+ + + + + + + +
+
); } diff --git a/docs/src/pages/components/transitions/SimpleCollapse.tsx b/docs/src/pages/components/transitions/SimpleCollapse.tsx index 607efa33ea28d4..4f6914f46104a1 100644 --- a/docs/src/pages/components/transitions/SimpleCollapse.tsx +++ b/docs/src/pages/components/transitions/SimpleCollapse.tsx @@ -8,10 +8,16 @@ import FormControlLabel from '@material-ui/core/FormControlLabel'; const useStyles = makeStyles((theme: Theme) => createStyles({ root: { - height: 180, + height: 300, }, container: { display: 'flex', + justifyContent: 'space-around', + height: 120, + width: 250, + }, + halfWidth: { + width: '50%', }, paper: { margin: theme.spacing(1), @@ -53,7 +59,7 @@ export default function SimpleCollapse() {
- + +
+
+ + + + + + + +
+
+ + + + + + + +
+
); } diff --git a/docs/src/pages/components/transitions/transitions.md b/docs/src/pages/components/transitions/transitions.md index 88e10949961490..ecac73b911a689 100644 --- a/docs/src/pages/components/transitions/transitions.md +++ b/docs/src/pages/components/transitions/transitions.md @@ -37,8 +37,9 @@ export default Main() { ## Collapse -Expand vertically from the top of the child element. -The `collapsedHeight` property can be used to set the minimum height when not expanded. +Expand from the start edge of the child element. +Use the `orientation` prop if you need a horizontal collapse. +The `collapsedSize` prop can be used to set the minimum width/height when not expanded. {{"demo": "pages/components/transitions/SimpleCollapse.js", "bg": true}} diff --git a/packages/material-ui/src/Collapse/Collapse.d.ts b/packages/material-ui/src/Collapse/Collapse.d.ts index 42ffc952e5f946..d3f4cccab1cfe3 100644 --- a/packages/material-ui/src/Collapse/Collapse.d.ts +++ b/packages/material-ui/src/Collapse/Collapse.d.ts @@ -9,9 +9,9 @@ export interface CollapseProps extends StandardProps ({ /* Styles applied to the container element. */ container: { - height: 0, + position: 'relative', overflow: 'hidden', + height: 0, transition: theme.transitions.create('height'), + '&$horizontal': { + width: 0, + height: 'auto', + transition: theme.transitions.create('width'), + }, }, + /* Pseudo-class applied to the root element if `orientation="horizontal"`. */ + horizontal: {}, /* Styles applied to the container element when the transition has entered. */ entered: { height: 'auto', overflow: 'visible', + '&$horizontal': { + height: 'initial', + width: 'auto', + }, }, - /* Styles applied to the container element when the transition has exited and `collapsedHeight` != 0px. */ + /* Styles applied to the container element when the transition has exited and `collapsedSize` != 0px. */ hidden: { visibility: 'hidden', }, @@ -28,10 +40,19 @@ export const styles = (theme) => ({ wrapper: { // Hack to get children with a negative margin to not falsify the height computation. display: 'flex', + width: '100%', + '&$horizontal': { + width: 'initial', + height: '100%', + }, }, /* Styles applied to the inner wrapper element. */ wrapperInner: { width: '100%', + '&$horizontal': { + width: 'initial', + height: '100%', + }, }, }); @@ -45,7 +66,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { children, classes, className, - collapsedHeight: collapsedHeightProp = '0px', + collapsedSize: collapsedSizeProp = '0px', component: Component = 'div', disableStrictModeCompat = false, in: inProp, @@ -55,6 +76,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { onExit, onExited, onExiting, + orientation = 'vertical', style, timeout = duration.standard, // eslint-disable-next-line react/prop-types @@ -65,8 +87,10 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { const timer = React.useRef(); const wrapperRef = React.useRef(null); const autoTransitionDuration = React.useRef(); - const collapsedHeight = - typeof collapsedHeightProp === 'number' ? `${collapsedHeightProp}px` : collapsedHeightProp; + const collapsedSize = + typeof collapsedSizeProp === 'number' ? `${collapsedSizeProp}px` : collapsedSizeProp; + const isHorizontal = orientation === 'horizontal'; + const size = isHorizontal ? 'width' : 'height'; React.useEffect(() => { return () => { @@ -93,8 +117,15 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { } }; + const getWrapperSize = () => + wrapperRef.current ? wrapperRef.current[isHorizontal ? 'clientWidth' : 'clientHeight'] : 0; + const handleEnter = normalizedTransitionCallback((node, isAppearing) => { - node.style.height = collapsedHeight; + if (wrapperRef.current) { + // Set absolute position to get the size of collapsed content + wrapperRef.current.style.position = 'absolute'; + } + node.style[size] = collapsedSize; if (onEnter) { onEnter(node, isAppearing); @@ -102,7 +133,12 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { }); const handleEntering = normalizedTransitionCallback((node, isAppearing) => { - const wrapperHeight = wrapperRef.current ? wrapperRef.current.clientHeight : 0; + const wrapperSize = getWrapperSize(); + + if (wrapperRef.current) { + // After the size is read reset the position back to default + wrapperRef.current.style.position = ''; + } const { duration: transitionDuration } = getTransitionProps( { style, timeout }, @@ -112,7 +148,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { ); if (timeout === 'auto') { - const duration2 = theme.transitions.getAutoHeightDuration(wrapperHeight); + const duration2 = theme.transitions.getAutoHeightDuration(wrapperSize); node.style.transitionDuration = `${duration2}ms`; autoTransitionDuration.current = duration2; } else { @@ -120,7 +156,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { typeof transitionDuration === 'string' ? transitionDuration : `${transitionDuration}ms`; } - node.style.height = `${wrapperHeight}px`; + node.style[size] = `${wrapperSize}px`; if (onEntering) { onEntering(node, isAppearing); @@ -128,7 +164,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { }); const handleEntered = normalizedTransitionCallback((node, isAppearing) => { - node.style.height = 'auto'; + node.style[size] = 'auto'; if (onEntered) { onEntered(node, isAppearing); @@ -136,8 +172,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { }); const handleExit = normalizedTransitionCallback((node) => { - const wrapperHeight = wrapperRef.current ? wrapperRef.current.clientHeight : 0; - node.style.height = `${wrapperHeight}px`; + node.style[size] = `${getWrapperSize()}px`; if (onExit) { onExit(node); @@ -147,8 +182,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { const handleExited = normalizedTransitionCallback(onExited); const handleExiting = normalizedTransitionCallback((node) => { - const wrapperHeight = wrapperRef.current ? wrapperRef.current.clientHeight : 0; - + const wrapperSize = getWrapperSize(); const { duration: transitionDuration } = getTransitionProps( { style, timeout }, { @@ -157,7 +191,9 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { ); if (timeout === 'auto') { - const duration2 = theme.transitions.getAutoHeightDuration(wrapperHeight); + // TODO: rename getAutoHeightDuration to something more generic (width support) + // Actually it just calculates animation duration based on size + const duration2 = theme.transitions.getAutoHeightDuration(wrapperSize); node.style.transitionDuration = `${duration2}ms`; autoTransitionDuration.current = duration2; } else { @@ -165,7 +201,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { typeof transitionDuration === 'string' ? transitionDuration : `${transitionDuration}ms`; } - node.style.height = collapsedHeight; + node.style[size] = collapsedSize; if (onExiting) { onExiting(node); @@ -198,20 +234,32 @@ const Collapse = React.forwardRef(function Collapse(props, ref) { className={clsx( classes.container, { + [classes.horizontal]: isHorizontal, [classes.entered]: state === 'entered', - [classes.hidden]: state === 'exited' && !inProp && collapsedHeight === '0px', + [classes.hidden]: state === 'exited' && !inProp && collapsedSize === '0px', }, className, )} style={{ - minHeight: collapsedHeight, + [isHorizontal ? 'minWidth' : 'minHeight']: collapsedSize, ...style, }} ref={handleRef} {...childProps} > -
-
{children}
+
+
+ {children} +
)} @@ -238,9 +286,9 @@ Collapse.propTypes = { */ className: PropTypes.string, /** - * The height of the container when collapsed. + * The width (horizontal) or height (vertical) of the container when collapsed. */ - collapsedHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + collapsedSize: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), /** * The component used for the root node. * Either a string to use a HTML element or a component. @@ -280,6 +328,10 @@ Collapse.propTypes = { * @ignore */ onExiting: PropTypes.func, + /** + * The collapse transition orientation. + */ + orientation: PropTypes.oneOf(['horizontal', 'vertical']), /** * @ignore */ diff --git a/packages/material-ui/src/Collapse/Collapse.test.js b/packages/material-ui/src/Collapse/Collapse.test.js index c4530917536f02..0fc826bb83dd62 100644 --- a/packages/material-ui/src/Collapse/Collapse.test.js +++ b/packages/material-ui/src/Collapse/Collapse.test.js @@ -251,25 +251,23 @@ describe('', () => { }); }); - describe('prop: collapsedHeight', () => { - const collapsedHeight = '10px'; + describe('prop: collapsedSize', () => { + const collapsedSize = '10px'; it('should work when closed', () => { - const { container } = render( - , - ); + const { container } = render(); const collapse = container.firstChild; - expect(collapse.style.minHeight).to.equal(collapsedHeight); + expect(collapse.style.minHeight).to.equal(collapsedSize); }); it('should be taken into account in handleExiting', () => { const handleExiting = spy(); const { setProps } = render( - , + , ); setProps({ in: false }); - expect(handleExiting.args[0][0].style.height).to.equal(collapsedHeight); + expect(handleExiting.args[0][0].style.height).to.equal(collapsedSize); }); }); From 2eb8a8b29a5a4c7f3f1883cfe589c6718b6f03a4 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 3 Jul 2020 01:34:53 +0200 Subject: [PATCH 2/3] update migration guide --- docs/src/pages/guides/migration-v4/migration-v4.md | 9 +++++++++ packages/material-ui/src/Collapse/Collapse.js | 5 ++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/src/pages/guides/migration-v4/migration-v4.md b/docs/src/pages/guides/migration-v4/migration-v4.md index d7e06b84e2904e..90275bfa7c4a3a 100644 --- a/docs/src/pages/guides/migration-v4/migration-v4.md +++ b/docs/src/pages/guides/migration-v4/migration-v4.md @@ -64,6 +64,15 @@ yarn add @material-ui/core@next +