Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7c4fbe5

Browse files
refactor(site): make HelpTooltip easier to reuse and compose (coder#11242)
1 parent f2606a7 commit 7c4fbe5

File tree

19 files changed

+330
-448
lines changed

19 files changed

+330
-448
lines changed

site/src/components/ActiveUserChart/ActiveUserChart.tsx

+11-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
HelpTooltip,
1818
HelpTooltipTitle,
1919
HelpTooltipText,
20+
HelpTooltipContent,
21+
HelpTooltipTrigger,
2022
} from "components/HelpTooltip/HelpTooltip";
2123
import dayjs from "dayjs";
2224
import { useTheme } from "@emotion/react";
@@ -139,12 +141,15 @@ export const ActiveUsersTitle: FC = () => {
139141
return (
140142
<div css={{ display: "flex", alignItems: "center", gap: 8 }}>
141143
Active Users
142-
<HelpTooltip size="small">
143-
<HelpTooltipTitle>How do we calculate active users?</HelpTooltipTitle>
144-
<HelpTooltipText>
145-
When a connection is initiated to a user&apos;s workspace they are
146-
considered an active user. e.g. apps, web terminal, SSH
147-
</HelpTooltipText>
144+
<HelpTooltip>
145+
<HelpTooltipTrigger size="small" />
146+
<HelpTooltipContent>
147+
<HelpTooltipTitle>How do we calculate active users?</HelpTooltipTitle>
148+
<HelpTooltipText>
149+
When a connection is initiated to a user&apos;s workspace they are
150+
considered an active user. e.g. apps, web terminal, SSH
151+
</HelpTooltipText>
152+
</HelpTooltipContent>
148153
</HelpTooltip>
149154
</div>
150155
);

site/src/components/HelpTooltip/HelpTooltip.tsx

+63-152
Original file line numberDiff line numberDiff line change
@@ -1,170 +1,107 @@
11
import Link from "@mui/material/Link";
2-
// This is used as base for the main HelpTooltip component
3-
// eslint-disable-next-line no-restricted-imports -- Read above
4-
import Popover, { type PopoverProps } from "@mui/material/Popover";
52
import HelpIcon from "@mui/icons-material/HelpOutline";
63
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
74
import {
8-
createContext,
9-
useContext,
10-
useRef,
11-
useState,
125
type FC,
136
type PropsWithChildren,
147
type HTMLAttributes,
158
type ReactNode,
9+
forwardRef,
10+
ComponentProps,
1611
} from "react";
1712
import { Stack } from "components/Stack/Stack";
18-
import type { CSSObject } from "@emotion/css";
13+
import { type CSSObject } from "@emotion/css";
1914
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
20-
import { type ClassName, useClassName } from "hooks/useClassName";
15+
import {
16+
Popover,
17+
PopoverContent,
18+
PopoverTrigger,
19+
usePopover,
20+
} from "components/Popover/Popover";
2121

2222
type Icon = typeof HelpIcon;
2323

2424
type Size = "small" | "medium";
2525

26-
export const HelpTooltipContext = createContext<
27-
{ open: boolean; onClose: () => void } | undefined
28-
>(undefined);
29-
30-
const useHelpTooltip = () => {
31-
const helpTooltipContext = useContext(HelpTooltipContext);
26+
export const HelpTooltipIcon = HelpIcon;
3227

33-
if (!helpTooltipContext) {
34-
throw new Error(
35-
"This hook should be used in side of the HelpTooltipContext.",
36-
);
37-
}
38-
39-
return helpTooltipContext;
28+
export const HelpTooltip: FC<ComponentProps<typeof Popover>> = (props) => {
29+
return <Popover mode="hover" {...props} />;
4030
};
4131

42-
interface HelpPopoverProps extends PopoverProps {
43-
onOpen: () => void;
44-
onClose: () => void;
45-
}
46-
47-
export const HelpPopover: FC<HelpPopoverProps> = ({
48-
onOpen,
49-
onClose,
50-
children,
51-
...props
52-
}) => {
53-
const popover = useClassName(classNames.popover, []);
54-
const paper = useClassName(classNames.paper, []);
32+
export const HelpTooltipContent = (
33+
props: ComponentProps<typeof PopoverContent>,
34+
) => {
35+
const theme = useTheme();
5536

5637
return (
57-
<Popover
58-
className={popover}
59-
classes={{ paper }}
60-
onClose={onClose}
61-
anchorOrigin={{
62-
vertical: "bottom",
63-
horizontal: "left",
64-
}}
65-
transformOrigin={{
66-
vertical: "top",
67-
horizontal: "left",
68-
}}
69-
PaperProps={{
70-
onMouseEnter: onOpen,
71-
onMouseLeave: onClose,
72-
}}
38+
<PopoverContent
7339
{...props}
74-
>
75-
{children}
76-
</Popover>
40+
css={{
41+
"& .MuiPaper-root": {
42+
fontSize: 14,
43+
width: 304,
44+
padding: 20,
45+
color: theme.palette.text.secondary,
46+
},
47+
}}
48+
/>
7749
);
7850
};
7951

80-
export interface HelpTooltipProps {
81-
// Useful to test on storybook
82-
open?: boolean;
52+
type HelpTooltipTriggerProps = HTMLAttributes<HTMLButtonElement> & {
8353
size?: Size;
84-
icon?: Icon;
85-
buttonStyles?: Interpolation<Theme>;
86-
iconStyles?: Interpolation<Theme>;
87-
children?: ReactNode;
88-
}
89-
90-
export const HelpTooltip: FC<HelpTooltipProps> = ({
91-
children,
92-
open = false,
93-
size = "medium",
94-
icon: Icon = HelpIcon,
95-
buttonStyles,
96-
iconStyles,
97-
}) => {
98-
const theme = useTheme();
99-
const anchorRef = useRef<HTMLButtonElement>(null);
100-
const [isOpen, setIsOpen] = useState(open);
101-
const id = isOpen ? "help-popover" : undefined;
54+
hoverEffect?: boolean;
55+
};
10256

103-
const onClose = () => {
104-
setIsOpen(false);
105-
};
57+
export const HelpTooltipTrigger = forwardRef<
58+
HTMLButtonElement,
59+
HelpTooltipTriggerProps
60+
>((props, ref) => {
61+
const {
62+
size = "medium",
63+
children = <HelpTooltipIcon />,
64+
hoverEffect = true,
65+
...buttonProps
66+
} = props;
67+
68+
const hoverEffectStyles = css({
69+
opacity: 0.5,
70+
"&:hover": {
71+
opacity: 0.75,
72+
},
73+
});
10674

10775
return (
108-
<>
76+
<PopoverTrigger>
10977
<button
110-
ref={anchorRef}
111-
aria-describedby={id}
78+
{...buttonProps}
79+
aria-label="More info"
80+
ref={ref}
11281
css={[
11382
css`
11483
display: flex;
11584
align-items: center;
11685
justify-content: center;
117-
width: ${theme.spacing(getButtonSpacingFromSize(size))};
118-
height: ${theme.spacing(getButtonSpacingFromSize(size))};
119-
padding: 0;
86+
padding: 4px 0;
12087
border: 0;
12188
background: transparent;
122-
color: ${theme.palette.text.primary};
123-
opacity: 0.5;
12489
cursor: pointer;
90+
color: inherit;
12591
126-
&:hover {
127-
opacity: 0.75;
92+
& svg {
93+
width: ${getIconSpacingFromSize(size)}px;
94+
height: ${getIconSpacingFromSize(size)}px;
12895
}
12996
`,
130-
buttonStyles,
97+
hoverEffect ? hoverEffectStyles : null,
13198
]}
132-
onClick={(event) => {
133-
event.stopPropagation();
134-
setIsOpen(true);
135-
}}
136-
onMouseEnter={() => {
137-
setIsOpen(true);
138-
}}
139-
onMouseLeave={() => {
140-
setIsOpen(false);
141-
}}
142-
aria-label="More info"
14399
>
144-
<Icon
145-
css={[
146-
{
147-
width: theme.spacing(getIconSpacingFromSize(size)),
148-
height: theme.spacing(getIconSpacingFromSize(size)),
149-
},
150-
iconStyles,
151-
]}
152-
/>
100+
{children}
153101
</button>
154-
<HelpPopover
155-
id={id}
156-
open={isOpen}
157-
anchorEl={anchorRef.current}
158-
onOpen={() => setIsOpen(true)}
159-
onClose={() => setIsOpen(false)}
160-
>
161-
<HelpTooltipContext.Provider value={{ open: isOpen, onClose }}>
162-
{children}
163-
</HelpTooltipContext.Provider>
164-
</HelpPopover>
165-
</>
102+
</PopoverTrigger>
166103
);
167-
};
104+
});
168105

169106
export const HelpTooltipTitle: FC<HTMLAttributes<HTMLHeadingElement>> = ({
170107
children,
@@ -213,7 +150,7 @@ export const HelpTooltipAction: FC<HelpTooltipActionProps> = ({
213150
onClick,
214151
ariaLabel,
215152
}) => {
216-
const tooltip = useHelpTooltip();
153+
const popover = usePopover();
217154

218155
return (
219156
<button
@@ -222,7 +159,7 @@ export const HelpTooltipAction: FC<HelpTooltipActionProps> = ({
222159
onClick={(event) => {
223160
event.stopPropagation();
224161
onClick();
225-
tooltip.onClose();
162+
popover.setIsOpen(false);
226163
}}
227164
>
228165
<Icon css={styles.actionIcon} />
@@ -239,42 +176,16 @@ export const HelpTooltipLinksGroup: FC<PropsWithChildren> = ({ children }) => {
239176
);
240177
};
241178

242-
const getButtonSpacingFromSize = (size?: Size): number => {
243-
switch (size) {
244-
case "small":
245-
return 2.5;
246-
case "medium":
247-
default:
248-
return 3;
249-
}
250-
};
251-
252179
const getIconSpacingFromSize = (size?: Size): number => {
253180
switch (size) {
254181
case "small":
255-
return 1.5;
182+
return 12;
256183
case "medium":
257184
default:
258-
return 2;
185+
return 16;
259186
}
260187
};
261188

262-
const classNames = {
263-
popover: (css) => css`
264-
pointer-events: none;
265-
`,
266-
267-
paper: (css, theme) => css`
268-
${theme.typography.body2 as CSSObject}
269-
270-
margin-top: 4px;
271-
width: 304px;
272-
padding: 20px;
273-
color: ${theme.palette.text.secondary};
274-
pointer-events: auto;
275-
`,
276-
} satisfies Record<string, ClassName>;
277-
278189
const styles = {
279190
title: (theme) => ({
280191
marginTop: 0,

site/src/components/InfoTooltip/InfoTooltip.tsx

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { type FC, type ReactNode } from "react";
22
import {
33
HelpTooltip,
4+
HelpTooltipContent,
5+
HelpTooltipIcon,
46
HelpTooltipText,
57
HelpTooltipTitle,
8+
HelpTooltipTrigger,
69
} from "components/HelpTooltip/HelpTooltip";
7-
import InfoIcon from "@mui/icons-material/InfoOutlined";
810
import { Interpolation, Theme, css, useTheme } from "@emotion/react";
911

1012
interface InfoTooltipProps {
@@ -22,14 +24,16 @@ export const InfoTooltip: FC<InfoTooltipProps> = ({
2224
const theme = useTheme();
2325

2426
return (
25-
<HelpTooltip
26-
size="small"
27-
icon={InfoIcon}
28-
iconStyles={{ color: theme.experimental.roles[type].outline }}
29-
buttonStyles={styles.button}
30-
>
31-
<HelpTooltipTitle>{title}</HelpTooltipTitle>
32-
<HelpTooltipText>{message}</HelpTooltipText>
27+
<HelpTooltip>
28+
<HelpTooltipTrigger size="small" css={styles.button}>
29+
<HelpTooltipIcon
30+
css={{ color: theme.experimental.roles[type].outline }}
31+
/>
32+
</HelpTooltipTrigger>
33+
<HelpTooltipContent>
34+
<HelpTooltipTitle>{title}</HelpTooltipTitle>
35+
<HelpTooltipText>{message}</HelpTooltipText>
36+
</HelpTooltipContent>
3337
</HelpTooltip>
3438
);
3539
};

site/src/components/Popover/Popover.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
useId,
1010
useRef,
1111
useState,
12+
HTMLAttributes,
1213
} from "react";
1314
// This is used as base for the main Popover component
1415
// eslint-disable-next-line no-restricted-imports -- Read above
@@ -79,8 +80,11 @@ export const usePopover = () => {
7980
return context;
8081
};
8182

82-
export const PopoverTrigger = (props: { children: TriggerElement }) => {
83+
export const PopoverTrigger = (
84+
props: HTMLAttributes<HTMLElement> & { children: TriggerElement },
85+
) => {
8386
const popover = usePopover();
87+
const { children, ...elementProps } = props;
8488

8589
const clickProps = {
8690
onClick: () => {
@@ -98,6 +102,7 @@ export const PopoverTrigger = (props: { children: TriggerElement }) => {
98102
};
99103

100104
return cloneElement(props.children, {
105+
...elementProps,
101106
...(popover.mode === "click" ? clickProps : hoverProps),
102107
"aria-haspopup": true,
103108
"aria-owns": popover.isOpen ? popover.id : undefined,

0 commit comments

Comments
 (0)