Dismiss Material UI Popper after a few seconds automatically - reactjs

I want this popper to show when the "copy link" button is clicked to let the user know that it has been copied, but then disappear on its own after a second or two. Here is the code for the popper
import * as React from 'react';
import Box from '#mui/material/Box';
import Popper from '#mui/material/Popper';
export default function SimplePopper() {
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : undefined;
return (
<div>
<button aria-describedby={id} type="button" onClick={handleClick}>
Copy Link
</button>
<Popper id={id} open={open} anchorEl={anchorEl}>
<Box sx={{ border: 1, p: 1, bgcolor: 'background.paper' }}>
Link Copied
</Box>
</Popper>
</div>
);
}

You might be able to do something with setTimeout in handleClick.
Try modifying handleClick like so:
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
setTimeout(() => setAnchorEl(null), 3000);
};

Related

Shared Popper requires two clicks to reopen

I've got buttons that represent sports. When a user clicks on a button a Popper is opened with the sport's associated component/content. The same Popper is shared between all the buttons.
This works fine, but once the Popper is open it requires two clicks to open the Popper for another sport. One click to close the Popper and the second click to open it. So if I click the 'Baseball' button, I'll have to click the 'Basketball' button twice before the Popper is reopened with the Basketball content.
Is there any way to accomplish this with just a single click? Link to Sandbox
import React, { useEffect, useState } from 'react';
import { Popper, Button, Paper, Typography } from "#material-ui/core";
function CategoryMenuItemContent(props) {
return (
<>
<p>{props.menu.label}</p>
<p>{props.menu.content}</p>
</>
);
}
export default function App() {
const baseballCategory = {
label: 'Baseball',
content: <p>some content</p>
};
const basketballCategory = {
label: 'Basketball',
content: <p>some content</p>
};
const footballCategory = {
label: 'Football',
content: <p>some content</p>
};
const hockeyCategory = {
label: 'Hockey',
content: <p>some content</p>
};
const [activeCategoryMenuContent, setActiveCategoryMenuContent] = React.useState('baseball');
const categoryMenuItemContentComponents = {
"baseball": <CategoryMenuItemContent menu={baseballCategory} />,
"basketball": <CategoryMenuItemContent menu={basketballCategory} />,
"football": <CategoryMenuItemContent menu={footballCategory} />,
"hockey": <CategoryMenuItemContent menu={hockeyCategory} />,
};
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
setActiveCategoryMenuContent(event.currentTarget.textContent.toLowerCase());
};
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : undefined;
return (
<>
<Button onClick={(event) => {handleClick(event);}}>Baseball</Button>
<Button onClick={(event) => {handleClick(event);}}>Basketball</Button>
<Button onClick={(event) => {handleClick(event);}}>Football</Button>
<Button onClick={(event) => {handleClick(event);}}>Hockey</Button>
<Popper id={id} open={open} anchorEl={anchorEl}>
{activeCategoryMenuContent === 'baseball' && categoryMenuItemContentComponents['baseball']}
{activeCategoryMenuContent === 'basketball' && categoryMenuItemContentComponents['basketball']}
{activeCategoryMenuContent === 'football' && categoryMenuItemContentComponents['football']}
{activeCategoryMenuContent === 'hockey' && categoryMenuItemContentComponents['hockey']}
</Popper>
</>
);
}
This would make more sense:
const handleClick = (event) => {
if (anchorEl === event.currentTarget) {
setAnchorEl(null)
setActiveCategoryMenuContent('')
} else {
setAnchorEl(event.currentTarget);
setActiveCategoryMenuContent(event.currentTarget.textContent.toLowerCase())
}
};

Trying to disable a button onPress - ReactJS

I'm kinda new to ReactJs, I'm trying to disable a button in my app when that button is pressed. I have a function called "disableButtonEG" but it does nothing when I pressed it.
function disableButtonEG= () => {
button.disabled = true
};
<Button
title="Press to disable"
buttonStyle={{backgroundColor: PrimaryColor, borderRadius: 10}}
titleStyle={{fontSize:13}}
onPress={disableButtonEG}
/>
Any assist is appreciated
Try this,
function App() {
const [disable, setDisable] = React.useState(false);
return (
<button disabled={disable} onClick={() => setDisable(true)}>
Click me!
</button>
);
}
Buttons have a disabled attribute, you can set this to true or false to disable the button. You should also be using onClick instead of onPress.
Here is an example of one way to achieve what you are trying to do in ReactJS
import React from "react";
function App() {
const [disable, setDisable] = React.useState(false);
return (
<button disabled={disable} onClick={() => setDisable(true)}>
Disable Button
</button>
);
}
export default App
EDIT: Disable via function
import React from "react";
function App() {
const [disable, setDisable] = React.useState(false);
function disableButton() {
setDisable(true)
}
return (
<button disabled={disable} onClick={disableButton}>
Disable Button
</button>
);
}
export default App
I don't think onPress handler exists in button.
Please try with onMouseDown instead of onPress
Looks like you wrote the function syntax wrong. If you are still having problems after fixing it, You can use state. For example, I have a disabled state which has an initial value of false
const [disabled, setDisabled] = useState(false);
And a function that will change it to true
const disableButtonEG = () => {
setDisabled(true);
};
Then modify the Button component like this
<Button
title="Press to disable"
buttonStyle={{ backgroundColor: PrimaryColor, borderRadius: 10 }}
titleStyle={{ fontSize: 13 }}
disabled={disabled}
onPress={disableButtonEG}
/>

Hide popper of one field when opening popper of another field

I am implementing two text fields next to each other and they have end adornments as a buttons. Those buttons toggle popper visibility. Also each popper has clickawaylistener so the popper is closed when mouse clicks outside popper. If first popper is opened it should be closed when I click button of second text field adornment. Issue is that end adornments have event propagation stopped. I do that to prevent clickaway event when clicking adornment, so to prevent instant closing of popper when it is opened by toggle handler.
I was thinking about wrapping TextField into ClickAwayListener but it didn't work.
P.S. Both TextField will be rendered from separate components and I don't want to share any props between them as they should be independent.
https://codesandbox.io/s/basictextfields-material-demo-forked-rykrx
const [firstPopperVisible, setFirstPopperVisible] = React.useState(false);
const [secondPopperVisible, setSecondPopperVisible] = React.useState(false);
const firstTextFieldRef = React.useRef();
const secondTextFieldRef = React.useRef();
const toggleFirstPopperVisible = (e) => {
e.stopPropagation();
setFirstPopperVisible((prev) => !prev);
};
const handleFirstPopperClickAway = (e) => {
setFirstPopperVisible(false);
};
const toggleSecondPopperVisible = (e) => {
e.stopPropagation();
setSecondPopperVisible((prev) => !prev);
};
const handleSecondPoppertClickAway = (e) => {
setSecondPopperVisible(false);
};
return (
<div style={{ display: "flex", flexDirection: "row" }}>
<div>
<TextField
label="Outlined"
variant="outlined"
inputRef={firstTextFieldRef}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
edge="end"
onClick={toggleFirstPopperVisible}
>
<Visibility />
</IconButton>
</InputAdornment>
)
}}
/>
<ClickAwayListener onClickAway={handleFirstPopperClickAway}>
<Popper
open={firstPopperVisible}
anchorEl={firstTextFieldRef.current}
placement="bottom-start"
>
Content
</Popper>
</ClickAwayListener>
</div>
<div>
<TextField
label="Outlined"
variant="outlined"
inputRef={secondTextFieldRef}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
edge="end"
onClick={toggleSecondPopperVisible}
>
<Visibility />
</IconButton>
</InputAdornment>
)
}}
/>
<ClickAwayListener onClickAway={handleSecondPoppertClickAway}>
<Popper
open={secondPopperVisible}
anchorEl={secondTextFieldRef.current}
placement="bottom-start"
>
Content
</Popper>
</ClickAwayListener>
</div>
</div>
);
}
EDIT: Found a temporary solution by wrapping TextField into div and then wrapping tat ClickawayListener. Also prevented propagation on popper itself where needed. This is not ideal, but for my case it worked.
<ClickAwayListener onClickAway={handleFirstPopperClickAway}>
<div style={{display: inline-box}>
<TextField>
.....
</TextField>
</div>
</ClickawayListener>
<Popper>
....
</Popper>
UPDATED
Wrap the ClickAwayListener in a conditional statement:
{firstPopperVisible && (
<ClickAwayListener onClickAway={handleFirstPopperClickAway}>
<Popper open={firstPopperVisible} anchorEl={firstTextFieldRef.current} placement="bottom-start">
Content
</Popper>
</ClickAwayListener>
)}
...
{secondPopperVisible && (
<ClickAwayListener onClickAway={handleSecondPoppertClickAway}>
<Popper open={secondPopperVisible} anchorEl={secondTextFieldRef.current} placement="bottom-start">
Content
</Popper>
</ClickAwayListener>
)}
Codesandbox Demo
PREVIOUS
I recommend you look at Portals for this. Instead of having multiple elements in the dom, you have one that gets added where needed, as needed.
Portals provide a first-class way to render children into a DOM node
that exists outside the DOM hierarchy of the parent component.
Your single Portal component:
import ReactDOM from 'react-dom';
import React, { useEffect, useState } from 'react';
const Component = ({ content, handleCloseClick }) => {
return <div onClick={handleCloseClick}>{content}</div>;
};
interface PortalProps {
isShowing: boolean;
content: any;
location: any;
handleCloseClick: () => void;
}
const Portal = ({ isShowing, content, handleCloseClick, location }: PortalProps) => (isShowing ? ReactDOM.createPortal(<Component handleCloseClick={handleCloseClick} content={content} />, location.current) : null);
export default Portal;
Which is then used once in your main component:
import React, { useState, useRef } from 'react';
import Widget from './Widget';
import './styles.css';
export default function App() {
const [isShowing, setIsShowing] = useState<boolean>(false);
const [content, setContent] = useState<string>();
const buttonRef = useRef(null);
const handleClick = (e) => {
const { target } = e;
buttonRef.current = e.target.parentNode;
setContent(target.dataset.content);
setIsShowing(true);
};
const handleCloseClick = () => {
buttonRef.current = null;
setContent('');
setIsShowing(false);
};
return (
<div className="App">
<div>
<button onClick={handleClick} data-content={`content for one`}>
One
</button>
</div>
<div>
<button onClick={handleClick} data-content={`content for two`}>
Two
</button>
</div>
<Widget isShowing={isShowing} location={buttonRef} content={content} handleCloseClick={handleCloseClick} />
</div>
);
}
Codesandbox Demo

Conditionally show Tooltip depending on parent Popper component being open

I am trying to understand the best way of conditionally showing a Tooltip, based on if a sibling component Popper is open or not. I want to to show it by default on hover of its child the ButtonBase. I want the tooltip to never be open if the Popper is open. The tooltip title is acting as a summary of what's selected in the options list in the Popper when its closed, having it open with the Popper open is not ideal and cluttered. I am new to hooks so trying to understand how I can incorporate a hook to set the tooltipOpen state correctly with the conditional need.
export default function TooltipWithPopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
const [value, setValue] = React.useState([options[1], options[11]]);
const [pendingValue, setPendingValue] = React.useState([]);
const [tooltipOpen, setTooltipOpen] = React.useState(false);
const handleClick = (event) => {
setPendingValue(value);
setAnchorEl(event.currentTarget);
setTooltipOpen(false);
};
const handleClose = (event, reason) => {
if (reason === "toggleInput") {
return;
}
setValue(pendingValue);
if (anchorEl) {
anchorEl.focus();
}
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? "github-label" : undefined;
return (
<React.Fragment>
<div className={classes.root}>
<Tooltip title={value.map((i) => i.title).join(", ")}>
<ButtonBase
disableRipple
className={classes.button}
aria-describedby={id}
onClick={handleClick}
>
<span>Label</span>
{value.length}/{options.length}
</ButtonBase>
</Tooltip>
</div>
<Popper
id={id}
open={open}
anchorEl={anchorEl}
placement="bottom-start"
className={classes.popper}
>
<Autocomplete
open
onClose={handleClose}
multiple
classes={{
paper: classes.paper,
option: classes.option,
popperDisablePortal: classes.popperDisablePortal
}}
value={pendingValue}
onChange={(event, newValue) => {
setPendingValue(newValue);
}}
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No labels"
.....
/>
</Popper>
</React.Fragment>
);
}
Here is a demo of the tooltip being applied to the trigger element. How can I set it to only be open depending on another components' state? I've tried adding a setTooltipOpen(false) call when the handleClick is called when opens the Popper.
Demo: https://codesandbox.io/s/material-demo-forked-0wgyh?file=/demo.js:0-6181
You can control the Tooltip open prop value with your tooltipOpen state (implementation is up to you) and provide conditions that if the Popper is open, then automatically, the Tooltip open prop value computation will disregard the tooltipOpen state and assign false.
In my example below, I control the tooltipOpen state via onMouseEnter && onMouseLeave events
<Tooltip
open={open === true ? false : tooltipOpen}
title={value.map((i) => i.title).join(", ")}
>
<ButtonBase
disableRipple
className={classes.button}
aria-describedby={id}
onClick={handleClick}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
<span>Label</span>
{value.length}/{options.length}
</ButtonBase>
</Tooltip>

Material-ui: open menu by event hover

Currently the menuItem only opens your children after a click. Is there an attribute that I agree to open via Hover?
<MenuItem key={index}
menuItems={menuitems}
**onHover={true}**
>
menuItem
</MenuItem>
There is not specific attribute available through the material-ui library. However, you could create this yourself pretty easily using the onMouseOver event.
I've adapted the Simple Menu example from the material-ui site to show you how this can be done:
import React from 'react';
import Button from 'material-ui/Button';
import Menu, { MenuItem } from 'material-ui/Menu';
class SimpleMenu extends React.Component {
state = {
anchorEl: null,
open: false,
};
handleClick = event => {
this.setState({ open: true, anchorEl: event.currentTarget });
};
handleRequestClose = () => {
this.setState({ open: false });
};
render() {
return (
<div>
<Button
aria-owns={this.state.open ? 'simple-menu' : null}
aria-haspopup="true"
onClick={this.handleClick}
{ // The following line makes the menu open whenever the mouse passes over the menu }
onMouseOver={this.handleClick}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={this.state.anchorEl}
open={this.state.open}
onRequestClose={this.handleRequestClose}
>
<MenuItem onClick={this.handleRequestClose}>Profile</MenuItem>
<MenuItem onClick={this.handleRequestClose}>My account</MenuItem>
<MenuItem onClick={this.handleRequestClose}>Logout</MenuItem>
</Menu>
</div>
);
}
}
export default SimpleMenu;
I got it to work by upping the z-index of the button. Otherwise, the mouse technically goes out of the button when the modal appears on top of the button. Then the menu will close since the user is no longer hovering.
If you add onMouseLeave to Menu then onMouseLeave will only trigger if you go out of the browser. So instead, I added onMouseLeave to MuiList which doesn't take up the whole page.
I also added need some extra conditionals in the handleOpen to account for if the mouse leaves the button but enters the menu.
import React, { useState } from "react";
import Button from "#material-ui/core/Button";
import Menu from "#material-ui/core/Menu";
import { createMuiTheme, ThemeProvider } from "#material-ui/core/styles";
const theme = createMuiTheme({});
const MyMenu = () => {
const [anchorEl, setAnchorEl] = useState(null);
const [open, setOpen] = useState(false);
const handleOpen = (event) => {
setAnchorEl(event.currentTarget);
setOpen(true);
};
const handleClose = (e) => {
if (e.currentTarget.localName !== "ul") {
const menu = document.getElementById("simple-menu").children[2];
const menuBoundary = {
left: menu.offsetLeft,
top: e.currentTarget.offsetTop + e.currentTarget.offsetHeight,
right: menu.offsetLeft + menu.offsetWidth,
bottom: menu.offsetTop + menu.offsetHeight
};
if (
e.clientX >= menuBoundary.left &&
e.clientX <= menuBoundary.right &&
e.clientY <= menuBoundary.bottom &&
e.clientY >= menuBoundary.top
) {
return;
}
}
setOpen(false);
};
theme.props = {
MuiList: {
onMouseLeave: (e) => {
handleClose(e);
}
}
};
return (
<div>
<ThemeProvider theme={theme}>
<Button
id="menubutton1"
aria-owns={open ? "simple-menu" : null}
aria-haspopup="true"
onMouseOver={handleOpen}
onMouseLeave={handleClose}
style={{ zIndex: 1301 }}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={open}
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
transformOrigin={{
vertical: "top",
horizontal: "center"
}}
>
Menu
<br />
Items
</Menu>
</ThemeProvider>
</div>
);
};
export default MyMenu;
CodeSandbox
I have added mouseLeave listener on container div to close the menu, and mouseOver listener on menu button to open menu. This worked for me...
<div onMouseLeave={closeMenu}>
<button onMouseOver=(openMenu) />
<Menu />
<MenuItems />
</div>
This is how I did it:
https://codesandbox.io/s/mui-menu-hover-to-show-dropdown-iguukw?file=/src/TopMenu.tsx
I used on onMouseLeave and onMouseEnter events to control when to show and hide the dropdown menus.
I also used a string state to determine which dropdown menu should show. Only one dropdown menu should show at one moment of time.

Resources