I am currently using simple-menu of material UI as shown in the codesandbox here. In my react app, I am displaying this menu in a primereact dialog.
However, my main requirement is to display a dropdown list in a dialog so that when a user selects something, it can be selected and I can do something based on that.
For example, if a user selects Car, I can see it selected. Also, I have some values associated with each entries.
For example, Company has value 0,Car has value 1 and Office has value 2.
So I am planning to have some buttons in the dialog so that after an item from the list is selected, and Ok button is selected, I can send the value associated with the selected item in a webservice call.
The closest I could find is simplelist:
https://material-ui.com/components/lists/#simple-list
But nothing close to Drowpdown list. Am I missing on something here?
More info:
The dialog looks like this:
And let's say, when I select an item, let's say, Car, it won't show Car but will display Open Menu which I am not looking for.
What I have achieved is when the dropdown is opened and some option is selected then users can see what they have selected and dropdown doesn't close when some option is selected and in the console you can see what option is selected by index number.
You can follow this code:
import React, { useState } from "react";
import Button from "#material-ui/core/Button";
import Menu from "#material-ui/core/Menu";
import MenuItem from "#material-ui/core/MenuItem";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
width: "100%",
maxWidth: 360,
backgroundColor: theme.palette.background.paper
}
}));
export default function SimpleMenu() {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [visval, setVisval] = useState("Open Menu");
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
//console.log(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const classes = useStyles();
const [selectedIndex, setSelectedIndex] = React.useState();
const handleListItemClick = (event, index) => {
setSelectedIndex(index);
handleClose();
//console.log(index);
if (index === 0) {
setVisval("Company");
} else if (index === 1) {
setVisval("Car");
} else {
setVisval("Office");
}
};
return (
<div>
<Button
aria-controls="simple-menu"
aria-haspopup="true"
onClick={handleClick}
>
{visval}
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem
data-my-value={1}
selected={selectedIndex === 0}
onClick={(event) => handleListItemClick(event, 0)}
>
Company
</MenuItem>
<MenuItem
data-my-value={2}
selected={selectedIndex === 1}
onClick={(event) => handleListItemClick(event, 1)}
>
Car
</MenuItem>
<MenuItem
data-my-value={3}
selected={selectedIndex === 2}
onClick={(event) => handleListItemClick(event, 2)}
>
Office
</MenuItem>
</Menu>
</div>
);
}
You can checkout the updated and working codesandbox here
EDIT :
I have achieved what you asked i.e, the menu showing the three options should close when we select some option and the same should be visible in the place of Open Menu. But what I saw is that the code is no longer reusable and is becoming too large (I was aware of it).
If you want your code to be minimised then follow the bellow suggestion
SUGGESTION :
I saw that Material-ui's autocomplete just does the work with a clean code.
It gives you a set of options like a dropdown menu and when the user select's something the selected option is seen by the user.
Link to the Working Demo
You can migrate the same code into your dialog's content it will work fine.
Related
I have a list of rows in a table. This only happens when i have more rows in a table. I am using modal of antd.
This is my code:
import {Modal,Button} from "antd";
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const handleOk = (id) => {
store.dispatch(deleteProductList(id));
setIsDeleteModalVisible(false);
};
const handleCancel = () => {
setIsDeleteModalVisible(false);
};
<Button
icon={<DeleteFilled />}
style={{
backgroundColor: "#D15B47",
color: "#ffffff",
}}
onClick={showDeleteModal}
>
Delete
</Button>
<Modal
title="Confirm Delete"
visible={isDeleteModalVisible}
onOk={() => handleOk(record.id)}
onCancel={handleCancel}
>
<p>Are you sure you want to delete this vehicle?</p>
</Modal>
Not much to go on from the content in the question. But I assume that the backdrop has full opacity or a color that has an alpha of 1? Try to inspect the backdrop and see if the color is correct.
Let's say I have a button that opens a Dialog component. The button has custom theming/styling to specify various states, one of them being the :focus state:
const useStyles = makeStyles({
root: {
"&:focus": {
backgroundColor: "#3A7DA9"
}
}
});
export default function App() {
const [open, setOpen] = useState(false);
const classes = useStyles();
return (
<div className="App">
<Button
id="button-that-opens-modal"
className={classes.root}
onClick={() => setOpen(true)}
>
Open the modal
</Button>
<Dialog open={open}>
<h3>This is the modal</h3>
<Button onClick={() => setOpen(false)}>
Close
</Button>
</Dialog>
</div>
);
}
What I've noticed is that every time I have this pattern, (where a button opens a dialog modal), when the modal is closed, the #button-that-opens-modal is left with a :focus state, which looks bad in terms of styling. Here's a quick gif:
Codesandbox demonstrating the issue
Is this a known issue? I don't see why the :focus should be automatically applied to the button when the modal closes. How can I stop this?
I tried this:
I can add a ref to the button, and make sure to manually unfocus the button in various places. Adding it in the onExited method of the Dialog works, but flashes the focus state for a second:
export default function App() {
const [open, setOpen] = useState(false);
const buttonRef = useRef();
const classes = useStyles();
return (
<div className="App">
<Button
ref={buttonRef}
className={classes.root}
onClick={() => setOpen(true)}
>
Open the modal
</Button>
<Dialog
open={open}
TransitionProps={{
onExited: () => {
buttonRef.current?.blur(); // helps but creates a flash
}
}}
>
<h3>This is the modal</h3>
<Button onClick={() => {setOpen(false)}}>
Close
</Button>
</Dialog>
</div>
);
}
sandbox showing this very imperfect solution
And even if I found exactly the right event handler to blur the button such the styling looks correct, this is not something I want to do for every Dialog in an app that has many Button - Dialog pairs. Is there a Material-UI prop I can use to disable this 'auto-focus' back on the button, rather than having to create a ref and manually .blur it for every Dialog?
This is for accessibilty purpose. You can disable it by adding prop disableRestoreFocus on your Dialog :)
I have made autocomplete features using Downshift using react js. But the problem is when I am searching for something its input field value is disappearing when I click on the outside. Here is the sample code.
import logo from './logo.svg';
import './App.css';
import React, { useState } from "react";
import Highlighter from "react-highlight-words";
import Downshift from "downshift";
import axios from 'axios';
function App() {
const [names, setnames] = useState([{
const [searchTerm, setSearchTerm] = useState('')
const [movie, setmovie] = useState([])
fetchMovies = fetchMovies.bind(this);
inputOnChange = inputOnChange.bind(this);
function inputOnChange(event) {
if (!event.target.value) {
return;
}
fetchMovies(event.target.value);
}
function downshiftOnChange(selectedMovie) {
alert(`your favourite movie is ${selectedMovie.title}`);
}
function fetchMovies(movie) {
const moviesURL = `https://api.themoviedb.org/3/search/movie?api_key=1b5adf76a72a13bad99b8fc0c68cb085&query=${movie}`;
axios.get(moviesURL).then(response => {
setmovie(response.data.results);
// this.setState({ movies: response.data.results });
});
}
return (
<Downshift
onChange={downshiftOnChange}
itemToString={item => (item ? item.title : "")}
>
{({
selectedItem,
getInputProps,
getItemProps,
highlightedIndex,
isOpen,
inputValue,
getLabelProps
}) => (
<div>
<label
style={{ marginTop: "1rem", display: "block" }}
{...getLabelProps()}
>
Choose your favourite movie
</label>{" "}
<br />
<input
{...getInputProps({
placeholder: "Search movies",
onChange: inputOnChange
})}
/>
{isOpen ? (
<div className="downshift-dropdown">
{movie
.filter(
item =>
!inputValue ||
item.title
.toLowerCase()
.includes(inputValue.toLowerCase())
)
.slice(0, 10)
.map((item, index) => (
<div
className="dropdown-item"
{...getItemProps({ key: index, index, item })}
style={{
backgroundColor:
highlightedIndex === index ? "lightgray" : "white",
fontWeight: selectedItem === item ? "bold" : "normal"
}}
>
{item.title}
</div>
))}
</div>
) : null}
</div>
)}
</Downshift>
);
}
export default App;
This is the sample code I have written. Also, when I click shift+home, it is also not working.
Problem 1: when the user clicked the outside text field value whatever I searched this is disappearing.
Problem 2: shift + home is not working also.
Anyone has any idea how to solve this problem?
when the user clicked the outside text field value whatever I searched this is disappearing.
One way you could do it is to set the stateReducer on the Downshift component:
This function will be called each time downshift sets its internal state (or calls your onStateChange handler for control props). It allows you to modify the state change that will take place which can give you fine grain control over how the component interacts with user updates without having to use Control Props. It gives you the current state and the state that will be set, and you return the state that you want to set.
state: The full current state of downshift.
changes: These are the properties that are about to change. This also has a type property which you can learn more about in the stateChangeTypes section.
function stateReducer(state, changes) {
switch (changes.type) {
case Downshift.stateChangeTypes.mouseUp:
return {
...changes,
isOpen: true,
inputValue: state.inputValue,
};
default:
return changes;
}
}
This way if you click outside the text field the dropdown will stay open and the input value won't be reset.
For a list of all state change types see the documentation here
You might also be able to get something working using the onBlur prop on the input, but I didn't get that working.
Using the mui-datatable library in react, create a custom column using customBodyRender to add a menu and when you click on one of the options, it will perform an action.
The problem is that when you click on the action, the data that enters the function are always those of the last record in the table, regardless of selecting the menu in the first row, the data is always the last.
What I need is that when I click on the menu of a row, it returns the data of that row.
Sample code: https://codesandbox.io/s/wonderful-mclaren-ivvlq?file=/src/App.js
I've also experienced this. The way I solved it is to set the row selected index in a state variable when selecting the Menu icon, and not inside MenuItem. The Index is passed on from customBodyRenderLite or customBodyRender.
const [anchorIndex, setAnchorIndex] = useState(0);
const handleClick = (event, index) => {
setAnchorEl(event.currentTarget);
setAnchorIndex(index);
};
const iconButtonElement = (index) => {
return (
<div>
<IconButton
aria-label="More"
aria-owns={open ? "long-menu" : null}
aria-haspopup="true"
onClick={event => handleClick(event, index)}
>
<MoreVertIcon color={"action"}/>
</IconButton>
{rightIconMenu(index)}
</div>
)
}
When something should be action on the menu item, it would use the value in the state.
const rightIconMenu = () => {
return (
<Menu elevation={2} anchorEl={anchorEl} open={open} onClose={onMenuClose}>
<MenuItem onClick={handleCellClick}>View</MenuItem>
</Menu>
)
};
function handleCellClick() {
history.push('/vehicleForm', {data: currentDataSet[anchorIndex]});
}
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);
};