import React from 'react'; import type {PopupPlacement} from '@gravity-ui/uikit'; import type {PreparedVDisk} from '../../utils/disks/types'; import {Stack} from '../Stack/Stack'; import type {VDiskProps} from './VDisk'; import {VDisk} from './VDisk'; interface VDiskWithDonorsStackProps extends Omit { data?: PreparedVDisk; className?: string; stackClassName?: string; highlightedVDisk?: string; setHighlightedVDisk?: (id?: string) => void; progressBarClassName?: string; renderContent?: boolean; placeholderProps?: React.HTMLAttributes; hideMainPDiskInPopup?: boolean; } const diskInStackPlacement: PopupPlacement = ['left', 'right']; const diskInStackPopupOffset = {mainAxis: 7, crossAxis: 0}; const EMPTY_DONORS: PreparedVDisk[] = []; export function VDiskWithDonorsStack({ data, className, stackClassName, withIcon, compact, hideMainPDiskInPopup, highlightedVDisk, setHighlightedVDisk, showPopup: _showPopup, onShowPopup: _onShowPopup, onHidePopup: _onHidePopup, onClosePopup, highlighted: _highlighted, renderContent = true, placeholderProps, ...restProps }: VDiskWithDonorsStackProps) { const donors = data?.Donors ?? EMPTY_DONORS; const shouldRenderDonorStack = donors.length > 0; const stackId = data?.StringifiedId; const isHighlighted = Boolean(stackId && highlightedVDisk === stackId); const [internalHighlightedVDisk, setInternalHighlightedVDisk] = React.useState(); const donorIds = React.useMemo( () => new Set(donors.map((donor) => donor.StringifiedId)), [donors], ); const highlightedVDiskInStack = Boolean( internalHighlightedVDisk && (internalHighlightedVDisk === stackId || donorIds.has(internalHighlightedVDisk)), ); const onShowPopup = React.useCallback(() => { if (stackId) { setInternalHighlightedVDisk(stackId); setHighlightedVDisk?.(stackId); } }, [setHighlightedVDisk, stackId]); const onHidePopup = React.useCallback(() => { setInternalHighlightedVDisk(undefined); setHighlightedVDisk?.(undefined); }, [setHighlightedVDisk]); // Keep the stack's state and popup anchors until a main/donor interaction finishes. if (!renderContent && !isHighlighted && !highlightedVDiskInStack) { return (
); } // Spread restProps first, then explicitly override critical fields to prevent // external code from breaking the stack's highlight/popup coordination. const mainVDiskProps: Partial = { ...restProps, compact, hidePDiskInPopup: hideMainPDiskInPopup, withIcon, showPopup: isHighlighted, highlighted: isHighlighted, onShowPopup, onHidePopup, onClosePopup, }; // Donor VDisks intentionally avoid using and changing highlightedVDiskState. // Only the main VDisk acts as the hover target for the entire stack to avoid // conflicting interactions when hovering over stacked elements. const donorVDiskProps: Partial = { ...restProps, compact, withIcon, popupOffset: diskInStackPopupOffset, withOpaqueBackground: true, }; const mainDiskPlacement = shouldRenderDonorStack ? diskInStackPlacement : undefined; const content = shouldRenderDonorStack ? ( {donors.map((donor, index) => ( { if (donor.StringifiedId) { setInternalHighlightedVDisk(donor.StringifiedId); } }} onHidePopup={() => { setInternalHighlightedVDisk(undefined); }} {...donorVDiskProps} /> ))} ) : ( ); return (
{content}
); }