I'm using the Autocomplete component as in the country example, but decorated with the InputAdornment component in order to show the flag of the select country.
Here the working code:
The problem: after picking a country, if the user clicks exactly on the flag, country's name is selected. And, if the user clicks on the remaining part of the Autocomplete, the Popper is showing up (as expected and that's totally ok).
Current behaviour:
MY GOAL: I'd like to open the Autocomplete Popper even when clicking on the flag.
Expected behaviour:
I tried to use the option disablePointerEvents in the InputAdornment parameters, but nothing changed.
I tried the same on a pure Textfield MUI component and it worked, so it maybe it is something related to Autocomplete only.
Is there a workaround for this issue?
Same issue here
One solution is to control the open state of the Autocomplete using the open, onOpen, and onClose props and then add an onClick (instead of disablePointerEvents) to the InputAdornment to open the Autocomplete.
Here is a working example based on your sandbox:
import * as React from "react";
import Box from "#mui/material/Box";
import TextField from "#mui/material/TextField";
import Autocomplete from "#mui/material/Autocomplete";
import InputAdornment from "#mui/material/InputAdornment";
export default function CountrySelect() {
const [value, setValue] = React.useState(null);
const [open, setOpen] = React.useState(false);
return (
<Autocomplete
id="country-select-demo"
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
sx={{ width: 300 }}
options={countries}
autoHighlight
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
getOptionLabel={(option) => option.label}
renderOption={(props, option) => (
<Box
component="li"
sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
{...props}
>
<img
loading="lazy"
width="20"
src={`https://flagcdn.com/w20/${option.code.toLowerCase()}.png`}
srcSet={`https://flagcdn.com/w40/${option.code.toLowerCase()}.png 2x`}
alt=""
/>
{option.label} ({option.code}) +{option.phone}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
inputProps={{
...params.inputProps,
autoComplete: "new-password" // disable autocomplete and autofill
}}
InputProps={{
...params.InputProps,
startAdornment: value ? (
<InputAdornment position="start" onClick={() => setOpen(true)}>
<img
loading="lazy"
width="48"
src={`https://flagcdn.com/w20/${value.code.toLowerCase()}.png`}
srcSet={`https://flagcdn.com/w40/${value.code.toLowerCase()}.png 2x`}
alt=""
/>
</InputAdornment>
) : null
}}
/>
)}
/>
);
}
Related
I have an Autocomplete component which displays the coutries name and flags as in the example from the MUI doc.
My goal is simply the following: once the Autocomplete component is clicked, the country's name must be cleared displaying only the placeholder.
I achieved this with a simple onClick event in the renderInput which triggers the following function:
const handleClear = (e) => {
e.target.value = "";
};
If trying the code everything works as expected, apparently.
Actually, the clearing happens only when the country's name is clicked, but if a different portion of the component is clicked, like the flag or the dropdown arrow, the country's name is simply focused, not cleared.
In short, here the current behaviour:
and here the expected behaviour:
Is there a way to fix this?
That's behavior occurs because when you click on the flag, the e.target won´t be the input element, but the wrapper div. You can see this just adding a console.log to the handleClear function:
const handleClear = (e) => {
console.log("clicked TARGET ELEMENT: ", e.target);
// If you click on the input, will see:
// <input ...
// And if you click on the flag, you will see:
// <div ...
};
If you want to control the input state value and the text value separately, you probably should go with the two states control - check it on MUI docs.
The code will be something like:
export default function CountrySelect() {
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = React.useState("");
const handleClear = (e) => {
console.log("clicked TARGET ELEMENT: ", e.target);
setInputValue("");
};
return (
<Autocomplete
id="country-select-demo"
disableClearable
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
openOnFocus
sx={{ width: 300 }}
options={countries}
autoHighlight
getOptionLabel={(option) => option.label}
renderOption={(props, option) => (
<Box
component="li"
sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
{...props}
>
<img
loading="lazy"
width="20"
src={`https://flagcdn.com/w20/${option.code.toLowerCase()}.png`}
srcSet={`https://flagcdn.com/w40/${option.code.toLowerCase()}.png 2x`}
alt=""
/>
{option.label} ({option.code}) +{option.phone}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
placeholder="Choose a country"
onClick={handleClear}
InputProps={{
...params.InputProps,
startAdornment: value ? (
<InputAdornment disablePointerEvents position="start">
<img
loading="lazy"
width="48"
src={`https://flagcdn.com/w20/${value.code.toLowerCase()}.png`}
srcSet={`https://flagcdn.com/w40/${value.code.toLowerCase()}.png 2x`}
alt=""
/>
</InputAdornment>
) : null
}}
/>
)}
/>
);
}
Instead of using onClick on TextField, you can use onOpen props and pass handleClear function in it. It works then. Selected value gets cleared whenever autocomplete is open.
Working Demo: CodeSandBox.io
This is part of the code:
<MobileDatePicker
showTodayButton
showToolbar={false}
disableCloseOnSelect={false}
inputFormat="YYYY-MM-DD"
views={['day']}
value={row.value}
onChange={(newValue) => row.onChange(newValue)}
renderInput={(params) => (
<InputBase {...params} className={classes.datePicker} />
)}
/>
On the mobile side, he does not show trigger Icon.
How to display to give users a clear indication.
The MobileDatePicker doesn't have a suffix icon because you can open it by focusing the TextField unlike the DesktopDatePicker where you have to click the icon to open the picker. But if you still want to include the icon anyway, just add one in the endAdornment of the TextField:
import InputAdornment from '#mui/material/InputAdornment';
import EventIcon from '#mui/icons-material/Event';
const [value, setValue] = React.useState<Date | null>(new Date());
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<MobileDatePicker
label="For mobile"
value={value}
open={open}
onOpen={handleOpen}
onClose={handleClose}
onChange={setValue}
renderInput={(params) => (
<TextField
{...params}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton edge="end" onClick={handleOpen}>
<EventIcon />
</IconButton>
</InputAdornment>
),
}}
/>
)}
/>
);
Related answer
How to change the icon in MUI DatePicker?
import React, { useEffect, useRef } from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function FreeSolo(props) {
const [vendors, setVendors] = React.useState([]);
const [value, setValue] = React.useState();
const nameRef = useRef();
useEffect(() => {
sendDataToParent();
}, [value]);
const sendDataToParent = async () => {
await props.parentFunction(value);
};
return (
<div style={{}}>
<Autocomplete
freeSolo
id="free-solo-2-demo"
options={props.vendorData.map((option) => option.name)}
renderInput={(params) => (
<TextField
{...params}
value={value}
required
inputRef={nameRef}
onChange={(e) => {
setValue(e.target.value);
sendDataToParent();
}}
label="Vendor Name"
margin="normal"
variant="standard"
InputProps={{ ...params.InputProps, type: "search" }}
/>
)}
/>
</div>
);
}
I tried to do it using renderOption but could not get it working. I need to have the options to be clickable links so that whenever user selects of the options, he is redirected to the link.
EDIT: Solved using renderOption
renderOption={(option) => (
<React.Fragment>
<span
style={{ cursor: "pointer" }}
onClick={() => {
window.location.href = `/allvendors/${option.id}`;
}}
>
{option.name} - Click to visit the Vendor
</span>
</React.Fragment>
)}
Instead of making the options clickable links, you can redirect to the link using the onChange prop of the Autocomplete component.
I'm assuming each option in your vendorData has a name and also a link e.g.
{
name: "Google",
link: "https://www.google.com"
}
To be able to access the link from this object in the Autocomplete component's onChange, you'll need to change the options map function to return the whole option. After this change, if you try to click to open the dropdown, it will throw an error because the option label needs to be a string (e.g. the option name) and not an object (e.g. option). So, we need to add the getOptionLabel prop and return the option.name.
Finally, in the onChange function, we set the window.location.href equal to the option.link, which changes the current page's URL to the link and directs the user to that link.
<div style={{}}>
<Autocomplete
freeSolo
id="free-solo-2-demo"
getOptionLabel={(option) => option.name}
options={props.vendorData.map((option) => option)}
onChange={(event: any, option: any) => {
window.location.href = option.link;
}}
renderInput={(params) => (
<TextField
{...params}
value={value}
required
inputRef={nameRef}
onChange={(e) => {
setValue(e.target.value);
sendDataToParent();
}}
label="Vendor Name"
margin="normal"
variant="standard"
InputProps={{ ...params.InputProps, type: "search" }}
/>
)}
/>
</div>
I'm trying to make a multi-select component that's also creatable using Material UI, but I'm unable to figure out how to do that from the documentation. autocomplate documentation page
The following example is a multi-select component and it does add new values if I clicked Enter on the keyboard but it doesn't tell the user that he can add that new value. However, even in that case, I'm not sure how I can access the new array of selected options.
<Autocomplete
multiple
id="tags-filled"
options={top100Films.map(option => option.title)}
defaultValue={[top100Films[13].title]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
label={option}
{...getTagProps({ index })}
/>
))
}
renderInput={params => (
<TextField
{...params}
variant="filled"
label="freeSolo"
placeholder="Favorites"
/>
)}
/>
I found another example that suggest adding the new value using the filterOptions prop, but for some reason it doesn't work with the previous component.
filterOptions={(options, params) => {
const filtered = filter(options, params);
// Suggest the creation of a new value
if (params.inputValue !== "") {
filtered.push({
inputValue: params.inputValue,
title: `Add "${params.inputValue}"`
});
}
return filtered;
}}
Here's a codesandbox for the example I mentioned:
codesandbox example
So what I'm trying to achieve is making that multiselect component creatable by displaying an option for the user to add the new value and also access the final array of options.
Thank you so much for your help.
While this may not solve your exact wants - it does allow you to capture the values and hitting enter creates the chips which follows gmail's functionality for email adds.
/* eslint-disable no-use-before-define */
import React from "react";
import Chip from "#material-ui/core/Chip";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
const useStyles = makeStyles((theme) => ({
root: {
width: 500,
"& > * + *": {
marginTop: theme.spacing(3)
}
}
}));
export default function Tags() {
const classes = useStyles();
const handleChange = (x, emails) => console.log(x, emails);
return (
<div className={classes.root}>
<Autocomplete
multiple
id="tags-filled"
onChange={handleChange}
options={[]}
defaultValue={""}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="Emails"
placeholder="Add Email"
/>
)}
/>
</div>
);
}
I am making a custom input component with MUI InputBase, and I want to have a "Clear" button endAdornment that only appears when you hover over the input:
<InputBase
inputComponent={getCustomInputComponent()}
onClick={onClick}
...
endAdornment={
<IconButton
size='small'
onClick={handleClear}>
<IconClear fontSize='small'/>
</IconButton>
}
/>
Similar to how their new "Autocomplete" component works: https://material-ui.com/components/autocomplete/
I've looked at the source code of Autocomplete but I can't get it working in my component, any suggestions?
Below is an example that is roughly equivalent to what is being done in Autocomplete. The gist of the approach is to make the icon hidden by default, then flip the visibility to visible on hover of the input (&:hover $clearIndicatorDirty) and when the input is focused (& .Mui-focused), but only if there is currently text in the input (clearIndicatorDirty is only applied when value.length > 0).
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import IconButton from "#material-ui/core/IconButton";
import ClearIcon from "#material-ui/icons/Clear";
import clsx from "clsx";
const useStyles = makeStyles(theme => ({
root: {
"&:hover $clearIndicatorDirty, & .Mui-focused $clearIndicatorDirty": {
visibility: "visible"
}
},
clearIndicatorDirty: {},
clearIndicator: {
visibility: "hidden"
}
}));
export default function CustomizedInputBase() {
const classes = useStyles();
const [value, setValue] = React.useState("");
return (
<TextField
variant="outlined"
className={classes.root}
value={value}
onChange={e => setValue(e.target.value)}
InputProps={{
endAdornment: (
<IconButton
className={clsx(classes.clearIndicator, {
[classes.clearIndicatorDirty]: value.length > 0
})}
size="small"
onClick={() => {
setValue("");
}}
>
<ClearIcon fontSize="small" />
</IconButton>
)
}}
/>
);
}
Related documentation:
https://cssinjs.org/jss-plugin-nested?v=v10.0.0#use-rulename-to-reference-a-local-rule-within-the-same-style-sheet