Display a modal from Material UI dropdown selection - reactjs

The goal is to open a delete option modal from a material ui dropdown menu.
The first step towards that is understanding how to simply select an item from a dropdown, and then trigger some form of action (opening the modal) depending on which item is selected.
The material UI docs all seem to offer samples in which onClick simply handles the closing of the menu. It seems there are rare examples of selecting something from a dropdown and then opening/doing things from there?
I'm having trouble seeing where I would insert the logic/event handling to handle 'if the user selects option x, open modal regarding option x' within the context of a material ui menu.
Here's my code to show the rabbit hole I'm down currently:
import * as React from 'react';
import IconButton from '#mui/material/IconButton';
import Menu from '#mui/material/Menu';
import MenuItem from '#mui/material/MenuItem';
import { useState } from 'react'
import Select from '#mui/material/Select';
interface IProps extends Omit<unknown, 'children'> {
children: any;
options: string[];
}
const ITEM_HEIGHT = 48;
const DropdownMenu = ({ children, options }: IProps) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const [value, setValue] = useState('');
const handleChange = (event: any) => {
setValue(event.target.value);
console.log(value)
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<IconButton
aria-label="more"
id="long-button"
aria-controls="long-menu"
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
{children}
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
'aria-labelledby': 'long-button'
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: '20ch'
}
}}
>
<Select value={value} onChange={handleChange}>
{options.map(option => (
<MenuItem key={option} onClick={handleClose} >
{option}
</MenuItem>
))}
</Select>
</Menu>
</div>
);
};
export default DropdownMenu;
How would I use the dropdown to trigger the invocation of a Modal component based on a string value?

If I understand your question right then this should be the solution. simply assign a ref to the triggering element (in this case it's the IconButton). then you can open the menu via setting the open state. BTW why should the button render children?
import * as React from 'react';
import IconButton from '#mui/material/IconButton';
import Menu from '#mui/material/Menu';
import MenuItem from '#mui/material/MenuItem';
import { FC, useState } from 'react';
import Select from '#mui/material/Select';
type DropdownMenuProps = {
options: string[];
};
const ITEM_HEIGHT = 48;
const DropdownMenu: FC<DropdownMenuProps> = ({ children, options }) => {
const anchorEl = React.useRef<null | HTMLButtonElement>(null);
const [open, setOpen] = useState<boolean>(false);
const handleClick: React.MouseEventHandler = () => {
setOpen(true);
};
const [value, setValue] = useState('');
const handleChange: React.ChangeEventHandler<HTMLSelectElement> = (event) => {
const value = event.target.value;
setValue(value);
if (value === 'the value you want to trigger the menu') {
setOpen(true);
}
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<IconButton
aria-label="more"
id="long-button"
aria-controls="long-menu"
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}>
{children}
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
'aria-labelledby': 'long-button',
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: '20ch',
},
}}>
<Select value={value} onChange={handleChange}>
{options.map((option) => (
<MenuItem key={option} onClick={handleClose}>
{option}
</MenuItem>
))}
</Select>
</Menu>
</div>
);
};
export default DropdownMenu;

Related

how to add a custom action menu in material table

I need to display a menu in the actions column from material-table.
there is a post already here:
How show menu item in Material Table
which does it but I couldn't ask for the sandbox so I can see and play with the implementation
looking something like this
I would really appreciate any help on this
This is the working piece of what was looking for
this is the sandbox with the example
I hope this is useful in case you are looking for something similar
https://codesandbox.io/s/elastic-kalam-4987cm?file=/src/ActionMenu.js
here the code as well
import React, { useState } from "react";
import IconButton from "#mui/material/IconButton";
import Menu from "#mui/material/Menu";
import MenuItem from "#mui/material/MenuItem";
import MoreVertIcon from "#mui/icons-material/MoreVert";
export default function ActionMenu({ data }) {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const add = () => {
console.log("Add: ", data.name);
handleClose();
};
const remove = () => {
console.log("remove: ", data.name);
handleClose();
};
return (
<div>
<IconButton
aria-label="more"
id="long-button"
aria-controls={open ? "long-menu" : undefined}
aria-expanded={open ? "true" : undefined}
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
"aria-labelledby": "long-button"
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
<MenuItem key="1" onClick={add}>
add
</MenuItem>
<MenuItem key="2" onClick={remove}>
remove
</MenuItem>
</Menu>
</div>
);
}

TransformedProps not a recognized as a type in a Typescript/React project?

I'm getting an error when using an OOTB Material UI component in React18, Deno (TypeScript) and Material UI. Specifically on not finding the TransformedProps type:
https://mui.com/material-ui/react-button-group/
I cannot get it to load TransformedProps, here is the code from the above link:
import * as React from 'react';
import Button from '#mui/material/Button';
import ButtonGroup from '#mui/material/ButtonGroup';
import ArrowDropDownIcon from '#mui/icons-material/ArrowDropDown';
import ClickAwayListener from '#mui/material/ClickAwayListener';
import Grow from '#mui/material/Grow';
import Paper from '#mui/material/Paper';
import Popper from '#mui/material/Popper';
import MenuItem from '#mui/material/MenuItem';
import MenuList from '#mui/material/MenuList';
const options = ['Create a merge commit', 'Squash and merge', 'Rebase and merge'];
export default function SplitButton() {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLDivElement>(null);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const handleClick = () => {
console.info(`You clicked ${options[selectedIndex]}`);
};
const handleMenuItemClick = (
event: React.MouseEvent<HTMLLIElement, MouseEvent>,
index: number,
) => {
setSelectedIndex(index);
setOpen(false);
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event: Event) => {
if (
anchorRef.current &&
anchorRef.current.contains(event.target as HTMLElement)
) {
return;
}
setOpen(false);
};
return (
<React.Fragment>
<ButtonGroup variant="contained" ref={anchorRef} aria-label="split button">
<Button onClick={handleClick}>{options[selectedIndex]}</Button>
<Button
size="small"
aria-controls={open ? 'split-button-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-label="select merge strategy"
aria-haspopup="menu"
onClick={handleToggle}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom' ? 'center top' : 'center bottom',
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="split-button-menu" autoFocusItem>
{options.map((option, index) => (
<MenuItem
key={option}
disabled={index === 2}
selected={index === selectedIndex}
onClick={(event) => handleMenuItemClick(event, index)}
>
{option}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</React.Fragment>
);
}
Due to Deno/TypeScript I cannot use npm style imports, so mine look like this:
import React from "react";
import {
Button,
ButtonGroup,
ClickAwayListener,
Grow,
MenuItem,
MenuList,
Paper,
Popper,
TransitionProps as TransitionPropsType
} from ""https://esm.sh/#mui/material#5.6.1";
import { ArrowDropDown } from ""https://esm.sh/#mui/icons-material#5.6.1";
I also changed the "TransformedProps" line:
{({ TransitionProps, placement }: { TransitionProps: TransitionPropsType, placement: string }) => (
<Grow
{...TransitionProps}
Deno prevents me from using any so I tried to import but it still fails. Since it is minified it is hard to see where it is exported and what file. Thoughts and thoughts on how to solve these issues in the future? It seems not using npm and just using strict TypeScript is difficult no matter what library I use.

Materail UI x React: Autocomplete closes when an item tries to show Popover

I'm trying to have a customized Autocomplete and each item has a more icon when you hover a mouse pointer.
When you click the icon, it supposes to bring Popover up, but instead it closes the Autocomplete list.
The image above, I'm hovering a pointer on the icon, and when I clicked it, it closed the list.
App.js
import React, { useState } from "react";
import { TextField, Paper } from "#material-ui/core";
import { Autocomplete, useAutocomplete } from "#material-ui/lab";
import Item from "./Item";
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const countries = [
{ code: "US", label: "USA", phone: "1" },
{ code: "HU", label: "Hungary ", phone: "2" },
{ code: "IT", label: "Italy ", phone: "3" }
];
const openMultipleOptionSearch = (event) => {
setIsOpen(true);
setAnchorEl(event.currentTarget);
};
const {
getRootProps,
getInputLabelProps,
getInputProps,
getListboxProps,
getOptionProps,
groupedOptions
} = useAutocomplete({
id: "use-autocomplete-demo",
options: countries,
getOptionLabel: (option) => option.label,
disableCloseOnSelect: true
});
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<Paper>
{groupedOptions.length > 0 &&
groupedOptions.map((option, index) => (
<Item key={index} text={option.label} onClose={() => {}} />
))}
{groupedOptions.length === 0 && <p>No match.</p>}
</Paper>
</div>
);
};
export default App;
Item.js
import React, { useState } from "react";
import MoreHoriz from "#material-ui/icons/MoreHoriz";
import { Box, Typography, Button } from "#material-ui/core";
import Popup from "./Popup";
const Item = (prop) => {
const [onMouseOver, setOnMouseOver] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const openDeleteItem = (event) => {
setAnchorEl(event.currentTarget);
setIsOpen(true);
};
const closeDeleteItem = () => {
setAnchorEl(null);
setIsOpen(false);
setOnMouseOver(false);
prop.onClose();
};
return (
<Box
onMouseOver={() => setOnMouseOver(true)}
onMouseOut={() => isOpen || setOnMouseOver(false)}
>
<div className="item-container">
<Typography variant="body1">{prop.text}</Typography>
</div>
<Button
style={{ display: onMouseOver ? "" : "none" }}
onClick={openDeleteItem}
>
<MoreHoriz />
</Button>
<Popup
isOpen={isOpen}
anchorEl={anchorEl}
onClose={() => closeDeleteItem()}
/>
</Box>
);
};
export default Item;
Popup.js
import React from 'react';
import { Popover } from '#material-ui/core';
const Popup = (prop) => {
return (
<Popover
id={prop.isOpen ? 'simple-popover' : undefined}
open={prop.isOpen}
anchorEl={prop.anchorEl}
onClose={() => prop.onClose()}
>
Popup content
</Popover>
);
};
export default Popup;
Does anyone know how to fix this?
I think your autocomplete popup is getting blurred when you click on more button, so it looses focus and closes the dropdown itself.
this might help you - OnBlur closing react component if clicked inside

Customizing/Disabling Material-UI Dialog's Touch Mechanics

may somebody please help me customize/disable the Material-UI Dialog's touch mechanics? I have the Cancel and Confirm buttons, and that's just what I want to use to close the dialog. However, making a selection from a dropdown menu caused the dialog to autoclose according to the doc. And I couldn't see how to do that in the doc itself.
The problem
The problem I have is that the auto-closing is doing the job I want the cancel button to do. i.e: Closing the dialog and empty an array.
I'd appreciate your help.
Any special reason to use the Material-UI Simple Dialog?
From your question, seems like a Confirmation dialog is what you're looking for :)
Simple dialog touch mechanism
Touch mechanics:
Choosing an option immediately commits the option and closes the menu
Touching outside of the dialog, or pressing Back, cancels the action and closes the dialog
In the other hand, matching your needs:
Confirmation dialogs
Confirmation dialogs require users to explicitly confirm their choice before an option is committed. For example, users can listen to multiple ringtones but only make a final selection upon touching “OK”.
Touching “Cancel” in a confirmation dialog, or pressing Back, cancels the action, discards any changes, and closes the dialog.
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import DialogTitle from '#material-ui/core/DialogTitle';
import DialogContent from '#material-ui/core/DialogContent';
import DialogActions from '#material-ui/core/DialogActions';
import Dialog from '#material-ui/core/Dialog';
import RadioGroup from '#material-ui/core/RadioGroup';
import Radio from '#material-ui/core/Radio';
import FormControlLabel from '#material-ui/core/FormControlLabel';
const options = [
'None',
'Atria',
'Callisto',
'Dione',
'Ganymede',
'Hangouts Call',
'Luna',
'Oberon',
'Phobos',
'Pyxis',
'Sedna',
'Titania',
'Triton',
'Umbriel',
];
function ConfirmationDialogRaw(props) {
const { onClose, value: valueProp, open, ...other } = props;
const [value, setValue] = React.useState(valueProp);
const radioGroupRef = React.useRef(null);
React.useEffect(() => {
if (!open) {
setValue(valueProp);
}
}, [valueProp, open]);
const handleEntering = () => {
if (radioGroupRef.current != null) {
radioGroupRef.current.focus();
}
};
const handleCancel = () => {
onClose();
};
const handleOk = () => {
onClose(value);
};
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<Dialog
disableBackdropClick
disableEscapeKeyDown
maxWidth="xs"
onEntering={handleEntering}
aria-labelledby="confirmation-dialog-title"
open={open}
{...other}
>
<DialogTitle id="confirmation-dialog-title">Phone Ringtone</DialogTitle>
<DialogContent dividers>
<RadioGroup
ref={radioGroupRef}
aria-label="ringtone"
name="ringtone"
value={value}
onChange={handleChange}
>
{options.map((option) => (
<FormControlLabel value={option} key={option} control={<Radio />} label={option} />
))}
</RadioGroup>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCancel} color="primary">
Cancel
</Button>
<Button onClick={handleOk} color="primary">
Ok
</Button>
</DialogActions>
</Dialog>
);
}
ConfirmationDialogRaw.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
value: PropTypes.string.isRequired,
};
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
maxWidth: 360,
backgroundColor: theme.palette.background.paper,
},
paper: {
width: '80%',
maxHeight: 435,
},
}));
export default function ConfirmationDialog() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState('Dione');
const handleClickListItem = () => {
setOpen(true);
};
const handleClose = (newValue) => {
setOpen(false);
if (newValue) {
setValue(newValue);
}
};
return (
<div className={classes.root}>
<List component="div" role="list">
<ListItem button divider disabled role="listitem">
<ListItemText primary="Interruptions" />
</ListItem>
<ListItem
button
divider
aria-haspopup="true"
aria-controls="ringtone-menu"
aria-label="phone ringtone"
onClick={handleClickListItem}
role="listitem"
>
<ListItemText primary="Phone ringtone" secondary={value} />
</ListItem>
<ListItem button divider disabled role="listitem">
<ListItemText primary="Default notification ringtone" secondary="Tethys" />
</ListItem>
<ConfirmationDialogRaw
classes={{
paper: classes.paper,
}}
id="ringtone-menu"
keepMounted
open={open}
onClose={handleClose}
value={value}
/>
</List>
</div>
);
}
you can have a look of the working dialog here:
https://4zgol.csb.app/
Hope it helps and if not, feel free to explain more about the problem or even add a code snippet :)

How to type onFocus function event parameter for material ui Input

I am creating a custom dropdown using material ui Input and Popper. When user focuses in Input, I want to set popper open to true. I am also using typescript.
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import Grow from '#material-ui/core/Grow';
import Input from '#material-ui/core/Input';
import Paper from '#material-ui/core/Paper';
import Popper from '#material-ui/core/Popper';
import MenuItem from '#material-ui/core/MenuItem';
import MenuList from '#material-ui/core/MenuList';
import React from 'react';
export function PlaceTreeSearch() {
const [searchTerm, setSearchTerm] = React.useState('');
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const handleSearchTermChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};
const onFocus = (event: any) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
return (
<div>
<ClickAwayListener onClickAway={handleClose}>
<div>
<Input
id='custom-select'
onChange={handleSearchTermChange}
onFocus={onFocus}
value={searchTerm}
placeholder='Search'
/>
<Popper
open={open}
anchorEl={anchorEl}
transition={true}
placement='bottom-start'
disablePortal={true}
style={{ zIndex: 999, width: '100%' }}
>
{({ TransitionProps }: any) => {
return (
<Grow
{...TransitionProps}
style={{ transformOrigin: '0 0 0' }}
>
<Paper>
<MenuList autoFocusItem={open} id="menu-list-grow">
<MenuItem>Profile</MenuItem>
<MenuItem>My account</MenuItem>
<MenuItem>Logout</MenuItem>
</MenuList>
</Paper>
</Grow>
)}}
</Popper>
</div>
</ClickAwayListener>
</div>
);
}
Currently, I have the onFocus function as follows
const onFocus = (event: any) => {
setAnchorEl(event.currentTarget);
};
But I want to type the event properly instead of giving any.
I tried the following
const onFocus = (event: React.MouseEvent<EventTarget>) => {
setAnchorEl(event.currentTarget as HTMLElement);
};
But this is throwing the following error
Type '(event: React.MouseEvent<EventTarget, MouseEvent>) => void' is not assignable to type '(event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void'
How do I fix this error?
Use the suggested type would be fine
FocusEvent<HTMLInputElement | HTMLTextAreaElement>
It's a common way to do that, and that's the method of how those stack of the error messages are designed and used.

Resources