Remove highlight on mouse leave in MUI Autocomplete - reactjs

Using Autocomplete and hovering over the list of options, and moving the mouse out of it leaves the latest option, that the mouse was hovering over, still highlighted.
What's the best way to remove highlight when the mouse pointer is outside of the list altogether?

You can add mouseenter and mouseleave handlers to know when the mouse is inside the Listbox and override the background if the mouse is outside and the option is focused. Note that the double ampersand is necessary to increase the CSS specificity and put your styles above the one from MUI:
const [mouseInListBox, setMouseInListBox] = React.useState(false);
return (
<Autocomplete
{...props}
ListboxProps={{
onMouseEnter: () => setMouseInListBox(true),
onMouseLeave: () => setMouseInListBox(false),
sx: {
'&& li.Mui-focused': {
bgcolor: !mouseInListBox ? 'white' : undefined,
},
},
}}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
);
Live Demo

Related

react-select doesn't play well in mobile when dropdown icon is "overridden" and onMenuOpen+onMenuClose are used

I am using react-select for value input in my web app. I have to override dropdown indicator icon according to design and some dynamic modifications has to be performed depending on the opened/closed status of the menu. I am using styled-components and I have simplified the code a bit to be presented here:
const DropdownIndicator = (props) => {
return (
<components.DropdownIndicator {...props}>
</components.DropdownIndicator>
);
};
<S.InputField
disabled={disabled}
labelLeft={labelLeft}
noLabel={!label}
className={className}
fieldType={state?.type}
inputType={state?.inputType}
notClearable={notClearable}
extendOnOpen={extendOnOpen && menuIsOpen}
menuIsOpen={menuIsOpen}
menuHeight={menuHeight}
>
<label htmlFor={id}>{label}</label>
<div className="input-wrapper">
<Select
id={id}
placeholder={'test'}
components={{ DropdownIndicator }}
// menuIsOpen={menuIsOpen}
onMenuOpen={() => {
console.log('menu opened');
setMenuIsOpen(() => true);
}}
onMenuClose={() => {
console.log('menu closed');
setTimeout(() => setMenuIsOpen(() => false), 3000);
}}
/>
</div>
</S.InputField>
Now, when I try to open the menu clicking on the Select's control field, the menu opens as expected, but when I want to open it clicking on the dropdown icon, the operation becomes hardly predictable - basically I get the 'menu opened' and 'menu closed' directly after, so menu doesn't stay open. Why could that be? Is it a bug in the react-select design?
The funny thing is, if I comment either components={{ DropdownIndicator }} or one of onMenuOpen or onMenuClosed, the menu opens/closes as expected, but never when both ("overridden" DropdownIndicator and onMenuOpen/onMenuClosed) are employed. And I put "overridden" in quotes as it is not technically overridden (I've removed the icon change from <components.DropdownIndicator {...props}></components.DropdownIndicator> as it doesn't impact the outcome), it's actually mimicking (as I understand) the default behaviour of react-select's menu flow.
Important! It only happens in mobile resolutions. Desktop res works just fine. So my understanding this has got something to do with onFocus/onBlur and the way they are treated in touch devices.
Any thoughts?

Unable to fix popper placement in autocomplete

I am using the <Autocomplete /> component of Material-UI and I have a situation where I want my drop-down to always appear at the bottom. Therefore I did this:
PopperComponent={(props) => <Popper {...props} placement='bottom-start' />}
My drop-down still appear at the top sometimes.
Moreover, when i did the above, the width of my popper is no longer the width of my autocomplete.
I decided then that i want to change the zIndex of the popper so that the app bar won't cover it if the position of the popper switches to the top.
How can i fix it?
Yes, placement appears to be broken when used in Autocomplete's Popper (material-ui v. 4.11.4).
A hack that worked for me is as follows:
<Autocomplete
// Force menu to open below, with the correct width
PopperComponent={({ style, ...props }) => (
<Popper
{...props}
style={{ ...style, height: 0 }} // width is passed in 'style' prop
/>
)}
// Set menu max height (optional)
ListboxProps={{ style: { maxHeight: '30vh' } }}
/>
I am using MUI 5.4.4 and ran into a similar issue where the Autocompeletes Popper component was trying to flip to the top (and therefor disappearing) when there wasn't enough space on the bottom of the page. I fixed the issue by creating custom popper component with a flip modifier with the fallbackPlacements option set to an empty array and setting the popperOptions placement to bottom so that the Popper menu is always at the bottom regardless of if there is enough space or not.
The popper docs for the flip modifier explained it pretty well.
Custom popper:
const CustomerPopper = (props) => {
const modifiers = [
{
name: 'flip',
options: {
fallbackPlacements: []
},
},
]
return (
<Popper
{...props}
modifiers={modifiers}
popperOptions={{
placement: 'bottom',
}}
/>
)
}
Autocomplete:
<Autocomplete
{...otherStuff}
PopperComponent={(props) => <CustomerPopper {...props} />}
/>
If anyone is still looking for an answer. You can achieve this by using flip modifier
const CustomerPopper = (props: any) => <Popper
{...props}
modifiers={{
flip: {
enabled: false,
}
}}
popperOptions={{
placement:'bottom',
}}
/>;

Using hover and click in materia ui ToolTip causes issues in closing the tooltip

I am able to use hover and click functionality separately in material ui Tooltip.
Now i want following functionality using both.
when i hover the tooltip should open. If i click the tooltip should remain open unless i close it.
I have done following to achive hover and onclick
1. initially disableHoverListener is false as a result am able to show tooltip on hover
2. when i click on the button to open the tool tip i set open = true. The tooltip remains open. If i try to close the tool tip am able to set the open = false. but the tooltip doesnot close until i move the mouse.
Can someone guide me in solving the problem
Here is the code for whatever I could understand from your description.
You want the tooltip to show on hover (default behaviour). But if you make it controlled component. i.e you want to set open true on click and false otherwise the default behaviour won't work.
Working Example: CodeSandbox
Here's code hope it helped.
const [show, setShow] = React.useState(false);
const handleClick = () => {
if (show) {
setShow(false);
} else {
setShow(true);
}
};
return (
<div
style={{ display: "inline" }}
onMouseOver={() => setShow(true)}
onMouseLeave={() => setShow(false)}
>
<Tooltip title="You want to see me!" open={show} onClick={handleClick}>
<IconButton aria-label="delete">
<DeleteIcon />
</IconButton>
</Tooltip>
</div>
);

Material UI Popper over TextField how to keep popper open if the popper options are selected

I am using React Material UI, and I have a Textfield which if I focus on it will deploy a Popper with a simple Menu. If the Textfield loses the focus then the Popper closes itself. The thing is I need to select any option from the menu without close the Popper, but when I do that the Textfield loses the focus. What I need is to keep the Popper on only if I click outside of the Textfield or the Menu.
Everything is on this codesandbox.
I tried this:
const selected = prop => {
console.log(prop);
}
...
<Paper elevation={3} className={classes.paper}>
<MenuList>
<MenuItem onClick={() => selected('first')}>
First Option
</MenuItem>
<MenuItem onClick={() => selected('next')}>
Next Option
</MenuItem>
<MenuItem onClick={() => selected('last')}>
And Last Option
</MenuItem>
</MenuList>
</Paper>
</Popper>
Also tried with ClickAwayListener wrapping both components, the TextField and the Popper:
<ClickAwayListener onClickAway={blur}>
<>
<TextField ... />
<Popper ...>
...
</Popper>
</>
</ClickAwayListener>
Unsuccessfully both times... How can I achieve this?
Although the solution by #Dekel is working well enough.
But in my opinion, it would be better if we would use React.useRef() for focusing on the text field.
Here is the updated solution link:
https://codesandbox.io/s/goofy-frost-bb88l?file=/src/MyApp.js
const textFieldRef = React.useRef();
Inside return ()
<TextField
aria-describedby={id}
onFocus={focus}
onBlur={blur}
placeholder="Focus on me"
inputRef={textFieldRef}
/>
On selecting any menu list item
const selected = event => {
console.log("Selected ", event.target.innerText);
textFieldRef.current.focus();
};
I think it's best to implement this using the Autocomplete, but since the OP requested another solution - here is another option:
Once blur - check the element that caused the blur. If that element is one of the items in the popper - don't blur:
if (e.relatedTarget && e.relatedTarget.classList.contains("MuiListItem-root")) {
return;
}
The full blur function will look like this:
const blur = (e) => {
if (e.relatedTarget && e.relatedTarget.classList.contains("MuiListItem-root")) {
e.target.focus();
return;
}
setAnchorEl(null);
};

How to I keep a Material-ui Select open when I click on only one of the items in it

I have been writing a custom Material-UI Select dropdown which has an optional text field at the top to allow the user to search / filter items in the Select if there were many entries.
I am struggling with how to keep the Select open when I click on the text field (rendered as an InputBase) and just have the normal behavior (of closing the Select when a regular MenuItem is selected.
CodeSandbox here : https://codesandbox.io/s/inspiring-newton-9qsyf
const searchField: TextField = props.searchable ? (
<InputBase
className={styles.searchBar}
onClick={(event: Event) => {
event.stopPropagation();
event.preventDefault();
}}
endAdornment={
<InputAdornment position="end">
<Search />
</InputAdornment>
}
/>
) : null;
return (
<FormControl>
<Select
className={styles.root}
input={<InputBase onClick={(): void => setIconOpen(!iconOpen)} />}
onBlur={(): void => setIconOpen(false)}
IconComponent={iconOpen ? ExpandMore : ExpandLess}
{...props}
>
{searchField}
{dropdownElements.map(
(currEntry: string): HTMLOptionElement => (
<MenuItem key={currEntry} value={currEntry}>
{currEntry}
</MenuItem>
)
)}
</Select>
</FormControl>
);
As you can see above I've tried using stopPropagation and preventDefault but to no avail.
check out this codesandbox link: https://codesandbox.io/s/busy-paper-9pdnu
You can use open prop of Select API
I was able to make a controlled open select by providing open prop as a react state variable and implementing correct event handlers. To make it controlled you must provide onOpen and onClose props of the Select and make sure the open prop stays true when the custom textfield is clicked.
One more important thing I had to do was override the default keyDown behavior of the Select component. If you open up a Select and start typing into it, it shifts focus to the select option that matches what you are typing. For example, if you Select had an option with the text Foobar and if you start typing Food and water, it would cause focus to shift from your custom text input onto the Foobar option. This behavior is overridden in the onKeyDown handler of the custom textfield
Working sandbox here
Edit: even though this worked in the codepen, I had to add onChange={handleOpen} to the Select as well to get it working on a real browser with React and Next.
You can still use stopPropagation to make it work
// open state
const [isSelectorOpen, setisSelectorOpen] = useState(false)
// handle change
const handleChange = event => {
const { value } = event.target
event.stopPropagation()
// set your value
}
// selector
<Select
multiple
open={isSelectorOpen}
onChange={handleChange}
input={(
<Input
onClick={() => setisSelectorOpen(!isSelectorOpen)}
/>
)}
// other attribute
>
<MenuItem>a</MenuItem>
<MenuItem>b</MenuItem>
<MenuItem>c</MenuItem>
</Select>
In my case, none of the above worked, but this did the trick to stop closing the Select:
<MenuItem
onClickCapture={(e) => {
e.stopPropagation();
}}>
You can also change onMouseEnter to change some default styling that comes with using MenuItem (like pointer cursor when mouse enters its layout)
onMouseEnter={(e) => {
e.target.style.backgroundColor = "#ffffff";
e.target.style.cursor = "default";
}}
In my case i also needed to remove the grey clicking effect that MenuItem makes on click, which is a new object generated in MuiTouchRipple-root, so changing display to none did the trick.
sx={{
"& .MuiTouchRipple-root": {
display: "none",
},
}}

Resources