单击菜单以外的其他位置时如何关闭自定义菜单

IT技术 javascript reactjs material-ui
2021-05-19 10:57:47

为了补充上一个问题,我希望在单击菜单以外的其他地方时关闭菜单。

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

这是当前的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

1个回答

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

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

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;

单击后编辑关闭菜单