Skip to content

Commit

Permalink
Refactor tooltip theming
Browse files Browse the repository at this point in the history
Copying the styling and `overridesResolver` from MUIs TooltipPopper is
required because when overriding the Tooltips Popper we cannot extend
the existing Popper.

Overriding the Popper is required to implement our variant-specific
styling, as we cannot set the styles from the Root element, as the
Popper is not rendered as a child of the Tooltip in the DOM.
  • Loading branch information
jamesricky committed Nov 16, 2023
1 parent b5753e6 commit 7287c1e
Showing 1 changed file with 172 additions and 60 deletions.
232 changes: 172 additions & 60 deletions packages/admin/admin/src/common/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,166 @@
import { ClickAwayListener, ComponentsOverrides, Theme, Tooltip as MuiTooltip, tooltipClasses, TooltipProps as MuiTooltipProps } from "@mui/material";
import { createStyles, WithStyles, withStyles } from "@mui/styles";
import {
ClickAwayListener,
ComponentsOverrides,
Popper as MuiPopper,
Theme,
Tooltip as MuiTooltip,
tooltipClasses,
TooltipClassKey as MuiTooltipClassKey,
TooltipProps as MuiTooltipProps,
} from "@mui/material";
import { css, styled, SxProps, useThemeProps } from "@mui/material/styles";
import React, { cloneElement } from "react";

export interface TooltipProps extends MuiTooltipProps {
trigger?: "hover" | "focus" | "click";
variant?: Variant;
sx?: SxProps<Theme>;
}

type Variant = "light" | "dark" | "neutral" | "primary";

export type TooltipClassKey = Variant;
export type TooltipClassKey = "root" | Variant | MuiTooltipClassKey;

const styles = (theme: Theme) =>
createStyles<TooltipClassKey, TooltipProps>({
light: {
[`& .${tooltipClasses.arrow}`]: {
color: theme.palette.common.white,
},
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.common.white,
color: theme.palette.common.black,
boxShadow: theme.shadows[1],
},
},
dark: {
[`& .${tooltipClasses.arrow}`]: {
color: theme.palette.grey[900],
},
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.grey[900],
color: theme.palette.common.white,
boxShadow: theme.shadows[1],
},
},
neutral: {
[`& .${tooltipClasses.arrow}`]: {
color: theme.palette.grey[100],
},
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.grey[100],
color: theme.palette.common.black,
},
},
primary: {
[`& .${tooltipClasses.arrow}`]: {
color: theme.palette.primary.light,
},
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.primary.light,
color: theme.palette.common.black,
},
},
});
type TooltipPopperProps = {
variant: Variant;
ownerState: MuiTooltipProps;
};

const TooltipRoot = styled(MuiTooltip, {
name: "CometAdminTooltip",
slot: "Root",
overridesResolver(_, styles) {
return [styles.root];
},
})();

const TooltipPopper = styled(MuiPopper, {
name: "CometAdminTooltip",
slot: "Popper",
overridesResolver({ variant, ownerState }: TooltipPopperProps, styles) {
return [
styles.popper,
styles[variant],
// Copied the following from MUIs default TooltipPopper: https://github.com/mui/material-ui/blob/a13c0c026692aafc303756998a78f1d6c2dd707d/packages/mui-material/src/Tooltip/Tooltip.js#L48
!ownerState.disableInteractive && styles.popperInteractive,
ownerState.arrow && styles.popperArrow,
!ownerState.open && styles.popperClose,
];
},
})<TooltipPopperProps>(
({ theme, variant, ownerState }) => css`
${variant === "light" &&
css`
.${tooltipClasses.arrow} {
color: ${theme.palette.common.white};
}
.${tooltipClasses.tooltip} {
background-color: ${theme.palette.common.white};
color: ${theme.palette.common.black};
box-shadow: ${theme.shadows[1]};
}
`}
${variant === "dark" &&
css`
.${tooltipClasses.arrow} {
color: ${theme.palette.grey[900]};
}
.${tooltipClasses.tooltip} {
background-color: ${theme.palette.grey[900]};
color: ${theme.palette.common.white};
box-shadow: ${theme.shadows[1]};
}
`}
${variant === "neutral" &&
css`
.${tooltipClasses.arrow} {
color: ${theme.palette.grey[100]};
}
.${tooltipClasses.tooltip} {
background-color: ${theme.palette.grey[100]};
color: ${theme.palette.common.black};
}
`}
${variant === "primary" &&
css`
.${tooltipClasses.arrow} {
color: ${theme.palette.primary.light};
}
.${tooltipClasses.tooltip} {
background-color: ${theme.palette.primary.light};
color: ${theme.palette.common.black};
}
`};
// Copied the following from MUIs default TooltipPopper: https://github.com/mui/material-ui/blob/a13c0c026692aafc303756998a78f1d6c2dd707d/packages/mui-material/src/Tooltip/Tooltip.js#L55
z-index: ${theme.zIndex.tooltip};
pointer-events: none;
${!ownerState.disableInteractive &&
css`
pointer-events: auto;
`};
${!ownerState.open &&
css`
pointer-events: none;
`};
${ownerState.arrow &&
css`
&[data-popper-placement*="bottom"] .${tooltipClasses.arrow} {
top: 0;
margin-top: -0.71em;
&::before {
transform-origin: 0 100%;
}
}
&[data-popper-placement*="top"] .${tooltipClasses.arrow} {
bottom: 0;
margin-bottom: -0.71em;
&::before {
transform-origin: 100% 0;
}
}
&[data-popper-placement*="right"] .${tooltipClasses.arrow} {
${!ownerState.isRtl
? css`
left: 0;
margin-left: -0.71em;
`
: css`
right: 0;
margin-right: -0.71em;
`}
height: 1em;
width: 0.71em;
&::before {
transform-origin: 100% 100%;
}
}
&[data-popper-placement*="left"] .${tooltipClasses.arrow} {
${!ownerState.isRtl
? css`
right: 0;
margin-right: -0.71em;
`
: css`
left: 0;
margin-left: -0.71em;
`}
height: 1em;
width: 0.71em;
&::before {
transform-origin: 0 0;
}
}
`};
`,
);

export const Tooltip = (inProps: TooltipProps): JSX.Element => {
const { trigger = "hover", variant = "dark", children, sx, ...props } = useThemeProps({ props: inProps, name: "CometAdminTooltip" });

const Tooltip = ({ trigger = "hover", variant = "dark", children, classes, ...props }: TooltipProps & WithStyles<typeof styles>): JSX.Element => {
const [open, setOpen] = React.useState(false);

const handleTooltipClose = () => {
Expand All @@ -64,36 +172,40 @@ const Tooltip = ({ trigger = "hover", variant = "dark", children, classes, ...pr
setOpen(!open);
};

const commonTooltipProps = {
sx,
variant,
slots: {
popper: TooltipPopper,
},
slotProps: {
popper: {
variant,
},
},
...props,
};

return trigger === "click" ? (
<ClickAwayListener onClickAway={handleTooltipClose}>
<MuiTooltip
classes={{ popper: classes[variant] }}
<TooltipRoot
onClose={handleTooltipClose}
open={open}
disableFocusListener
disableHoverListener
disableTouchListener
{...props}
{...commonTooltipProps}
>
{cloneElement(children, { onClick: toggleTooltip })}
</MuiTooltip>
</TooltipRoot>
</ClickAwayListener>
) : (
<MuiTooltip
classes={{ popper: classes[variant] }}
disableFocusListener={trigger === "hover"}
disableHoverListener={trigger === "focus"}
{...props}
>
<TooltipRoot disableFocusListener={trigger === "hover"} disableHoverListener={trigger === "focus"} {...commonTooltipProps}>
{children}
</MuiTooltip>
</TooltipRoot>
);
};

const TooltipWithStyles = withStyles(styles, { name: "CometAdminTooltip" })(Tooltip);

export { TooltipWithStyles as Tooltip };

declare module "@mui/material/styles" {
interface ComponentsPropsList {
CometAdminTooltip: Partial<TooltipProps>;
Expand Down

0 comments on commit 7287c1e

Please sign in to comment.