Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions src/components/DiskStateProgressBar/DiskStateProgressBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
$block: &;

$border-width: 1px;
$outer-border-radius: 4px;
$outer-border-radius: var(--g-border-radius-s);
$inner-border-radius: $outer-border-radius - $border-width;
$outer-compact-border-radius: var(--g-border-radius-xs);
$inner-compact-border-radius: $outer-compact-border-radius - $border-width;

--progress-bar-full-height: var(--g-text-body-3-line-height);
--progress-bar-full-height: var(--g-text-subheader-2-line-height);
--progress-bar-compact-height: 12px;

--stripe-width: 4px;
Expand All @@ -16,6 +18,8 @@
position: relative;
z-index: 0;

overflow: hidden;

min-width: 50px;
height: var(--progress-bar-full-height);

Expand All @@ -25,13 +29,16 @@
border: $border-width solid var(--entity-state-border-color);
border-radius: $outer-border-radius;
background-color: var(--entity-state-background-color);
@include mixins.entity-state-colors();

transition: opacity 300ms ease-in-out;

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

&_compact {
min-width: 0;
min-width: 8px;
height: var(--progress-bar-compact-height);

border-radius: 2px;
border-radius: $outer-compact-border-radius;
}

&_faded {
Expand All @@ -42,6 +49,10 @@
opacity: 0.5;
}

&_darkened {
opacity: 0.8;
}

&_empty {
color: var(--g-color-text-hint);
border-style: dashed;
Expand Down Expand Up @@ -78,7 +89,7 @@
}

&_compact {
border-radius: 1px;
border-radius: $inner-compact-border-radius;
}

&_inverted {
Expand All @@ -93,20 +104,21 @@
position: relative;
z-index: 2;

margin-right: var(--g-spacing-1);
margin-right: var(--g-spacing-half);

font-size: var(--g-text-body-1-font-size);
font-family: var(--g-text-caption-font-family);
font-size: var(--g-text-caption-1-font-size);
// bar height minus borders
line-height: calc(var(--progress-bar-full-height) - #{$border-width * 2});

color: inherit;
color: var(--entity-state-font-color);
}

&__icon {
position: relative;
z-index: 2;

margin-left: var(--g-spacing-1);
margin-left: calc(var(--g-spacing-1) - $border-width);

color: var(--entity-state-border-color);
}
Expand Down
29 changes: 22 additions & 7 deletions src/components/DiskStateProgressBar/DiskStateProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {cn} from '../../utils/cn';
import {DONOR_COLOR} from '../../utils/disks/constants';
import {getSeverityColor, getVDiskStatusIcon} from '../../utils/disks/helpers';
import {useSetting} from '../../utils/hooks';
import {isNumeric} from '../../utils/utils';

import './DiskStateProgressBar.scss';

Expand All @@ -24,6 +25,9 @@ interface DiskStateProgressBarProps {
className?: string;
isDonor?: boolean;
withIcon?: boolean;
highlighted?: boolean;
darkened?: boolean;
noDataPlaceholder?: React.ReactNode;
}

export function DiskStateProgressBar({
Expand All @@ -38,6 +42,9 @@ export function DiskStateProgressBar({
className,
isDonor,
withIcon,
highlighted,
darkened,
noDataPlaceholder,
}: DiskStateProgressBarProps) {
const [inverted] = useSetting<boolean | undefined>(SETTING_KEYS.INVERTED_DISKS);

Expand All @@ -48,6 +55,8 @@ export function DiskStateProgressBar({
empty,
inactive,
striped,
highlighted,
darkened,
};

if (isDonor) {
Expand All @@ -59,33 +68,39 @@ export function DiskStateProgressBar({
}
}

const hasAllocatedPercent = isNumeric(diskAllocatedPercent) && diskAllocatedPercent >= 0;

const renderAllocatedPercent = () => {
if (compact) {
return <div className={b('fill-bar', mods)} style={{width: '100%'}} />;
}

if (!hasAllocatedPercent) {
return null;
}

// diskAllocatedPercent could be more than 100
let fillWidth = Math.min(diskAllocatedPercent, 100);
if (inverted) {
fillWidth = Math.max(100 - diskAllocatedPercent, 0);
}

if (diskAllocatedPercent >= 0) {
return <div className={b('fill-bar', mods)} style={{width: `${fillWidth}%`}} />;
}

return null;
return <div className={b('fill-bar', mods)} style={{width: `${fillWidth}%`}} />;
};

const renderContent = () => {
if (content) {
return content;
}

if (!compact && diskAllocatedPercent >= 0) {
if (!compact && hasAllocatedPercent) {
return <div className={b('title')}>{`${Math.floor(diskAllocatedPercent)}%`}</div>;
}

if (!compact && !hasAllocatedPercent && noDataPlaceholder) {
return <div className={b('title')}>{noDataPlaceholder}</div>;
}

return null;
};

Expand All @@ -111,7 +126,7 @@ export function DiskStateProgressBar({
aria-label="Disk allocated space"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={diskAllocatedPercent}
aria-valuenow={hasAllocatedPercent ? diskAllocatedPercent : undefined}
>
{iconElement}
{renderAllocatedPercent()}
Expand Down
54 changes: 40 additions & 14 deletions src/components/HoverPopup/HoverPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,44 +34,47 @@ export const HoverPopup = ({
delayOpen = DEBOUNCE_TIMEOUT,
}: HoverPopupProps) => {
const [isPopupVisible, setIsPopupVisible] = React.useState(false);
const anchor = React.useRef<HTMLDivElement>(null);
const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
const [isFocused, setIsFocused] = React.useState(false);

const anchor = React.useRef<HTMLSpanElement>(null);

const debouncedHandleShowPopup = React.useMemo(
() =>
debounce(() => {
setIsPopupVisible(true);
onShowPopup?.();
}, delayOpen),
[onShowPopup, delayOpen],
[delayOpen],
);

const hidePopup = React.useCallback(() => {
setIsPopupVisible(false);
onHidePopup?.();
}, [onHidePopup]);
}, []);

const debouncedHandleHidePopup = React.useMemo(
() => debounce(hidePopup, delayClose),
[hidePopup, delayClose],
);

const onMouseEnter = debouncedHandleShowPopup;
const onMouseEnter = () => {
debouncedHandleHidePopup.cancel();
debouncedHandleShowPopup();
};

const onMouseLeave = () => {
debouncedHandleShowPopup.cancel();
debouncedHandleHidePopup();
};

const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
const [isFocused, setIsFocused] = React.useState(false);

const onPopupMouseEnter = React.useCallback(() => {
setIsPopupContentHovered(true);
}, []);
debouncedHandleHidePopup.cancel();
}, [debouncedHandleHidePopup]);

const onPopupMouseLeave = React.useCallback(() => {
setIsPopupContentHovered(false);
}, []);
debouncedHandleHidePopup();
}, [debouncedHandleHidePopup]);

const onPopupContextMenu = React.useCallback(() => {
setIsFocused(true);
Expand All @@ -87,16 +90,39 @@ export const HoverPopup = ({
hidePopup();
}, [hidePopup]);

const open = isPopupVisible || showPopup || isPopupContentHovered || isFocused;
const internalOpen = isPopupVisible || isPopupContentHovered || isFocused;
const open = internalOpen || showPopup;

const prevInternalOpenRef = React.useRef(internalOpen);

React.useEffect(() => {
const prev = prevInternalOpenRef.current;

if (prev === internalOpen) {
return;
}

if (internalOpen) {
onShowPopup?.();
} else {
onHidePopup?.();
}

prevInternalOpenRef.current = internalOpen;
}, [internalOpen, onShowPopup, onHidePopup]);

// Do not render Popup until it is available
// to avoid a brief initial render at (0, 0) before positioning is applied.
const anchorElement = anchorRef?.current || anchor.current;

return (
<React.Fragment>
<span ref={anchor} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
{children}
</span>
{open ? (
{open && anchorElement ? (
<Popup
anchorElement={anchorRef?.current || anchor.current}
anchorElement={anchorElement}
onOpenChange={(_open, _event, reason) => {
if (reason === 'escape-key') {
onPopupEscapeKeyDown();
Expand Down
12 changes: 9 additions & 3 deletions src/components/VDisk/VDisk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface VDiskProps {
delayOpen?: number;
delayClose?: number;
withIcon?: boolean;
highlighted?: boolean;
darkened?: boolean;
}

export const VDisk = ({
Expand All @@ -35,13 +37,15 @@ export const VDisk = ({
delayClose,
delayOpen,
withIcon,
highlighted,
darkened,
}: VDiskProps) => {
const getVDiskLink = useVDiskPagePath();
const vDiskPath = getVDiskLink({nodeId: data.NodeId, vDiskId: data.StringifiedId});

const severity = data.Severity;
const isReplicatingColor = severity === DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Blue;
const isHealthyDonor = data.DonorMode && isReplicatingColor;
const isDonor = data.DonorMode;

return (
<HoverPopup
Expand All @@ -60,10 +64,12 @@ export const VDisk = ({
severity={severity}
compact={compact}
inactive={inactive}
striped={isReplicatingColor}
isDonor={isHealthyDonor}
striped={isReplicatingColor || isDonor}
isDonor={isDonor}
className={progressBarClassName}
withIcon={withIcon}
highlighted={highlighted}
darkened={darkened}
/>
</InternalLink>
</div>
Expand Down
Loading
Loading