MUI Tooltip Superset — Console Tooltips
How and why did I create a wrapper for MUI base tooltips?
First of all, what is a tooltip? A tooltip is a message which appears when a cursor is positioned over an icon, image, hyperlink, or another element in a graphical user interface. Something like this.
Since we use the material UI library with the choreo console we already have a ready-made tooltip component that we can use to show tooltips. But there is a problem with that.
The Problem
If you look at the API reference you can see that it accepts a title
prop which basically shows the tooltip message (a string). But what if we need to show a code snippet or a paragraph with custom styles inside a single tooltip? We should be able to pass more props and should be more flexible. I’m talking about something like this:
So in this screenshot, we have a few different sections with different styles; A title, a content block, and a link with a divider in between. This is the problem in question. Let’s see how I solved this.
The Solution
Luckily the MUI tooltip component accepts a JSX fragment/ Node for the title prop! So what needed to be done is just build the node with custom styles and pass that to the base tooltip. So basically what it means is we are creating a superset component of the MUI base tooltip component (A component that includes all the base component features and then some).
The Design/Code
First I needed to finalize what custom props should be passed to the component so after going through the UI mockup I finalized these props.
In order to cater to these props, we need to extend the base tooltip component (because we should allow the user to use the base props as well). Therefore TooltipPropsExtended
interface was created extending TooltipProps
from the base class. It included the custom props I mentioned earlier.
interface TooltipPropsExtended extends TooltipProps {
darkTheme?: boolean;
heading?: string;
content?: string;
example?: string;
action?: { link: string; text: string };
disabled?: boolean;
}
Inside the custom component, I’ve destructed the props and added ...restProps
them so that the props inherited from the base class can be used as well.
const { children, heading, content, example, disabled, action, darkTheme, ...restProps } = props;
Oh, another thing I forgot to mention was that the tooltip is theme friendly as well, so there is a prop called darTheme
and it decides whether the tooltip should be in darker colors or lighter colors. To do that we need to override the root tooltip classes. That was done by conditional props inside the Tooltip base component. The classes are shown below:
tooltipLight: {
color: theme.palette.grey[600],
backgroundColor: theme.palette.common.white,
},
arrowLight: {
color: theme.palette.common.white,
},
tooltipDark: {
color: theme.palette.grey[100],
backgroundColor: theme.palette.grey[500],
},
arrowDark: {
color: theme.palette.grey[500],
},
})
Next up is the most important part, which is the custom node we are building with those custom props. So depending on whether the prop is there I have rendered a specific element with its own CSS class. It goes like this:
const tooltipComponent = (
<Box p={1}>
{heading && (
<Box mb={1}>
<Typography variant="h5">{heading}</Typography>
</Box>
)}
{content && <Typography variant="body2">{content}</Typography>}
{(action || example) && (
<Divider
className={clsx({
[classes.divider]: true,
[classes.dividerDark]: darkTheme,
[classes.dividerLight]: !darkTheme,
})}
/>
)}
{example && (
<Typography
className={clsx({
[classes.exampleContent]: true,
[classes.exampleContentDark]: darkTheme,
[classes.exampleContentLight]: !darkTheme,
})}
variant="h4"
>
Eg: {example}
</Typography>
)}
{action && (
<Link
href={action.link}
className={classes.buttonLink}
target="_blank"
rel="noreferrer"
>
{action.text}
</Link>
)}
</Box>
);
A few points I need to mention here is clsx
is a way to give multiple classes to a single component in a reader-friendly manner. Box
,Link
and Typography
are components that are available in MUI. The styles used here are as follows:
divider: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
buttonLink: {
color: theme.palette.primary.main,
cursor: 'pointer',
marginTop: theme.spacing(1.5),
textDecoration: 'none',
},
dividerDark: {
backgroundColor: theme.palette.grey[200],
},
dividerLight: {
backgroundColor: theme.palette.grey[200],
},
exampleContent: {
fontSize: 12,
fontWeight: 100,
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
exampleContentDark: {
color: theme.palette.grey[200],
},
exampleContentLight: {
color: theme.palette.grey[500],
},
})
Before passing the created node to the base tooltips the disabled prop is checked in order to hide/show the tooltip. That was handled like this:
if (disabled) {
return <>{children}</>;
}
childern
is the node we are wrapping the tooltip from. So finally in the return statement, we simply pass the node to the title
prop and that creates a beautiful fully customizable tooltip for the console.
return (
<>
{children && (
<TooltipBase
{...restProps}
classes={
darkTheme
? {
tooltip: baseClasses.tooltipDark,
arrow: baseClasses.arrowDark,
}
: {
tooltip: baseClasses.tooltipLight,
arrow: baseClasses.arrowLight,
}
}
interactive
title={tooltipComponent}
>
{children}
</TooltipBase>
)}
</>
)
So that’s it for the custom tooltip people. See you again with another fun tech blog soon. Peace!