Search code examples
javascriptreactjsmaterial-uipopper.jsreact-popper

MATERIAL-UI React - Popper of another Popper


I'm working on a calendar app.

The problem: clicking popper of a popper closes both poppers, because it fires the click outside event of the first popper which closes it.

I have a component <Event /> which uses Material-UI React <Popper /> and it works fine with it. combining it with the <AwayClickListener /> it closes when clicking out side, and stay open when clicking inside the popper. I created <Events /> which is a list of <Event />.

 event details popper

when click the + more text, popper with all the events in that day should appear, on top of the cell.

the popper children is also <Events />:

expanded events list

clicking an event should open a popper with the event details, as clicking it in the cell was. since i use the same component <Events /> it does that, but not fully as expected:

clicking the event details popper closes both poppers.

That is the issue: the requirement is that clicking out side of the poppers will close the poppers, but clicking inside will leave them open and interactive

events list popper with it's event details popper

debugging shows that clicking the second popper, fires the outside clicked event of the first popper which closes it. also, taking out the click away listener function from the first popper leave the second popper open for most of the clicks - clicking some places in it, fires it's clicked away function which closes it. e.g: clicking the title closes it, clicking the location or summary divs does not.

  1. I tried wrapping the entire cell with <ClickAwayListener />.
  2. I tried wrapping the children of the popper with <ClickAwayListener />
  3. Tried using the material-ui-popup-state npm, and gave the popper id attribute. than when click away, compare the target id to 'popper', and if equal stay open. but, the id that was extracted from the event object of onClickAway event was empty string. even when clicking the popper.

CODE

<Popper> - costume wrapper for the material ui popper

const popper = ({
  placement,
  open,
  anchorEl,
  handleClickAway=null,
  title,
  handleCloseClick=null,
  children,
  popperStyle = {},
  calendarPopoverClass = ''
}) => {
  const useStyles = makeStyles({
    Popper: popperStyle
  })
  const styles = useStyles();
  return (
    <Popper modifiers={{
      flip: {
        enabled: false,
      },
      preventOverflow: {
        enabled: false,
        boundariesElement: 'scrollParent',
      }
    }}
      className={styles.Popper}
      placement={placement}
      open={open}
      anchorEl={anchorEl}
    >
      <ClickAwayListener onClickAway={handleClickAway}>
        <CalendarPopover className={st(classes[calendarPopoverClass])} isShown withArrow={false} title={title} onClose={handleCloseClick}>
          {children}
        </CalendarPopover>
      </ClickAwayListener>
    </Popper>
  )
}

<Event />

const event = ({ PROPS }) => {
const [expanded, setExpanded] = React.useState(null);
const closeExpanded = () => setExpanded(null)
return (
        <>
          <div
            className={st(classes.Event, { isTimeShown, isNextWeekFirstFiller, isLastFiller, isMultiDay, isAllDay, isFiller })}
            style={inlineStyle}
            onClick={onEventClick}
          >
            <div className={classes.Time}>{timeToDisplay}</div>
            <div className={classes.Title}>{title}</div>
          </div>
          <Popper
      placement={popperPlacement}
      title={title}
      handleCloseClick={closeExpanded}
      handleClickAway={closeExpanded}
      open={Boolean(expanded)}
      anchorEl={expanded}
      popperStyle={popperStyle}
      calendarPopoverClass='Event'
    >
      <ExpandedEvent
        startDate={startDate}
        endDate={endDate}
        location={location}
        summary={summary}
      />
    </Popper>
        </>
      );
}

<Events />

const Events = ({ events, isTimeShown, localeToggle, popperPlacement, popperStyle, handleShowMoreClick=null }) => {
  const eventsToShow: JSX.Element[] = [];
  if (events.length > 0) {
    let eventsToShowAmount = 3;
    const moreEventsCount = events.length - eventsToShowAmount;
    eventsToShowAmount = moreEventsCount > 0 ? eventsToShowAmount : events.length;
    for (let i = 0; i < eventsToShowAmount; i++) {
      eventsToShow.push(
        <Event
          key={events[i].id}
          {...events[i]}
          isTimeShown={isTimeShown}
          popperPlacement={popperPlacement}
          popperStyle={popperStyle}
        />
      )
    }
    if (moreEventsCount > 0) {
      eventsToShow.push(<ShowMore key='ShowMore' handleClick={handleShowMoreClick} moreEventsCount={moreEventsCount} />)
    }
  }

  return (
    <div className={classes.Events}>
      {eventsToShow}
    </div>
  );
}

<MonthlyCell />

const MonthlyCell = ({
  events,
  isTimeShown,
  popperPlacement,
  popperStyle
}) => {

  const [expandedEvents, setExpandedEvents] = React.useState(null);
  const cell = React.useRef<HTMLDivElement>(null)


  const eventsList = (handleShowMoreClick = null) => (
    <Events
      events={events}
      isTimeShown={isTimeShown}
      localeToggle={true}
      popperPlacement={popperPlacement}
      popperStyle={popperStyle}
      handleShowMoreClick={handleShowMoreClick}
    />
  );

  const handleShowMoreClick = () => setExpandedEvents(eventsList());

  const closeExpandedEvents = () => {
    setExpandedEvents(null);
  }

  return (
    <>
      <div ref={cell} className={classes.MonthlyCell} >
        {eventsList(handleShowMoreClick)}
      </div>
      <Popper
        placement='left'
        open={Boolean(expandedEvents)}
        title='hello'
        handleClickAway={closeExpandedEvents}
        anchorEl={cell.current}
        popperStyle={{ left: '17% !important' }}
        handleCloseClick={closeExpandedEvents}
      >
        {eventsList()}
      </Popper>
    </>
  );
}

hope it was clear enough. let me know if anything else is needed. Thank you

EDIT 1

another attempt was giving the parent popper bigger z-index, but it didn't work


Solution

  • the solution was surrounding the popper children in a div. component i used caused this un-wanted behaviour, because it didn't had forwardRef support. so adding div wrapper solved that.

    also, dropping the modifiers attribute:

    <Popper 
          // modifiers={{
          // flip: {
          //  enabled: false,
          // },
          // preventOverflow: {
          //   enabled: false,
          //   boundariesElement: 'scrollParent',
         //  }
        // }}
    

    link for working solution: https://codesandbox.io/s/popper-in-a-popper-s6dfr?file=/src/Popper/Popper.js:372-519