【问题标题】:How to make a custom menu close when I click somewhere else other than the menu当我单击菜单以外的其他位置时如何关闭自定义菜单
【发布时间】:2021-02-10 19:11:45
【问题描述】:

要搭载previous question,我希望在单击菜单以外的其他位置时关闭菜单。

目前,当我单击“汉堡菜单按钮”时,它将打开/关闭。如果我单击菜单上的链接或菜单本身,它将关闭,但是当我“模糊”远离它时,我也想关闭。这就像如果您收到一条警报消息并单击其他位置,警报就会关闭。

这是当前的CodeSandbox

相关代码在Header.tsx中:

import React, { useState } from "react";
import { Link } from "react-router-dom";
import {
  AppBar,
  Container,
  createStyles,
  IconButton,
  makeStyles,
  Theme,
  Toolbar,
  Typography
} from "@material-ui/core";
import MenuIcon from "@material-ui/icons/Menu";
import clsx from "clsx";

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: {
      flexGrow: 1,
      "& a": {
        color: "white",
        textDecoration: "none"
      }
    },
    menuButton: {
      marginRight: theme.spacing(2),
      zIndex: 2
    },
    title: {
      flexGrow: 1,
      zIndex: 2
    },
    toolBar: {
      "& div": {
        transition: "left .1s"
      }
    },
    menu: {
      zIndex: 1,
      width: 200,
      height: "100%",
      position: "fixed",
      top: 48,
      transition: "left .1s",
      marginRight: theme.spacing(2),
      left: -200,
      background: "#3f51b5",
      "& div:first-element": {
        marginTop: 100
      }
    },
    menuOpen: {
      left: 0,
      transition: "left .1s"
    },
    menuClose: {
      left: -200,
      transition: "left .1s"
    },

    topMenu: {
      display: "flex",
      "& div": {
        marginLeft: theme.spacing(1)
      }
    }
  })
);
const UserMenu = () => {
  const classes = useStyles();
  const [menuOpen, setMenuOpen] = useState(false);
  const toggleMenu = () => setMenuOpen(!menuOpen);
  const handleMenuClick = () => toggleMenu();
  return (
    <>
      <IconButton
        edge="start"
        className={classes.menuButton}
        color="inherit"
        aria-label="menu"
        onClick={toggleMenu}
      >
        <MenuIcon />
      </IconButton>
      <div className={classes.toolBar}>
        <Container
          className={clsx(classes.menu, {
            [classes.menuOpen]: menuOpen,
            [classes.menuClose]: !menuOpen,
            [classes.toolBar]: true
          })}
          onClick={handleMenuClick}
        >
          <div>
            <Link to="#">My Profile</Link>
          </div>
          <div>
            <Link to="#">Account</Link>
          </div>
          <div>
            <Link to="#">Admin</Link>
          </div>
        </Container>
      </div>
    </>
  );
};

const Header: React.FC = ({ children }) => {
  const classes = useStyles();

  return (
    <AppBar position="static" className={classes.root}>
      <Toolbar variant="dense">
        <UserMenu />
        <Typography variant="h6" className={classes.title}>
          <Link to="#">Widgets, LLC</Link>
        </Typography>
        <div className={classes.topMenu}>
          <div>
            <Link to="#">Sign out</Link>
          </div>
          <div>
            <Link to="#">One more</Link>
          </div>
        </div>
      </Toolbar>
    </AppBar>
  );
};

export default Header;

**编辑:**这是应用 Ryan 的更改并将 UserMenu 移动到自己的文件后的 current Sandbox

【问题讨论】:

    标签: javascript reactjs material-ui


    【解决方案1】:

    您可以为此使用 Material-UI 的 ClickAwayListener。唯一棘手的部分是避免在单击按钮打开菜单后立即关闭菜单(因为单击事件由打开菜单的按钮处理,然后由 ClickAwayListener 处理相同的单击事件关闭菜单) .通常,您希望避免在菜单打开之前呈现ClickAwayListener,但我认为这可能会破坏菜单上的转换,除非您进行进一步的更改。我的示例通过在菜单按钮 (handleOpen) 的单击处理程序中调用 event.stopPropagation() 来解决此问题。

    这是您的代码/沙盒的修改版本,展示了这一点:

    import React, { useState } from "react";
    import { Link } from "react-router-dom";
    import {
      AppBar,
      Container,
      ClickAwayListener,
      createStyles,
      IconButton,
      makeStyles,
      Theme,
      Toolbar,
      Typography
    } from "@material-ui/core";
    import MenuIcon from "@material-ui/icons/Menu";
    import clsx from "clsx";
    
    const useStyles = makeStyles((theme: Theme) =>
      createStyles({
        root: {
          flexGrow: 1,
          "& a": {
            color: "white",
            textDecoration: "none"
          }
        },
        menuButton: {
          marginRight: theme.spacing(2),
          zIndex: 2
        },
        title: {
          flexGrow: 1,
          zIndex: 2
        },
        toolBar: {
          "& div": {
            transition: "left .1s"
          }
        },
        menu: {
          zIndex: 1,
          width: 200,
          height: "100%",
          position: "fixed",
          top: 48,
          transition: "left .1s",
          marginRight: theme.spacing(2),
          left: -200,
          background: "#3f51b5",
          "& div:first-element": {
            marginTop: 100
          }
        },
        menuOpen: {
          left: 0,
          transition: "left .1s"
        },
        menuClose: {
          left: -200,
          transition: "left .1s"
        },
    
        topMenu: {
          display: "flex",
          "& div": {
            marginLeft: theme.spacing(1)
          }
        }
      })
    );
    const UserMenu = () => {
      const classes = useStyles();
      const [menuOpen, setMenuOpen] = useState(false);
      const handleOpen = (event: React.MouseEvent) => {
        if (!menuOpen) {
          event.stopPropagation();
          setMenuOpen(true);
        }
      };
      const handleClose = (event: React.MouseEvent<any, MouseEvent>) => {
        if (menuOpen) {
          setMenuOpen(false);
        }
      };
      return (
        <>
          <IconButton
            edge="start"
            className={classes.menuButton}
            color="inherit"
            aria-label="menu"
            onClick={handleOpen}
          >
            <MenuIcon />
          </IconButton>
          <div className={classes.toolBar}>
            <ClickAwayListener onClickAway={handleClose}>
              <Container
                className={clsx(classes.menu, {
                  [classes.menuOpen]: menuOpen,
                  [classes.menuClose]: !menuOpen,
                  [classes.toolBar]: true
                })}
                onClick={handleClose}
              >
                <div>
                  <Link to="#">My Profile</Link>
                </div>
                <div>
                  <Link to="#">Account</Link>
                </div>
                <div>
                  <Link to="#">Admin</Link>
                </div>
              </Container>
            </ClickAwayListener>
          </div>
        </>
      );
    };
    
    const Header: React.FC = ({ children }) => {
      const classes = useStyles();
    
      return (
        <AppBar position="static" className={classes.root}>
          <Toolbar variant="dense">
            <UserMenu />
            <Typography variant="h6" className={classes.title}>
              <Link to="#">Widgets, LLC</Link>
            </Typography>
            <div className={classes.topMenu}>
              <div>
                <Link to="#">Sign out</Link>
              </div>
              <div>
                <Link to="#">One more</Link>
              </div>
            </div>
          </Toolbar>
        </AppBar>
      );
    };
    
    export default Header;
    

    【讨论】:

    • 我分叉了你的沙箱并将UserMenu 移动到它自己的组件中(参见问题编辑)。除了一个用例外,这很好用。当您单击菜单并关闭它,通过“汉堡菜单”重新打开菜单时,需要在菜单外单击两次而不是单击一次才能关闭它。我要试试看能不能解决这个问题。如果可以,我将编辑您的答案并将其标记为正确。如果您想尝试一下这个用例,我将不胜感激。
    • handleClose 方法中删除event.stopPropagation() 调用修复了这个问题。编辑您的答案并标记为已回答。感谢您的帮助。
    • @aarona 太棒了!我更新了沙箱以匹配。在某些时候,我想深入研究 ClickAwayListener 代码以了解为什么 stopPropagation 调用会产生这种效果,但不确定我什么时候有时间。
    • 是的,这真的很有趣,因为我正在将这些更改应用到我的项目中,并且当我打开菜单时会调用 onClickAway(尽管从未将鼠标悬停在菜单本身上),所以结果是它调用了handleOpen(好),然后调用了handleClickAway(我正在使用atm的方法名称),然后关闭了菜单,所以它就像它永远不会打开一样。我将看看我是否可以在沙盒中重新创建它,我可能会提出另一个问题并标记您,看看您是否可以帮助我解决这个问题。您是否在任何闲散小组或任何 React 社区中,我可以在那里弯下别人的耳朵?
    • @aarona 这就是为什么handleOpen 需要调用event.stopPropagation()。这种行为是意料之中的。 ClickAwayListener 不知道您的菜单是否已打开,它只是触发传递给onClickAway 的函数,只要在ClickAwayListener 内呈现的内容之外发生点击。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    • 1970-01-01
    • 2021-05-24
    • 2015-10-24
    • 1970-01-01
    • 2019-09-02
    相关资源
    最近更新 更多