diff --git a/src/components/CompositeBar/CompositeBar.scss b/src/components/CompositeBar/CompositeBar.scss index 849c2aba..4054d602 100644 --- a/src/components/CompositeBar/CompositeBar.scss +++ b/src/components/CompositeBar/CompositeBar.scss @@ -12,4 +12,9 @@ $block: '.#{variables.$ns}composite-bar'; & &__root-menu-item[class] { background-color: transparent; } + + &__list:hover > div > div:first-child { + transform: translateY(0); + outline: solid 2px var(--g-color-line-info); + } } diff --git a/src/components/CompositeBar/CompositeBar.tsx b/src/components/CompositeBar/CompositeBar.tsx index cb985a1e..6fc27a15 100644 --- a/src/components/CompositeBar/CompositeBar.tsx +++ b/src/components/CompositeBar/CompositeBar.tsx @@ -1,6 +1,7 @@ import React, {FC, ReactNode, useCallback, useContext, useRef} from 'react'; import {List} from '@gravity-ui/uikit'; +import debounce from 'lodash/debounce'; import AutoSizer, {Size} from 'react-virtualized-auto-sizer'; import {useAsideHeaderContext} from '../AsideHeader/AsideHeaderContext'; @@ -23,6 +24,8 @@ import { import './CompositeBar.scss'; +const DEFAULT_DEBOUNCE_INTERVAL = 10; + const b = block('composite-bar'); export type CompositeBarItem = MenuItem | SubheaderMenuItem; @@ -46,6 +49,17 @@ type CompositeBarViewProps = CompositeBarProps & { collapseItems?: MenuItem[]; }; +function getTime() { + const now = new Date(); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + const milliseconds = String(now.getMilliseconds()).padStart(3, '0'); + + const formattedTime = `${hours}:${minutes}:${seconds}.${milliseconds}`; + return formattedTime; +} + const CompositeBarView: FC = ({ type, items, @@ -62,8 +76,46 @@ const CompositeBarView: FC = ({ active: multipleTooltipActive, activeIndex, lastClickedItemIndex, + hoverState, } = useContext(MultipleTooltipContext); const {compact} = useAsideHeaderContext(); + const [needRecalcTooltip, setNeedRecalcTooltip] = React.useState(false); + const prevHoverStateRef = React.useRef(); + + const handleTransitionRun = React.useCallback<(e: TransitionEvent) => void>( + (e) => { + if (e.target && e.target === ref.current?.refContainer.current?.node) { + const computedStyle = getComputedStyle(e.target as HTMLElement); + const isHovered = computedStyle.transform !== 'none'; + console.log('isHovered=', isHovered, getTime()); + if (hoverState !== isHovered) { + prevHoverStateRef.current = !isHovered; + console.log('prevHoverStateRef.current=', prevHoverStateRef.current); + setMultipleTooltipContextValue({hoverState: isHovered}); + setNeedRecalcTooltip(true); + } + } + }, + [multipleTooltip, multipleTooltipActive], + ); + + React.useEffect(() => { + if (ref.current?.refContainer.current?.node) { + const listNode = ref.current.refContainer.current.node as HTMLElement; + // this hack allow to detect hover events on element like browser css engine + listNode.style.setProperty('transition', 'transform 0.001ms step-start'); + listNode.style.setProperty('transition-behavior', 'allow-discrete'); + listNode.addEventListener('transitionrun', handleTransitionRun); + + return () => { + listNode.removeEventListener('transitionrun', handleTransitionRun); + listNode.style.removeProperty('transition-behavior'); + listNode.style.removeProperty('transition'); + }; + } + + return; + }, []); React.useEffect(() => { function handleBlurWindow() { @@ -80,20 +132,27 @@ const CompositeBarView: FC = ({ }, [multipleTooltip, multipleTooltipActive, setMultipleTooltipContextValue]); const onTooltipMouseEnter = useCallback( - (e: {clientX: number}) => { - if ( - multipleTooltip && - compact && - !multipleTooltipActive && - document.hasFocus() && - activeIndex !== lastClickedItemIndex && - e.clientX <= ASIDE_HEADER_COMPACT_WIDTH - ) { - setMultipleTooltipContextValue?.({ - active: true, - }); - } - }, + debounce( + (e: {clientX: number}) => { + if ( + multipleTooltip && + compact && + !multipleTooltipActive && + document.hasFocus() && + activeIndex !== lastClickedItemIndex && + e.clientX <= ASIDE_HEADER_COMPACT_WIDTH + ) { + setMultipleTooltipContextValue?.({ + active: true, + }); + } + }, + DEFAULT_DEBOUNCE_INTERVAL, + { + leading: true, + trailing: false, + }, + ), [ multipleTooltip, compact, @@ -104,34 +163,53 @@ const CompositeBarView: FC = ({ ], ); - const onTooltipMouseLeave = useCallback(() => { - if (multipleTooltip && multipleTooltipActive && document.hasFocus()) { - setMultipleTooltipContextValue?.({ - active: false, - lastClickedItemIndex: undefined, - }); - } - }, [multipleTooltip, multipleTooltipActive, setMultipleTooltipContextValue]); + const onTooltipMouseLeave = useCallback( + debounce( + () => { + if (multipleTooltip && multipleTooltipActive && document.hasFocus()) { + setMultipleTooltipContextValue?.({ + active: false, + activeIndex: undefined, + lastClickedItemIndex: undefined, + }); + } + }, + DEFAULT_DEBOUNCE_INTERVAL, + { + leading: true, + trailing: false, + }, + ), + [multipleTooltip, multipleTooltipActive, setMultipleTooltipContextValue], + ); const onMouseEnterByIndex = useCallback( - (itemIndex: number) => () => { - if (multipleTooltip && document.hasFocus()) { - let multipleTooltipActiveValue = multipleTooltipActive; - if (!multipleTooltipActive && itemIndex !== lastClickedItemIndex) { - multipleTooltipActiveValue = true; - } - if ( - activeIndex === itemIndex && - multipleTooltipActive === multipleTooltipActiveValue - ) { - return; - } - setMultipleTooltipContextValue({ - activeIndex: itemIndex, - active: multipleTooltipActiveValue, - }); - } - }, + (itemIndex: number) => + debounce( + () => { + if (multipleTooltip && document.hasFocus()) { + let multipleTooltipActiveValue = multipleTooltipActive; + if (!multipleTooltipActive && itemIndex !== lastClickedItemIndex) { + multipleTooltipActiveValue = true; + } + if ( + activeIndex === itemIndex && + multipleTooltipActive === multipleTooltipActiveValue + ) { + return; + } + setMultipleTooltipContextValue({ + activeIndex: itemIndex, + active: multipleTooltipActiveValue, + }); + } + }, + DEFAULT_DEBOUNCE_INTERVAL, + { + leading: true, + trailing: false, + }, + ), [ multipleTooltip, multipleTooltipActive, @@ -141,26 +219,36 @@ const CompositeBarView: FC = ({ ], ); - const onMouseLeave = useCallback(() => { - if (compact && document.hasFocus()) { - ref.current?.activateItem(undefined as unknown as number); - if ( - multipleTooltip && - (activeIndex !== undefined || lastClickedItemIndex !== undefined) - ) { - setMultipleTooltipContextValue({ - activeIndex: undefined, - lastClickedItemIndex: undefined, - }); - } - } - }, [ - activeIndex, - compact, - lastClickedItemIndex, - multipleTooltip, - setMultipleTooltipContextValue, - ]); + const onMouseLeave = useCallback( + debounce( + () => { + if (compact && document.hasFocus()) { + ref.current?.activateItem(undefined as unknown as number); + if ( + multipleTooltip && + (activeIndex !== undefined || lastClickedItemIndex !== undefined) + ) { + setMultipleTooltipContextValue({ + activeIndex: undefined, + lastClickedItemIndex: undefined, + }); + } + } + }, + DEFAULT_DEBOUNCE_INTERVAL, + { + leading: true, + trailing: false, + }, + ), + [ + activeIndex, + compact, + lastClickedItemIndex, + multipleTooltip, + setMultipleTooltipContextValue, + ], + ); const onItemClickByIndex = useCallback( (itemIndex: number): ItemProps['onItemClick'] => @@ -187,6 +275,19 @@ const CompositeBarView: FC = ({ ], ); + React.useEffect(() => { + console.log(`useEffect: hover=${hoverState}, prevHoverState=${prevHoverStateRef.current}`); + if (needRecalcTooltip && hoverState !== prevHoverStateRef.current) { + console.log( + `needRecalcTooltip: hover=${hoverState}, active=${multipleTooltipActive}, activeIndex=${activeIndex}`, + ); + setMultipleTooltipContextValue({ + active: multipleTooltipActive && hoverState === true, + }); + setNeedRecalcTooltip(false); + } + }, [needRecalcTooltip, hoverState]); + return (
= ({ onMouseLeave={onTooltipMouseLeave} > + className={b('list', {hover: hoverState})} ref={ref} items={items} selectedItemIndex={type === 'menu' ? getSelectedItemIndex(items) : undefined} diff --git a/src/components/CompositeBar/MultipleTooltip/MultipleTooltipContext.tsx b/src/components/CompositeBar/MultipleTooltip/MultipleTooltipContext.tsx index 817ebead..8c22b0a8 100644 --- a/src/components/CompositeBar/MultipleTooltip/MultipleTooltipContext.tsx +++ b/src/components/CompositeBar/MultipleTooltip/MultipleTooltipContext.tsx @@ -5,6 +5,7 @@ interface MultipleTooltipContextProps { activeIndex: number | undefined; hideCollapseItemTooltip: boolean; lastClickedItemIndex: number | undefined; + hoverState: boolean | undefined; setValue>( value: | Pick, K> @@ -17,6 +18,7 @@ const multipleTooltipContextDefaults = { activeIndex: undefined, hideCollapseItemTooltip: false, lastClickedItemIndex: undefined, + hoverState: undefined, setValue: () => {}, };