mantinedev / mantine

A fully featured React components library
https://mantine.dev
MIT License
26.09k stars 1.85k forks source link

Menu close on click outside doesn't work as expected #2593

Closed Ryiski closed 1 year ago

Ryiski commented 1 year ago

What package has an issue

@mantine/core

Describe the bug

The menu feature "close on click outside" works only the first time but then doesn't work as intended

Also when its stuck, closeOnEscape refuses to close

Also, there is a case when 1 - set closeOnClickOutside it false and closeOnEscape true 2 - click outside 3 - hitting ESC won't work

menu

import {
  Avatar,
  createStyles,
  Group,
  Menu,
  Text,
  UnstyledButton,
} from "@mantine/core";
import { IconLogout, IconSettings, IconSwitchHorizontal } from "@tabler/icons";
import { FC, Fragment, useState } from "react";

const useStyles = createStyles((theme, _, getRef) => {
  const { primaryColor, colors, spacing, radius, fn, white, black } = theme;
  const pColor = colors[primaryColor][0];

  const userBtnRef = getRef("userButton");
  const userNameRef = getRef("userName");
  const userAvatarRef = getRef("userAvatar");
  const userActiveRef = getRef("userActive");

  return {
    userButton: {
      ref: userBtnRef,
      paddingLeft: `${spacing.sm}px`,
      borderTopLeftRadius: radius.md,
      borderTopRightRadius: radius.md,
      borderBottomLeftRadius: radius.md,
      borderBottomRightRadius: radius.md,
      transition: "background-color 100ms ease-in-out",
      "&:hover": {
        backgroundColor: fn.lighten(pColor, 0.2),
        color: white,
        borderTopRightRadius: radius.xl,
        borderBottomRightRadius: radius.xl,
      },
    },

    userName: {
      ref: userNameRef,
      fontSize: ".7rem",
    },
    userAvatar: {
      ref: userAvatarRef,
      borderRadius: radius.xl,
      height: 30,
      width: 30,
      minWidth: 30,
      transition: "border-radius 100ms ease-in-out",
      "& *": {
        transition: "background-color 100ms ease-in-out",
        fontSize: "12px",
        backgroundColor: fn.lighten(pColor, 0.2),
        color: white,
      },
      [`.${userActiveRef} &,.${userBtnRef}:hover &`]: {
        borderTopRightRadius: 0,
        borderBottomRightRadius: 0,
        "& *": {
          fontSize: "12px",
          background: white,
          color: black,
        },
      },
    },
    userActive: {
      ref: userActiveRef,
      backgroundColor: pColor,
      color: white,
      borderTopRightRadius: radius.xl,
      borderBottomRightRadius: radius.xl,
    },
  };
});

export const HeaderUSer: FC = ({}) => {
  const { classes, cx } = useStyles();
  const { userButton, userName, userActive, userAvatar } = classes;

  const [menuOpened, setMenuOpened] = useState(false);

  const user = { displayName: "Mantine" };

  const onSignOut = async () => {};

  return (
    <Fragment>
      <Menu
        position="bottom-end"
        transition="pop-top-right"
        closeOnClickOutside
        onOpen={() => setMenuOpened(true)}
        onClose={() => setMenuOpened(false)}
      >
        <Menu.Target>
          <UnstyledButton
            className={cx(userButton, {
              [userActive]: menuOpened,
            })}
          >
            <Group>
              <Text weight={700} className={userName}>
                {user?.displayName}
              </Text>
              <Avatar className={userAvatar}>MA</Avatar>
            </Group>
          </UnstyledButton>
        </Menu.Target>

        <Menu.Dropdown mt="sm">
          <Menu.Label>Settings</Menu.Label>
          <Menu.Item icon={<IconSettings size={14} stroke={1.5} />}>
            Account settings
          </Menu.Item>
          <Menu.Item icon={<IconSwitchHorizontal size={14} stroke={1.5} />}>
            Change account
          </Menu.Item>
          <Menu.Item
            icon={<IconLogout size={14} stroke={1.5} />}
            onClick={onSignOut}
          >
            Logout
          </Menu.Item>
        </Menu.Dropdown>
      </Menu>
    </Fragment>
  );
};

export default HeaderUSer;

What version of @mantine/hooks page do you have in package.json?

^5.4.1

If possible, please include a link to a codesandbox with the reproduced problem

https://codesandbox.io/s/beautiful-einstein-whij6t?file=/src/App.tsx

Do you know how to fix the issue

No

Are you willing to participate in fixing this issue and create a pull request with the fix

No response

Possible fix

No idea : (

Ryiski commented 1 year ago

The issue is coming from Popover Component

rtivital commented 1 year ago

It works as intended – when you click outside the menu, it loses focus. Menu/Popover and other similar components can only spy on escape key presses if focus is inside the dropdown (otherwise there will be issues with escape key in nested overlays, for example, Menu inside Modal). closeOnClickOutside is an accessibility option – if you turn it off, you should expect some accessibility features not to function as intended.

Ryiski commented 1 year ago

@rtivital ok the closeOnEscape makes sense

As for, when closeOnClickOutside is true, it should always close the menu when a mouse down or touch event is triggered.

But it only works on the first closeOnClickOutside event, every other closeOnClickOutside events work at random times.

I came up with a workaround and it's working as I believe it should have

I added it to the sandbox so you can have a look at it

https://codesandbox.io/s/patient-fog-9uhrk9

PS: correct me if I'm wrong on how the closeOnClickOutside is expected to work

rtivital commented 1 year ago

It is a separate issue, see https://github.com/mantinedev/mantine/issues/2551, will be fixed with next release