fix(ui): tooltip stays open on hover [EE-3445] (#8051)

pull/8160/head
Prabhat Khera 2022-12-05 09:47:43 +13:00 committed by GitHub
parent c173888b64
commit cbaba43842
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 191 additions and 177 deletions

View File

@ -398,3 +398,50 @@ input:-webkit-autofill {
border-bottom-right-radius: 8px;
padding: 5px;
}
/* Tippy */
/* For triangle arrow (Sharp) */
/* Tippy */
/* For triangle arrow (Sharp) */
.tippy-box[data-placement^='top'] > .tippy-arrow:before {
border-top: 8px solid var(--bg-tooltip-color);
}
.tippy-box[data-placement^='bottom'] > .tippy-arrow:before {
border-bottom: 8px solid var(--bg-tooltip-color);
}
.tippy-box[data-placement^='right'] > .tippy-arrow:before {
border-right: 8px solid var(--bg-tooltip-color);
}
.tippy-box[data-placement^='left'] > .tippy-arrow:before {
border-left: 8px solid var(--bg-tooltip-color);
}
/* Sidebar */
.sidebar .tippy-box {
font-size: 12px;
}
.sidebar .tippy-box[data-placement^='right'] > .tippy-arrow {
width: 12px;
height: 12px;
}
.sidebar .tippy-box[data-placement^='right'] > .tippy-arrow:before {
border-right: 8px solid var(--ui-gray-9);
border-width: 6px 8px 6px 0;
}
[theme='dark'] .sidebar .tippy-box[data-placement^='right'] > .tippy-arrow:before {
border-right: 8px solid var(--ui-gray-true-9);
}
.sidebar .tippy-content {
white-space: nowrap;
}
.tippy-content {
padding: 0;
}

View File

@ -14,6 +14,7 @@ export function YAMLReplace({ featureId }: Props) {
BEFeatureID={featureId}
message="Applies any changes that you make in the YAML editor by calling the Kubernetes API to patch the relevant resources. Any resource removals or unexpected resource additions that you make in the YAML will be ignored. Note that editing is disabled for resources in namespaces marked as system."
>
<div className="float-right">
<Button
type="button"
color="warninglight"
@ -23,6 +24,7 @@ export function YAMLReplace({ featureId }: Props) {
>
Apply changes
</Button>
</div>
</TooltipWithChildren>
);
}

View File

@ -1,6 +1,7 @@
import clsx from 'clsx';
import { PropsWithChildren } from 'react';
import ReactTooltip from 'react-tooltip';
import { Tooltip } from '@@/Tip/Tooltip';
import './BoxSelectorItem.css';
@ -28,16 +29,8 @@ export function BoxOption<T extends number | string>({
type = 'radio',
children,
}: PropsWithChildren<Props<T>>) {
const tooltipId = `box-option-${radioName}-${option.id}`;
return (
<div
className={clsx('box-selector-item', className)}
data-tip
data-for={tooltipId}
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
>
<div className={clsx('box-selector-item', className)}>
<input
type={type}
name={radioName}
@ -52,13 +45,11 @@ export function BoxOption<T extends number | string>({
{children}
</label>
{tooltip && (
<ReactTooltip
place="bottom"
<Tooltip
position="bottom"
className="portainer-tooltip"
id={tooltipId}
>
{tooltip}
</ReactTooltip>
message={tooltip}
/>
)}
</div>
);

View File

@ -1,8 +1,9 @@
import ReactTooltip from 'react-tooltip';
import { HelpCircle } from 'lucide-react';
import clsx from 'clsx';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren';
import { getFeatureDetails } from '@@/BEFeatureIndicator/utils';
interface Props {
@ -10,35 +11,24 @@ interface Props {
featureId?: FeatureId;
}
export function LimitedToBeIndicator({ tooltipId, featureId }: Props) {
export function LimitedToBeIndicator({ featureId, tooltipId }: Props) {
const { url } = getFeatureDetails(featureId);
return (
<>
<div className="absolute left-0 top-0 w-full">
<div className="mx-auto max-w-fit bg-warning-4 rounded-b-lg py-1 px-3 flex gap-1 text-sm items-center">
<a href={url} target="_blank" rel="noopener noreferrer">
<span className="text-warning-9">Pro Feature</span>
</a>
<HelpCircle
className="lucide !text-warning-7"
data-tip
data-for={tooltipId}
tooltip-append-to-body="true"
tooltip-placement="top"
tooltip-class="portainer-tooltip"
/>
</div>
</div>
<ReactTooltip
className="portainer-tooltip"
id={tooltipId}
place="top"
delayHide={1000}
<TooltipWithChildren
position="bottom"
className={clsx(tooltipId, 'portainer-tooltip')}
heading="Business Edition feature."
message="This feature is currently limited to Business Edition users only."
>
Business Edition feature. <br />
This feature is currently limited to Business Edition users only.
</ReactTooltip>
</>
<HelpCircle className="ml-1 !text-warning-7" aria-hidden="true" />
</TooltipWithChildren>
</div>
</div>
);
}

View File

@ -1,25 +0,0 @@
:global(portainer-tooltip) {
@apply inline-flex;
}
.tooltip-wrapper {
display: inline-block;
position: relative;
}
.tooltip {
background-color: var(--bg-tooltip-color) !important;
padding: 0.833em 1em !important;
color: var(--text-tooltip-color) !important;
border-radius: 10px !important;
box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15) !important;
max-width: 400px;
text-align: center;
font-size: 12px !important;
font-weight: 400;
}
.icon {
margin-left: 5px;
cursor: pointer;
}

View File

@ -1,11 +1,6 @@
import ReactTooltip from 'react-tooltip';
import { HelpCircle } from 'lucide-react';
import clsx from 'clsx';
import _ from 'lodash';
import styles from './Tooltip.module.css';
type Position = 'top' | 'right' | 'bottom' | 'left';
import { TooltipWithChildren, Position } from '../TooltipWithChildren';
export interface Props {
position?: Position;
@ -14,24 +9,15 @@ export interface Props {
}
export function Tooltip({ message, position = 'bottom', className }: Props) {
const id = _.uniqueId('tooltip-');
return (
<span
data-tip={message}
data-for={id}
className={clsx(styles.icon, 'inline-flex text-base')}
<TooltipWithChildren
message={message}
position={position}
className={className}
>
<HelpCircle className="lucide" aria-hidden="true" />
<ReactTooltip
id={id}
multiline
type="info"
place={position}
effect="solid"
className={clsx(styles.tooltip, className)}
arrowColor="transparent"
/>
<span className="inline-flex text-base">
<HelpCircle className="lucide ml-1" aria-hidden="true" />
</span>
</TooltipWithChildren>
);
}

View File

@ -2,20 +2,14 @@
@apply inline-flex;
}
.tooltip-wrapper {
display: inline-block;
position: relative;
}
.tooltip {
background-color: var(--bg-tooltip-color) !important;
padding: 0.833em 1em !important;
color: var(--text-tooltip-color) !important;
border-radius: 10px !important;
box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15) !important;
max-width: 400px;
min-width: 300px;
font-size: 1rem !important;
text-align: left;
font-size: 12px !important;
font-weight: 400;
}
@ -26,6 +20,8 @@
.tooltip-container {
line-height: 18px;
padding: 8px 10px !important;
font-size: 12px !important;
}
.tooltip-heading {

View File

@ -1,7 +1,9 @@
import ReactTooltip from 'react-tooltip';
import React, { MouseEvent } from 'react';
import Tippy from '@tippyjs/react';
import clsx from 'clsx';
import _ from 'lodash';
import ReactDOMServer from 'react-dom/server';
import 'tippy.js/dist/tippy.css';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
@ -9,13 +11,13 @@ import { getFeatureDetails } from '@@/BEFeatureIndicator/utils';
import styles from './TooltipWithChildren.module.css';
type Position = 'top' | 'right' | 'bottom' | 'left';
export type Position = 'top' | 'right' | 'bottom' | 'left';
export interface Props {
position?: Position;
message: string;
className?: string;
children: React.ReactNode;
children: React.ReactElement;
heading?: string;
BEFeatureID?: FeatureId;
}
@ -35,14 +37,11 @@ export function TooltipWithChildren({
: { url: '', limitedToBE: false };
const messageHTML = (
<div className={styles.tooltipContainer}>
<div
className={clsx(
'w-full mb-2 inline-flex justify-between',
styles.tooltipHeading
)}
>
{heading && <span>{heading}</span>}
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div className={styles.tooltipContainer} onClick={onClickHandler}>
{(heading || (BEFeatureID && limitedToBE)) && (
<div className="w-full mb-3 inline-flex justify-between">
<span>{heading}</span>
{BEFeatureID && limitedToBE && (
<a
href={url}
@ -54,30 +53,37 @@ export function TooltipWithChildren({
</a>
)}
</div>
)}
<div>{message}</div>
</div>
);
return (
<span
data-html
data-multiline
data-tip={ReactDOMServer.renderToString(messageHTML)}
data-for={id}
className={clsx(styles.icon, 'inline-flex text-base')}
<Tippy
className={clsx(id, styles.tooltip, className)}
content={messageHTML}
delay={[50, 500]} // 50ms to open, 500ms to hide
zIndex={1000}
placement={position}
maxWidth={400}
arrow
allowHTML
interactive
>
{children}
<ReactTooltip
id={id}
multiline
type="info"
place={position}
effect="solid"
className={clsx(styles.tooltip, className)}
arrowColor="var(--bg-tooltip-color)"
delayHide={400}
clickable
/>
</span>
</Tippy>
);
}
// Preventing click bubbling to the parent as it is affecting
// mainly toggles when full row is clickable.
function onClickHandler(e: MouseEvent) {
const target = e.target as HTMLInputElement;
if (target.tagName.toLowerCase() === 'a') {
const url = target.getAttribute('href');
if (url) {
window.open(url, '_blank');
}
}
e.preventDefault();
}

View File

@ -1 +1,2 @@
export { TooltipWithChildren } from './TooltipWithChildren';
export type { Position } from './TooltipWithChildren';

View File

@ -30,7 +30,7 @@ export function Sidebar() {
return (
/* in the future (when we remove r2a) this should wrap the whole app - to change root styles */
<SidebarProvider>
<div className={clsx(styles.root, 'flex flex-col')}>
<div className={clsx(styles.root, 'sidebar flex flex-col')}>
<UpgradeBEBanner />
<nav
className={clsx(

View File

@ -5,10 +5,11 @@ import {
} from '@uirouter/react';
import clsx from 'clsx';
import { ComponentProps } from 'react';
import ReactTooltip from 'react-tooltip';
import Tippy from '@tippyjs/react';
import { AutomationTestingProps } from '@/types';
import 'tippy.js/dist/tippy.css';
import { Link } from '@@/Link';
import { IconProps, Icon } from '@@/Icon';
@ -40,7 +41,7 @@ export function Head({
ignorePaths
);
return (
const anchor = (
<a
href={anchorProps.href}
onClick={anchorProps.onClick}
@ -54,22 +55,30 @@ export function Head({
'justify-center w-8': !isOpen,
}
)}
data-tip={label}
data-cy={dataCy}
>
{!!icon && <Icon icon={icon} className={clsx('flex [&>svg]:w-4')} />}
{isOpen && <span>{label}</span>}
<ReactTooltip
type="info"
place="right"
effect="solid"
className="!opacity-100 bg-blue-9 be:bg-gray-9 !rounded-md !py-1 !px-2"
arrowColor="transparent"
disable={isOpen}
/>
</a>
);
if (isOpen) return anchor;
return (
<Tippy
className="!opacity-100 bg-blue-9 be:bg-gray-9 th-dark:bg-gray-true-9 !rounded-md !py-2 !px-3"
content={label}
delay={[0, 0]}
duration={[0, 0]}
zIndex={1000}
placement="right"
arrow
allowHTML
interactive
>
{anchor}
</Tippy>
);
}
function useSrefActive(

View File

@ -33,6 +33,10 @@ export function Menu({
const CollapseButtonIcon = isOpen ? ChevronUp : ChevronDown;
if (!isSidebarOpen) {
return head as JSX.Element;
}
return (
<div className="flex-1">
<div className="flex w-full justify-between items-center relative ">

View File

@ -39,7 +39,7 @@ export function SidebarItem({
);
return (
<Wrapper label={label}>
<Wrapper label={label} className="sidebar">
{children ? (
<Menu head={head} openOnPaths={[...openOnPaths, ...childrenPath]}>
{children}

View File

@ -71,6 +71,7 @@
"@open-amt-cloud-toolkit/ui-toolkit-react": "2.0.0",
"@reach/dialog": "^0.17.0",
"@reach/menu-button": "^0.16.1",
"@tippyjs/react": "^4.2.6",
"@uirouter/angularjs": "1.0.11",
"@uirouter/react": "^1.0.7",
"@uirouter/react-hybrid": "^1.0.4",
@ -131,12 +132,12 @@
"react-query": "^3.33.4",
"react-select": "^5.2.1",
"react-table": "^7.7.0",
"react-tooltip": "^4.2.21",
"sanitize-html": "^2.5.3",
"semver-compare": "^1.0.0",
"spinkit": "^2.0.1",
"splitargs": "github:deviantony/splitargs#semver:~0.2.0",
"strip-ansi": "^6.0.0",
"tippy.js": "^6.3.7",
"toastr": "^2.1.4",
"uuid": "^3.3.2",
"xterm": "^3.8.0",

View File

@ -3068,6 +3068,11 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
"@popperjs/core@^2.9.0":
version "2.11.6"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
"@reach/auto-id@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.16.0.tgz#dfabc3227844e8c04f8e6e45203a8e14a8edbaed"
@ -4382,6 +4387,13 @@
dependencies:
"@babel/runtime" "^7.12.5"
"@tippyjs/react@^4.2.6":
version "4.2.6"
resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.6.tgz#971677a599bf663f20bb1c60a62b9555b749cc71"
integrity sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==
dependencies:
tippy.js "^6.3.1"
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
@ -15684,14 +15696,6 @@ react-time-picker@^4.5.0:
react-fit "^1.4.0"
update-input-width "^1.2.2"
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-transition-group@^4.3.0:
version "4.4.2"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
@ -17707,6 +17711,13 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tippy.js@^6.3.1, tippy.js@^6.3.7:
version "6.3.7"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==
dependencies:
"@popperjs/core" "^2.9.0"
title-case@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa"
@ -18332,11 +18343,6 @@ uuid@^3.3.2:
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==
uuid@^8.3.0, uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"