1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

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

This commit is contained in:
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

@ -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 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>
<TooltipWithChildren
position="bottom"
className={clsx(tooltipId, 'portainer-tooltip')}
heading="Business Edition feature."
message="This feature is currently limited to Business Edition users only."
>
<HelpCircle className="ml-1 !text-warning-7" aria-hidden="true" />
</TooltipWithChildren>
</div>
<ReactTooltip
className="portainer-tooltip"
id={tooltipId}
place="top"
delayHide={1000}
>
Business Edition feature. <br />
This feature is currently limited to Business Edition users only.
</ReactTooltip>
</>
</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>
<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,49 +37,53 @@ 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>}
{BEFeatureID && limitedToBE && (
<a
href={url}
target="_blank"
rel="noreferrer"
className={styles.tooltipBeteaser}
>
Business Edition Only
</a>
)}
</div>
// 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}
target="_blank"
rel="noreferrer"
className={styles.tooltipBeteaser}
>
Business Edition Only
</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}