diff --git a/src/renderer/src/pages/home/Messages/ChatNavigation.tsx b/src/renderer/src/pages/home/Messages/ChatNavigation.tsx index faf96d71..2d8bbc41 100644 --- a/src/renderer/src/pages/home/Messages/ChatNavigation.tsx +++ b/src/renderer/src/pages/home/Messages/ChatNavigation.tsx @@ -1,7 +1,7 @@ import { DownOutlined, UpOutlined } from '@ant-design/icons' import { useSettings } from '@renderer/hooks/useSettings' import { Button, Tooltip } from 'antd' -import { FC, useCallback, useEffect, useState } from 'react' +import { FC, useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -12,21 +12,52 @@ interface ChatNavigationProps { const ChatNavigation: FC = ({ containerId }) => { const { t } = useTranslation() const [isVisible, setIsVisible] = useState(false) + const [isNearButtons, setIsNearButtons] = useState(false) const [hideTimer, setHideTimer] = useState(null) + const lastMoveTime = useRef(0) const { topicPosition, showTopics } = useSettings() const showRightTopics = topicPosition === 'right' && showTopics const right = showRightTopics ? 'calc(var(--topic-list-width) + 16px)' : '16px' + // Reset hide timer and make buttons visible const resetHideTimer = useCallback(() => { if (hideTimer) { clearTimeout(hideTimer) } + setIsVisible(true) + + // Only set a hide timer if cursor is not near the buttons + if (!isNearButtons) { + const timer = setTimeout(() => { + setIsVisible(false) + }, 1500) + setHideTimer(timer) + } + }, [hideTimer, isNearButtons]) + + // Handle mouse entering button area + const handleMouseEnter = useCallback(() => { + setIsNearButtons(true) + setIsVisible(true) + + // Clear any existing hide timer + if (hideTimer) { + clearTimeout(hideTimer) + setHideTimer(null) + } + }, [hideTimer]) + + // Handle mouse leaving button area + const handleMouseLeave = useCallback(() => { + setIsNearButtons(false) + + // Set a timer to hide the buttons const timer = setTimeout(() => { setIsVisible(false) - }, 1000) + }, 1500) setHideTimer(timer) - }, [hideTimer]) + }, []) const findUserMessages = () => { const container = document.getElementById(containerId) @@ -155,63 +186,107 @@ const ChatNavigation: FC = ({ containerId }) => { scrollToMessage(userMessages[targetIndex]) } + // Set up scroll event listener and mouse position tracking useEffect(() => { const container = document.getElementById(containerId) if (!container) return + // Handle scroll events on the container const handleScroll = () => { - setIsVisible(true) - resetHideTimer() + // Only show buttons when scrolling if cursor is near the button area + if (isNearButtons) { + resetHideTimer() + } } - container.addEventListener('scroll', handleScroll) + // Throttled mouse move handler to improve performance + const handleMouseMove = (e: MouseEvent) => { + // Throttle mouse move to every 50ms for performance + const now = Date.now() + if (now - lastMoveTime.current < 50) return + lastMoveTime.current = now + + // Calculate if the mouse is in the trigger area + const triggerWidth = 80 // Same as the width in styled component + + // Safe way to calculate position when using calc expressions + let rightOffset = 16 // Default right offset + if (showRightTopics) { + // When topics are shown on right, we need to account for topic list width + rightOffset = 16 + 300 // Assuming topic list width is 300px, adjust if different + } + + const rightPosition = window.innerWidth - rightOffset - triggerWidth + const topPosition = window.innerHeight * 0.3 // 30% from top + const height = window.innerHeight * 0.4 // 40% of window height + + const isInTriggerArea = + e.clientX > rightPosition && + e.clientX < rightPosition + triggerWidth && + e.clientY > topPosition && + e.clientY < topPosition + height + + // Update state based on mouse position + if (isInTriggerArea && !isNearButtons) { + handleMouseEnter() + } else if (!isInTriggerArea && isNearButtons) { + // Only trigger mouse leave when not in the navigation area + // This ensures we don't leave when hovering over the actual buttons + handleMouseLeave() + } + } + + // Use passive: true for better scroll performance + container.addEventListener('scroll', handleScroll, { passive: true }) + window.addEventListener('mousemove', handleMouseMove) + return () => { container.removeEventListener('scroll', handleScroll) + window.removeEventListener('mousemove', handleMouseMove) if (hideTimer) { clearTimeout(hideTimer) } } - }, [containerId, hideTimer, resetHideTimer]) + }, [ + containerId, + hideTimer, + resetHideTimer, + isNearButtons, + handleMouseEnter, + handleMouseLeave, + right, + showRightTopics + ]) return ( - <> - setIsVisible(true)} onMouseLeave={() => resetHideTimer()} /> - - - - } - onClick={handlePrevMessage} - aria-label={t('chat.navigation.prev')} - onMouseLeave={() => resetHideTimer()} - /> - - - - } - onClick={handleNextMessage} - aria-label={t('chat.navigation.next')} - onMouseLeave={() => resetHideTimer()} - /> - - - - + + + + } + onClick={handlePrevMessage} + aria-label={t('chat.navigation.prev')} + /> + + + + } + onClick={handleNextMessage} + aria-label={t('chat.navigation.next')} + /> + + + ) } -const TriggerArea = styled.div<{ $right: string }>` - position: fixed; - right: ${(props) => props.$right}; - top: 40%; - width: 20px; - height: 20%; - z-index: 998; -` - interface NavigationContainerProps { $isVisible: boolean $right: string @@ -228,12 +303,6 @@ const NavigationContainer = styled.div` transform 0.3s ease-in-out, opacity 0.3s ease-in-out; pointer-events: ${(props) => (props.$isVisible ? 'auto' : 'none')}; - - &:hover { - transform: translateY(-50%) translateX(0); - opacity: 1; - pointer-events: auto; - } ` const ButtonGroup = styled.div`