Skip to content

Commit 8d376e2

Browse files
committed
fix: add hooks and refactor
1 parent f5aa709 commit 8d376e2

File tree

6 files changed

+124
-96
lines changed

6 files changed

+124
-96
lines changed

src/components/PDiskPopup/PDiskPopup.tsx

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
1111
import {getPDiskId} from '../../utils/disks/helpers';
1212
import type {PreparedPDisk} from '../../utils/disks/types';
1313
import {useTypedSelector} from '../../utils/hooks';
14+
import {usePopupOpenState} from '../../utils/hooks/usePopupOpenState';
1415
import {bytesToGB} from '../../utils/utils';
1516
import type {InfoViewerItem} from '../InfoViewer';
1617
import {InfoViewer} from '../InfoViewer';
@@ -63,46 +64,29 @@ export const preparePDiskData = (data: PreparedPDisk, nodeHost?: string) => {
6364

6465
interface PDiskPopupProps extends PopupProps {
6566
data: PreparedPDisk;
67+
hidePopup?: VoidFunction;
6668
}
6769

68-
export const PDiskPopup = ({data, ...props}: PDiskPopupProps) => {
70+
export const PDiskPopup = ({data, hidePopup, ...props}: PDiskPopupProps) => {
6971
const nodeHostsMap = useTypedSelector(selectNodeHostsMap);
7072
const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined;
7173
const info = React.useMemo(() => preparePDiskData(data, nodeHost), [data, nodeHost]);
7274

73-
const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
74-
const [isFocused, setIsFocused] = React.useState(false);
75-
76-
const onMouseLeave = React.useCallback(() => {
77-
setIsPopupContentHovered(false);
78-
}, []);
79-
80-
const onMouseEnter = React.useCallback(() => {
81-
setIsPopupContentHovered(true);
82-
}, []);
83-
84-
const onContextMenu = React.useCallback(() => {
85-
setIsFocused(true);
86-
}, []);
87-
88-
const onBlur = React.useCallback(() => {
89-
setIsFocused(false);
90-
}, []);
75+
const {open, onMouseEnter, onMouseLeave, onContextMenu, onBlur, onEscapeKeyDown} =
76+
usePopupOpenState(hidePopup);
9177

9278
return (
9379
<Popup
9480
contentClassName={b()}
9581
placement={['top', 'bottom']}
9682
hasArrow
97-
// bigger offset for easier switching to neighbour nodes
98-
// matches the default offset for popup with arrow out of a sense of beauty
9983
offset={[0, 12]}
10084
onMouseLeave={onMouseLeave}
10185
onMouseEnter={onMouseEnter}
102-
onEscapeKeyDown={onBlur}
86+
onEscapeKeyDown={onEscapeKeyDown}
10387
onBlur={onBlur}
10488
{...props}
105-
open={isPopupContentHovered || props.open || isFocused}
89+
open={open || props.open}
10690
>
10791
<div onContextMenu={onContextMenu}>
10892
<InfoViewer title="PDisk" info={info} size="s" />

src/components/VDisk/VDisk.tsx

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React from 'react';
22

3-
import {debounce} from 'lodash';
4-
53
import {cn} from '../../utils/cn';
64
import type {PreparedVDisk} from '../../utils/disks/types';
5+
import {usePopupAnchor} from '../../utils/hooks/usePopupAnchor';
76
import {DiskStateProgressBar} from '../DiskStateProgressBar/DiskStateProgressBar';
87
import {InternalLink} from '../InternalLink';
98
import {VDiskPopup} from '../VDiskPopup/VDiskPopup';
@@ -14,8 +13,6 @@ import './VDisk.scss';
1413

1514
const b = cn('ydb-vdisk-component');
1615

17-
const DEBOUNCE_TIMEOUT = 100;
18-
1916
export interface VDiskProps {
2017
data?: PreparedVDisk;
2118
compact?: boolean;
@@ -35,19 +32,10 @@ export const VDisk = ({
3532
onHidePopup,
3633
progressBarClassName,
3734
}: VDiskProps) => {
38-
const [isPopupVisible, setIsPopupVisible] = React.useState(false);
39-
40-
const anchor = React.useRef(null);
41-
42-
const debouncedHandleShowPopup = debounce(() => {
43-
setIsPopupVisible(true);
44-
onShowPopup?.();
45-
}, DEBOUNCE_TIMEOUT);
46-
47-
const debouncedHandleHidePopup = debounce(() => {
48-
setIsPopupVisible(false);
49-
onHidePopup?.();
50-
}, DEBOUNCE_TIMEOUT);
35+
const {isPopupVisible, anchor, onMouseEnter, onMouseLeave, hidePopup} = usePopupAnchor(
36+
onShowPopup,
37+
onHidePopup,
38+
);
5139

5240
const vDiskPath = getVDiskLink(data);
5341

@@ -56,11 +44,8 @@ export const VDisk = ({
5644
<div
5745
className={b()}
5846
ref={anchor}
59-
onMouseEnter={debouncedHandleShowPopup}
60-
onMouseLeave={() => {
61-
debouncedHandleShowPopup.cancel();
62-
debouncedHandleHidePopup();
63-
}}
47+
onMouseEnter={onMouseEnter}
48+
onMouseLeave={onMouseLeave}
6449
>
6550
<InternalLink to={vDiskPath} className={b('content')}>
6651
<DiskStateProgressBar
@@ -72,7 +57,12 @@ export const VDisk = ({
7257
/>
7358
</InternalLink>
7459
</div>
75-
<VDiskPopup data={data} anchorRef={anchor} open={isPopupVisible || showPopup} />
60+
<VDiskPopup
61+
data={data}
62+
anchorRef={anchor}
63+
open={isPopupVisible || showPopup}
64+
hidePopup={hidePopup}
65+
/>
7666
</React.Fragment>
7767
);
7868
};

src/components/VDiskPopup/VDiskPopup.tsx

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters';
1212
import {isFullVDiskData} from '../../utils/disks/helpers';
1313
import type {PreparedVDisk, UnavailableDonor} from '../../utils/disks/types';
1414
import {useTypedSelector} from '../../utils/hooks';
15+
import {usePopupOpenState} from '../../utils/hooks/usePopupOpenState';
1516
import {bytesToGB, bytesToSpeed} from '../../utils/utils';
1617
import type {InfoViewerItem} from '../InfoViewer';
1718
import {InfoViewer} from '../InfoViewer';
@@ -132,29 +133,14 @@ const prepareVDiskData = (data: PreparedVDisk) => {
132133

133134
interface VDiskPopupProps extends PopupProps {
134135
data: PreparedVDisk | UnavailableDonor;
136+
hidePopup?: VoidFunction;
135137
}
136138

137-
export const VDiskPopup = ({data, ...props}: VDiskPopupProps) => {
139+
export const VDiskPopup = ({data, hidePopup, ...props}: VDiskPopupProps) => {
138140
const isFullData = isFullVDiskData(data);
139141

140-
const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
141-
const [isFocused, setIsFocused] = React.useState(false);
142-
143-
const onMouseLeave = React.useCallback(() => {
144-
setIsPopupContentHovered(false);
145-
}, []);
146-
147-
const onMouseEnter = React.useCallback(() => {
148-
setIsPopupContentHovered(true);
149-
}, []);
150-
151-
const onContextMenu = React.useCallback(() => {
152-
setIsFocused(true);
153-
}, []);
154-
155-
const onBlur = React.useCallback(() => {
156-
setIsFocused(false);
157-
}, []);
142+
const {open, onMouseEnter, onMouseLeave, onContextMenu, onBlur, onEscapeKeyDown} =
143+
usePopupOpenState(hidePopup);
158144

159145
const vdiskInfo = React.useMemo(
160146
() => (isFullData ? prepareVDiskData(data) : prepareUnavailableVDiskData(data)),
@@ -202,10 +188,10 @@ export const VDiskPopup = ({data, ...props}: VDiskPopupProps) => {
202188
offset={[0, 12]}
203189
onMouseEnter={onMouseEnter}
204190
onMouseLeave={onMouseLeave}
205-
onEscapeKeyDown={onBlur}
191+
onEscapeKeyDown={onEscapeKeyDown}
206192
onBlur={onBlur}
207193
{...props}
208-
open={isPopupContentHovered || props.open || isFocused}
194+
open={open || props.open}
209195
>
210196
<div onContextMenu={onContextMenu}>
211197
{data.DonorMode && <Label className={b('donor-label')}>Donor</Label>}

src/containers/Storage/PDisk/PDisk.tsx

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
import React from 'react';
22

3-
import {debounce} from 'lodash';
4-
53
import {DiskStateProgressBar} from '../../../components/DiskStateProgressBar/DiskStateProgressBar';
64
import {InternalLink} from '../../../components/InternalLink';
75
import {PDiskPopup} from '../../../components/PDiskPopup/PDiskPopup';
86
import {VDisk} from '../../../components/VDisk/VDisk';
9-
import routes, {createHref, getPDiskPagePath} from '../../../routes';
7+
import {getPDiskPagePath} from '../../../routes';
108
import {valueIsDefined} from '../../../utils';
119
import {cn} from '../../../utils/cn';
1210
import type {PreparedPDisk, PreparedVDisk} from '../../../utils/disks/types';
13-
import {STRUCTURE} from '../../Node/NodePages';
11+
import {usePopupAnchor} from '../../../utils/hooks/usePopupAnchor';
1412
import type {StorageViewContext} from '../types';
1513
import {isVdiskActive} from '../utils';
1614

1715
import './PDisk.scss';
1816

1917
const b = cn('pdisk-storage');
2018

21-
const DEBOUNCE_TIMEOUT = 100;
22-
2319
interface PDiskProps {
2420
data?: PreparedPDisk;
2521
vDisks?: PreparedVDisk[];
@@ -41,23 +37,14 @@ export const PDisk = ({
4137
progressBarClassName,
4238
viewContext,
4339
}: PDiskProps) => {
44-
const [isPopupVisible, setIsPopupVisible] = React.useState(false);
45-
46-
const anchor = React.useRef(null);
40+
const {isPopupVisible, anchor, onMouseEnter, onMouseLeave, hidePopup} = usePopupAnchor(
41+
onShowPopup,
42+
onHidePopup,
43+
);
4744

4845
const {NodeId, PDiskId} = data;
4946
const pDiskIdsDefined = valueIsDefined(NodeId) && valueIsDefined(PDiskId);
5047

51-
const debouncedHandleShowPopup = debounce(() => {
52-
setIsPopupVisible(true);
53-
onShowPopup?.();
54-
}, DEBOUNCE_TIMEOUT);
55-
56-
const debouncedHandleHidePopup = debounce(() => {
57-
setIsPopupVisible(false);
58-
onHidePopup?.();
59-
}, DEBOUNCE_TIMEOUT);
60-
6148
const renderVDisks = () => {
6249
if (!vDisks?.length) {
6350
return null;
@@ -90,10 +77,6 @@ export const PDisk = ({
9077

9178
let pDiskPath: string | undefined;
9279

93-
if (pDiskIdsDefined) {
94-
pDiskPath = createHref(routes.node, {id: NodeId, activeTab: STRUCTURE}, {pdiskId: PDiskId});
95-
}
96-
9780
if (pDiskIdsDefined) {
9881
pDiskPath = getPDiskPagePath(PDiskId, NodeId);
9982
}
@@ -105,11 +88,8 @@ export const PDisk = ({
10588
<InternalLink
10689
to={pDiskPath}
10790
className={b('content')}
108-
onMouseEnter={debouncedHandleShowPopup}
109-
onMouseLeave={() => {
110-
debouncedHandleShowPopup.cancel();
111-
debouncedHandleHidePopup();
112-
}}
91+
onMouseEnter={onMouseEnter}
92+
onMouseLeave={onMouseLeave}
11393
>
11494
<DiskStateProgressBar
11595
diskAllocatedPercent={data.AllocatedPercent}
@@ -119,7 +99,12 @@ export const PDisk = ({
11999
<div className={b('media-type')}>{data.Type}</div>
120100
</InternalLink>
121101
</div>
122-
<PDiskPopup data={data} anchorRef={anchor} open={isPopupVisible || showPopup} />
102+
<PDiskPopup
103+
data={data}
104+
anchorRef={anchor}
105+
open={isPopupVisible || showPopup}
106+
hidePopup={hidePopup}
107+
/>
123108
</React.Fragment>
124109
);
125110
};

src/utils/hooks/usePopupAnchor.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
3+
import debounce from 'lodash/debounce';
4+
5+
const DEBOUNCE_TIMEOUT = 100;
6+
7+
export function usePopupAnchor(onShowPopup?: VoidFunction, onHidePopup?: VoidFunction) {
8+
const [isPopupVisible, setIsPopupVisible] = React.useState(false);
9+
const anchor = React.useRef<HTMLDivElement>(null);
10+
11+
const debouncedHandleShowPopup = React.useMemo(
12+
() =>
13+
debounce(() => {
14+
setIsPopupVisible(true);
15+
onShowPopup?.();
16+
}, DEBOUNCE_TIMEOUT),
17+
[onShowPopup],
18+
);
19+
20+
const hidePopup = React.useCallback(() => {
21+
setIsPopupVisible(false);
22+
onHidePopup?.();
23+
}, [onHidePopup]);
24+
25+
const debouncedHandleHidePopup = React.useMemo(
26+
() => debounce(hidePopup, DEBOUNCE_TIMEOUT),
27+
[hidePopup],
28+
);
29+
30+
const onMouseEnter = debouncedHandleShowPopup;
31+
32+
const onMouseLeave = () => {
33+
debouncedHandleShowPopup.cancel();
34+
debouncedHandleHidePopup();
35+
};
36+
37+
return {
38+
isPopupVisible,
39+
anchor,
40+
onMouseEnter,
41+
onMouseLeave,
42+
hidePopup,
43+
};
44+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
3+
export function usePopupOpenState(hidePopup?: VoidFunction) {
4+
const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
5+
const [isFocused, setIsFocused] = React.useState(false);
6+
7+
const onMouseEnter = React.useCallback(() => {
8+
setIsPopupContentHovered(true);
9+
}, []);
10+
11+
const onMouseLeave = React.useCallback(() => {
12+
setIsPopupContentHovered(false);
13+
}, []);
14+
15+
const onContextMenu = React.useCallback(() => {
16+
setIsFocused(true);
17+
}, []);
18+
19+
const onBlur = React.useCallback(() => {
20+
setIsFocused(false);
21+
}, []);
22+
23+
const onEscapeKeyDown = React.useCallback(() => {
24+
setIsFocused(false);
25+
setIsPopupContentHovered(false);
26+
hidePopup?.();
27+
}, [hidePopup]);
28+
29+
const open = isPopupContentHovered || isFocused;
30+
31+
return {
32+
open,
33+
onMouseEnter,
34+
onMouseLeave,
35+
onContextMenu,
36+
onBlur,
37+
onEscapeKeyDown,
38+
};
39+
}

0 commit comments

Comments
 (0)