Skip to content

Commit 427512d

Browse files
Implement new highlighting
1 parent e039b39 commit 427512d

File tree

19 files changed

+319
-125
lines changed

19 files changed

+319
-125
lines changed

src/components/DiskStateProgressBar/DiskStateProgressBar.scss

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,13 @@
2525

2626
text-align: center;
2727

28-
opacity: 0.7;
2928
color: var(--g-color-text-primary);
3029
border: $border-width solid var(--entity-state-border-color);
3130
border-radius: $outer-border-radius;
3231
background-color: var(--entity-state-background-color);
3332

3433
@include mixins.entity-state-colors($block);
3534

36-
&:hover,
37-
&_highlighted {
38-
opacity: 1;
39-
}
40-
4135
&_compact {
4236
min-width: 0;
4337
height: var(--progress-bar-compact-height);
@@ -53,6 +47,10 @@
5347
opacity: 0.5;
5448
}
5549

50+
&_darkened {
51+
opacity: 0.7;
52+
}
53+
5654
&_empty {
5755
color: var(--g-color-text-hint);
5856
border-style: dashed;

src/components/DiskStateProgressBar/DiskStateProgressBar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface DiskStateProgressBarProps {
2525
isDonor?: boolean;
2626
withIcon?: boolean;
2727
highlighted?: boolean;
28+
darkened?: boolean;
2829
}
2930

3031
export function DiskStateProgressBar({
@@ -40,6 +41,7 @@ export function DiskStateProgressBar({
4041
isDonor,
4142
withIcon,
4243
highlighted,
44+
darkened,
4345
}: DiskStateProgressBarProps) {
4446
const [inverted] = useSetting<boolean | undefined>(SETTING_KEYS.INVERTED_DISKS);
4547

@@ -51,6 +53,7 @@ export function DiskStateProgressBar({
5153
inactive,
5254
striped,
5355
highlighted,
56+
darkened,
5457
};
5558

5659
if (isDonor) {

src/components/HoverPopup/HoverPopup.tsx

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,44 +34,50 @@ export const HoverPopup = ({
3434
delayOpen = DEBOUNCE_TIMEOUT,
3535
}: HoverPopupProps) => {
3636
const [isPopupVisible, setIsPopupVisible] = React.useState(false);
37+
const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
38+
const [isFocused, setIsFocused] = React.useState(false);
39+
3740
const anchor = React.useRef<HTMLDivElement>(null);
3841

3942
const debouncedHandleShowPopup = React.useMemo(
4043
() =>
4144
debounce(() => {
4245
setIsPopupVisible(true);
43-
onShowPopup?.();
4446
}, delayOpen),
45-
[onShowPopup, delayOpen],
47+
[delayOpen],
4648
);
4749

4850
const hidePopup = React.useCallback(() => {
4951
setIsPopupVisible(false);
50-
onHidePopup?.();
51-
}, [onHidePopup]);
52+
}, []);
5253

5354
const debouncedHandleHidePopup = React.useMemo(
54-
() => debounce(hidePopup, delayClose),
55+
() =>
56+
debounce(() => {
57+
hidePopup();
58+
}, delayClose),
5559
[hidePopup, delayClose],
5660
);
5761

58-
const onMouseEnter = debouncedHandleShowPopup;
62+
const onMouseEnter = () => {
63+
debouncedHandleHidePopup.cancel();
64+
debouncedHandleShowPopup();
65+
};
5966

6067
const onMouseLeave = () => {
6168
debouncedHandleShowPopup.cancel();
6269
debouncedHandleHidePopup();
6370
};
6471

65-
const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
66-
const [isFocused, setIsFocused] = React.useState(false);
67-
6872
const onPopupMouseEnter = React.useCallback(() => {
6973
setIsPopupContentHovered(true);
70-
}, []);
74+
debouncedHandleHidePopup.cancel();
75+
}, [debouncedHandleHidePopup]);
7176

7277
const onPopupMouseLeave = React.useCallback(() => {
7378
setIsPopupContentHovered(false);
74-
}, []);
79+
debouncedHandleHidePopup();
80+
}, [debouncedHandleHidePopup]);
7581

7682
const onPopupContextMenu = React.useCallback(() => {
7783
setIsFocused(true);
@@ -87,7 +93,26 @@ export const HoverPopup = ({
8793
hidePopup();
8894
}, [hidePopup]);
8995

90-
const open = isPopupVisible || showPopup || isPopupContentHovered || isFocused;
96+
const internalOpen = isPopupVisible || isPopupContentHovered || isFocused;
97+
const open = internalOpen || showPopup;
98+
99+
const prevInternalOpenRef = React.useRef(internalOpen);
100+
101+
React.useEffect(() => {
102+
const prev = prevInternalOpenRef.current;
103+
104+
if (prev === internalOpen) {
105+
return;
106+
}
107+
108+
if (internalOpen) {
109+
onShowPopup?.();
110+
} else {
111+
onHidePopup?.();
112+
}
113+
114+
prevInternalOpenRef.current = internalOpen;
115+
}, [internalOpen, onShowPopup, onHidePopup]);
91116

92117
return (
93118
<React.Fragment>

src/components/VDisk/VDisk.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export interface VDiskProps {
2222
delayOpen?: number;
2323
delayClose?: number;
2424
withIcon?: boolean;
25+
highlighted?: boolean;
26+
darkened?: boolean;
2527
}
2628

2729
export const VDisk = ({
@@ -35,6 +37,8 @@ export const VDisk = ({
3537
delayClose,
3638
delayOpen,
3739
withIcon,
40+
highlighted,
41+
darkened,
3842
}: VDiskProps) => {
3943
const getVDiskLink = useVDiskPagePath();
4044
const vDiskPath = getVDiskLink({nodeId: data.NodeId, vDiskId: data.StringifiedId});
@@ -64,7 +68,8 @@ export const VDisk = ({
6468
isDonor={isDonor}
6569
className={progressBarClassName}
6670
withIcon={withIcon}
67-
highlighted={showPopup}
71+
highlighted={highlighted}
72+
darkened={darkened}
6873
/>
6974
</InternalLink>
7075
</div>

src/components/VDisk/VDiskWithDonorsStack.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,64 @@ interface VDiskWithDonorsStackProps extends VDiskProps {
1010
data?: PreparedVDisk;
1111
className?: string;
1212
stackClassName?: string;
13+
highlightedVDisk?: string;
14+
setHighlightedVDisk?: (id?: string) => void;
15+
progressBarClassName?: string;
1316
}
1417

1518
export function VDiskWithDonorsStack({
1619
data,
1720
className,
1821
stackClassName,
1922
withIcon,
23+
highlightedVDisk,
24+
setHighlightedVDisk,
2025
...restProps
2126
}: VDiskWithDonorsStackProps) {
2227
const {Donors: donors, ...restData} = data || {};
2328

29+
const stackId = data?.StringifiedId;
30+
const isHighlighted = Boolean(stackId && highlightedVDisk === stackId);
31+
const isDarkened = Boolean(highlightedVDisk && highlightedVDisk !== stackId);
32+
33+
const handleShowPopup = () => {
34+
if (stackId) {
35+
setHighlightedVDisk?.(stackId);
36+
}
37+
};
38+
39+
const handleHidePopup = () => {
40+
setHighlightedVDisk?.(undefined);
41+
};
42+
43+
const commonVDiskProps: Partial<VDiskProps> = {
44+
withIcon,
45+
showPopup: isHighlighted,
46+
highlighted: isHighlighted,
47+
darkened: isDarkened,
48+
onShowPopup: handleShowPopup,
49+
onHidePopup: handleHidePopup,
50+
...restProps,
51+
};
52+
2453
const content =
2554
donors && donors.length > 0 ? (
2655
<Stack className={stackClassName}>
27-
<VDisk data={restData} withIcon={withIcon} {...restProps} />
56+
<VDisk data={restData} {...commonVDiskProps} />
2857
{donors.map((donor) => {
2958
const isFullData = isFullVDiskData(donor);
3059

31-
// donor and acceptor are always in the same group
3260
return (
3361
<VDisk
3462
key={stringifyVdiskId(isFullData ? donor.VDiskId : donor)}
3563
data={donor}
36-
withIcon={withIcon}
37-
{...restProps}
64+
{...commonVDiskProps}
3865
/>
3966
);
4067
})}
4168
</Stack>
4269
) : (
43-
<VDisk withIcon={withIcon} data={data} {...restProps} />
70+
<VDisk data={data} withIcon={withIcon} {...commonVDiskProps} />
4471
);
4572

4673
return <div className={className}>{content}</div>;

src/containers/Storage/Disks/Disks.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import React from 'react';
2-
31
import {Flex, useLayoutContext} from '@gravity-ui/uikit';
42

53
import {VDisk} from '../../../components/VDisk/VDisk';
@@ -23,11 +21,18 @@ interface DisksProps {
2321
viewContext?: StorageViewContext;
2422
erasure?: Erasure;
2523
withIcon?: boolean;
24+
highlightedVDisk?: string;
25+
setHighlightedVDisk?: (id?: string) => void;
2626
}
2727

28-
export function Disks({vDisks = [], viewContext, erasure, withIcon}: DisksProps) {
29-
const [highlightedVDisk, setHighlightedVDisk] = React.useState<string | undefined>();
30-
28+
export function Disks({
29+
vDisks = [],
30+
viewContext,
31+
erasure,
32+
withIcon,
33+
highlightedVDisk,
34+
setHighlightedVDisk,
35+
}: DisksProps) {
3136
const vDisksWithDCMargins = useVDisksWithDCMargins(vDisks, erasure);
3237

3338
const {
@@ -76,8 +81,8 @@ export function Disks({vDisks = [], viewContext, erasure, withIcon}: DisksProps)
7681
interface DisksItemProps {
7782
vDisk: PreparedVDisk;
7883
inactive?: boolean;
79-
highlightedVDisk: string | undefined;
80-
setHighlightedVDisk: (id: string | undefined) => void;
84+
highlightedVDisk?: string;
85+
setHighlightedVDisk?: (id?: string) => void;
8186
unavailableVDiskWidth?: number;
8287
withDCMargin?: boolean;
8388
withIcon?: boolean;
@@ -100,19 +105,24 @@ function VDiskItem({
100105
const minWidth = isNumeric(vDiskToShow.AllocatedSize) ? undefined : unavailableVDiskWidth;
101106
const flexGrow = Number(vDiskToShow.AllocatedSize) || 1;
102107

108+
const isHighlighted = highlightedVDisk === vDiskId;
109+
const darkened = Boolean(highlightedVDisk && highlightedVDisk !== vDiskId);
110+
103111
return (
104112
<div style={{flexGrow, minWidth}} className={b('vdisk-item')}>
105113
<VDisk
106114
data={vDiskToShow}
107115
compact
108116
withIcon={withIcon}
109117
inactive={inactive}
110-
showPopup={highlightedVDisk === vDiskId}
118+
showPopup={isHighlighted}
111119
delayOpen={DISKS_POPUP_DEBOUNCE_TIMEOUT}
112120
delayClose={DISKS_POPUP_DEBOUNCE_TIMEOUT}
113-
onShowPopup={() => setHighlightedVDisk(vDiskId)}
114-
onHidePopup={() => setHighlightedVDisk(undefined)}
121+
onShowPopup={() => setHighlightedVDisk?.(vDiskId)}
122+
onHidePopup={() => setHighlightedVDisk?.(undefined)}
115123
progressBarClassName={b('vdisk-progress-bar')}
124+
highlighted={isHighlighted}
125+
darkened={darkened}
116126
/>
117127
</div>
118128
);
@@ -127,6 +137,9 @@ function PDiskItem({
127137
}: DisksItemProps) {
128138
const vDiskId = vDisk.StringifiedId;
129139

140+
const isHighlighted = highlightedVDisk === vDiskId;
141+
const darkened = Boolean(highlightedVDisk && highlightedVDisk !== vDiskId);
142+
130143
if (!vDisk.PDisk) {
131144
return null;
132145
}
@@ -136,12 +149,14 @@ function PDiskItem({
136149
className={b('pdisk-item', {['with-dc-margin']: withDCMargin})}
137150
progressBarClassName={b('pdisk-progress-bar')}
138151
data={vDisk.PDisk}
139-
showPopup={highlightedVDisk === vDiskId}
152+
showPopup={isHighlighted}
140153
delayOpen={DISKS_POPUP_DEBOUNCE_TIMEOUT}
141154
delayClose={DISKS_POPUP_DEBOUNCE_TIMEOUT}
142-
onShowPopup={() => setHighlightedVDisk(vDiskId)}
143-
onHidePopup={() => setHighlightedVDisk(undefined)}
155+
onShowPopup={() => setHighlightedVDisk?.(vDiskId)}
156+
onHidePopup={() => setHighlightedVDisk?.(undefined)}
144157
withIcon={withIcon}
158+
highlighted={isHighlighted}
159+
darkened={darkened}
145160
/>
146161
);
147162
}

0 commit comments

Comments
 (0)