Skip to content
225 changes: 108 additions & 117 deletions packages/material-ui/src/Slide/Slide.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import EventListener from 'react-event-listener';
import debounce from 'debounce'; // < 1kb payload overhead when lodash/debounce is > 3kb.
import Transition from 'react-transition-group/Transition';
import ownerWindow from '../utils/ownerWindow';
import { setRef } from '../utils/reactHelpers';
import { useForkRef } from '../utils/reactHelpers';
import withTheme from '../styles/withTheme';
import { duration } from '../styles/transitions';
import { reflow, getTransitionProps } from '../transitions/utils';
Expand Down Expand Up @@ -73,63 +72,43 @@ export function setTranslateValue(props, node) {
* The Slide transition is used by the [Drawer](/demos/drawers/) component.
* It uses [react-transition-group](https://github.com/reactjs/react-transition-group) internally.
*/
class Slide extends React.Component {
mounted = false;

constructor() {
super();

if (typeof window !== 'undefined') {
this.handleResize = debounce(() => {
// Skip configuration where the position is screen size invariant.
if (this.props.in || this.props.direction === 'down' || this.props.direction === 'right') {
return;
}

if (this.childDOMNode) {
setTranslateValue(this.props, this.childDOMNode);
}
}, 166); // Corresponds to 10 frames at 60 Hz.
}
}

componentDidMount() {
this.mounted = true;

// state.mounted handle SSR, once the component is mounted, we need
// to properly hide it.
if (!this.props.in) {
// We need to set initial translate values of transition element
// otherwise component will be shown when in=false.
this.updatePosition();
}
}

componentDidUpdate(prevProps) {
if (prevProps.direction !== this.props.direction && !this.props.in) {
// We need to update the position of the drawer when the direction change and
// when it's hidden.
this.updatePosition();
}
}

componentWillUnmount() {
this.handleResize.clear();
}
function Slide(props) {
const {
children,
direction,
in: inProp,
onEnter,
onEntering,
onExit,
onExited,
style,
theme,
...other
} = props;

const mountedRef = React.useRef(false);
const childrenRef = React.useRef();
const [prevDirection, setPrevDirection] = React.useState();
/**
* used in cloneElement(children, { ref: handleRef })
*/
const handleOwnRef = ref => {
// #StrictMode ready
childrenRef.current = ReactDOM.findDOMNode(ref);
};
const handleRef = useForkRef(children.ref, handleOwnRef);

handleEnter = node => {
setTranslateValue(this.props, node);
const handleEnter = node => {
setTranslateValue(props, node);
reflow(node);

if (this.props.onEnter) {
this.props.onEnter(node);
if (onEnter) {
onEnter(node);
}
};

handleEntering = node => {
const { theme } = this.props;

const transitionProps = getTransitionProps(this.props, {
const handleEntering = node => {
const transitionProps = getTransitionProps(props, {
mode: 'enter',
});
node.style.webkitTransition = theme.transitions.create('-webkit-transform', {
Expand All @@ -142,15 +121,13 @@ class Slide extends React.Component {
});
node.style.webkitTransform = 'translate(0, 0)';
node.style.transform = 'translate(0, 0)';
if (this.props.onEntering) {
this.props.onEntering(node);
if (onEntering) {
onEntering(node);
}
};

handleExit = node => {
const { theme } = this.props;

const transitionProps = getTransitionProps(this.props, {
const handleExit = node => {
const transitionProps = getTransitionProps(props, {
mode: 'exit',
});
node.style.webkitTransition = theme.transitions.create('-webkit-transform', {
Expand All @@ -161,78 +138,92 @@ class Slide extends React.Component {
...transitionProps,
easing: theme.transitions.easing.sharp,
});
setTranslateValue(this.props, node);
setTranslateValue(props, node);

if (this.props.onExit) {
this.props.onExit(node);
if (onExit) {
onExit(node);
}
};

handleExited = node => {
const handleExited = node => {
// No need for transitions when the component is hidden
node.style.webkitTransition = '';
node.style.transition = '';

if (this.props.onExited) {
this.props.onExited(node);
if (onExited) {
onExited(node);
}
};

/**
* used in cloneElement(children, { ref: handleRef })
*/
handleRef = ref => {
// #StrictMode ready
this.childDOMNode = ReactDOM.findDOMNode(ref);
setRef(this.props.children.ref, ref);
};
const updatePosition = React.useCallback(() => {
if (childrenRef.current) {
setTranslateValue(props, childrenRef.current);
}
}, [props]);

React.useEffect(() => {
const handleResize = debounce(() => {
// Skip configuration where the position is screen size invariant.
if (inProp || direction === 'down' || direction === 'right') {
return;
}

if (childrenRef.current) {
setTranslateValue(props, childrenRef.current);
}
}, 166); // Corresponds to 10 frames at 60 Hz.

window.addEventListener('resize', handleResize);

updatePosition() {
if (this.childDOMNode) {
setTranslateValue(this.props, this.childDOMNode);
return () => {
handleResize.clear();
window.removeEventListener('resize', handleResize);
};
}, [direction, inProp, props]);

React.useEffect(() => {
// state.mounted handle SSR, once the component is mounted, we need
// to properly hide it.
if (!inProp && !mountedRef.current) {
// We need to set initial translate values of transition element
// otherwise component will be shown when in=false.
updatePosition();
}
}
mountedRef.current = true;
}, [inProp, updatePosition]);

render() {
const {
children,
direction,
in: inProp,
onEnter,
onEntering,
onExit,
onExited,
style,
theme,
...other
} = this.props;

return (
<EventListener target="window" onResize={this.handleResize}>
<Transition
onEnter={this.handleEnter}
onEntering={this.handleEntering}
onExit={this.handleExit}
onExited={this.handleExited}
appear
in={inProp}
{...other}
>
{(state, childProps) => {
return React.cloneElement(children, {
ref: this.handleRef,
style: {
visibility: state === 'exited' && !inProp ? 'hidden' : undefined,
...style,
...children.props.style,
},
...childProps,
});
}}
</Transition>
</EventListener>
);
}
React.useEffect(() => {
if (prevDirection !== direction && !inProp) {
// We need to update the position of the drawer when the direction change and
// when it's hidden.
updatePosition();
}
setPrevDirection(direction);
}, [direction, inProp, prevDirection, updatePosition]);

return (
<Transition
onEnter={handleEnter}
onEntering={handleEntering}
onExit={handleExit}
onExited={handleExited}
appear
in={inProp}
{...other}
>
{(state, childProps) => {
return React.cloneElement(children, {
ref: handleRef,
style: {
visibility: state === 'exited' && !inProp ? 'hidden' : undefined,
...style,
...children.props.style,
},
...childProps,
});
}}
</Transition>
);
}

Slide.propTypes = {
Expand Down
Loading