Skip to content

Commit 0b4747e

Browse files
committed
Add tooltip
1 parent a2dd126 commit 0b4747e

File tree

3 files changed

+91
-7
lines changed

3 files changed

+91
-7
lines changed

site/src/modules/workspaces/WorkspaceTiming/Chart/Bar.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import type { Interpolation, Theme } from "@emotion/react";
1+
import { css } from "@emotion/css";
2+
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
3+
import Tooltip from "@mui/material/Tooltip";
24
import { forwardRef, type HTMLProps, type ReactNode } from "react";
35

46
export type BarColor = {
@@ -22,21 +24,30 @@ type BarProps = Omit<HTMLProps<HTMLDivElement>, "size" | "color"> & {
2224
* The X position of the bar component.
2325
*/
2426
x?: number;
27+
/**
28+
* The tooltip content for the bar.
29+
*/
30+
tooltip?: ReactNode;
2531
};
2632

2733
export const Bar = forwardRef<HTMLDivElement, BarProps>(
28-
({ color, width, afterLabel, children, x, ...htmlProps }, ref) => {
29-
return (
34+
({ color, width, afterLabel, children, x, tooltip, ...htmlProps }, ref) => {
35+
const theme = useTheme();
36+
const row = (
3037
<div
3138
ref={ref}
32-
css={[styles.root, { transform: `translateX(${x}px)` }]}
39+
css={[styles.row, { transform: `translateX(${x}px)` }]}
3340
{...htmlProps}
3441
>
3542
<button
3643
type="button"
3744
css={[
3845
styles.bar,
39-
{ width, backgroundColor: color?.fill, borderColor: color?.border },
46+
{
47+
width,
48+
backgroundColor: color?.fill,
49+
borderColor: color?.border,
50+
},
4051
]}
4152
disabled={htmlProps.disabled}
4253
aria-labelledby={htmlProps["aria-labelledby"]}
@@ -46,16 +57,37 @@ export const Bar = forwardRef<HTMLDivElement, BarProps>(
4657
{afterLabel}
4758
</div>
4859
);
60+
61+
if (tooltip) {
62+
return (
63+
<Tooltip
64+
placement="top-start"
65+
classes={{
66+
tooltip: css({
67+
backgroundColor: theme.palette.background.default,
68+
border: `1px solid ${theme.palette.divider}`,
69+
width: 220,
70+
}),
71+
}}
72+
title={tooltip}
73+
>
74+
{row}
75+
</Tooltip>
76+
);
77+
}
78+
79+
return row;
4980
},
5081
);
5182

5283
const styles = {
53-
root: {
84+
row: {
5485
// Stack children horizontally for adjacent labels
5586
display: "flex",
5687
alignItems: "center",
5788
width: "fit-content",
5889
gap: 8,
90+
cursor: "pointer",
5991
},
6092
bar: (theme) => ({
6193
border: "1px solid",

site/src/modules/workspaces/WorkspaceTiming/Chart/Chart.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Interpolation, Theme } from "@emotion/react";
22
import { XGrid } from "./XGrid";
33
import { XAxis } from "./XAxis";
4-
import type { FC } from "react";
4+
import type { FC, ReactNode } from "react";
55
import { TimingBlocks } from "./TimingBlocks";
66
import {
77
YAxis,
@@ -40,6 +40,7 @@ export type Timing = Duration & {
4040
*/
4141
visible?: boolean;
4242
color?: BarColor;
43+
tooltip?: ReactNode;
4344
};
4445

4546
// Extracts the 'startedAt' and 'endedAt' date fields from the main Timing type.
@@ -128,6 +129,7 @@ export const Chart: FC<ChartProps> = ({ data, onBarClick }) => {
128129
const size = calcSize(durationTime(t));
129130
return (
130131
<Bar
132+
tooltip={t.tooltip}
131133
color={t.color}
132134
key={t.label}
133135
x={calcSize(offset)}

site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type { Interpolation, Theme } from "@emotion/react";
1010
import ChevronRight from "@mui/icons-material/ChevronRight";
1111
import { YAxisSidePadding, YAxisWidth } from "./Chart/YAxis";
1212
import { SearchField } from "components/SearchField/SearchField";
13+
import { Link } from "react-router-dom";
14+
import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined";
1315

1416
// TODO: Export provisioning stages from the BE to the generated types.
1517
const provisioningStages = ["init", "plan", "graph", "apply"];
@@ -194,6 +196,7 @@ export const selectChartData = (
194196
visible: !isCoderResource,
195197
// Resource timings don't have inner timings
196198
childrenCount: 0,
199+
tooltip: <ProvisionerTooltip timing={t} />,
197200
...extractDuration(t),
198201
} as Timing;
199202
});
@@ -208,6 +211,19 @@ export const selectChartData = (
208211
}
209212
};
210213

214+
const ProvisionerTooltip: FC<{ timing: ProvisionerTiming }> = ({ timing }) => {
215+
return (
216+
<div css={styles.tooltip}>
217+
<span>{timing.source}</span>
218+
<span css={styles.tooltipResource}>{timing.resource}</span>
219+
<Link to="" css={styles.tooltipLink}>
220+
<OpenInNewOutlined />
221+
view template
222+
</Link>
223+
</div>
224+
);
225+
};
226+
211227
const styles = {
212228
panelBody: {
213229
display: "flex",
@@ -305,4 +321,38 @@ const styles = {
305321
border: `1px solid ${theme.palette.divider}`,
306322
backgroundColor: theme.palette.background.default,
307323
}),
324+
tooltip: (theme) => ({
325+
display: "flex",
326+
flexDirection: "column",
327+
fontWeight: 500,
328+
fontSize: 12,
329+
color: theme.palette.text.secondary,
330+
}),
331+
tooltipResource: (theme) => ({
332+
color: theme.palette.text.primary,
333+
fontWeight: 600,
334+
marginTop: 4,
335+
display: "block",
336+
maxWidth: "100%",
337+
overflow: "hidden",
338+
textOverflow: "ellipsis",
339+
whiteSpace: "nowrap",
340+
}),
341+
tooltipLink: (theme) => ({
342+
color: "inherit",
343+
textDecoration: "none",
344+
display: "flex",
345+
alignItems: "center",
346+
gap: 4,
347+
marginTop: 8,
348+
349+
"&:hover": {
350+
color: theme.palette.text.primary,
351+
},
352+
353+
"& svg": {
354+
width: 12,
355+
height: 12,
356+
},
357+
}),
308358
} satisfies Record<string, Interpolation<Theme>>;

0 commit comments

Comments
 (0)