radix-ui / primitives

Radix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by @workos.
https://radix-ui.com/primitives
MIT License
15.93k stars 833 forks source link

[Bug]. Dialog and Dropdown Menu components conflict #3141

Open xkomiks opened 2 months ago

xkomiks commented 2 months ago

Bug report

Current Behavior

When using Dialog and Dropdown Menu components, a bug occurs.

Play

  1. open Code Sandbox
  2. Open Drowdown by clicking on button
  3. Open Dialog by clicking the Open button in Dropdown
  4. Close the Modal Window by clicking the Close button or the Background button
  5. Check that the open dropdown button does not open because of pointer-events: none on the body tag.

Note: When closing the window by clicking on the Dropdown button, the modal window closes as expected.

Expected behavior

There should not be pointer-events: none on the body tag when closing the modal window

Reproducible example

https://codesandbox.io/p/sandbox/radix-ui-dialog-forked-5zzj6h?workspaceId=a4b71791-adc5-4752-b50f-7be66d4db979

Suggested solution

Additional context

Your environment

Software Name(s) Version
Radix Package(s)
React n/a
Browser
Assistive tech
Node n/a
npm/yarn
Operating System
meszarosdezso commented 2 months ago

This is a known "issue", you can use modal={false} on the Dropdown to fix it as mentioned here.

xkomiks commented 2 months ago

@meszarosdezso interesting, thanks. I'll check it out.

By the way, I wrote my solution for this 5 minutes ago. Maybe someone will need it

// This is a workaround for the issue described here:
// https://github.com/radix-ui/primitives/issues/3141

// Dialog animation duration defined in Dialog.module.scss
const DIALOG_ANIMATION_DURATION = 300;

// Adding a slight buffer (100ms) to ensure the animation is complete before changing pointer events
const DELAYED_VALUE_TIMEOUT = DIALOG_ANIMATION_DURATION + 100;

export function useRadixDialogPointerEventsFix(open: boolean) {
  useEffect(() => {
    const timeoutId = setTimeout(() => {
      if (open) {
        return;
      }
      if (document.body.style.pointerEvents === 'none') {
        document.body.style.pointerEvents = '';
      }
    }, DELAYED_VALUE_TIMEOUT);

    return () => clearTimeout(timeoutId);
  }, [open]);
}
jvenus99 commented 1 month ago

Hi Guys! I found a other solution for this problem. I just install a dependency @radix-ui/react-dismissable-layer So, do npm i @radix-ui/react-dismissable-layer and rerun your project.

meganspaulding commented 1 month ago

This might be a separate issue but it seems similar to this. I found a bug where I had the following:

When the div container dialog closed, and the body level dialog opened, if I refresh the page, the pointer-event: none never gets reset on the currently visible dialog, leaving the dialog visible but unable to be clicked. I was able to fix the issue by removing the dialog portal from the div container dialog. 🤷‍♀️

felipedeboni commented 2 weeks ago

I just hit this as well. I have a dialog that triggers an alert. Closing both of them at the same time correctly removes data-scroll-locked="2" from the body, however pointer-events: none is left on style attribute.

I've made a solution similar to @xkomiks, however I put the hook in a single place and it takes care everywhere.

const useRadixDialogPointerEventsFix = () => {
  useEffect(() => {
    const body = document.body;

    const observer = new MutationObserver((mutationsList) => {
      for (const mutation of mutationsList) {
        if (
          mutation.type !== 'attributes' ||
          mutation.attributeName !== 'data-scroll-locked'
        ) {
          return;
        }

        const total = parseInt(body.dataset.scrollLocked ?? '0', 10);

        if (total > 0) {
          return;
        }

        body.style.pointerEvents = '';
      }
    });

    observer.observe(body, {
      attributes: true,
      attributeFilter: ['data-scroll-locked']
    });

    return () => observer.disconnect();
  }, []);
};