From be8ab09b3c1aa00ec543be3d14cc1fdd419b4e93 Mon Sep 17 00:00:00 2001 From: EthanHealy01 Date: Mon, 18 Aug 2025 17:02:42 +0100 Subject: [PATCH] change requests --- .../public/locales/en-GB/translation.json | 4 - .../src/components/shared/QuickAccessBar.tsx | 17 ++-- frontend/src/components/shared/Tooltip.tsx | 8 +- .../quickAccessBar/ActiveToolButton.tsx | 79 ++++++++++++++----- .../shared/quickAccessBar/QuickAccessBar.css | 8 ++ .../shared/quickAccessBar/QuickAccessBar.ts | 53 +++++++++---- 6 files changed, 116 insertions(+), 53 deletions(-) diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index eedfe350c..ae4c01f41 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -591,10 +591,6 @@ "title": "Advanced Colour options", "desc": "Replace colour for text and background in PDF and invert full colour of pdf to reduce file size" }, - "EMLToPDF": { - "title": "Email to PDF", - "desc": "Converts email (EML) files to PDF format including headers, body, and inline images" - }, "fakeScan": { "title": "Fake Scan", "desc": "Create a PDF that looks like it was scanned" diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx index 925890890..35e9480c4 100644 --- a/frontend/src/components/shared/QuickAccessBar.tsx +++ b/frontend/src/components/shared/QuickAccessBar.tsx @@ -31,7 +31,8 @@ const QuickAccessBar = forwardRef(({ const isOverflow = useIsOverflowing(scrollableRef); useEffect(() => { - setActiveButton(getActiveNavButton(leftPanelView, selectedToolKey, toolRegistry as any, readerMode)); + const next = getActiveNavButton(leftPanelView, selectedToolKey, toolRegistry as any, readerMode); + setActiveButton(next); }, [leftPanelView, selectedToolKey, toolRegistry, readerMode]); const handleFilesButtonClick = () => { @@ -159,20 +160,20 @@ const QuickAccessBar = forwardRef(({
{ config.onClick(); }} - style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen)} - className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen) ? 'activeIconScale' : ''} + style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView)} + className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'activeIconScale' : ''} data-testid={`${config.id}-button`} > {config.icon} - + {config.name}
@@ -201,15 +202,15 @@ const QuickAccessBar = forwardRef(({ size={config.size || 'lg'} variant="subtle" onClick={config.onClick} - style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen)} - className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen) ? 'activeIconScale' : ''} + style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView)} + className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'activeIconScale' : ''} data-testid={`${config.id}-button`} > {config.icon} - + {config.name} diff --git a/frontend/src/components/shared/Tooltip.tsx b/frontend/src/components/shared/Tooltip.tsx index 9830bd22d..0acc78057 100644 --- a/frontend/src/components/shared/Tooltip.tsx +++ b/frontend/src/components/shared/Tooltip.tsx @@ -199,9 +199,7 @@ export const Tooltip: React.FC = ({ ) : null; const handleMouseEnter = (e: React.MouseEvent) => { - if (openTimeoutRef.current) { - clearTimeout(openTimeoutRef.current); - } + clearTimers(); if (!isPinned) { const effectiveDelay = Math.max(0, delay || 0); openTimeoutRef.current = setTimeout(() => { @@ -213,10 +211,8 @@ export const Tooltip: React.FC = ({ }; const handleMouseLeave = (e: React.MouseEvent) => { - if (openTimeoutRef.current) { - clearTimeout(openTimeoutRef.current); + clearTimers(); openTimeoutRef.current = null; - } if (!isPinned) { handleOpenChange(false); diff --git a/frontend/src/components/shared/quickAccessBar/ActiveToolButton.tsx b/frontend/src/components/shared/quickAccessBar/ActiveToolButton.tsx index cceaacc70..8362c8224 100644 --- a/frontend/src/components/shared/quickAccessBar/ActiveToolButton.tsx +++ b/frontend/src/components/shared/quickAccessBar/ActiveToolButton.tsx @@ -13,7 +13,7 @@ */ import React, { useEffect, useRef, useState } from 'react'; -import { ActionIcon, Divider } from '@mantine/core'; +import { ActionIcon } from '@mantine/core'; import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded'; import { useToolWorkflow } from '../../../contexts/ToolWorkflowContext'; import FitText from '../FitText'; @@ -29,9 +29,9 @@ const NAV_IDS = ['read', 'sign', 'automate']; const ActiveToolButton: React.FC = ({ activeButton, setActiveButton }) => { const { selectedTool, selectedToolKey, leftPanelView, handleBackToTools } = useToolWorkflow(); - // Determine if the indicator should be visible + // Determine if the indicator should be visible (do not require selectedTool to be resolved yet) const indicatorShouldShow = Boolean( - selectedToolKey && selectedTool && leftPanelView === 'toolContent' && !NAV_IDS.includes(selectedToolKey) + selectedToolKey && leftPanelView === 'toolContent' && !NAV_IDS.includes(selectedToolKey) ); // Local animation and hover state @@ -41,53 +41,99 @@ const ActiveToolButton: React.FC = ({ activeButton, setAc const [isAnimating, setIsAnimating] = useState(false); const [isBackHover, setIsBackHover] = useState(false); const prevKeyRef = useRef(null); + const collapseTimeoutRef = useRef(null); + const animTimeoutRef = useRef(null); + const replayRafRef = useRef(null); const isSwitchingToNewTool = () => { return prevKeyRef.current && prevKeyRef.current !== selectedToolKey }; - const playGrowDown = () => { + const clearTimers = () => { + if (collapseTimeoutRef.current) { + window.clearTimeout(collapseTimeoutRef.current); + collapseTimeoutRef.current = null; + } + if (animTimeoutRef.current) { + window.clearTimeout(animTimeoutRef.current); + animTimeoutRef.current = null; + } + }; + const playGrowDown = () => { + clearTimers(); setIndicatorTool(selectedTool); setIndicatorVisible(true); - setReplayAnim(true); + // Force a replay even if the class is already applied + setReplayAnim(false); + if (replayRafRef.current) { + cancelAnimationFrame(replayRafRef.current); + replayRafRef.current = null; + } + replayRafRef.current = requestAnimationFrame(() => { + setReplayAnim(true); + }); setIsAnimating(true); - const t = window.setTimeout(() => { + prevKeyRef.current = (selectedToolKey as string) || null; + animTimeoutRef.current = window.setTimeout(() => { setReplayAnim(false); setIsAnimating(false); + animTimeoutRef.current = null; }, 500); - return () => window.clearTimeout(t); } const firstShow = () => { + clearTimers(); setIndicatorTool(selectedTool); setIndicatorVisible(true); setIsAnimating(true); prevKeyRef.current = (selectedToolKey as string) || null; - const tShow = window.setTimeout(() => setIsAnimating(false), 500); - return () => window.clearTimeout(tShow); + animTimeoutRef.current = window.setTimeout(() => { + setIsAnimating(false); + animTimeoutRef.current = null; + }, 500); } const triggerCollapse = () => { + clearTimers(); setIndicatorVisible(false); setIsAnimating(true); - const timeout = window.setTimeout(() => { + collapseTimeoutRef.current = window.setTimeout(() => { setIndicatorTool(null); prevKeyRef.current = null; setIsAnimating(false); + collapseTimeoutRef.current = null; }, 500); // match CSS transition duration - return () => window.clearTimeout(timeout); } useEffect(() => { if (indicatorShouldShow) { - if (isSwitchingToNewTool()) { - playGrowDown(); + clearTimers(); + if (!indicatorVisible) { + firstShow(); + return; } - firstShow() - } else if (indicatorTool) { + if (!indicatorTool) { + firstShow(); + } else if (isSwitchingToNewTool()) { + playGrowDown(); + } else { + // keep reference in sync + prevKeyRef.current = (selectedToolKey as string) || null; + } + } else if (indicatorTool || indicatorVisible) { triggerCollapse(); } }, [indicatorShouldShow, selectedTool, selectedToolKey]); + useEffect(() => { + return () => { + clearTimers(); + if (replayRafRef.current) { + cancelAnimationFrame(replayRafRef.current); + replayRafRef.current = null; + } + }; + }, []); + return ( <>
@@ -133,9 +179,6 @@ const ActiveToolButton: React.FC = ({ activeButton, setAc
)} - {(indicatorTool && !isAnimating) && ( - - )} ); }; diff --git a/frontend/src/components/shared/quickAccessBar/QuickAccessBar.css b/frontend/src/components/shared/quickAccessBar/QuickAccessBar.css index ce02d7c52..05f226417 100644 --- a/frontend/src/components/shared/quickAccessBar/QuickAccessBar.css +++ b/frontend/src/components/shared/quickAccessBar/QuickAccessBar.css @@ -212,6 +212,9 @@ .current-tool-slot.visible { max-height: 8.25rem; /* icon + up to 3-line label + divider (132px) */ opacity: 1; + border-bottom: 1px solid var(--color-gray-300); + padding-bottom: 0.75rem; /* push border down for spacing */ + margin-bottom: 1rem; } /* Replay the grow-down animation when switching tools while visible */ @@ -219,6 +222,11 @@ animation: currentToolGrowDown 450ms ease-out; } +/* Also animate the container itself when replaying so it "pushes down" again */ +.current-tool-slot.replay { + animation: currentToolGrowDown 450ms ease-out; +} + @keyframes currentToolGrowDown { 0% { max-height: 0; diff --git a/frontend/src/components/shared/quickAccessBar/QuickAccessBar.ts b/frontend/src/components/shared/quickAccessBar/QuickAccessBar.ts index 8f0fc826c..b5ad79b42 100644 --- a/frontend/src/components/shared/quickAccessBar/QuickAccessBar.ts +++ b/frontend/src/components/shared/quickAccessBar/QuickAccessBar.ts @@ -8,28 +8,44 @@ export const ROUND_BORDER_RADIUS = '0.5rem'; * Check if a navigation button is currently active */ export const isNavButtonActive = ( - config: ButtonConfig, - activeButton: string, - isFilesModalOpen: boolean, - configModalOpen: boolean + config: ButtonConfig, + activeButton: string, + isFilesModalOpen: boolean, + configModalOpen: boolean, + selectedToolKey?: string | null, + leftPanelView?: 'toolPicker' | 'toolContent' ): boolean => { - return ( - (config.type === 'navigation' && activeButton === config.id) || + const isActiveByLocalState = config.type === 'navigation' && activeButton === config.id; + const isActiveByContext = + config.type === 'navigation' && + leftPanelView === 'toolContent' && + selectedToolKey === config.id; + const isActiveByModal = (config.type === 'modal' && config.id === 'files' && isFilesModalOpen) || - (config.type === 'modal' && config.id === 'config' && configModalOpen) - ); + (config.type === 'modal' && config.id === 'config' && configModalOpen); + + return isActiveByLocalState || isActiveByContext || isActiveByModal; }; /** * Get button styles based on active state */ export const getNavButtonStyle = ( - config: ButtonConfig, - activeButton: string, - isFilesModalOpen: boolean, - configModalOpen: boolean + config: ButtonConfig, + activeButton: string, + isFilesModalOpen: boolean, + configModalOpen: boolean, + selectedToolKey?: string | null, + leftPanelView?: 'toolPicker' | 'toolContent' ) => { - const isActive = isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen); + const isActive = isNavButtonActive( + config, + activeButton, + isFilesModalOpen, + configModalOpen, + selectedToolKey, + leftPanelView + ); if (isActive) { return { @@ -75,9 +91,12 @@ export const getActiveNavButton = ( if (readerMode) { return 'read'; } - if (leftPanelView !== 'toolContent' || !selectedToolKey) { - return 'tools'; + // If a tool is selected, highlight it immediately even if the panel view + // transition to 'toolContent' has not completed yet. This prevents a brief + // period of no-highlight during rapid navigation. + if (selectedToolKey) { + return getTargetNavButton(selectedToolKey, registry) || selectedToolKey; } - - return getTargetNavButton(selectedToolKey, registry) || 'tools'; + // Default to All Tools when no tool is selected + return 'tools'; };