首页 > 解决方案 > reactjs popover 组件 - 推送属性/事件处理

问题描述

我正在构建一个 reactjs 应用程序——我正在尝试使这个弹出组件模块化——这样我可以让按钮看起来像一个徽章/图标组合——在悬停而不是点击时激活弹出菜单。

这是一个沙箱——但我需要为每个沙箱创建弹出菜单——此时它正在取代按钮。 https://codesandbox.io/s/material-demo-forked-wrn2g?file=/demo.js 在此处输入图像描述 在此处输入图像描述

这是当前的组件 http://jsfiddle.net/4benm6wo/


import React from 'react';
import Button from '@material-ui/core/Button';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import Grow from '@material-ui/core/Grow';
import Paper from '@material-ui/core/Paper';
import Popper from '@material-ui/core/Popper';
import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList';

import Badge from '@material-ui/core/Badge';
import PersonIcon from '@material-ui/icons/Person';

import './PopOverMenu.scss';


export default function MenuListComposition() {
  const [open, setOpen] = React.useState(false);
  const anchorRef = React.useRef(null);

  const handleToggle = () => {
    setOpen((prevOpen) => !prevOpen);
  };

  const handleClose = (event) => {
    if (anchorRef.current && anchorRef.current.contains(event.target)) {
      return;
    }

    setOpen(false);
  };

  function handleListKeyDown(event) {
    if (event.key === 'Tab') {
      event.preventDefault();
      setOpen(false);
    }
  }

  // return focus to the button when we transitioned from !open -> open
  const prevOpen = React.useRef(open);
  React.useEffect(() => {
    if (prevOpen.current === true && open === false) {
      anchorRef.current.focus();
    }

    prevOpen.current = open;
  }, [open]);

  return (
    <div className="popover-menu">
      <div>
        <Button
          ref={anchorRef}
          aria-controls={open ? 'menu-list-grow' : undefined}
          aria-haspopup="true"
          onClick={handleToggle}
        >
          <Badge badgeContent={11} color="primary">
            <PersonIcon />
          </Badge>
        </Button>

        <Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
          {({ TransitionProps, placement }) => (
            <Grow
              {...TransitionProps}
              style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
            >
              <Paper>
                <ClickAwayListener onClickAway={handleClose}>
                  <MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
                    <MenuItem onClick={handleClose}>Profile</MenuItem>
                    <MenuItem onClick={handleClose}>My account</MenuItem>
                    <MenuItem onClick={handleClose}>Logout</MenuItem>
                  </MenuList>
                </ClickAwayListener>
              </Paper>
            </Grow>
          )}
        </Popper>
      </div>
    </div>
  );
}

但我想创建一个 popperMenu 组件——我将图标、徽章计数推送到弹出窗口中——所以我需要帮助用道具和状态来实现它。

这是我目前的尝试 http://jsfiddle.net/4benm6wo/1/

class MenuListComposition extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = { open: false };
  }

  render() {
    return (
      <div className="popover-menu">
        <div>
          <Button
            ref={anchorRef}
            aria-controls={open ? 'menu-list-grow' : undefined}
            aria-haspopup="true"
            onClick={handleToggle}
          >
            <Badge badgeContent={11} color="primary">
              <PersonIcon />
            </Badge>
          </Button>

          <Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
            {({ TransitionProps, placement }) => (
              <Grow
                {...TransitionProps}
                style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
              >
                <Paper>
                  <ClickAwayListener onClickAway={handleClose}>
                    <MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
                      <MenuItem onClick={handleClose}>Profile</MenuItem>
                      <MenuItem onClick={handleClose}>My account</MenuItem>
                      <MenuItem onClick={handleClose}>Logout</MenuItem>
                    </MenuList>
                  </ClickAwayListener>
                </Paper>
              </Grow>
            )}
          </Popper>
        </div>
      </div>
    )
  }
}

export default MenuListComposition;

所以我会从外壳创建一个类似这样的按钮/徽章弹出窗口

<MenuListComposition badgeCount={10} icon={<PersonIcon />} menu={[{ "label": "Profile", "link": "user/1" }, { "label": "Logout", "link": "logout" }]} />

最新代码


LoginButton.js http://jsfiddle.net/z3L89x2e/

import React, { Component } from 'react'
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import Button from '@material-ui/core/Button';
import PopOverMenu from '../_SharedGlobalComponents/PopOverMenu/PopOverMenu';
import MailIcon from '@material-ui/icons/Mail';
import NotificationsIcon from '@material-ui/icons/Notifications';
import PersonIcon from '@material-ui/icons/Person';

class LoggedInButtons extends Component {
  
  /*
  constructor(props, context) {
    super(props, context);
  }
  */

  render() {

    return (
      <div className="login-badges">
        <PopOverMenu
          icon={<NotificationsIcon />}
          badgecount="3"
          menu={[
              { "label": "xxx", "value": "/" },
              { "label": "xxxx", "value": "/" },
              { "label": "xxxxx", "value": "/" },
          ]}
        />

        <PopOverMenu
          icon={<MailIcon />}
          badgecount="5"
          menu={[
              { "label": "xxx", "value": "/" },
              { "label": "xxxx", "value": "/" },
              { "label": "xxxxx", "value": "/" },
          ]}
        />

        <PopOverMenu
          icon={<PersonIcon />}
          badgecount="2"
          menu={[
              { "label": "Profile", "value": "/profile" },
              { "label": "My account", "value": "/my-account" },
              { "label": "Logout", "value": "/logout" },
          ]}
        />

        <Button 
           variant="text"
           color="default"
           startIcon={<PersonIcon />}
           href="/user/view/2"
        >
          User 2
        </Button>


        <Button variant="contained" color="secondary" href="/logout">
          log out
        </Button>


      </div>
    )
  }
}

function mapStateToProps(state) {
  return {
  };
}

function mapDispatchToProps(dispatch) {
 return bindActionCreators({  }, dispatch);
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoggedInButtons))

PopOverMenu.js http://jsfiddle.net/z3L89x2e/2/

import React, { Component } from 'react';
import Button from '@material-ui/core/Button';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import Grow from '@material-ui/core/Grow';
import Paper from '@material-ui/core/Paper';
import Popper from '@material-ui/core/Popper';
import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList';
import Badge from '@material-ui/core/Badge';

//import './PopOverMenu.scss';

class PopOverMenu extends Component {

    constructor(props, context) {
        super(props, context);
        this.state = { open: false };
        this.anchorRef = React.createRef(null);
    }


    handleToggle = () => {
        this.setState({open: !this.state.open});
    };

    handleClose = () => {        
        this.setState({open: false});
    };

    handleListKeyDown = (event) => {
        if (event.key === 'Tab') {
            event.preventDefault();
            this.setState({open: false});
        }
    }

showMenuItems = () => (
    this.props.menu.map((item, i) => (
        <MenuItem onClick={this.handleClose}>{item.label}</MenuItem>
    ))
)

render() {
    return (
        <div style={{display:'inline', float:'left', marginLeft:'20px', marginTop:'10px'}} className="popover-menu">
                <Button
                    ref={this.anchorRef}
                    aria-controls={this.state.open ? 'menu-list-grow' : null}
                    aria-haspopup="true"
                    onClick={this.handleToggle}
                >
                    <Badge badgeContent={this.props.badgecount} color="primary">
                        {this.props.icon}
                    </Badge>
                </Button>

                <Popper style={{position: 'relative'}} open={this.state.open} role={undefined} transition disablePortal>
                    {({ TransitionProps, placement }) => (
                        <Grow
                            {...TransitionProps}
                            style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
                        >
                            <Paper>
                                <ClickAwayListener onClickAway={this.handleClose}>
                                    <MenuList autoFocusItem={this.state.open} id="menu-list-grow" onKeyDown={this.handleListKeyDown}>
                                        {this.showMenuItems()}
                                    </MenuList>
                                </ClickAwayListener>
                            </Paper>
                        </Grow>
                    )}
                </Popper>
        </div>
    );
}

}

export default PopOverMenu;

这是最新的尝试——它几乎成功了——但后来我在 ancor el 中遇到了错误

我正在使用材质 ui 和图标/徽章模块。我开始在将鼠标悬停在菜单上时出错。

我试图遵循这样的方法 - 将 ancor el 放入状态 - 但它不起作用。

如何将弹出框 MATERIAL-UI 功能组件转换为基于类的组件?

import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import Grow from '@material-ui/core/Grow';
import Paper from '@material-ui/core/Paper';
import Popper from '@material-ui/core/Popper';
import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList';
import Badge from '@material-ui/core/Badge';

import './PopOverMenu.scss';

class PopOverMenu extends Component {

    constructor(props, context) {
        super(props, context);

//        this.state = { open: false};
//        this.anchorRef = React.createRef(null);

        this.state = { anchorEl: null, open: false };
    }

    handleToggle = (event) => {
        this.setState({open: !this.state.open});
//        this.state.ancherEl ? this.setState({ anchorEl: null }) : this.setState({ anchorEl: event.currentTarget });
    };

    handleOpen = (event) => {        
        this.setState({open: true});

        console.log("event.currentTarget", event.currentTarget);

        this.state.ancherEl ? this.setState({ anchorEl: null }) : this.setState({ anchorEl: event.currentTarget });
    };

    handleClose = () => {        
        this.setState({open: false});
        //this.setState({ anchorEl: null })
    };

    handleListKeyDown = (event) => {
        if (event.key === 'Tab') {
            event.preventDefault();
            this.setState({open: false});
        }
    }

    showMenuItems = () => (
        this.props.menu.map((item, i) => (
            <MenuItem key={i} onClick={this.handleClose}>
                <NavLink to={item.value}>{item.label}</NavLink>
            </MenuItem>
        ))
    )

render() {

    //console.log("this.anchorRef", this.anchorRef)

    return (
        <div className="popover-menu">
                <Button
                    //ref={this.anchorRef}
                    aria-controls={this.state.open ? 'menu-list-grow' : null}
                    aria-haspopup="true"
                    onClick={this.handleToggle}
                    onMouseOver={this.handleOpen}
                    onMouseLeave={this.handleClose}
                >
                    <Badge badgeContent={this.props.badgecount} color="primary">
                        {this.props.icon}
                    </Badge>
                </Button>

                <Popper 
                    className="popper-list" 
                    //anchorEl={this.anchorRef}
                    open={this.state.open} 
                    anchorEl={this.state.anchorEl}
                    //role={undefined} 
                    transition 
                    disablePortal
                    onMouseOver={this.handleOpen}
                    onMouseLeave={this.handleClose}
                >
                    {({ TransitionProps, placement }) => (
                        <Grow
                            {...TransitionProps}
                            style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
                        >
                            <Paper>
                                <ClickAwayListener onClickAway={this.handleClose}>
                                    <MenuList autoFocusItem={this.state.open} id="menu-list-grow" onKeyDown={this.handleListKeyDown}>
                                        {/*this.showMenuItems()*/}
                                    </MenuList>
                                </ClickAwayListener>
                            </Paper>
                        </Grow>
                    )}
                </Popper>
        </div>
    );
}

}

export default PopOverMenu;

.popover-menu{
    width: 40px;
    display: inline;
    float: left;
    //border: 1px solid red;
    //background: blue;
    padding: 5px;

    .popper-list{
        width: 160px; 
        position: relative!important;
        //border: 1px solid blue;
    }
}

标签: javascriptreactjsmaterial-ui

解决方案


这是一个有效的CodeSandbox

  1. 您使用了错误的参考。
    您需要在单击时将 ref 分配给 Popper(参见第 89 行)。
    为此,您需要使用 setState 而不是 createRef 来设置 ref。

  2. 悬停激活:您需要在父 div 上使用 onMouseEnter 和 onMouseLeave 切换它

  3. 添加 onClick on 按钮以切换弹出器


推荐阅读