Repository: mui/material-ui (TypeScript)
Date: 2021-03-31 13:32:43+00:00
Task type: Bug Fix

Issue:
[Select] Improve categories support
Edit @oliviertassinari 

### bug 1

- description: A click on the `ListSubheader` shouldn't trigger the selection.
- reproduction: https://material-ui.com/components/selects/#grouping

### bug 2

- description: Only one `ListSubheader` should be visible when scrolling
- reproduction: https://codesandbox.io/s/create-react-app-5z42i?fontsize=14


Issue discussion:
@nicks78 Please provide a full reproduction test case. This would help a lot 👷 .
A live example would be perfect. [This **codesandbox.io** template](https://codesandbox.io/s/github/callemall/material-ui/tree/master/examples/create-react-app) _may_ be a good starting point. Thank you!
https://codesandbox.io/s/create-react-app-5z42i?fontsize=14 

This is the reproduction with the error
@nicks78 Thank you for the reproduction. You can't do that, the Select requires a flat array. See #14943 for the issue that prevents it.
@oliviertassinari , I take the same exemple like it is in the library and there is still the same error :
https://material-ui.com/components/selects/ 
https://codesandbox.io/s/create-react-app-5z42i?fontsize=14
@nicks78 Thank you for the codesandbox.
Any progress on bug 1 here? Anything I can do to help? This is a bit of a dealbreaker for the Select component as I obviously don't want my headers being selected. The native Select option seems to handle this properly.

My current workaround is to add the following to my CSS stylesheet, but I know it's an ugly hack:
```css
.MuiListSubheader-root {
    pointer-events: none;
}
```

Perhaps putting a boolean prop on the ListSubheader that conditionally adds `pointer-events: none` would be good enough?
For a future iteration, let's not forget about the learnings of #19574.
As mentioned in #20448:

There is an accessibility bug when using `ListSubheader` in a Select. When the first child of a Select component is `ListSubheader`, you must press tab before up/down arrow key functionality works. [Codesandbox repo](https://codesandbox.io/s/loving-knuth-e5ovy).
Are there any workarounds for issue #2?
@wseagar I'm not sure if adding `position: sticky` to a ListSubheader would work or not; you could give that a shot.
Thanks @embeddedt 

For the moment I've used the native prop on the select with the native elements for the options

This solves the above 2 issues for my use case

(this example from the docs)
```
<Select native defaultValue="" id="grouped-native-select">
          <option aria-label="None" value="" />
          <optgroup label="Category 1">
            <option value={1}>Option 1</option>
            <option value={2}>Option 2</option>
          </optgroup>
          <optgroup label="Category 2">
            <option value={3}>Option 3</option>
            <option value={4}>Option 4</option>
          </optgroup>
        </Select>
```
I found a better solution to this problem (**It only solves if you have just one ListSubheader**). In the select component, you can pass the `MenuProps` object, and inside this object, you can pass the `MenuListProps` object which is the one that renders the `List` component that has a prop called [subheader](https://material-ui.com/api/list/#props) (which is the "right way" to render a `ListSubHeader` component).


So, you just have to do this:

```typescript
import * as React from 'react'
import MenuItem from '@material-ui/core/MenuItem'
import ListSubheader from '@material-ui/core/ListSubheader'
import Select, { SelectProps } from '@material-ui/core/Select'

interface Props {
  subheaderText: string
}

function MySelect(props: Props) {
  const { subheaderText } = props

  const menuProps: SelectProps['MenuProps'] = React.useMemo(
    () => ({
      TransitionProps: { timeout: 0 },
      MenuListProps: {
        subheader: <ListSubheader component="div">{subheaderText}</ListSubheader>,
      },
    }),
    [subheaderText]
  )

  return (
    <Select MenuProps={menuProps}>
      <MenuItem>0</MenuItem>
      <MenuItem>1</MenuItem>
      <MenuItem>2</MenuItem>
    </Select>
  )
}
```

This way nothing will happen if the user clicks in the subheader and also the `Select` will position the `Popover` in the right position
is it a problem to add one additional prop and one line of handler? facing the same issue and don't understand why it's not implemented for almost 2 years
I was checking ListSubheader for some time, but couldn't manage to disable selection.

Seems to me like an overkill to have a separate Component for the grouping menu item (ListSubheader), or at least seems like it should reuse much of the MenuItem functionality.

A workaround that I used to fix the select issue on the ListSubheader is to use the simple MenuItem component, set "disabled" on it and styled it.

Because disabled takes precedence over the custom style, I had to use !important on opacity.
```
category: {
  fontStyle: 'italic',
  fontWeight: 'bold',
  margin: '5px 0px',
  opacity: '1 !important',
}

...

<Select defaultValue="" id="grouped-select">
  <MenuItem disabled className={classes.category}>Category 1</MenuItem>
  <MenuItem value={1}>Option 1</MenuItem>
  <MenuItem value={2}>Option 2</MenuItem>
  <MenuItem disabled className={classes.category}>Category 2</MenuItem>
  <MenuItem value={3}>Option 3</MenuItem>
</Select>
```

![image](https://user-images.githubusercontent.com/19698636/109359471-da193280-7885-11eb-8bfd-8571c99e4212.png)


Solution patch:
diff --git a/packages/material-ui/src/Select/SelectInput.js b/packages/material-ui/src/Select/SelectInput.js
--- a/packages/material-ui/src/Select/SelectInput.js
+++ b/packages/material-ui/src/Select/SelectInput.js
@@ -169,6 +169,11 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
   const handleItemClick = (child) => (event) => {
     let newValue;
 
+    // We use the tabindex attribute to signal the available options.
+    if (!event.currentTarget.hasAttribute('tabindex')) {
+      return;
+    }
+
     if (multiple) {
       newValue = Array.isArray(value) ? value.slice() : [];
       const itemIndex = value.indexOf(child.props.value);


Test patch:
diff --git a/packages/material-ui/src/Select/Select.test.js b/packages/material-ui/src/Select/Select.test.js
--- a/packages/material-ui/src/Select/Select.test.js
+++ b/packages/material-ui/src/Select/Select.test.js
@@ -11,10 +11,11 @@ import {
   fireEvent,
   screen,
 } from 'test/utils';
-import MenuItem from '../MenuItem';
-import Input from '../Input';
-import InputLabel from '../InputLabel';
-import Select from './Select';
+import MenuItem from '@material-ui/core/MenuItem';
+import Input from '@material-ui/core/Input';
+import InputLabel from '@material-ui/core/InputLabel';
+import Select from '@material-ui/core/Select';
+import Divider from '@material-ui/core/Divider';
 
 describe('<Select />', () => {
   let classes;
@@ -1131,4 +1132,19 @@ describe('<Select />', () => {
     expect(handleChange.callCount).to.equal(1);
     expect(handleEvent.returnValues).to.have.members([options[0]]);
   });
+
+  it('should only select options', () => {
+    const handleChange = spy();
+    render(
+      <Select open onChange={handleChange} value="second">
+        <MenuItem value="first" />
+        <Divider />
+        <MenuItem value="second" />
+      </Select>,
+    );
+
+    const divider = document.querySelector('hr');
+    divider.click();
+    expect(handleChange.callCount).to.equal(0);
+  });
 });