How to make a button inside Material-UI tooltip's title - javascript

I was able to pass Material-UI's IconButton inside Material-UI's Tooltip title.
All the views are working properly, except I cannot press the button.
I want to add a Close button to make the tooltip close on clicking the close button, but I cannot do that. Is there a way to do this? If yes, please provide a solution to this.

MUI's Tooltip has pointer-events set to none by default, so you need to enable this. Add a class to your tooltip and then add this to the css:
pointer-events: auto;

I solved it by using Popper from Material-UI.

In case someone looking for this, here are some useful snippet for it.
import * as React from 'react';
import {Tooltip, Typography} from '#mui/material'
import CloseIcon from '#mui/icons-material/Close'
export const TooltipWithIcon = (title, message) => {
const [open, setOpen] = useState(false)
const handleClose = e => {
if (open) {
setOpen(false)
}
return null
}
return(
<Tooltip
open={open}
title={
<>
<CloseIcon sx={{ float: 'right' }} onClick={e => handleClose(e)}></CloseIcon>
<Typography variant="h6" sx={{ fontWeight: 700 }}>
{title}
</Typography>
<Typography variant="body1" sx={{ fontSize: 14 }}>
{message}
</Typography>
</>
}
>
</Tooltip>
)
}

Related

Cannot read property 'main' of undefined in MUI v5

I was following the tutorial to add page pagination for my Table and now I have this error in my browser :
Uncaught TypeError: Cannot read property 'main' of undefined
at ButtonRoot.ownerState.ownerState (Button.js:80)
at transformedStyleArg (createStyled.js:189)
at handleInterpolation (emotion-serialize.browser.esm.js:137)
at serializeStyles (emotion-serialize.browser.esm.js:262)
at emotion-styled-base.browser.esm.js:131
at emotion-element-cbed451f.browser.esm.js:36
at renderWithHooks (react-dom.development.js:14985)
at updateForwardRef (react-dom.development.js:17044)
at beginWork (react-dom.development.js:19098)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
Which completely prevents me from displaying my page.
I know there are big style changes from mui v4 to mui v5, I managed to "dodge" them by using simple CSS. So I don't understand my mistake at all. Especially since the error seems to be located in a "ButtonRoot" ? :
backgroundColor: alpha(theme.palette[ownerState.color].main, theme.palette.action.hoverOpacity),
So here is my code where I use a "theme" (I normally have the same code as the tutorial) :
import { useTheme } from '#mui/material';
import LastPageOutlinedIcon from "#mui/icons-material/LastPageOutlined";
import FirstPageIcon from "#mui/icons-material/FirstPage";
import KeyboardArrowLeftIcon from "#mui/icons-material/KeyboardArrowLeft";
import KeyboardArrowRightIcon from "#mui/icons-material/KeyboardArrowRight";
function TablePaginationActions(props) {
const theme = useTheme();
const { count, page, rowsPerPage, onPageChange } = props;
const handleFirstPageButtonClick = (event) => {
onPageChange(event, 0);
};
const handleBackButtonClick = (event) => {
onPageChange(event, page - 1);
};
const handleNextButtonClick = (event) => {
onPageChange(event, page + 1);
};
const handleLastPageButtonClick = (event) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
};
return (
<div style={{ flexShrink: 0, marginLeft: 2.5 }}>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="Première page"
>
{theme.direction === "rtl" ? (
<LastPageOutlinedIcon />
) : (
<FirstPageIcon />
)}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="Page précédente"
>
{theme.direction === "rtl" ? (
<KeyboardArrowRightIcon />
) : (
<KeyboardArrowLeftIcon />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="Page suivante"
>
{theme.direction === "rtl" ? (
<KeyboardArrowLeftIcon />
) : (
<KeyboardArrowRightIcon />
)}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="Dernière page"
>
{theme.direction === "rtl" ? (
<FirstPageIcon />
) : (
<LastPageOutlinedIcon />
)}
</IconButton>
</div>
);
}
export default function Importation() {
// Pagination
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
TablePaginationActions.propTypes = {
count: PropTypes.number.isRequired,
onPageChange: PropTypes.func.isRequired,
page: PropTypes.number.isRequired,
rowsPerPage: PropTypes.number.isRequired,
};
// Permet de changer de page
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
return (
<Grid
container
style={{ width: "100%", minHeight: "90vh" }}
{...getRootProps()}
>
<TablePagination
component="div"
rowsPerPageOptions={[]}
count={fichiers.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
ActionsComponent={TablePaginationActions}
/>
</Grid>
);
}
Note : It's note the full page (+1000 lines) but I think my problem comes from this "theme" style.
Finally, the code of the tutorial, on which I base myself :
https://codesandbox.io/s/ccw8hm?file=/demo.js
Hopefully this solves your issue as it did mine, but I believe it has to do with passing custom colors to the Button or IconButton component as described here. The error returned is the same that you are describing.
https://github.com/mui/material-ui/issues/33054
Essentially the problem is that some components dropped support for the 'default' color that was available in v4. In v5 they state they want to align with the material spec and no longer include the 'default' grey color because it isn't part of the material design guidelines. This has caused a major headache for me, since I used these default/grey buttons and other components extensively when making proper dark/light themes.
You would need to go through your code and ensure that there are no <Button color="default"/> instances and if there is, change the color to something thats supported per the docs, like primary or secondary for example. There are some components that still support 'default' like AppBar, Fab and IconButton, but not Button.
As a quick fix, you can update/create your custom MUI theme and add a default color, but it may end up looking weird, because it doesn't cover all the variants that v4 had (Here's a thread about replicating the color fully).
import { createTheme } from '#mui/material/styles';
import { grey } from "#mui/material/colors";
export const lightTheme = createTheme({
palette: {
mode: 'light',
default: {
main: grey[300],
dark: grey[400]
},
}
});
check this link You cannot customize themes in Material UI by just declaring them in your code, you have to make the style components for the every component you used by importing styled from MUI, in your code use sx={{}} instead of style={{}} for better reliability.

Raise Header's Z-Index Over Material UI Modal Backdrop

I'm building a mobile version of a web app using Material UI and having trouble matching designs. I'm using MUI App-Bar together with MUI Modal to create something similar to the mockups shown below.
The expected behavior is that the user selects the button in the top right of the header to open the modal, and has the option to use the top right button again to close the modal. The user should also be able to select My App logo in the header to navigate to the home page.
The actual behavior is that the header bar is covered by the modal's backdrop; I can add styling like marginTop: 150px to the backdrop to visually achieve the expected result, but the button and the logo are still not usable.
Is there a way to override the backdrop component so that the header will be usable?
const useStyles = makeStyles((theme: Theme) => ({
card: {
position: "absolute",
top: "160px",
width: "90vw",
borderTopLeftRadius: "0px",
borderTopRightRadius: "0px",
},
backDrop: {
marginTop: "160px",
},
}));
const Header = (props) => {
const classes = useStyles();
const [menuOpen, setMenuOpen] = React.useState(false);
const handleOpenUserMenu = () => {
setMenuOpen(true);
};
const handleCloseUserMenu = () => {
setMenuOpen(false);
};
return (
<AppBar
position="static"
style={{
backgroundColor: "#FFFFFF",
zIndex: -1,
}}
>
<Container maxWidth="xl">
<Toolbar>
<Box sx={{ flexGrow: 1 }}>
<Link to={"/home"}>
<Logo className={classes.logo} />
</Link>
</Box>
<Box sx={{ flexGrow: 0 }}>
<IconButton onClick={handleOpenUserMenu}>
<MenuIcon />
</IconButton>
<Modal
open={menuOpen}
onClose={onClose}
BackdropProps={{ classes: { root: classes.backDrop } }}
>
<Card className={classes.card}>
<CardContent>
<MenuItems menuOptions={menuOptions} />
</CardContent>
</Card>
</Modal>
</Box>
</Toolbar>
</Container>
</AppBar>
);
};
As stated in MUIModal documentation the modal component is supposed to behave like that I would try to use menu component instead
Quoting documentation:
If you are creating a modal dialog, you probably want to use the Dialog component rather than directly using Modal. Modal is a lower-level construct that is leveraged by the following components:
Dialog
Drawer
Menu <--
Popover

MUI breakpoints not recognizing "theme.breakpoints.down"

Goal: Hide navMenu when breakpoint is tablet and under, so I can replace with a hamburger menu
In terminal in VS code, it says compiled successfully, but in the browser I see:
TypeError: Cannot read properties of undefined (reading 'down')
I tried instructions here: StackOverflow Question, with no luck.
Can someone point me in the right direction?
import AppBar from "#mui/material/AppBar";
import Box from "#mui/material/Box";
import Toolbar from "#mui/material/Toolbar";
import Typography from "#mui/material/Typography";
import Button from "#mui/material/Button";
import { makeStyles } from "#mui/styles";
const useStyles = makeStyles((theme) => ({
navMenu: {
[theme.breakpoints.down('md')]: {
display: "none",
},
},
}));
const Navbar = () => {
const classes = useStyles();
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static" style={{ backgroundColor: "#061B2E" }}>
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Name
</Typography>
<Box className={classes.navMenu}>
<Button color="inherit">Item 1</Button>
<Button color="inherit">Item 2</Button>
<Button color="inherit">Item 3</Button>
<Button color="inherit">Item 4</Button>
</Box>
</Toolbar>
</AppBar>
</Box>
);
};
export default Navbar;
Great question, which version of MUI are you using? They're kind of shifting away from makeSyles in favor of styled components, but this method is still supported (we still use it exclusively on my team). You may need to change your import statement to import { makeStyles } from '#material-ui/core';

Change component prop through css hover in react-materialui

I am trying to change the noWrap property of a Typography element upon hover. I have declared a custom CSS class for the parent element and the hover does work. But I have no idea how to alter the noWrap property through CSS now.
I have tried this (the hover works):
const useStyles = makeStyles((theme) => ({
paper: {
padding: theme.spacing(2),
"&:hover .questionText": {
noWrap: true
}
}
}));
And my JSX:
return(
<Paper className={classes.paper}>
<Typography className="questionText" noWrap={false} gutterBottom variant="body2">test</Typography>
</Paper>);
Thanks for your help
I'd toggle the boolean flag for your noWrap attribute via setState (or state hooks) using a js mouseover. Something like -
const [ wrap, setWrap ] = useState(false);
function toggleWrap() {
setWrap(!wrap);
}
return(
<Paper ...>
<Typography noWrap={wrap} onMouseOver={()=>toggleWrap()} ... />
</Paper>
);
Setting noWrap={true} props in Typography will apply white-space: nowrap styles to your component. This is how you do it using jss:
const useStyles = makeStyles((theme) => ({
paper: {
padding: theme.spacing(2),
"&:hover .questionText": {
whiteSpace: "nowrap",
}
}
}));
Live Demo
Thanks to Ozrix, I have solved my problem with the following:
const [wrap, setWrap] = useState(false);
and
<Paper className={classes.paper} onMouseEnter={() => {setWrap(true)}} onMouseLeave={() => {setWrap(false)}}>
<Typography noWrap={wrap} gutterBottom variant="body2">
</Paper>

React Material-UI override popover completely

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>
);
}

Categories

Resources