Ryan Southcliff
Ryan Southcliff

Reputation: 223

How to use SubHeaders in Material UI Select Menu

I am struggling on how to properly set up a long list of Menu Items on a Select control that uses sticky SubHeaders. The problem is that when the items scroll they obscure the subheaders.

I looked at the Material UI examples of grouped Select items as a start. I wanted behavior that looked like the Material UI example with pinned subHeader Lists.

Here is a codeSandbox of what I'm trying.

Below is a snippet of my code:

<Select
        className={classes.root}
        MenuProps={{ className: classes.menu }}
        value="Pick one"
        onChange={e => {}}
      >
        {subHeaders.map(header => (
          <li key={header}>
            <ul>
              <ListSubheader>{header}</ListSubheader>
              {items.map(item => (
                <MenuItem key={item} value={item}>
                  {item}
                </MenuItem>
              ))}
            </ul>
          </li>
        ))}
      </Select>

Here is a snapshot of the problem:

enter image description here

Upvotes: 4

Views: 6541

Answers (3)

Pablo Briuzis
Pablo Briuzis

Reputation: 31

I had the same problem. To make it work you need to have only one list. The list must be something like:

[
  {
     value,
     text,
     isParent (is section)
  }
]

IMPORTANT! It has to be sorted like this: Parent 1 - Child 1 (parent 1) - Child 2 (parent 1) - Parent 2 - Child 1 (parent 2)....

Then:

const array = [
  { text: 'parent 1', isParent: true }, 
  { text: 'child 1 parent 1', value: 2, isParent: false }, 
  { text: 'child 2 parent 1', value: 3, isParent: false }, 
  { text: 'parent 2', isParent: true }, 
  { text: 'child 1 parent 2', value: 4, isParent: false }, 
  { text: 'child 2 parent 2', value: 5, isParent: false }, 
]

<Select>
  {array.map(option => {
    if(option.isParent)
      return (
        <ListSubheader>
          {option.text}
        </ListSubheader>
      )
    else
      return (
        <MenuItem 
          value={option.value}
        >
          {option.text}
        </MenuItem>
      )
  })}
</Select>

Upvotes: 0

Itay Tur
Itay Tur

Reputation: 1709

I managed to make a working solution of Material-ui select with and sticky MenuItems.

use MaterialUI MenuItem instead of all the <li> <ul> <ListSubheader>

    const [isOpen, setIsOpen] = useState(false);
    const [value, setValue] = useState();
    
    const onToggle = () => {
        setIsOpen((prev) => !prev);
      };
    
    const onClose = () => {
        setIsOpen(false);
      };
    
    const _onChange = (event: React.ChangeEvent<{ value: unknown }>) => {
        const valueToSelect = event.target.value as Value;
        if (
          isResetSeletced(valueToSelect) ||
          (multiple
            ? !valueToSelect.length ||
              valueToSelect.length < minSelections ||
              (valueToSelect as string[]).some((option) => !option)
            : !valueToSelect?.length && minSelections > 0)
        ) {
          return;
        }
        event.persist();
        onChange(valueToSelect);
      };
    
      const renderValue = (selected: any) => {
        if (!selected.length) {
          return '';
        }
        if (multiple) {
          const isReachedLimit = selected.length > MAX_SELECTIONS;
          const hiddenTags = isReachedLimit ? (
            <span>+{value.length - MAX_SELECTIONS}</span>
          ) : null;
          const selectionsToShow = isReachedLimit
            ? selected.slice(0, MAX_SELECTIONS)
            : selected;
          return (
            <StyledTagsContainer>
              <Tags values={selectionsToShow} onRemoveTag={onRemoveTag} />
              {hiddenTags}
            </StyledTagsContainer>
          );
        }
        return selected;
      };


  const resetMenuItem = secondaryOptions?.map((resetItem, index) => {
    return (
      <MenuItem
        key={resetItem.value + index}
        onClick={() => {
          resetItem.onClick();
        }}
        isLast={!index}
        isSelected={
          resetItem.value === resetSelected?.value ||
          resetItem.value === value ||
          (multiple && resetItem.value === value[0])
        }
        value={resetItem.value}
        icon={<RadioIcon />}
      >
        {resetItem.text}
      </MenuItem>
    );
  });
    
    
    <Select
        displayEmpty
        onClose={onClose}
        value={value}
        onChange={_onChange}
        renderValue={renderValue}
        open={isOpen}
      >
        {menuItems}

        <div style={{ position: 'sticky', bottom: 0 }}>
          {resetMenuItem}
        </div>

    </Select>

Upvotes: 0

Jairon Alves Lima
Jairon Alves Lima

Reputation: 323

Using the Select component we can even reproduce the behavior with some corrections. But it won't work for you. The Select component does not expect items nested within your child's elements. That way, we will never be able to identify the element that is selected.

Alternatively, we have the Autocomplete component. It can better supply what you need.

enter image description here

Regarding the example you provided, we can do something, but again, we will not be able to maintain the state of the selected item. To achieve the same behavior as the list, we need to apply the same behavior to the list that the Menu will render. Select will render a Menu that inherits List, so we can apply the same behavior as the list example through the prop MenuListProps property.

I applied the fixes to your example

Edit Button

I hope it helps.

Upvotes: 1

Related Questions