@@ -3,15 +3,11 @@ import React from 'react';
33import { Button , Flex , PortalProvider } from '@gravity-ui/uikit' ;
44import { createPortal } from 'react-dom' ;
55
6- import { getDiskStateColorMods } from '../../../components/DiskStateProgressBar/DiskStateProgressBar' ;
76import { SETTING_KEYS } from '../../../store/reducers/settings/constants' ;
87import { cn } from '../../../utils/cn' ;
9- import {
10- getDefaultDiskDisplayState ,
11- getDefaultPDiskDisplayState ,
12- } from '../../../utils/disks/displayState' ;
13- import { getDiskBarTone } from '../../../utils/disks/getDiskBarTone' ;
14- import type { DiskBarTone , PreparedPDisk , PreparedVDisk } from '../../../utils/disks/types' ;
8+ import { DONOR_COLOR } from '../../../utils/disks/constants' ;
9+ import { getDataSeverityColor } from '../../../utils/disks/helpers' ;
10+ import type { PreparedPDisk , PreparedVDisk } from '../../../utils/disks/types' ;
1511import { useSetting } from '../../../utils/hooks/useSetting' ;
1612import { isNumeric } from '../../../utils/utils' ;
1713import { PDisk } from '../PDisk' ;
@@ -62,13 +58,16 @@ interface PDiskPreviewItemProps extends PDiskPreviewSvgProps {
6258 id : string ;
6359 expandedWidth : number ;
6460 highlightedDisk ?: string ;
65- setHighlightedDisk : ( id ?: string ) => void ;
61+ setHighlightedDisk : React . Dispatch < React . SetStateAction < string | undefined > > ;
6662}
6763
64+ // The preview uses ordinary prepared-data severity, independently of Expert mode display states.
65+ type PreviewTone = ReturnType < typeof getDataSeverityColor > | typeof DONOR_COLOR ;
66+
6867interface VDiskPath {
6968 key : string ;
7069 path : string ;
71- tone : DiskBarTone ;
70+ tone : PreviewTone ;
7271 inactive : boolean ;
7372}
7473
@@ -86,8 +85,8 @@ function getFilledSize(percent: unknown, size: number, inverted = false) {
8685 return ( filledPercent / 100 ) * size ;
8786}
8887
89- function getBarClassName ( element : string , tone : DiskBarTone , highlighted ?: boolean ) {
90- return b ( 'color' , getDiskStateColorMods ( tone , highlighted ) , b ( element ) ) ;
88+ function getBarClassName ( element : string , tone : PreviewTone , highlighted ?: boolean ) {
89+ return b ( 'color' , { [ tone . toLowerCase ( ) ] : true , highlighted} , b ( element ) ) ;
9190}
9291
9392function getPDiskKey ( pDisk : PreparedPDisk , index : number ) {
@@ -102,10 +101,7 @@ function getVDiskPaths(vDisks: PreparedVDisk[], viewContext?: StorageViewContext
102101 const row = index % VDISKS_WITHOUT_PDISK_COLUMN ;
103102 const x = VDISK_X + column * ( VDISK_SIZE + VDISK_COLUMN_GAP ) ;
104103 const y = row * ( VDISK_SIZE + VDISK_GAP ) ;
105- const tone = getDiskBarTone ( {
106- severity : getDefaultDiskDisplayState ( vDisk , vDisk . DonorMode ) . severity ,
107- isDonor : vDisk . DonorMode ,
108- } ) ;
104+ const tone = vDisk . DonorMode ? DONOR_COLOR : getDataSeverityColor ( vDisk . Severity ) ;
109105 const inactive = ! isVdiskActive ( vDisk , viewContext ) ;
110106 const key = `${ tone } -${ inactive } ` ;
111107 const rectPath = `M${ x } ${ y } h${ VDISK_SIZE } v${ VDISK_SIZE } h-${ VDISK_SIZE } z` ;
@@ -143,9 +139,8 @@ function PDiskPreviewSvg({
143139 highlighted,
144140 inverted,
145141} : PDiskPreviewSvgProps ) {
146- const displayState = getDefaultPDiskDisplayState ( pDisk ) ;
147- const pDiskTone = getDiskBarTone ( { severity : displayState . severity } ) ;
148- const pDiskFilledHeight = getFilledSize ( displayState . allocatedPercent , PDISK_HEIGHT , inverted ) ;
142+ const pDiskTone = getDataSeverityColor ( pDisk . Severity ) ;
143+ const pDiskFilledHeight = getFilledSize ( pDisk . AllocatedPercent , PDISK_HEIGHT , inverted ) ;
149144 const pDiskFillY = inverted ? PDISK_Y : PDISK_Y + PDISK_HEIGHT - pDiskFilledHeight ;
150145 const vDiskPaths = React . useMemo (
151146 ( ) => getVDiskPaths ( vDisks , viewContext ) ,
@@ -232,9 +227,22 @@ function PDiskPreviewItem({
232227 const [ previewFocused , setPreviewFocused ] = React . useState ( false ) ;
233228 const popupContainerRef = React . useRef < HTMLElement | null > ( null ) ;
234229 const detailsRef = React . useRef < HTMLDivElement > ( null ) ;
230+ const diskLinkRef = React . useRef < HTMLAnchorElement > ( null ) ;
231+ const previewRef = React . useRef < HTMLButtonElement > ( null ) ;
232+ const transferFocusRef = React . useRef ( false ) ;
235233 const popupHostRef = React . useRef < HTMLDivElement > ( null ) ;
236234 const [ popupAvailableHeight , setPopupAvailableHeight ] = React . useState ( 0 ) ;
237235
236+ React . useLayoutEffect ( ( ) => {
237+ if ( ! transferFocusRef . current ) {
238+ return ;
239+ }
240+ transferFocusRef . current = false ;
241+ // Transfer keyboard focus after React replaces the activated control.
242+ const target = detailsOpened ? diskLinkRef . current : previewRef . current ;
243+ target ?. focus ( { preventScroll : true } ) ;
244+ } , [ detailsOpened ] ) ;
245+
238246 React . useLayoutEffect ( ( ) => {
239247 const container = popupContainerRef . current ;
240248 const details = detailsRef . current ;
@@ -264,15 +272,26 @@ function PDiskPreviewItem({
264272 window . removeEventListener ( 'resize' , updatePopupHeight ) ;
265273 } ;
266274 } , [ detailsOpened , highlightedDisk ] ) ;
275+ const clearOwnHighlight = React . useCallback ( ( ) => {
276+ setHighlightedDisk ( ( current ) =>
277+ current === id || vDisks . some ( ( disk ) => disk . StringifiedId === current )
278+ ? undefined
279+ : current ,
280+ ) ;
281+ } , [ id , vDisks , setHighlightedDisk ] ) ;
267282 const detailsVersionRef = React . useRef ( 0 ) ;
268283 const detailsVersion = detailsVersionRef . current ;
269284 const setDetailsHighlightedDisk = React . useCallback (
270285 ( diskId ?: string ) => {
271286 if ( detailsVersionRef . current === detailsVersion ) {
272- setHighlightedDisk ( diskId ) ;
287+ if ( diskId === undefined ) {
288+ clearOwnHighlight ( ) ;
289+ } else {
290+ setHighlightedDisk ( diskId ) ;
291+ }
273292 }
274293 } ,
275- [ detailsVersion , setHighlightedDisk ] ,
294+ [ detailsVersion , setHighlightedDisk , clearOwnHighlight ] ,
276295 ) ;
277296
278297 const closeDetails = React . useCallback ( ( ) => {
@@ -281,8 +300,8 @@ function PDiskPreviewItem({
281300 setPreviewHovered ( false ) ;
282301 setPreviewFocused ( false ) ;
283302 setDetailsOpened ( false ) ;
284- setHighlightedDisk ( undefined ) ;
285- } , [ setHighlightedDisk ] ) ;
303+ clearOwnHighlight ( ) ;
304+ } , [ clearOwnHighlight ] ) ;
286305
287306 const handleOpenedDetailsClick = React . useCallback (
288307 ( event : React . MouseEvent < unknown > ) => {
@@ -295,6 +314,7 @@ function PDiskPreviewItem({
295314
296315 event . preventDefault ( ) ;
297316 event . stopPropagation ( ) ;
317+ transferFocusRef . current = event . detail === 0 ;
298318 closeDetails ( ) ;
299319 } ,
300320 [ closeDetails ] ,
@@ -305,12 +325,14 @@ function PDiskPreviewItem({
305325 < Button
306326 view = "flat"
307327 className = { b ( 'control' ) }
328+ ref = { previewRef }
308329 onMouseEnter = { ( ) => setPreviewHovered ( true ) }
309330 onMouseLeave = { ( ) => setPreviewHovered ( false ) }
310331 onFocus = { ( ) => setPreviewFocused ( true ) }
311332 onBlur = { ( ) => setPreviewFocused ( false ) }
312333 onClick = { ( event ) => {
313334 event . stopPropagation ( ) ;
335+ transferFocusRef . current = event . detail === 0 ;
314336 // Keep popup wheel events in the table's native scroll container.
315337 let container = event . currentTarget . parentElement ;
316338 while (
@@ -361,6 +383,7 @@ function PDiskPreviewItem({
361383 onClickCapture = { handleOpenedDetailsClick }
362384 >
363385 < PDisk
386+ linkRef = { diskLinkRef }
364387 data = { pDisk }
365388 inactive = { ! isPdiskActive ( pDisk , viewContext ) }
366389 vDisks = { vDisks }
0 commit comments