diff --git a/packages/mui-material/src/Drawer/Drawer.js b/packages/mui-material/src/Drawer/Drawer.js
index 8783d063064120..f65dffc5cecfec 100644
--- a/packages/mui-material/src/Drawer/Drawer.js
+++ b/packages/mui-material/src/Drawer/Drawer.js
@@ -13,6 +13,7 @@ import rootShouldForwardProp from '../styles/rootShouldForwardProp';
import { styled, useTheme } from '../zero-styled';
import memoTheme from '../utils/memoTheme';
import { useDefaultProps } from '../DefaultPropsProvider';
+import useForkRef from '../utils/useForkRef';
import { getDrawerUtilityClass } from './drawerClasses';
import useSlot from '../utils/useSlot';
import { FOCUSABLE_ATTRIBUTE } from '../utils/focusable';
@@ -222,10 +223,17 @@ const Drawer = React.forwardRef(function Drawer(inProps, ref) {
// We use this state is order to skip the appear transition during the
// initial mount of the component.
const mounted = React.useRef(false);
+ const rootRef = React.useRef(null);
+ const handleRef = useForkRef(ref, rootRef);
+
React.useEffect(() => {
mounted.current = true;
}, []);
+ // Resolve the container lazily so Slide reads the mounted modal root
+ // after refs are assigned, rather than the initial null ref during render.
+ const resolveSlideContainer = React.useCallback(() => rootRef.current, []);
+
const anchorInvariant = getAnchor({ direction: isRtl ? 'rtl' : 'ltr' }, anchorProp);
const anchor = anchorProp;
@@ -256,7 +264,7 @@ const Drawer = React.forwardRef(function Drawer(inProps, ref) {
};
const [RootSlot, rootSlotProps] = useSlot('root', {
- ref,
+ ref: handleRef,
elementType: DrawerRoot,
className: clsx(classes.root, classes.modal, className),
shouldForwardComponentProp: true,
@@ -267,6 +275,7 @@ const Drawer = React.forwardRef(function Drawer(inProps, ref) {
...ModalProps,
},
additionalProps: {
+ closeAfterTransition: true,
open,
onClose,
hideBackdrop,
@@ -299,7 +308,7 @@ const Drawer = React.forwardRef(function Drawer(inProps, ref) {
const [DockedSlot, dockedSlotProps] = useSlot('docked', {
elementType: DrawerDockedRoot,
- ref,
+ ref: handleRef,
className: clsx(classes.root, classes.docked, className),
ownerState,
externalForwardedProps,
@@ -315,6 +324,10 @@ const Drawer = React.forwardRef(function Drawer(inProps, ref) {
direction: oppositeDirection[anchorInvariant],
timeout: transitionDuration,
appear: mounted.current,
+ ...(variant === 'temporary' &&
+ (slots.transition == null || slots.transition === Slide) && {
+ container: resolveSlideContainer,
+ }),
},
});
diff --git a/packages/mui-material/src/Drawer/Drawer.test.js b/packages/mui-material/src/Drawer/Drawer.test.js
index 7ed0e3327ceed0..57a7f3667e3390 100644
--- a/packages/mui-material/src/Drawer/Drawer.test.js
+++ b/packages/mui-material/src/Drawer/Drawer.test.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import { expect } from 'chai';
-import { spy } from 'sinon';
-import { createRenderer, screen, isJsdom } from '@mui/internal-test-utils';
+import { spy, stub } from 'sinon';
+import { act, createRenderer, screen, isJsdom } from '@mui/internal-test-utils';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import Drawer, { drawerClasses as classes } from '@mui/material/Drawer';
import { modalClasses } from '@mui/material/Modal';
@@ -155,6 +155,101 @@ describe('', () => {
});
});
+ describe('scroll lock', () => {
+ it('should keep the scroll locked until the exit transition completes by default', () => {
+ const transitionDuration = 123;
+ const { setProps } = render(
+
+
+ ,
+ );
+
+ expect(document.body.style.overflow).to.equal('');
+
+ setProps({ open: true });
+ clock.runToLast();
+
+ expect(document.body.style.overflow).to.equal('hidden');
+
+ setProps({ open: false });
+
+ expect(document.body.style.overflow).to.equal('hidden');
+
+ act(() => {
+ clock.runToLast();
+ });
+
+ expect(document.body.style.overflow).to.equal('');
+ });
+
+ it('should allow opting out of waiting for the exit transition before unlocking scroll', () => {
+ const transitionDuration = 123;
+ const { setProps } = render(
+
+
+ ,
+ );
+
+ setProps({ open: true });
+ clock.runToLast();
+
+ expect(document.body.style.overflow).to.equal('hidden');
+
+ setProps({ open: false });
+
+ expect(document.body.style.overflow).to.equal('');
+ });
+ });
+
+ describe('transition container', () => {
+ it.skipIf(isJsdom())('should slide relative to the drawer root by default', function test() {
+ let nodeExitingTransformStyle;
+ const { setProps } = render(
+ {
+ nodeExitingTransformStyle = node.style.transform;
+ },
+ },
+ }}
+ >
+
+ ,
+ );
+
+ const root = document.querySelector(`.${classes.root}`);
+ const paper = document.querySelector(`.${classes.paper}`);
+
+ const rootRectStub = stub(root, 'getBoundingClientRect').callsFake(() => ({
+ width: 1000,
+ height: 500,
+ left: 0,
+ right: 1000,
+ top: 0,
+ bottom: 500,
+ }));
+ const paperRectStub = stub(paper, 'getBoundingClientRect').callsFake(() => ({
+ width: 200,
+ height: 500,
+ left: 800,
+ right: 1000,
+ top: 0,
+ bottom: 500,
+ }));
+
+ try {
+ setProps({ open: false });
+ expect(nodeExitingTransformStyle).to.equal('translateX(200px)');
+ } finally {
+ rootRectStub.restore();
+ paperRectStub.restore();
+ }
+ });
+ });
+
describe('accessibility', () => {
it('should have role="dialog" and aria-modal="true" when variant is temporary', () => {
render(