I'm trying to make a pop-up menu that has a couple different filters (buttons, selects, and text fields). I'm using the Material UI Menu component, but have run into an issue when trying to use the text fields. Because the Menu component is acting like a <Select />, when I type something in the text fields, it tries to select different MenuItems instead of staying focused on the text box.
So using the example found in the sandbox below, users wouldn't be able to type anything in the "search for a different food" textbox if it started with an "A", "B", or "C". If you wanted to type in "apricots", the menu would change focus from the textbox to the "Apples" MenuItem.
I don't see any props for this in the Menu API, so I'm curious to know if there is any work arounds, or even a different component that is more suited for this.
Thanks!
Here's a codesandbox: https://codesandbox.io/s/wizardly-mccarthy-zy2b7
import { useState } from "react";
import "./styles.css";
import { Button, Menu, MenuItem, TextField } from "#material-ui/core";
export default function App() {
const [menu, setMenu] = useState(null);
const [text, setText] = useState("");
return (
<div className="App">
<Button variant="outlined" onClick={(e) => setMenu(e.currentTarget)}>
Filters
</Button>
<Menu
anchorEl={menu}
open={Boolean(menu)}
getContentAnchorEl={null}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
onClose={() => setMenu(null)}
>
<MenuItem>Apples</MenuItem>
<MenuItem>Bananas</MenuItem>
<MenuItem>Carrots</MenuItem>
<MenuItem>
<TextField
value={text}
onChange={(e) => setText(e.target.value)}
variant={"outlined"}
label={"search a different food..."}
/>
</MenuItem>
</Menu>
</div>
);
}
Use e.stopPropagation() in the onKeyDown event for the MenuItem containing the TextField. This will prevent the key down event from propagating to the Menu component.
<MenuItem onKeyDown={(e) => e.stopPropagation()}>
<TextField
value={text}
onChange={(e) => setText(e.target.value)}
variant={"outlined"}
label={"search a different food..."}
/>
</MenuItem>
Reference: How to disable the selection of an item when the first letter of the option is pressed in the Select component?
Related
I'm using antd version 3 and want to change the outcome onChange when Tab is clicked.
Here the picture of my desire objective enter image description here
My tab title has a checkbox. Outcome should be in this way: when user is stand at Tab1 should have a possibility to change value checkbox of Tab2 or Tab3 without setting active Tab or moving to Tab2 or Tab3. I have read that tab didn't support HOC... I tried a lot of ways to solve but didn't work out with that. event.PreventDefault / event.stopPropagation similarly result. Even if handlers like onTabClick / onChange didn't set active Tab with a new key after checked checkbox active key is old but tab is render with new information.
Here example of custom tab title component with some extract from code
<Tabs
defaultActiveKey={'Tab 1'}
onChange={(key) => onChangeHandler(key)}
onTabClick={(key: string, event: MouseEvent) => onTabClick(key, event)}
>
<TabPane
tab={<Header name={'Tab1'} />}
key={'Tab 1'}
/>
<TabPane
tab={<Header name={'Tab 2'} />}
key={'Tab 2'}
/>
<TabPane
tab={<Header name={'Tab 3'} />}
key={'Tab 3'}
/>
const Header = ({
name,
onChange,
isActive,
id,
}) => {
return (
<div>
<Checkbox
isActive={isActive}
value={id}
checked={isActive}
onChange={onChange}
/>
<Title >{name}</Title>
</div>
)
}
As far as I understand you want the tabs not to change when you click on the check box.
To do this, you can pass the onClick function to the Checkbox and stop the event bubbling there
const Header = ({ name, onChange, isActive, id }) => {
return (
<div>
<Checkbox
isActive={isActive}
value={id}
checked={isActive}
onChange={onChange}
onClick={(e) => {
e.stopPropagation();
}}
/>
<Title>{name}</Title>
</div>
);
};
Here is a link to the code that is referenced below in my question - https://codesandbox.io/s/material-demo-forked-gtztx?file=/demo.tsx
In the code below (or demo.tsx from the codesandbox link above ), I am trying to combine a new FormControlLabel radio button (line 24) into the dynamically generated material UI radio buttons.
I don't want to add this new radio button into the "options" list within "index.tsx", but I still want this new radio button to be within the same "payment" radio group.
Current behavior: After the page renders, this custom radio with the label as Unknown always remains checked.
Expected behavior: Make this custom radio button work as a part of the dynamically created "payment" radio groups without adding it to the "options" list (see index.tsx)
export default function RadioButtonsGroup(props) {
const [value, setValue] = React.useState(null);
console.log(value);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue((event.target as HTMLInputElement).value);
};
return (
<FormControl component="fieldset">
<FormLabel component="legend">Payment</FormLabel>
<RadioGroup
aria-label="payment"
name="payment1"
value={value}
onChange={handleChange}
>
{/* <FormControlLabel value={value} control={<Radio />} label="Unknown" /> */}
{props.options.map(([value, readable], index) => (
<FormControlLabel
key={index}
value={value}
control={<Radio />}
label={readable}
/>
))}
</RadioGroup>
</FormControl>
);
}
This is what you need - https://codesandbox.io/s/material-demo-forked-jgopr
Basically, I added a checked prop to all the radio buttons (the custom one and the ones in the optionList) to correctly show the selected state of radio control for the value in the component state.
This is my JSX:
<FormControl>
<ButtonGroup className="groupedHorizontal">
<InputLabel htmlFor="category">Category:</InputLabel>
<Select onChange={(event) => that.handleCategoryChange(event)} native={true} id="category">
<option></option>
{catOptions}
</Select>
<BrandsPopup />
<Button onClick={(e) => that.removeCategory(e)}>Del</Button>
</ButtonGroup>
</FormControl>
The BrandsPopup is a component which render a material-ui Button within <React.Fragment>. The select and the "Del" button are fine bordered as ButtonGroup elements. The problem is, BrandsPopup is not bordered and does not appear as part of the group. How to apply ButtonGroup styles on the button, rendered from the child component?
ButtonGroup uses cloneElement and thereby assigns its own props to its children. You should be able to log them to the console inside BrandsPopup and then just need to assign them to your button component. It is, of course, possible that this conflicts with how you are using BrandsPopup elsewhere in your app.
And if BrandsPopup indeed only contains one Button component you don't need the Fragment wrapper.
<ButtonGroup className="groupedHorizontal">
<BrandsPopup />
</ButtonGroup>
const BrandsPopup = (props) => (
<React.Fragment>
<Button
// these come from ButtonGroup 🧙
className={props.className}
color={props.color}
variant={props.variant}
>
click me
</Button>
</React.Fragment>
);
I'm using React 16.13.0 and material's KeyboardDatePicker component -- https://material-ui-pickers.dev/api/KeyboardDatePicker . I have set it up like so ...
import {
KeyboardDatePicker,
KeyboardTimePicker,
MuiPickersUtilsProvider,
} from "#material-ui/pickers";
...
<KeyboardDatePicker
margin="normal"
id="date-pickUp"
label="Select Date"
format="MM/dd/yyyy"
value={pickUpDateLabel}
onChange={(date) => handleDate(date, "pickUp")}
KeyboardButtonProps={{
"aria-label": "change date",
}}
/>
The thing I'd like to tweak is that when I click on the text field where you can type in the date or click the icon graphic to bring up the date picker, I would like the date picker UI to come up automatically. I'm not sure how to configure things though such that as soon as I click on the text field, the UI for the date picker pops up.
Edit: I'm unable to get a working app up with the code but here's a screen shot of the text field that is rendered by default with the icon at the right ...
Right now you have to click on that icon for the date picker to come up, but I would like to be able to click on the text field and immediately have the date picker appear.
Edit 2: Screen shot in response to answer given ...
Multiple issues to handle while solving this:
Since you want to the focus on the Input to control the opening of the DatePicker Popover - has to be a controlled component (which you control the opening of it using a state.
This state is actually the open prop of the KeyboardDatePicker
Next issue is that once the Popover is closed - the focus is getting back to the Input, and once we have a focus the the Popover will open (not good). We can solve this using the disableRestoreFocus prop of the Popover.
We need to use the onFocus of the Input to open the Popover, but the onClose of the Popover to actually close it (because we control the open-state of the Popover).
Lastly - the icon is no longer controlling the opening of the Popover. We need to do this, using the onFocus of the KeyboardButtonProps.
This is the complete code:
const KeyDatePickerContainer = () => {
const [selectedDate, handleDateChange] = useState(null);
const [isOpen, setIsOpen] = useState(false);
return (
<KeyboardDatePicker
variant="inline"
value={selectedDate}
inputVariant="outlined"
label="With keyboard"
format="MM/dd/yyyy"
onChange={newDate => {
handleDateChange(newDate);
}}
KeyboardButtonProps={{
onFocus: e => {
setIsOpen(true);
}
}}
PopoverProps={{
disableRestoreFocus: true,
onClose: () => {
setIsOpen(false);
}
}}
InputProps={{
onFocus: () => {
setIsOpen(true);
}
}}
open={isOpen}
/>
);
};
Here is a link to a working example: https://codesandbox.io/s/material-pickers-open-modal-click-on-text-kjgjk
Update: if you want to also close the DatePicker once the date was selected you can use the onChange function to not only set the new date, but also close the Popover:
onChange={newDate => {
handleDateChange(newDate);
setIsOpen(false); // Add this
}}
For anyone who uses updated material-ui library (v5), you can use open={bool} attribute to make use of when to open DatePicker.
const [dateOpen,setDateOpen] = useState(false);
const [dueDate,setDueDate] = useState(new Date());
<DatePicker
clearable={true}
open={dateOpen}
onClose={() => setDateOpen(false)}
label="Due Date"
value={dueDate}
minDate={new Date()}
onChange={(newValue) => {
setDueDate(newValue);
}}
renderInput={(params) => (
<TextField
{...params}
onClick={() => setDateOpen(true)}
/>
)}
/>
For anyone else interested I came up with the following solution:
You change the KeyboardDatePicker to DatePicker
You take advantage of InputProps and add the calendar icon
InputProps={{
endAdornment: (
<InputAdornment>
<Event />
</InputAdornment>
)
}}
Working sandbox:
https://codesandbox.io/s/customicon-forked-zitm9?file=/src/DatePicker.js
I am trying to determine if SelectField is expanded or not, i.e. if the dropdown and it's MenuItems are visible.
Currently I use roughly following approach:
<SelectField onClick={() => this.setState({ isExpanded: true })} >
<MenuItem primaryText={
<MenuItemContent onHide={() => this.setState({ isExpanded: false })}} />
}>
</SelectField>
and in MenuItemContent I implement
class MenuItemContent extends React.Component {
componentWillUnmount = () => this.props.onHide()
}
This approach has a drawback, that when you click outside the menu, the componentWillUnmount call is not triggered immidiately, but cca after 200ms, despite the MenuItems are no longer visible.
The Select field menu can be expanded by clicking it, and when is expanded, it will hide either when some menu item is clicked, or user clicks outside the menu.
This is how the SelectField looks when is expanded:
And here is collapsed:
For the better handling of SelectField close event you can use property dropDownMenuProps:
/**
* Object that can handle and override any property of component DropDownMenu.
*/
dropDownMenuProps: PropTypes.object,
By using this prop we can pass to DropDownMenu component onClose prop with handler function, which will be fired instantly after DropDownMenu close (caused by ESC, outside click or item select). Unfortunately DropDownMenu component dont provide similar prop to determine the opening so the only way (without
extending the component) is to follow your approach with onClick event handler.
Here is my working test example:
onSelectClose = () => {
console.log("close")
}
onSelectOpen = () => {
console.log("open")
}
render() {
return (
<MuiThemeProvider>
<div className="App">
<SelectField
floatingLabelText="Frequency"
onClick={this.onSelectOpen}
dropDownMenuProps={{
onClose: this.onSelectClose
}}
value={this.state.value}
onChange={this.handleChange}>
<MenuItem value={1} primaryText="Never" />
<MenuItem value={2} primaryText="Every Night" />
<MenuItem value={3} primaryText="Weeknights" />
<MenuItem value={4} primaryText="Weekends" />
<MenuItem value={5} primaryText="Weekly" />
</SelectField>
</div>
</MuiThemeProvider>
);
}
Detecting if React Material UI Select Field is expanded
The answer is available in Materia-UI Select-API documentation.
Link: https://material-ui.com/api/select/