I currently using a Select component in my app.
I built a custom modal component that I want to launch instead of the list items when the select is clicked. Is there a way to override the handler for clicks on all portions of the component, such as icon, text field, and dropdown arrow to launch my modal? I want to take just the styling of this component essentially and override the onChange and MenuItem stuff.
<Select
value={props.selectedValue}
onChange={props.onTimeChange}
displayEmpty
startAdornment={
<InputAdornment position="start">
<DateRangeIcon />
</InputAdornment>
}
>
{/* DONT USE THESE MENU ITEMS AND USE CUSTOM MODAL INSTEAD */}
{/*<MenuItem value={-1} disabled>*/}
{/* Start Date*/}
{/*</MenuItem>*/}
{/*<MenuItem value={1}>Last Hour</MenuItem>*/}
{/*<MenuItem value={24}>Last Day</MenuItem>*/}
{/*<MenuItem value={24 * 7}>Last Week</MenuItem>*/}
{/*<MenuItem value={24 * 31}>Last Month</MenuItem>*/}
{/*<MenuItem value={''}>All</MenuItem>*/}
</Select>
In order for it to make sense to leverage Select while using an alternative display for the options, it is important that you provide it with menu items for all the allowed values, because the display of the selected item is based on finding a matching MenuItem for the current value (though it would also be possible to provide the Select with a single MenuItem with a dynamic value and text matching whatever the current selected value is).
You can use a "controlled" approach for managing the open state of the Select using the open and onOpen props (you can leave out onClose since the close should always be triggered by your custom display of the options). This way, rather than trying to override the different events that cause the Select to open, you just let it tell you when it should open (via the onOpen prop), but instead of opening the Select, leave its open prop as always false and only open your custom popup.
Here's a working example:
import React from "react";
import InputAdornment from "#material-ui/core/InputAdornment";
import Button from "#material-ui/core/Button";
import DateRangeIcon from "#material-ui/icons/DateRange";
import Popover from "#material-ui/core/Popover";
import Box from "#material-ui/core/Box";
import Select from "#material-ui/core/Select";
import MenuItem from "#material-ui/core/MenuItem";
export default function SimplePopover() {
const [value, setValue] = React.useState(1);
const [open, setOpen] = React.useState(false);
const selectRef = React.useRef();
const handleSelection = newValue => {
setValue(newValue);
setOpen(false);
};
return (
<Box m={2}>
<Select
ref={selectRef}
value={value}
onChange={e => setValue(e.target.value)}
displayEmpty
open={false}
onOpen={() => setOpen(true)}
startAdornment={
<InputAdornment position="start">
<DateRangeIcon />
</InputAdornment>
}
>
<MenuItem value={1}>Last Hour</MenuItem>
<MenuItem value={24}>Last Day</MenuItem>
<MenuItem value={24 * 7}>Last Week</MenuItem>
<MenuItem value={24 * 31}>Last Month</MenuItem>
<MenuItem value={""}>All</MenuItem>
</Select>
<Popover
id="simple-popover"
open={open}
anchorEl={selectRef.current}
onClose={() => handleSelection(value)}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<Button onClick={() => handleSelection(1)}>Last Hour</Button>
<Button onClick={() => handleSelection(24)}>Last Day</Button>
</Popover>
</Box>
);
}
Here's a second example using a single, dynamic MenuItem for the selected value instead of a comprehensive set of menu items:
import React from "react";
import InputAdornment from "#material-ui/core/InputAdornment";
import Button from "#material-ui/core/Button";
import DateRangeIcon from "#material-ui/icons/DateRange";
import Popover from "#material-ui/core/Popover";
import Box from "#material-ui/core/Box";
import Select from "#material-ui/core/Select";
import MenuItem from "#material-ui/core/MenuItem";
export default function SimplePopover() {
const [value, setValue] = React.useState(1);
const [text, setText] = React.useState("Last Hour");
const [open, setOpen] = React.useState(false);
const selectRef = React.useRef();
const handleSelection = (newValue, newText) => {
setValue(newValue);
setText(newText);
setOpen(false);
};
return (
<Box m={2}>
<Select
ref={selectRef}
value={value}
onChange={e => setValue(e.target.value)}
displayEmpty
open={false}
onOpen={() => setOpen(true)}
startAdornment={
<InputAdornment position="start">
<DateRangeIcon />
</InputAdornment>
}
>
<MenuItem value={value}>{text}</MenuItem>
</Select>
<Popover
id="simple-popover"
open={open}
anchorEl={selectRef.current}
onClose={() => handleSelection(value)}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<Button onClick={() => handleSelection(1, "Last Hour")}>
Last Hour
</Button>
<Button onClick={() => handleSelection(24, "Last Day")}>
Last Day
</Button>
</Popover>
</Box>
);
}
Related
I am having trouble trying to implement a customized dropdown that does not seem to be a built in feature in Material UI. I am using Material UI for all these components btw. So I have a TextField with a Chip for the End Adornment.
The expected behavior is that if the user were to click on the chip, there should be a dropdown that pops up under the TextField where the user can select the type of vehicle. However, I don't think there is a built in Material UI way to do this. Any suggestions? Any suggestions would be appreciated. Thanks!
You can implement it like this:
https://codesandbox.io/s/hungry-golick-kdoylz?file=/src/App.js
import { useState } from "react";
import { TextField, Chip, InputAdornment, Menu, MenuItem } from "#mui/material";
import KeyboardArrowDown from "#mui/icons-material/KeyboardArrowDown";
export default function App() {
const [selectedItem, setSelectedItem] = useState("Jeep");
return (
<TextField
label="With normal TextField"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<ChipDropDown
items={["Jeep", "Volvo", "Ferrari"]}
selectedItem={selectedItem}
onChanged={setSelectedItem}
/>
</InputAdornment>
)
}}
variant="filled"
/>
);
}
const ChipDropDown = ({ items, selectedItem, onChanged }) => {
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (item) => {
onChanged(item);
setAnchorEl(null);
};
return (
<div>
<Chip
label={selectedItem}
onClick={(e) => setAnchorEl(e.currentTarget)}
onDelete={(e) => e}
deleteIcon={<KeyboardArrowDown />}
/>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={(e) => setAnchorEl(null)}
>
{items.map((item) => (
<MenuItem key={item} onClick={(e) => handleClick(item)}>
{item}
</MenuItem>
))}
</Menu>
</div>
);
};
I'm using material ui autocomplete textfield in my project inside a dialog.
The problem is, when i set a defaultValue or a first value, you can see the placeholder moving on the opening of the dialog.
https://codesandbox.io/embed/material-ui-autocomplete-forked-xwd3k?fontsize=14&hidenavigation=1&theme=dark
Here is the code
import React from "react";
import ReactDOM from "react-dom";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { TextField } from "#material-ui/core";
import DialogTitle from "#material-ui/core/DialogTitle";
import Dialog from "#material-ui/core/Dialog";
import Button from "#material-ui/core/Button";
import "./styles.css";
function App() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = (value) => {
setOpen(false);
};
return (
<>
<Dialog
onClose={handleClose}
aria-labelledby="simple-dialog-title"
open={open}
>
<DialogTitle id="simple-dialog-title">Set backup account</DialogTitle>
<Autocomplete
freeSolo
id="free-solo-demo"
defaultValue={"a"}
options={["a", "b", "c"]}
renderInput={(params) => (
<TextField
{...params}
label="freeSolo"
margin="normal"
variant="outlined"
fullWidth
/>
)}
/>
</Dialog>
<div>
<br />
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open simple dialog
</Button>
</div>
</>
);
}
Is there a way to see the placeholder already fixed when the dialog is open? I have an other autocomplete that is with multiple, and this textfield is not giving me this error at all.
Add the following InputLabelProps to the TextField component.
<TextField
{...params}
InputLabelProps={{ // this part
shrink: true
}}
label="freeSolo"
margin="normal"
variant="outlined"
fullWidth
/>
If you check the implementation of the TextField component, you can see that the input label prop gets notched if the shrink parameter is truthy.
Edit: Which means that if the field becomes empty you will have to manage that yourself.
I've been trying to conditionally render the list items based on the user's selection but i can't seem to figure it out. I'd like to guide the users through steps in order to arrive at a final solution. I even tried rendering a different menu but it looks like the component does not re-render once the option selected change so the menu stays the same.
import React, { Fragment, useState, useEffect } from 'react';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
function TroubleshootingForm() {
const [anchorEl, setAnchorEl] = useState(null);
const [softorHardProb, setSoftorHardProb] = useState(null);
const [selectedIndex, setSelectedIndex] = useState(1);
const options = ['Select issue type', 'Software', 'Hardware'];
const softwareOptions = ['Smart Assistant', 'Sounds', '3rd Party Apps', 'OPPO Clone Phone'];
const hardwareOptions = ['Dislay', 'Battery', 'Charger', 'DOA'];
const handleClickListItem = event => {
setAnchorEl(event.currentTarget);
};
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setAnchorEl(null);
console.log(selectedIndex);
};
const handleClose = () => {
setAnchorEl(null);
};
// useEffect(() => {}, []);
return (
<Fragment>
<Typography variant="h6" gutterBottom>
Hardware or Software?
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<div>
<List component="nav" aria-label="Device settings">
<ListItem
button
aria-haspopup="true"
aria-controls="lock-menu"
aria-label="when device is locked"
onClick={handleClickListItem}
>
<ListItemText
primary="When device is locked"
secondary={
selectedIndex === 1
? options[selectedIndex]
: softwareOptions[
(
<ListItemText
primary="When device is locked"
secondary={
selectedIndex === 1
? options[selectedIndex]
: softwareOptions[selectedIndex]
}
/>
)
]
}
/>
</ListItem>
</List>
<Menu
id="lock-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{options.map((option, index) => (
<MenuItem
key={option}
disabled={index === 0}
selected={index === selectedIndex}
onClick={event => handleMenuItemClick(event, index)}
>
{option}
</MenuItem>
))}
</Menu>
</div>
</Grid>
{/* <Grid item xs={12}>
{renderNextstep()}
</Grid> */}
</Grid>
</Fragment>
);
}
export default TroubleshootingForm;
The idea is for the user to chose options and the render the next step with other options and then so on until the final solution based on the previous selection. I added a screenshot from another app and the idea is that the current rendering of the component changes based on the selection. It starts by showing step 1 > select an option and component re-renders showing step 2 etc...
I have a material ui react select in a component that may be on a page multiple times.
In the examples all labelled selects use InputLabel with htmlFor that must be the id of the select.
I cannot give the select an id because id has to be unique for a page and this is a component that doesn't need to know all the id's on the page (nor anywhere in my code do I want to know about all the id's in the page).
According to the InputLabel documentation it doesn't even have a documented htmlFor propery.
Is it possible to label a MUI select without giving it an id?
So long as you don't run into any styling difficulties with your nested solution, then that is perfectly fine, but here's an example using a generated id that would allow you to avoid nesting the Select within the InputLabel:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
let nextIdSuffix = 1;
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
flexWrap: "wrap"
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
const CustomSelect = () => {
const idRef = React.useRef();
const classes = useStyles();
const [value, setValue] = React.useState("");
if (!idRef.current) {
idRef.current = `CustomSelect${nextIdSuffix}`;
nextIdSuffix++;
}
return (
<FormControl className={classes.formControl}>
<InputLabel htmlFor={idRef.current}>Age</InputLabel>
<Select
value={value}
onChange={e => setValue(e.target.value)}
inputProps={{
name: idRef.current,
id: idRef.current
}}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
);
};
export default CustomSelect;
Same as the html label tag; I did the nested the Select in the InputLabel:
<InputLabel className="paging">
Page:
<Select
value={current}
onChange={e => changePage(e.target.value)}
inputProps={{
name: 'page',
id: 'page',
}}
>
{options.map(option => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
</InputLabel>
I created an AppBar with a menu based on the examples given on the material UI site and it works fine with one menu. But when I try adding a second dropdown menu, on clicking either icon, I get the same set of MenuItems showing up as seen in the image.
Here is the list of menu items that are showing up when either icon is clicked
import React, { Component } from 'react';
// Material UI Imports
import { withStyles } from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import IconButton from 'material-ui/IconButton';
import Tooltip from 'material-ui/Tooltip';
import Menu, { MenuItem } from 'material-ui/Menu';
import PeopleIcon from 'material-ui-icons/People';
import ViewListIcon from 'material-ui-icons/ViewList';
import CompareArrowsIcon from 'material-ui-icons/CompareArrows';
const styles = {
root: {
width: '100%',
},
flex: {
flex: 1,
},
menuItem: {
paddingLeft: '10px'
}
}
class Header extends Component {
constructor(props) {
super(props);
this.state = { anchorEl: null };
}
handleMenu = event => {
console.log(event.currentTarget);
this.setState({ anchorEl: event.currentTarget });
}
handleClose = () => {
this.setState({ anchorEl: null });
}
render() {
const { classes } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
return(
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<Typography type="title" color="inherit" className={classes.flex}>
New Faces
</Typography>
{/* Menu Item One */}
<div>
<Tooltip title="Lists" className={classes.menuItem}>
<IconButton
color="inherit"
aria-owns={open ? 'menu-list' : null}
aria-haspopup="true"
onClick={this.handleMenu}
>
<ViewListIcon />
</IconButton>
</Tooltip>
<Menu
id="menu-list"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal:'right',
}}
transformOrigin={{
vertical: 'top',
horizontal:'right',
}}
open={open}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Create List</MenuItem>
<MenuItem onClick={this.handleClose}>List 1</MenuItem>
<MenuItem onClick={this.handleClose}>List 2</MenuItem>
</Menu>
</div>
{/* Menu Item Two */}
<div>
<Tooltip title="User Management" className={classes.menuItem}>
<IconButton
color="inherit"
aria-owns={open ? 'menu-appbar' : null}
aria-haspopup="true"
onClick={this.handleMenu}
>
<PeopleIcon />
</IconButton>
</Tooltip>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal:'right',
}}
transformOrigin={{
vertical: 'top',
horizontal:'right',
}}
open={open}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>User Management</MenuItem>
<MenuItem onClick={this.handleClose}>Logout</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
</div>
);
}
}
export default withStyles(styles)(Header);
How do you assign which MenuItems show up according to the icon that is clicked? I assumed it was supposed to show the MenuItems that are directly under the selected anchorEl. Any help would be much appreciated!
Check this working solution https://codesandbox.io/s/84xl2v8wm2 I whipped up
What I have done is extract the common code into a separate component lets call it MenuButton and use it at multiple places. So that each menu button has its own scope, own event handlers etc. Currently, the issue is that you are using same event handlers for two different elements and using the same variable to keep track of the state of the menu. Either use two variables like open and open1 or extract the code into a separate component like I have done.
Parent file
<MenuButton iconType={AccountCircle} items={['Create','List1', 'List2']}/>
<MenuButton iconType={MenuIcon} items={['Profile','User Management', 'Logout']}/>
menuButton.js file
import React from 'react';
import AccountCircle from 'material-ui-icons/AccountCircle';
import Menu, { MenuItem } from 'material-ui/Menu';
import IconButton from 'material-ui/IconButton';
import { withStyles } from 'material-ui/styles';
class MenuButton extends React.Component {
state = {
anchorEl: null
};
handleChange = (event, checked) => {
this.setState({ auth: checked });
};
handleMenu = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { classes } = this.props;
const { auth, anchorEl } = this.state;
const open = Boolean(anchorEl);
const Wrapper = this.props.iconType;
const listItems = this.props.items.map((link) =>
<MenuItem onClick={this.handleClose} >{link}</MenuItem>
);
return (
<div>
<IconButton
aria-owns={open ? 'menu-appbar' : null}
aria-haspopup="true"
onClick={this.handleMenu}
color="contrast"
>
{<Wrapper />}
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={open}
onClose={this.handleClose}
>
{listItems}
</Menu>
</div>
);
}
}
export default MenuButton;