Add a tooltip to MUI Badge content? - reactjs

I want to add a tooltip to my MUI Badge component.
I tried wrapping the badge with a ToolTip component from MUI but tooltip text also displays when the children are hovered, I'd like it to only appear when the Badge itself is hovered.
I have also tried using the primitive title prop on the badge component but this has the same issue.
Does anyone know of a better way to add a tooltip to a Badge component?
my usage:
<Badge
title={'Click to view more info'} // not ideal as the tooltip shows when the children are hovered too
badgeContent={getTotalVulnerabilitiesCount()}
showZero={false}
>
{children}
</Badge>

You're very close, badgeContent prop also accepts a ReactNode so you can put the Badge content inside a Tooltip without affecting the other component:
<Badge
color="primary"
badgeContent={
<Tooltip title="Delete">
<span>1</span>
</Tooltip>
}
>
<MailIcon color="action" />
</Badge>

I ended up building my own badge component, its not too long either so good solution imo. If anyone has feedback for the code please let me know :)
import React from 'react';
import { makeStyles, Tooltip } from '#material-ui/core';
const useStyles = makeStyles({
badgeStyles: {
minHeight: '24px',
minWidth: '24px',
position: 'absolute',
top: '-12px',
left: 'calc(100% - 12px)',
color: 'white',
borderRadius: '50%',
backgroundColor: 'tomato',
padding: '3px',
fontSize: '.75rem'
}
});
const Badge = props => {
const {
children,
showZero,
...badgeContentProps
} = props;
return (
<span>
{children}
{
(showZero || props.badgeContent !== 0) && (
<BadgeComponent {...badgeContentProps}/>
)
}
</span>
);
};
const BadgeComponent = props => {
const classes = useStyles();
const {
badgeContent,
badgeClasses,
onClick,
tooltipText,
tooltipPlacement
} = props;
// If no tooltiptext provided render without Tooltip
if(tooltipText == null) return (
<span
className = {`${badgeClasses ?? ''} ${classes.badgeStyles}`}
onClick={onClick ? onClick : undefined}
>
{badgeContent}
</span>
);
// Render with Tooltip
return (
<Tooltip title={tooltipText} placement={tooltipPlacement}>
<span
className = {`${badgeClasses} ${classes.notifyCount}`}
onClick={onClick ? onClick : undefined}
>
{badgeContent}
</span>
</Tooltip>
);
};
export default Badge;

Related

Using chakra-ui ToolTip with a floating button

I am attempting to create a floating "emergency exit" button for my React typescript application which will immediately take the user to weather.com. I'm having no trouble creating the button, but the requirements call for a tooltip when hovering over the button. Since we use chakra-ui throughout the product, using the Tooltip component they provide seems natural to me.
My first attempt looks like this:
Button.tsx
import React from "react";
import { Button as ChakraButton, ButtonProps } from "#chakra-ui/react";
interface Props extends ButtonProps {
buttonColor: string;
}
const Button: React.FC<Props> = ({
buttonColor,
children,
...restProps
}: Props) => (
<ChakraButton
backgroundColor={buttonColor}
color="white"
_hover={{
background: buttonColor
}}
_active={{
background: buttonColor
}}
padding="15px 30px"
height="auto"
fontSize="sm"
minWidth="200px"
borderRadius="100px"
fontFamily="AvenirBold"
{...restProps}
>
{children}
</ChakraButton>
);
export default Button;
EmergencyExitButton.tsx
import styled from "#emotion/styled";
import React from "react";
import Button from "./Button";
import { Tooltip } from "#chakra-ui/react";
const StyledButton = styled(Button)`
z-index: 99999;
position: fixed;
margin-left: calc(50% - 100px);
margin-top: 5px;
`;
export const EmergencyExitButton: React.FC = ({ children }) => {
const handleClick = () => {
window.open("https://weather.com", "_self");
};
return (
<>
<Tooltip
width="100%"
label="Immediately exit to the Weather Channel. Unsaved changes will be lost."
placement="bottom"
bg="black"
color="white"
>
<StyledButton buttonColor="#CC0000" onClick={handleClick}>
Emergency Exit
</StyledButton>
</Tooltip>
{children}
</>
);
};
When I insert this button into the application and hover over it, the tooltip appears in the top left corner of the screen and doesn't go away when you move the pointer away from the button. (codesandbox: https://codesandbox.io/s/objective-rain-z5szs7)
After consulting the chakra-ui documentation on Tooltip, I realized that I should be using a forwardRef for the wrapped component, so I modified EmergencyExitButton to look like this:
import * as React from "react";
import Button from "./Button";
import { Tooltip } from "#chakra-ui/react";
const EmergencyButton = React.forwardRef<HTMLDivElement>((props, ref) => {
const handleClick = () => {
window.open("https://weather.com", "_self");
};
return (
<div
ref={ref}
style={{
zIndex: 99999,
position: "fixed",
marginLeft: "calc(75% - 100px)",
marginTop: "5px"
}}
>
<Button buttonColor="#CC0000" onClick={handleClick}>
EmergencyExit
</Button>
</div>
);
});
EmergencyButton.displayName = "EmergencyButton";
export const EmergencyExitButton: React.FC = ({ children }) => (
<>
<Tooltip
width="100%"
label="Immediately exit to the Weather Channel. Unsaved changes will be lost."
placement="bottom"
bg="black"
color="white"
hasArrow
style={{ zIndex: 99999 }}
>
<EmergencyButton />
</Tooltip>
{children}
</>
);
In this iteration, the tooltip doesn't appear at all. (codesandbox: https://codesandbox.io/s/kind-voice-i230ku)
I would really appreciate any advice or ideas on how to make this work.
Edited to fix the code a little.
I figured it out. It turns out that instead of creating a forwardRef, I just needed to wrap the button in a span tag.
import React from 'react';
import Button from './Button';
import { Tooltip } from '#chakra-ui/react';
export const EmergencyExitButton: React.FC = ({ children }) => {
const handleClick = () => {
window.open('https://weather.com', '_self');
};
return (
<>
<Tooltip
width='100%'
label='Immediately exit to the Weather Channel. Unsaved changes will be lost.'
placement='bottom'
bg='black'
color='white'
>
<span style={{ zIndex: 99999, position: 'fixed', marginLeft: 'calc(50% - 100px)', marginTop: '5px'}}>
<Button buttonColor='#CC0000' onClick={handleClick}>Emergency Exit</Button>
</span>
</Tooltip>
{children}
</>
);
};
I think the prop shouldWrapChildren could be used in this case.
See https://chakra-ui.com/docs/components/tooltip/props

How can I put a hover on this Button Style?

These are my codes for the Button. Somehow, background color only works if I do declare the style in the Button itself. Stating the background color in the const useStyles does not work, hence, I only did this. How can I code this where the background color changes when it hover on it?
<Button
variant="contained"
{...otherProps}
className={classes.margin}
style={{ backgroundColor: " #e31837" }}
>
{children}
</Button>
you can use the root property to change your button style (docs), and also change its background on hover:
const useStyles = makeStyles((theme) => ({
root: {
backgroundColor: 'red',
'&:hover': {
backgroundColor: 'blue'
}
}
}));
<Button
variant="contained"
{...otherProps}
className={classes.root}
style={{ backgroundColor: " #e31837" }}
>
{children}
</Button>
You can add this to the styles:
'&:hover': {
backgroundColor: ‘red’
}
<Button
variant="contained"
{...otherProps}
className={classes.margin}
onMouseOver={this.toggleHover}
onMouseOut={this.toggleHover}
style={btnStyle}
>
{children}
</Button>
Then add a toggleHover function
toggleHover(){
this.setState({hover: !this.state.hover})
}
Finally on your render function set your style as a variable
let btnStyle = {'backgroundColor: #e31837'};
if (this.state.hover) {
btnStyle = {'backgroundColor: #000000'}
}
You can refer the following code snippet
import React from 'react';
import { createMuiTheme, withStyles, makeStyles, ThemeProvider } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import { green, purple } from '#material-ui/core/colors';
const ColorButton = withStyles((theme) => ({
root: {
color: theme.palette.getContrastText(purple[500]),
backgroundColor: purple[500],
'&:hover': {
backgroundColor: purple[700],
},
},
}))(Button);
export default function CustomizedButtons() {
const classes = useStyles();
return (
<div>
<ColorButton variant="contained" color="primary" className={classes.margin}>
Custom CSS
</ColorButton>
</div>
)
}

React Hooks useRef initialization issue, useRef only works on subsequent calls

I am implementing useRef into my project. I have a form that has clickable sections. Once clicked it opens the form. I'm using Reactstrap Collapse to show/hide the form. I need to be able to open the form and show the section that needs to be filled out, however the scrollIntoView once I click the section doesn't work until I open and close the form again. I'm stumped. I console.log(formRef), the ref returns as expected of the component that I want to be scrolled to the top of viewport on subsequent calls. My guess would be that the formRef is being initialized as null to begin with so initial calls to the ref do not work. However, once it knows the ref the subsequent calls work. I'm not sure how to go about this..
If I need to provide an example that is stripped please let me know. I am expecting this to be just an initialization issue.
Form
import React, { useRef, useContext, useEffect } from "react";
import {
FormQuestionsContext,
FormAnswersContext,
ExpandedSectionContext,
} from "../../Store";
import SectionHeader from "../SectionHeader";
import ImageUploader from "../CommentsSection";
import Ratings from "../Ratings";
import { Collapse, Button, CardBody, Card } from "reactstrap";
import FontAwesome from "react-fontawesome";
import styles from "./bedthreeform.module.css";
function BedThreeForm({ Name }) {
const formRef = useRef(null); //useRef Initialization
const [expandedSection, setExpandedSection] = useContext(
ExpandedSectionContext
);
const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
const [formAnswers, setFormAnswers] = useContext(FormAnswersContext);
const array = formQuestions.bedthree;
const onChange = (e, name) => {
const { value } = e.target;
setFormAnswers((state) => ({
...state,
[Name]: { ...state[Name], [name]: value },
}));
};
//! The function I use when I want to tell useRef to scrollIntoView
const handleOpen = () => {
expandedSection === Name
? setExpandedSection("")
: setExpandedSection(Name);
formRef.current.scrollIntoView();
};
const answeredQuestions = formAnswers.bedthree
? Object.keys(formAnswers.bedthree)
: null;
console.log(formRef);
return (
<div>
<Button
className={styles["CollapseBtn"]}
onClick={handleOpen} //Calling the function here
style={
answeredQuestions &&
answeredQuestions.length === formQuestions.bedthree.length
? {
color: "white",
":focus": {
backgroundColor: "#02BD43",
},
backgroundColor: "#02BD43",
marginBottom: "1rem",
width: "100%",
}
: answeredQuestions &&
answeredQuestions.length !== formQuestions.bedthree.length
? {
color: "white",
":focus": {
backgroundColor: "#bd0202",
},
backgroundColor: "#bd0202",
marginBottom: "1rem",
width: "100%",
}
: {
":focus": {
backgroundColor: "#fafafa",
},
marginBottom: "1rem",
width: "100%",
}
}
>
<p>BEDROOM #3 INSPECTION</p>
<FontAwesome
className="super-crazy-colors"
name="angle-up"
rotate={expandedSection === Name ? null : 180}
size="lg"
style={{
marginTop: "5px",
textShadow: "0 1px 0 rgba(0, 0, 0, 0.1)",
}}
/>
</Button>
<Collapse
className={styles["Collapse"]}
isOpen={expandedSection === Name}
>
<Card>
<CardBody>
{array ? (
<div>
<SectionHeader title="Bedroom #3 Inspection" name={Name} />
<div
ref={formRef}
className={styles["BedroomThreeFormWrapper"]}
id="bedroom-three-form"
>
{array.map((question, index) => {
const selected =
formAnswers[Name] && formAnswers[Name][question]
? formAnswers[Name][question]
: "";
return (
<div className={styles["CheckboxWrapper"]} key={index}>
<h5>{question}</h5>
<Ratings
section={Name}
question={question}
onChange={onChange}
selected={selected}
/>
</div>
);
})}
</div>
{!answeredQuestions ? (
""
) : (
<Button
onClick={(e) => e.preventDefault()}
style={
!answeredQuestions ||
(answeredQuestions &&
answeredQuestions.length !==
formQuestions.bedthree.length)
? {
backgroundColor: "#bd0202",
color: "white",
pointerEvents: "none",
}
: {
backgroundColor: "#02BD43",
color: "white",
pointerEvents: "none",
}
}
>
{!answeredQuestions ||
(answeredQuestions &&
answeredQuestions.length !==
formQuestions.bedthree.length)
? "Incomplete"
: "Complete"}
</Button>
)}
<br />
<ImageUploader name="bedthree" title={"Bedroom #3"} />
</div>
) : (
<div></div>
)}
</CardBody>
</Card>
</Collapse>
</div>
);
}
export default BedThreeForm;
CodeSandbox Stripped Form Doesn't work as expected, however that is the stripped code.
Update I'm open to suggestions to bypass this, or an alternative way to do this. I'm not sure why it only does it on subsequent calls.
Look at these lines:
<CardBody>
{array ? (
...
<div
ref={formRef}
...
This (virtual) dom will be evaluated only if array is defined. In case you would like to have your formRef always to point to the dom, then You'll have to strip it out from your condition.
I've figured out the issue, the issue is calling it when the content in the collapse hasn't been loaded yet, Reactstrap has an attribute onEntered which basically when set, will run the function as soon as the collapse has fully opened. The example that I found is here. Also, by setting the attribute innerRef on a Reactstrap component I can manipulate it just like I could a regular component using ref.

MUI - Remove overlay in Drawer?

I am trying to use the Drawer component and I have noticed that when the drawer is fed the prop open={true}, there is a default dimmed overlay on the underlying page / div.
Is there a best-practice Material-approved way to remove the dimming? I have had some partial success with setting the prop variant={"persistent"}. This stops the dimming, but it also forces me to add a close button just to close the drawer.
What I am looking for is the drawer to be closable when clicking outside its boundary, and while it is open I would like to have the dimming go away (without resorting to a button to close the drawer).
I have looked at the docs and tried passing the prop
variant={"persistent"}
Which gets rid of the overlay, but now when I click outside the drawer it doesn't auto-close.
<Drawer
open={open}
anchor="top"
onClose={toggleDrawer}
variant={"persistent"}
modal={true}
>
I would like to have the dimming go away (without resorting to a button).
Are there any options that are Material - approved? I can try CSS hacks but I don't want to break Material's CSS or have glitchy flashes of overlay.
You can add BackdropProps={{ invisible: true }}.
Working Example:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import Button from "#material-ui/core/Button";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import MailIcon from "#material-ui/icons/Mail";
const useStyles = makeStyles({
list: {
width: 250
}
});
export default function TemporaryDrawer() {
const classes = useStyles();
const [state, setState] = React.useState({
top: false,
left: false,
bottom: false,
right: false
});
const toggleDrawer = (side, open) => event => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
setState({ ...state, [side]: open });
};
const sideList = side => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer(side, false)}
onKeyDown={toggleDrawer(side, false)}
>
<List>
{["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
return (
<div>
<Button onClick={toggleDrawer("left", true)}>Open Left</Button>
<Drawer
BackdropProps={{ invisible: true }}
open={state.left}
onClose={toggleDrawer("left", false)}
>
{sideList("left")}
</Drawer>
</div>
);
}
Relevant documentation links:
https://material-ui.com/api/backdrop/#props
Documents the invisible prop
https://material-ui.com/api/modal/#props
Documents the BackdropProps prop of Modal
https://material-ui.com/api/drawer/#import
The props of the Modal component are available when variant="temporary" is set.
ModalProps={{
hideBackdrop: true,
}}
That solved the problem for me :)
It removes the whole backdrop thing and you are free to press whatever you want or the same button you opened it to close it :)
In later versions of Material UI you can just use the following:
<Drawer
hideBackdrop
>
For MUI 5+ Drawer (temporary):
<Drawer
PaperProps={{
style: {
//style props for your drawer menu here
},
}}
ModalProps={{
slots: { backdrop: "div" //override the backdrop component },
slotProps: {
root: { //override the fixed position + the size of backdrop
style: {
position: "absolute",
top: "unset",
bottom: "unset",
left: "unset",
right: "unset",
},
},
},
}}
>

How to add padding and margin to all Material-UI components?

I need to add padding or margin to some of Material-UI components, but could not find an easy way to do it. Can I add these properties to all components? something like this:
<Button color="default" padding={10} margin={5}>
I know that this is possible using pure CSS and classes but I want to do it the Material-UI way.
You can use de "Spacing" in a BOX component just by importing the component first:
import Box from '#material-ui/core/Box';
The Box component works as a "Wrapper" for the component you want to "Modify" the spacing.
then you can use the next properties on the component:
The space utility converts shorthand margin and padding props to margin and padding CSS declarations. The props are named using the format {property}{sides}.
Where property is one of:
m - for classes that set margin
p - for classes that set padding
Where sides is one of:
t - for classes that set margin-top or padding-top
b - for classes that set margin-bottom or padding-bottom
l - for classes that set margin-left or padding-left
r - for classes that set margin-right or padding-right
x - for classes that set both *-left and *-right
y - for classes that set both *-top and *-bottom
blank - for classes that set a margin or padding on all 4 sides of the element
as an example:
<Box m={2} pt={3}>
<Button color="default">
Your Text
</Button>
</Box>
Material-UI's styling solution uses JSS at its core. It's a high performance JS to CSS compiler which works at runtime and server-side.
import { withStyles} from '#material-ui/core/styles';
const styles = theme => ({
buttonPadding: {
padding: '30px',
},
});
function MyButtonComponent(props) {
const { classes } = props;
return (
<Button
variant="contained"
color="primary"
className={classes.buttonPadding}
>
My Button
</Button>
);
}
export default withStyles(styles)(MyButtonComponent);
You can inject styles with withStyle HOC into your component. This is how it works and it's very much optimized.
EDITED: To apply styles across all components you need to use createMuiTheme and wrap your component with MuiThemeprovider
const theme = createMuiTheme({
overrides: {
MuiButton: {
root: {
margin: "10px",
padding: "10px"
}
}
}
});
<MuiThemeProvider theme={theme}>
<Button variant="contained" color="primary">
Custom CSS
</Button>
<Button variant="contained" color="primary">
MuiThemeProvider
</Button>
<Button variant="contained" color="primary">
Bootstrap
</Button>
</MuiThemeProvider>
In Material-UI v5, one can change the button style using the sx props. You can see the margin/padding system properties and its equivalent CSS property here.
<Button sx={{ m: 2 }} variant="contained">
margin
</Button>
<Button sx={{ p: 2 }} variant="contained">
padding
</Button>
<Button sx={{ pt: 2 }} variant="contained">
padding top
</Button>
<Button sx={{ px: 2 }} variant="contained">
padding left, right
</Button>
<Button sx={{ my: 2 }} variant="contained">
margin top, bottom
</Button>
The property shorthands like m or p are optional if you want to quickly prototype your component, you can use normal CSS properties if you want your code more readable.
The code below is equivalent to the above but use CSS properties:
<Button sx={{ margin: 2 }} variant="contained">
margin
</Button>
<Button sx={{ padding: 2 }} variant="contained">
padding
</Button>
<Button sx={{ paddingTop: 2 }} variant="contained">
padding top
</Button>
<Button sx={{ paddingLeft: 3, paddingRight: 3 }} variant="contained">
padding left, right
</Button>
<Button sx={{ marginTop: 2, marginBottom: 2 }} variant="contained">
margin top, bottom
</Button>
Live Demo
import Box from '#material-ui/core/Box';
<Box m={1} p={2}>
<Button color="default">
Your Text
</Button>
</Box>
We can use makeStyles of material-ui to achieve this without using Box component.
Create a customSpacing function like below.
customSpacing.js
import { makeStyles } from "#material-ui/core";
const spacingMap = {
t: "Top", //marginTop
b: "Bottom",//marginBottom
l: "Left",//marginLeft
r: "Right",//marginRight
a: "", //margin (all around)
};
const Margin = (d, x) => {
const useStyles = makeStyles(() => ({
margin: () => {
// margin in x-axis(left/right both)
if (d === "x") {
return {
marginLeft: `${x}px`,
marginRight: `${x}px`
};
}
// margin in y-axis(top/bottom both)
if (d === "y") {
return {
marginTop: `${x}px`,
marginBottom: `${x}px`
};
}
return { [`margin${spacingMap[d]}`]: `${x}px` };
}
}));
const classes = useStyles();
const { margin } = classes;
return margin;
};
const Padding = (d, x) => {
const useStyles = makeStyles(() => ({
padding: () => {
if (d === "x") {
return {
paddingLeft: `${x}px`,
paddingRight: `${x}px`
};
}
if (d === "y") {
return {
paddingTop: `${x}px`,
paddingBottom: `${x}px`
};
}
return { [`padding${spacingMap[d]}`]: `${x}px` };
}
}));
const classes = useStyles();
const { padding } = classes;
return padding;
};
const customSpacing = () => {
return {
m: Margin,
p: Padding
};
};
export default customSpacing;
Now import above customSpacing function into your Component and use it like below.
App.js
import React from "react";
import "./styles.css";
import customSpacing from "./customSpacing";
const App = () => {
const { m, p } = customSpacing();
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2
style={{ background: "red" }}
className={`${m("x", 20)} ${p("x", 2)}`}
>
Start editing to see some magic happen!
</h2>
</div>
);
};
export default App;
click to open codesandbox
We can use makeStyles or styles props on the Typography component to give margin until version 4.0.
I highly recommend to use version 5.0 of material ui and on this version Typography is having margin props and it makes life easy.
Specific for "padding-top" (10px) using Global style
Read this!
import React from "react";
import { Container, makeStyles, Typography } from "#material-ui/core";
import { Home } from "#material-ui/icons";
const useStyles = makeStyles((theme) => ({
container: {
paddingTop: theme.spacing(10),
},
}));
const LeftBar = () => {
const classes = useStyles();
return (
<Container className={classes.container}>
<div className={classes.item}>
<Home className={classes.icon} />
<Typography className={classes.text}>Homepage</Typography>
</div>
</Container>
);
};
export default LeftBar;
<Button color="default" p=10px m='5px'>
set initial spacing first in the themeprovider i.e the tag enclosing you app entry. It should look like this
import { createMuiTheme } from '#material-ui/core/styles';
import purple from '#material-ui/core/colors/purple';
import green from '#material-ui/core/colors/green';
const theme = createMuiTheme({
palette: {
primary: {
main: purple[500],
},
secondary: {
main: green[500],
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<LandingPage />
</ThemeProvider>
);
}
that's it. so add the theme section to the code and use margin/padding as you wish
const theme = {
spacing: 8,
}
<Box m={-2} /> // margin: -16px;
<Box m={0} /> // margin: 0px;
<Box m={0.5} /> // margin: 4px;
<Box m={2} /> // margin: 16px;
you can use "margin" or "m" for short same applies to padding
or
const theme = {
spacing: value => value ** 2,
}
<Box m={0} /> // margin: 0px;
<Box m={2} /> // margin: 4px;
or
<Box m="2rem" /> // margin: 2rem;
<Box mx="auto" /> // margin-left: auto; margin-right: auto

Resources