Material-ui: [Drawer] - How to Initialize Drawer Inside Container Element

Created on 6 Jun 2018  路  26Comments  路  Source: mui-org/material-ui


I'm trying to use a "temporary" right Drawer component, and I want the drawer and it's backdrop to be contained within the parent div.

I can't find any working examples of this, and I'm not sure how to target the Backdrop component CSS from my Drawer component.

Expected Behavior


When specifying a container prop for my Drawer, I expect that the drawer, and it's backdrop would be enclosed inside that container. Since this is not the case, it's unclear how to override the desired styles.

It should function similar to this sidebar example

Current Behavior


The Drawer, Modal, and Backdrop components use fixed for their positioning, resulting in them always displaying at the top-right of the viewport.

Context


I created a CodeSandbox with an example scenario.

If you click on the "Open Drawer" button, <DrawerRight> opens to the right of the viewport as expected.
It overlays all other elements, including the <Navbar>.

The goal is to have the drawer open, but only overlay elements in it's containing element.

I attempted to override the styling with classes as shown in the docs

Here is an updated version of the above CodeSandbox with my changes.
The main differences are:

  1. Pass reference to <MainContent> as container prop to <Drawer>
  2. Use withStyles to add styles based on <Drawer> CSS API

Note that the drawer now seemed to be sized correctly, but the backdrop is still covering the entire viewport.
Also, the animation has become jittery, with the other elements being shifted while the drawer is opening.

Your Environment

| Tech | Version |
|--------------|---------|
| Material-UI | v1.2.0 |
| React | v16.3.2 |
| browser | Firefox 60.0.1 |

Drawer

Most helpful comment

For anyone that still wonders about the entire solution, the following worked for me.

Please note that I used document.getElementById here and that requires that you set the ID property to that value if you want the drawer to be contained in that element. It should also be possible to use a ref in this case, and that is preferred in my opinion.

Step 1: Set an ID attribute for the element that you want to contain the drawer elements

<div id="drawer-container" style="position: relative">
  <span>Some elements</span>
</div>

Make sure that you add position: relative to this element.

Step 2: Set correct styling and reference to container element on Drawer element.

<Drawer
  open={true}
  onClose={() => {}}
  PaperProps={{ style: { position: 'absolute' } }}
  BackdropProps={{ style: { position: 'absolute' } }}
  ModalProps={{
    container: document.getElementById('drawer-container'),
    style: { position: 'absolute' }
  }}
  variant="temporary"
>
  <span>Some elements</span>
</Drawer>

The things to take away from the example above are the PaperProps, BackdropProps and the ModalProps props. For them to be contained within the container element that we created in step one, these elements have to be absolutely positioned.

Also note the document.getElementById to get a reference to the container element.

All 26 comments

Does this help?

import React from "react";

const styles = {
  position: "relative",
  zIndex: 1301,
  backgroundColor: "red"
};

const Navbar = () => (
  <div style={styles}>Navbar: this should not be covered</div>
);

export default Navbar;

The default zIndex provided to Drawer component is 1200, you can over ride it like that. :point_up:

@adeelibr Thanks for the suggestion!

That should work for the simplified example I provided, but in my actual use-case, there are several more components (eg. a sidebar) that I don't want to be covered.

I don't think it's a maintainable solution to hard-code component z-index values whenever I want a contained drawer.

I was hoping there was a way I can use the CSS API or classes to pass down styles Drawer -> Modal -> Backdrop

Can you provide the use case example via a code pen & i'll look into it.

Here's a sandbox which more accurately represents my use-case:
CodeSandbox

I want the drawer and backdrop to only overlay the orange section.
Preferably without having to hard-code z-index values for the other elements.

I don't see any other way other then provide a zIndex with a relative position. https://codesandbox.io/s/9jw7y3q00p

Hmm not the solution I was hoping for, but I don't see a straight-forward alternative either.

Thanks!

I have the same requirement. Can we reopen this as a feature request?

For anyone who stumbles on this page looking for an answer. I managed to do it by setting the modal container, then specifying the css positioning on the modal root, backdrop and docked drawer to absolute:
<Drawer variant="temporary" open={this.props.showDrawer} anchor="left" onClose={this.props.toggleDrawer} ModalProps={this.ref.current ? {container: this.ref.current} : {}} classes={{ paperAnchorLeft: "class1", modal: "class2" }} BackdropProps={{ className: "class3" }} >

For anyone that still wonders about the entire solution, the following worked for me.

Please note that I used document.getElementById here and that requires that you set the ID property to that value if you want the drawer to be contained in that element. It should also be possible to use a ref in this case, and that is preferred in my opinion.

Step 1: Set an ID attribute for the element that you want to contain the drawer elements

<div id="drawer-container" style="position: relative">
  <span>Some elements</span>
</div>

Make sure that you add position: relative to this element.

Step 2: Set correct styling and reference to container element on Drawer element.

<Drawer
  open={true}
  onClose={() => {}}
  PaperProps={{ style: { position: 'absolute' } }}
  BackdropProps={{ style: { position: 'absolute' } }}
  ModalProps={{
    container: document.getElementById('drawer-container'),
    style: { position: 'absolute' }
  }}
  variant="temporary"
>
  <span>Some elements</span>
</Drawer>

The things to take away from the example above are the PaperProps, BackdropProps and the ModalProps props. For them to be contained within the container element that we created in step one, these elements have to be absolutely positioned.

Also note the document.getElementById to get a reference to the container element.

Hi ! I tried the example above, but there is this error : "document is not defined". Do you have any information about this. How should I define it ? Thanks in advance

I have the same requirement. Can we reopen this as a feature request?

Hi thanks, it worked! But when I try to set the anchor to "right" (I need to open the drawer from the right side) it opens weirdly (shaking). Any solution for that?

Hi thanks, it worked! But when I try to set the anchor to "right" (I need to open the drawer from the right side) it opens weirdly (shaking). Any solution for that?

variant="persistent" on Drawer worked for me.

variant="persistent" on Drawer worked for me.

Me too! Thanks @kelseyleftwich !!!

@LeroyDoornebal
thank you so much it worked

@LeroyDoornebal
Although it's working fine for the most part. The only problem is with slide exit animation, the drawer slides out of the container it is contained in and goes off-screen. Whereas the expected behavior should be that the drawer should end slide exit animation inside the container it is in.
Can you help me fix it?

@kelseyleftwich While this fixed the weird behavior, it also removed the backdrop which I need.

@sandeepgahlawat I'm also with the same issue as you. Did you find any solution for this?

@giovanniantonaccio yes, I did figure out a way to fix it, and below is what I did. In case you still have doubts feel free to ask.

      <Box
        width="100%"
        height="100%"
        id="drawer-container"
        position="relative"
        bgcolor="white"
        component="div"
        style={{ overflowY: "scroll", overflowX: "hidden" }}
       >
        <Drawer
          open={props.openDrawer}
          onClose={() => {}}
          elevation={5}
          PaperProps={{ style: { position: "absolute", width: "486px" } }}
          BackdropProps={{ style: { position: "absolute" } }}
          ModalProps={{
            container: document.getElementById("drawer-container"),
            style: { position: "absolute" },
          }}
          SlideProps={{
            onExiting: (node) => {
              node.style.webkitTransform = "scaleX(0)";
              node.style.transform = "scaleX(0)";
              node.style.transformOrigin = "top left ";
            },
          }}
        >
          <DrawerContent />
        </Drawer>
      </Box>

Thanks @sandeepgahlawat! Is solved the problem!

The solution described by @LeroyDoornebal doesn't seem to work.

Here's a code sandbox. The drawer still covers the whole page. Any advice about what I'm doing wrong?

Edit: figured it out. The drawer container needs to be visible and take up space (e.g., by setting a width and height). I've updated the code sandbox to demonstrate.

Edit 2: OK, that doesn't quite do it either. The drawer still covers the whole page upon loading the sandbox, but then moves into the correct position upon re-rendering, when the code is changed.

Edit 3: After some experimentation, it seems to work if the drawer variant is "permanent", and fail (but then fix itself after re-rendering) when "temporary".

The solution described by @LeroyDoornebal doesn't seem to work.

Here's a code sandbox. The drawer still covers the whole page. Any advice about what I'm doing wrong?

Edit: figured it out. The drawer container needs to be visible and take up space (e.g., by setting a width and height). I've updated the code sandbox to demonstrate.

Edit 2: OK, that doesn't quite do it either. The drawer still covers the whole page upon loading the sandbox, but then moves into the correct position upon re-rendering, when the code is changed.

Edit 3: After some experimentation, it seems to work if the drawer variant is "permanent", and fail (but then fix itself after re-rendering) when "temporary".

just remove variant prop and it will work perfectly

@sandeepgahlawat removing the prop will default to "temporary".
https://material-ui.com/api/drawer/

It also doesn't seem to work properly with an anchor='bottom'. I modified @frankpape 's example to use a button trigger with setState, and you can see the behavior isn't working as intended (100px blue box sliding up from the green box, and disappearing into the white on toggle off).

https://codesandbox.io/s/material-demo-7nor0?file=/demo.js

Can someone help me with this problem: I used similar approach but with a element higher than the screen height.

https://codesandbox.io/s/material-demo-forked-x8wi2?file=/demo.js

Works fine in Firefox. But in Chrome and Edge the browser scrolls to the top of the drawer. How can I prevent the scrolling?

@mmartinsky I have a similar problem to yours. Did you manage to fix it?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

chris-hinds picture chris-hinds  路  3Comments

zabojad picture zabojad  路  3Comments

finaiized picture finaiized  路  3Comments

ghost picture ghost  路  3Comments

FranBran picture FranBran  路  3Comments