diff --git a/src/components/CellWithPopover/CellWithPopover.tsx b/src/components/CellWithPopover/CellWithPopover.tsx
index 272df0dd78..14529d8b42 100644
--- a/src/components/CellWithPopover/CellWithPopover.tsx
+++ b/src/components/CellWithPopover/CellWithPopover.tsx
@@ -11,6 +11,8 @@ interface CellWithPopoverProps extends PopoverProps {
wrapperClassName?: string;
}
+const DELAY_TIMEOUT = 100;
+
export function CellWithPopover({
children,
className,
@@ -19,7 +21,12 @@ export function CellWithPopover({
}: CellWithPopoverProps) {
return (
diff --git a/src/components/PDiskPopup/PDiskPopup.tsx b/src/components/PDiskPopup/PDiskPopup.tsx
index c73f4e5a9c..0bca72c7f9 100644
--- a/src/components/PDiskPopup/PDiskPopup.tsx
+++ b/src/components/PDiskPopup/PDiskPopup.tsx
@@ -70,6 +70,14 @@ export const PDiskPopup = ({data, ...props}: PDiskPopupProps) => {
const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined;
const info = React.useMemo(() => preparePDiskData(data, nodeHost), [data, nodeHost]);
+ const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
+ const onMouseLeave = React.useCallback(() => {
+ setIsPopupContentHovered(false);
+ }, []);
+ const onMouseEnter = React.useCallback(() => {
+ setIsPopupContentHovered(true);
+ }, []);
+
return (
{
// bigger offset for easier switching to neighbour nodes
// matches the default offset for popup with arrow out of a sense of beauty
offset={[0, 12]}
+ onMouseLeave={onMouseLeave}
+ onMouseEnter={onMouseEnter}
{...props}
+ open={isPopupContentHovered || props.open}
>
diff --git a/src/components/VDisk/VDisk.tsx b/src/components/VDisk/VDisk.tsx
index cc59cffb8a..c369719c67 100644
--- a/src/components/VDisk/VDisk.tsx
+++ b/src/components/VDisk/VDisk.tsx
@@ -1,5 +1,7 @@
import React from 'react';
+import {debounce} from 'lodash';
+
import {cn} from '../../utils/cn';
import type {PreparedVDisk} from '../../utils/disks/types';
import {DiskStateProgressBar} from '../DiskStateProgressBar/DiskStateProgressBar';
@@ -12,6 +14,8 @@ import './VDisk.scss';
const b = cn('ydb-vdisk-component');
+const DEBOUNCE_TIMEOUT = 100;
+
export interface VDiskProps {
data?: PreparedVDisk;
compact?: boolean;
@@ -35,15 +39,15 @@ export const VDisk = ({
const anchor = React.useRef(null);
- const handleShowPopup = () => {
+ const debouncedHandleShowPopup = debounce(() => {
setIsPopupVisible(true);
onShowPopup?.();
- };
+ }, DEBOUNCE_TIMEOUT);
- const handleHidePopup = () => {
+ const debouncedHandleHidePopup = debounce(() => {
setIsPopupVisible(false);
onHidePopup?.();
- };
+ }, DEBOUNCE_TIMEOUT);
const vDiskPath = getVDiskLink(data);
@@ -52,8 +56,11 @@ export const VDisk = ({
{
+ debouncedHandleShowPopup.cancel();
+ debouncedHandleHidePopup();
+ }}
>
{
const isFullData = isFullVDiskData(data);
+ const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
+ const onMouseLeave = React.useCallback(() => {
+ setIsPopupContentHovered(false);
+ }, []);
+ const onMouseEnter = React.useCallback(() => {
+ setIsPopupContentHovered(true);
+ }, []);
+
const vdiskInfo = React.useMemo(
() => (isFullData ? prepareVDiskData(data) : prepareUnavailableVDiskData(data)),
[data, isFullData],
@@ -181,7 +189,10 @@ export const VDiskPopup = ({data, ...props}: VDiskPopupProps) => {
// bigger offset for easier switching to neighbour nodes
// matches the default offset for popup with arrow out of a sense of beauty
offset={[0, 12]}
+ onMouseEnter={onMouseEnter}
+ onMouseLeave={onMouseLeave}
{...props}
+ open={isPopupContentHovered || props.open}
>
{data.DonorMode && }
diff --git a/src/containers/Storage/PDisk/PDisk.tsx b/src/containers/Storage/PDisk/PDisk.tsx
index f0ff784ae0..a48162925a 100644
--- a/src/containers/Storage/PDisk/PDisk.tsx
+++ b/src/containers/Storage/PDisk/PDisk.tsx
@@ -1,5 +1,7 @@
import React from 'react';
+import {debounce} from 'lodash';
+
import {DiskStateProgressBar} from '../../../components/DiskStateProgressBar/DiskStateProgressBar';
import {InternalLink} from '../../../components/InternalLink';
import {PDiskPopup} from '../../../components/PDiskPopup/PDiskPopup';
@@ -16,6 +18,8 @@ import './PDisk.scss';
const b = cn('pdisk-storage');
+const DEBOUNCE_TIMEOUT = 100;
+
interface PDiskProps {
data?: PreparedPDisk;
vDisks?: PreparedVDisk[];
@@ -44,15 +48,15 @@ export const PDisk = ({
const {NodeId, PDiskId} = data;
const pDiskIdsDefined = valueIsDefined(NodeId) && valueIsDefined(PDiskId);
- const handleShowPopup = () => {
+ const debouncedHandleShowPopup = debounce(() => {
setIsPopupVisible(true);
onShowPopup?.();
- };
+ }, DEBOUNCE_TIMEOUT);
- const handleHidePopup = () => {
+ const debouncedHandleHidePopup = debounce(() => {
setIsPopupVisible(false);
onHidePopup?.();
- };
+ }, DEBOUNCE_TIMEOUT);
const renderVDisks = () => {
if (!vDisks?.length) {
@@ -101,8 +105,11 @@ export const PDisk = ({
{
+ debouncedHandleShowPopup.cancel();
+ debouncedHandleHidePopup();
+ }}
>