Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions packages/mui-material/src/Drawer/Drawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand All @@ -267,6 +275,7 @@ const Drawer = React.forwardRef(function Drawer(inProps, ref) {
...ModalProps,
},
additionalProps: {
closeAfterTransition: true,
open,
onClose,
hideBackdrop,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
}),
},
});

Expand Down
99 changes: 97 additions & 2 deletions packages/mui-material/src/Drawer/Drawer.test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -155,6 +155,101 @@ describe('<Drawer />', () => {
});
});

describe('scroll lock', () => {
it('should keep the scroll locked until the exit transition completes by default', () => {
const transitionDuration = 123;
const { setProps } = render(
<Drawer open={false} transitionDuration={transitionDuration}>
<div />
</Drawer>,
);

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(
<Drawer open={false} transitionDuration={transitionDuration} closeAfterTransition={false}>
<div />
</Drawer>,
);

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(
<Drawer
open
anchor="right"
slotProps={{
transition: {
onExit: (node) => {
nodeExitingTransformStyle = node.style.transform;
},
},
}}
>
<div />
</Drawer>,
);

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(
Expand Down
Loading