Skip to content

Commit 63dbbc7

Browse files
authored
Use React Tippy for dropdowns and tooltips (#645)
1 parent ac8e1f2 commit 63dbbc7

File tree

9 files changed

+170
-144
lines changed

9 files changed

+170
-144
lines changed

package-lock.json

+33-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@
2727
"@babel/preset-typescript": "^7.21.0",
2828
"@babel/traverse": "^7.20.7",
2929
"@messageformat/core": "3.4.0",
30+
"@tippyjs/react": "^4.2.6",
3031
"babel-loader": "^9.1.2",
3132
"babel-plugin-import": "^1.13.6",
3233
"react": "18.3.1",
3334
"react-dom": "18.3.1",
34-
"react-tippy": "^1.4.0",
3535
"react-virtuoso": "^4.6.2"
3636
},
3737
"devDependencies": {
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,59 @@
1-
import React, { useEffect, useRef } from "react";
2-
3-
declare global {
4-
interface Window {
5-
Behaviour: any;
6-
}
7-
}
1+
import Tippy from "@tippyjs/react";
2+
import React, { useState } from "react";
3+
import Tooltip from "./tooltip";
84

95
/**
10-
* Provides a bridge between React and the Jenkins' dropdown component
6+
* A customized (and customizable) implementation of Tippy dropdowns
117
*/
128
export default function Dropdown({ items, disabled }: DropdownProps) {
13-
const buttonRef = useRef<HTMLButtonElement>(null);
14-
15-
useEffect(() => {
16-
if (!buttonRef.current) {
17-
return;
18-
}
19-
20-
const template = document.createElement("template");
21-
template.innerHTML = `
22-
<div class="jenkins-dropdown">
23-
${items
24-
.map(
25-
(item) => `
26-
<template data-dropdown-type="CUSTOM">
27-
<a class="jenkins-dropdown__item" href=${item.href}
28-
${item.target ? `target="${item.target}"` : ""}
29-
${item.download ? `download="${item.download}"` : ""}>
30-
<div class="jenkins-dropdown__item__icon">${item.icon}</div>
31-
${item.text}
32-
</a>
33-
</template>
34-
`,
35-
)
36-
.join("")}
37-
</div>
38-
`;
39-
buttonRef.current.parentNode?.appendChild(template);
40-
41-
window.Behaviour?.applySubtree(buttonRef.current.parentNode, true);
42-
}, []);
9+
const [visible, setVisible] = useState(false);
10+
const show = () => setVisible(true);
11+
const hide = () => setVisible(false);
4312

4413
return (
45-
<>
46-
<button
47-
className="jenkins-button jenkins-button--tertiary"
48-
type="button"
49-
data-dropdown="true"
50-
{...{ tooltip: "More actions" }}
51-
ref={buttonRef}
52-
disabled={disabled}
14+
<Tooltip content={"More actions"}>
15+
<Tippy
16+
theme="dropdown"
17+
duration={250}
18+
touch={true}
19+
visible={visible}
20+
animation="dropdown"
21+
onClickOutside={hide}
22+
interactive={true}
23+
offset={[0, 0]}
24+
placement="bottom-start"
25+
arrow={false}
26+
content={
27+
<div className="jenkins-dropdown">
28+
{items.map((item, index) => (
29+
<a
30+
key={index}
31+
className="jenkins-dropdown__item"
32+
href={item.href}
33+
target={item.target}
34+
download={item.download}
35+
>
36+
<div className="jenkins-dropdown__item__icon">{item.icon}</div>
37+
{item.text}
38+
</a>
39+
))}
40+
</div>
41+
}
5342
>
54-
<div className="jenkins-overflow-button__ellipsis">
55-
<span></span>
56-
<span></span>
57-
<span></span>
58-
</div>
59-
</button>
60-
</>
43+
<button
44+
className="jenkins-button jenkins-button--tertiary"
45+
type="button"
46+
disabled={disabled}
47+
onClick={visible ? hide : show}
48+
>
49+
<div className="jenkins-overflow-button__ellipsis">
50+
<span></span>
51+
<span></span>
52+
<span></span>
53+
</div>
54+
</button>
55+
</Tippy>
56+
</Tooltip>
6157
);
6258
}
6359

@@ -69,7 +65,7 @@ interface DropdownProps {
6965
interface DropdownItem {
7066
text: string;
7167
href?: string;
72-
icon: string;
68+
icon: React.ReactNode;
7369
target?: string;
7470
download?: string;
7571
}
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,23 @@
1-
import React, { useEffect, useRef } from "react";
2-
3-
declare global {
4-
interface Window {
5-
Behaviour: any;
6-
}
7-
}
1+
import Tippy, { TippyProps } from "@tippyjs/react";
2+
import React from "react";
83

94
/**
10-
* Provides a bridge between React and the Jenkins' tooltip component
5+
* A customized (and customizable) implementation of Tippy tooltips
116
*/
12-
export default function Tooltip({ text, children }: TooltipProps) {
13-
const ref = useRef<HTMLButtonElement>(null);
14-
15-
useEffect(() => {
16-
if (!ref.current) {
17-
return;
18-
}
19-
20-
window.Behaviour?.applySubtree(ref.current.parentNode, true);
21-
}, []);
7+
export default function Tooltip(props: TippyProps) {
8+
if (props.content === undefined) {
9+
return props.children;
10+
}
2211

2312
return (
24-
<span {...{ tooltip: text }} ref={ref}>
25-
{children}
26-
</span>
13+
<Tippy
14+
theme="tooltip"
15+
animation="tooltip"
16+
duration={250}
17+
touch={false}
18+
{...props}
19+
>
20+
{props.children}
21+
</Tippy>
2722
);
2823
}
29-
30-
interface TooltipProps {
31-
text: string;
32-
children: React.ReactNode;
33-
}

src/main/frontend/pipeline-console-view/pipeline-console/main/ConsoleLogCard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export default function ConsoleLogCard(props: ConsoleLogCardProps) {
118118
</div>
119119
</a>
120120

121-
<Tooltip text={"View step as plain text"}>
121+
<Tooltip content={"View step as plain text"}>
122122
<a
123123
href={`log?nodeId=${props.step.id}`}
124124
className={"jenkins-button jenkins-button--tertiary"}

src/main/frontend/pipeline-console-view/pipeline-console/main/pipeline-console.scss

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
@import "react-tippy/dist/tippy.css";
2-
31
.ansi-fg-0 {
42
color: var(--black, #333);
53
}

0 commit comments

Comments
 (0)