UNPKG

@material-ui/core

Version:

React components that implement Google's Material Design.

284 lines (252 loc) 8.44 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; import * as React from 'react'; import PropTypes from 'prop-types'; import * as ReactDOM from 'react-dom'; import debounce from '../utils/debounce'; import { Transition } from 'react-transition-group'; import { elementAcceptingRef } from '@material-ui/utils'; import useForkRef from '../utils/useForkRef'; import useTheme from '../styles/useTheme'; import { duration } from '../styles/transitions'; import { reflow, getTransitionProps } from '../transitions/utils'; // Translate the node so he can't be seen on the screen. // Later, we gonna translate back the node to his original location // with `none`.` function getTranslateValue(direction, node) { const rect = node.getBoundingClientRect(); let transform; if (node.fakeTransform) { transform = node.fakeTransform; } else { const computedStyle = window.getComputedStyle(node); transform = computedStyle.getPropertyValue('-webkit-transform') || computedStyle.getPropertyValue('transform'); } let offsetX = 0; let offsetY = 0; if (transform && transform !== 'none' && typeof transform === 'string') { const transformValues = transform.split('(')[1].split(')')[0].split(','); offsetX = parseInt(transformValues[4], 10); offsetY = parseInt(transformValues[5], 10); } if (direction === 'left') { return `translateX(${window.innerWidth}px) translateX(${offsetX - rect.left}px)`; } if (direction === 'right') { return `translateX(-${rect.left + rect.width - offsetX}px)`; } if (direction === 'up') { return `translateY(${window.innerHeight}px) translateY(${offsetY - rect.top}px)`; } // direction === 'down' return `translateY(-${rect.top + rect.height - offsetY}px)`; } export function setTranslateValue(direction, node) { const transform = getTranslateValue(direction, node); if (transform) { node.style.webkitTransform = transform; node.style.transform = transform; } } const defaultTimeout = { enter: duration.enteringScreen, exit: duration.leavingScreen }; /** * The Slide transition is used by the [Drawer](/components/drawers/) component. * It uses [react-transition-group](https://github.com/reactjs/react-transition-group) internally. */ const Slide = /*#__PURE__*/React.forwardRef(function Slide(props, ref) { const { children, direction = 'down', in: inProp, onEnter, onEntered, onEntering, onExit, onExited, onExiting, style, timeout = defaultTimeout, // eslint-disable-next-line react/prop-types TransitionComponent = Transition } = props, other = _objectWithoutPropertiesLoose(props, ["children", "direction", "in", "onEnter", "onEntered", "onEntering", "onExit", "onExited", "onExiting", "style", "timeout", "TransitionComponent"]); const theme = useTheme(); const childrenRef = React.useRef(null); /** * used in cloneElement(children, { ref: handleRef }) */ const handleOwnRef = React.useCallback(instance => { // #StrictMode ready childrenRef.current = ReactDOM.findDOMNode(instance); }, []); const handleRefIntermediary = useForkRef(children.ref, handleOwnRef); const handleRef = useForkRef(handleRefIntermediary, ref); const normalizedTransitionCallback = callback => isAppearing => { if (callback) { // onEnterXxx and onExitXxx callbacks have a different arguments.length value. if (isAppearing === undefined) { callback(childrenRef.current); } else { callback(childrenRef.current, isAppearing); } } }; const handleEnter = normalizedTransitionCallback((node, isAppearing) => { setTranslateValue(direction, node); reflow(node); if (onEnter) { onEnter(node, isAppearing); } }); const handleEntering = normalizedTransitionCallback((node, isAppearing) => { const transitionProps = getTransitionProps({ timeout, style }, { mode: 'enter' }); node.style.webkitTransition = theme.transitions.create('-webkit-transform', _extends({}, transitionProps, { easing: theme.transitions.easing.easeOut })); node.style.transition = theme.transitions.create('transform', _extends({}, transitionProps, { easing: theme.transitions.easing.easeOut })); node.style.webkitTransform = 'none'; node.style.transform = 'none'; if (onEntering) { onEntering(node, isAppearing); } }); const handleEntered = normalizedTransitionCallback(onEntered); const handleExiting = normalizedTransitionCallback(onExiting); const handleExit = normalizedTransitionCallback(node => { const transitionProps = getTransitionProps({ timeout, style }, { mode: 'exit' }); node.style.webkitTransition = theme.transitions.create('-webkit-transform', _extends({}, transitionProps, { easing: theme.transitions.easing.sharp })); node.style.transition = theme.transitions.create('transform', _extends({}, transitionProps, { easing: theme.transitions.easing.sharp })); setTranslateValue(direction, node); if (onExit) { onExit(node); } }); const handleExited = normalizedTransitionCallback(node => { // No need for transitions when the component is hidden node.style.webkitTransition = ''; node.style.transition = ''; if (onExited) { onExited(node); } }); const updatePosition = React.useCallback(() => { if (childrenRef.current) { setTranslateValue(direction, childrenRef.current); } }, [direction]); React.useEffect(() => { // Skip configuration where the position is screen size invariant. if (inProp || direction === 'down' || direction === 'right') { return undefined; } const handleResize = debounce(() => { if (childrenRef.current) { setTranslateValue(direction, childrenRef.current); } }); window.addEventListener('resize', handleResize); return () => { handleResize.clear(); window.removeEventListener('resize', handleResize); }; }, [direction, inProp]); React.useEffect(() => { if (!inProp) { // We need to update the position of the drawer when the direction change and // when it's hidden. updatePosition(); } }, [inProp, updatePosition]); return /*#__PURE__*/React.createElement(TransitionComponent, _extends({ nodeRef: childrenRef, onEnter: handleEnter, onEntered: handleEntered, onEntering: handleEntering, onExit: handleExit, onExited: handleExited, onExiting: handleExiting, appear: true, in: inProp, timeout: timeout }, other), (state, childProps) => { return /*#__PURE__*/React.cloneElement(children, _extends({ ref: handleRef, style: _extends({ visibility: state === 'exited' && !inProp ? 'hidden' : undefined }, style, children.props.style) }, childProps)); }); }); process.env.NODE_ENV !== "production" ? Slide.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the d.ts file and run "yarn proptypes" | // ---------------------------------------------------------------------- /** * A single child content element. */ children: elementAcceptingRef, /** * Direction the child node will enter from. */ direction: PropTypes.oneOf(['down', 'left', 'right', 'up']), /** * If `true`, show the component; triggers the enter or exit animation. */ in: PropTypes.bool, /** * @ignore */ onEnter: PropTypes.func, /** * @ignore */ onEntered: PropTypes.func, /** * @ignore */ onEntering: PropTypes.func, /** * @ignore */ onExit: PropTypes.func, /** * @ignore */ onExited: PropTypes.func, /** * @ignore */ onExiting: PropTypes.func, /** * @ignore */ style: PropTypes.object, /** * The duration for the transition, in milliseconds. * You may specify a single timeout for all transitions, or individually with an object. */ timeout: PropTypes.oneOfType([PropTypes.number, PropTypes.shape({ appear: PropTypes.number, enter: PropTypes.number, exit: PropTypes.number })]) } : void 0; export default Slide;