diff --git a/app/assets/css/app.css b/app/assets/css/app.css
index 7f348fd09..95cdaeb73 100644
--- a/app/assets/css/app.css
+++ b/app/assets/css/app.css
@@ -129,25 +129,6 @@ a[ng-click] {
background-color: var(--bg-service-datatable-tbody);
}
-.tooltip.portainer-tooltip .tooltip-inner {
- font-family: Montserrat;
- background-color: var(--bg-tooltip-color);
- padding: 0.833em 1em;
- color: var(--text-tooltip-color);
- border: 1px solid var(--border-tooltip-color);
- border-radius: 0.14285714rem;
- box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15);
-}
-
-.tooltip.portainer-tooltip .tooltip-arrow {
- display: none;
-}
-
-.fa.tooltip-icon {
- margin-left: 5px;
- font-size: 1.3em;
-}
-
.fa.green-icon {
color: #23ae89;
}
diff --git a/app/portainer/components/Tooltip/Tooltip.css b/app/portainer/components/Tooltip/Tooltip.css
new file mode 100644
index 000000000..9c31167e7
--- /dev/null
+++ b/app/portainer/components/Tooltip/Tooltip.css
@@ -0,0 +1,18 @@
+.tooltip.portainer-tooltip .tooltip-inner {
+ font-family: Montserrat;
+ background-color: var(--bg-tooltip-color);
+ padding: 0.833em 1em;
+ color: var(--text-tooltip-color);
+ border: 1px solid var(--border-tooltip-color);
+ border-radius: 0.14285714rem;
+ box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15);
+}
+
+.tooltip.portainer-tooltip .tooltip-arrow {
+ display: none;
+}
+
+.fa.tooltip-icon {
+ margin-left: 5px;
+ font-size: 1.3em;
+}
diff --git a/app/portainer/components/Tooltip/Tooltip.module.css b/app/portainer/components/Tooltip/Tooltip.module.css
new file mode 100644
index 000000000..b4185ec4d
--- /dev/null
+++ b/app/portainer/components/Tooltip/Tooltip.module.css
@@ -0,0 +1,19 @@
+.tooltip-wrapper {
+ display: inline-block;
+ position: relative;
+}
+
+.tooltip {
+ font-family: Montserrat !important;
+ background-color: var(--bg-tooltip-color) !important;
+ padding: 0.833em 1em !important;
+ color: var(--text-tooltip-color) !important;
+ border: 1px solid var(--border-tooltip-color) !important;
+ border-radius: 0.14285714rem !important;
+ box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15) !important;
+}
+
+.icon {
+ margin-left: 5px;
+ font-size: 1.3em;
+}
diff --git a/app/portainer/components/Tooltip/Tooltip.stories.tsx b/app/portainer/components/Tooltip/Tooltip.stories.tsx
new file mode 100644
index 000000000..9c52e6f68
--- /dev/null
+++ b/app/portainer/components/Tooltip/Tooltip.stories.tsx
@@ -0,0 +1,23 @@
+import { Meta, Story } from '@storybook/react';
+
+import { Tooltip, Props } from './Tooltip';
+
+export default {
+ component: Tooltip,
+ title: 'Components/Tooltip',
+} as Meta;
+
+function Template({ message, position }: JSX.IntrinsicAttributes & Props) {
+ return (
+
+ Example tooltip
+
+
+ );
+}
+
+export const Primary: Story = Template.bind({});
+Primary.args = {
+ message: 'Tooltip example',
+ position: 'bottom',
+};
diff --git a/app/portainer/components/Tooltip/Tooltip.tsx b/app/portainer/components/Tooltip/Tooltip.tsx
new file mode 100644
index 000000000..f10cf83bd
--- /dev/null
+++ b/app/portainer/components/Tooltip/Tooltip.tsx
@@ -0,0 +1,31 @@
+import ReactTooltip from 'react-tooltip';
+import clsx from 'clsx';
+
+import styles from './Tooltip.module.css';
+
+type Place = 'top' | 'right' | 'bottom' | 'left';
+
+export interface Props {
+ position?: Place;
+ message: string;
+}
+
+export function Tooltip({ message, position = 'bottom' }: Props) {
+ return (
+
+
+
+
+ );
+}
diff --git a/app/portainer/components/Tooltip/TooltipAngular.ts b/app/portainer/components/Tooltip/TooltipAngular.ts
new file mode 100644
index 000000000..eb2113474
--- /dev/null
+++ b/app/portainer/components/Tooltip/TooltipAngular.ts
@@ -0,0 +1,21 @@
+import { IComponentOptions } from 'angular';
+import './Tooltip.css';
+
+export const TooltipAngular: IComponentOptions = {
+ bindings: {
+ message: '@',
+ position: '@',
+ },
+ template: `
+
+ `,
+};
diff --git a/app/portainer/components/Tooltip/index.ts b/app/portainer/components/Tooltip/index.ts
new file mode 100644
index 000000000..f6672ef55
--- /dev/null
+++ b/app/portainer/components/Tooltip/index.ts
@@ -0,0 +1,4 @@
+import { Tooltip } from './Tooltip';
+import { TooltipAngular } from './TooltipAngular';
+
+export { Tooltip, TooltipAngular };
diff --git a/app/portainer/components/index.js b/app/portainer/components/index.js
index efdbe10f3..a6da97272 100644
--- a/app/portainer/components/index.js
+++ b/app/portainer/components/index.js
@@ -6,7 +6,9 @@ import porAccessManagementModule from './accessManagement';
import formComponentsModule from './form-components';
import { ReactExampleAngular } from './ReactExample';
+import { TooltipAngular } from './Tooltip';
export default angular
.module('portainer.app.components', [sidebarModule, gitFormModule, porAccessManagementModule, formComponentsModule])
+ .component('portainerTooltip', TooltipAngular)
.component('reactExample', ReactExampleAngular).name;
diff --git a/app/portainer/components/tooltip.js b/app/portainer/components/tooltip.js
deleted file mode 100644
index 3c9c9e4c4..000000000
--- a/app/portainer/components/tooltip.js
+++ /dev/null
@@ -1,17 +0,0 @@
-angular.module('portainer.app').directive('portainerTooltip', [
- function portainerTooltip() {
- var directive = {
- scope: {
- message: '@',
- position: '@',
- customStyle: '',
- },
- template: `
-
-
- `,
- restrict: 'E',
- };
- return directive;
- },
-]);
diff --git a/package.json b/package.json
index bbdf0501a..452d2181d 100644
--- a/package.json
+++ b/package.json
@@ -106,6 +106,7 @@
"parse-duration": "^1.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
+ "react-tooltip": "^4.2.21",
"source-map-loader": "^1.1.2",
"spinkit": "^2.0.1",
"splitargs": "github:deviantony/splitargs#semver:~0.2.0",
diff --git a/yarn.lock b/yarn.lock
index 613c09b6d..ff6b30e77 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15568,6 +15568,14 @@ react-textarea-autosize@^8.3.0:
use-composed-ref "^1.0.0"
use-latest "^1.0.0"
+react-tooltip@^4.2.21:
+ version "4.2.21"
+ resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"
+ integrity sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==
+ dependencies:
+ prop-types "^15.7.2"
+ uuid "^7.0.3"
+
react@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@@ -18325,6 +18333,11 @@ uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
+ integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
+
v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"