diff --git a/frontend/src/components/shared/AllToolsNavButton.tsx b/frontend/src/components/shared/AllToolsNavButton.tsx index 45c618db0..62fad704e 100644 --- a/frontend/src/components/shared/AllToolsNavButton.tsx +++ b/frontend/src/components/shared/AllToolsNavButton.tsx @@ -5,6 +5,7 @@ import { Tooltip } from './Tooltip'; import AppsIcon from '@mui/icons-material/AppsRounded'; import { useToolWorkflow } from '../../contexts/ToolWorkflowContext'; import { useSidebarNavigation } from '../../hooks/useSidebarNavigation'; +import { handleUnlessSpecialClick } from '../../utils/clickHandlers'; interface AllToolsNavButtonProps { activeButton: string; @@ -29,14 +30,7 @@ const AllToolsNavButton: React.FC = ({ activeButton, set const navProps = getHomeNavigation(); const handleNavClick = (e: React.MouseEvent) => { - // Check if it's a special click (middle click, ctrl+click, etc.) - if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) { - return; // Let browser handle it via href - } - - // For regular clicks, prevent default and use SPA navigation - e.preventDefault(); - handleClick(); + handleUnlessSpecialClick(e, handleClick); }; const iconNode = ( diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx index 290b5387c..9930c3e10 100644 --- a/frontend/src/components/shared/QuickAccessBar.tsx +++ b/frontend/src/components/shared/QuickAccessBar.tsx @@ -8,6 +8,7 @@ import { useIsOverflowing } from '../../hooks/useIsOverflowing'; import { useFilesModalContext } from '../../contexts/FilesModalContext'; import { useToolWorkflow } from '../../contexts/ToolWorkflowContext'; import { useSidebarNavigation } from '../../hooks/useSidebarNavigation'; +import { handleUnlessSpecialClick } from '../../utils/clickHandlers'; import { ButtonConfig } from '../../types/sidebar'; import './quickAccessBar/QuickAccessBar.css'; import AllToolsNavButton from './AllToolsNavButton'; @@ -50,14 +51,10 @@ const QuickAccessBar = forwardRef(({ const handleClick = (e?: React.MouseEvent) => { if (navProps && e) { - // Check if it's a special click (middle click, ctrl+click, etc.) - if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) { - return; // Let browser handle it via href - } - // For regular clicks, prevent default and use SPA navigation - e.preventDefault(); + handleUnlessSpecialClick(e, config.onClick); + } else { + config.onClick(); } - config.onClick(); }; // Render with URL navigation if available, otherwise regular div diff --git a/frontend/src/components/shared/quickAccessBar/ActiveToolButton.tsx b/frontend/src/components/shared/quickAccessBar/ActiveToolButton.tsx index 830b7f403..60c915593 100644 --- a/frontend/src/components/shared/quickAccessBar/ActiveToolButton.tsx +++ b/frontend/src/components/shared/quickAccessBar/ActiveToolButton.tsx @@ -17,6 +17,7 @@ import { ActionIcon } from '@mantine/core'; import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded'; import { useToolWorkflow } from '../../../contexts/ToolWorkflowContext'; import { useSidebarNavigation } from '../../../hooks/useSidebarNavigation'; +import { handleUnlessSpecialClick } from '../../../utils/clickHandlers'; import FitText from '../FitText'; import { Tooltip } from '../Tooltip'; @@ -147,15 +148,10 @@ const ActiveToolButton: React.FC = ({ activeButton, setAc component="a" href={getHomeNavigation().href} onClick={(e: React.MouseEvent) => { - // Check if it's a special click (middle click, ctrl+click, etc.) - if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) { - return; // Let browser handle it via href - } - - // For regular clicks, prevent default and use SPA navigation - e.preventDefault(); - setActiveButton('tools'); - handleBackToTools(); + handleUnlessSpecialClick(e, () => { + setActiveButton('tools'); + handleBackToTools(); + }); }} size={'xl'} variant="subtle" diff --git a/frontend/src/components/tools/toolPicker/ToolButton.tsx b/frontend/src/components/tools/toolPicker/ToolButton.tsx index 164b3bc96..ee9c6062c 100644 --- a/frontend/src/components/tools/toolPicker/ToolButton.tsx +++ b/frontend/src/components/tools/toolPicker/ToolButton.tsx @@ -1,8 +1,9 @@ import React from "react"; -import { Button, Anchor } from "@mantine/core"; +import { Button } from "@mantine/core"; import { Tooltip } from "../../shared/Tooltip"; import { ToolRegistryEntry } from "../../../data/toolsTaxonomy"; import { useToolNavigation } from "../../../hooks/useToolNavigation"; +import { handleUnlessSpecialClick } from "../../../utils/clickHandlers"; import FitText from "../../shared/FitText"; interface ToolButtonProps { @@ -49,14 +50,7 @@ const ToolButton: React.FC = ({ id, tool, isSelected, onSelect ); const handleExternalClick = (e: React.MouseEvent) => { - // Check if it's a special click (ctrl+click, etc.) - if (e.metaKey || e.ctrlKey || e.shiftKey) { - return; // Let browser handle it via href - } - - // For regular clicks, prevent default and use window.open - e.preventDefault(); - handleClick(id); + handleUnlessSpecialClick(e, () => handleClick(id)); }; const buttonElement = navProps ? ( diff --git a/frontend/src/hooks/useSidebarNavigation.ts b/frontend/src/hooks/useSidebarNavigation.ts index 995248ba5..fb60e2502 100644 --- a/frontend/src/hooks/useSidebarNavigation.ts +++ b/frontend/src/hooks/useSidebarNavigation.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import { useToolNavigation } from './useToolNavigation'; import { useToolManagement } from './useToolManagement'; +import { handleUnlessSpecialClick } from '../utils/clickHandlers'; export interface SidebarNavigationProps { /** Full URL for the navigation (for href attribute) */ @@ -21,8 +22,9 @@ export function useSidebarNavigation(): { const { getSelectedTool } = useToolManagement(); const defaultNavClick = useCallback((e: React.MouseEvent) => { - if (e.metaKey || e.ctrlKey || e.shiftKey) return; - e.preventDefault(); + handleUnlessSpecialClick(e, () => { + // SPA navigation will be handled by the calling component + }); }, []); const getHomeNavigation = useCallback((): SidebarNavigationProps => { diff --git a/frontend/src/hooks/useToolNavigation.ts b/frontend/src/hooks/useToolNavigation.ts index f17f8ac86..021ea6432 100644 --- a/frontend/src/hooks/useToolNavigation.ts +++ b/frontend/src/hooks/useToolNavigation.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react'; import { ToolId } from '../types/toolId'; import { ToolRegistryEntry, getToolUrlPath } from '../data/toolsTaxonomy'; import { useToolWorkflow } from '../contexts/ToolWorkflowContext'; +import { handleUnlessSpecialClick } from '../utils/clickHandlers'; export interface ToolNavigationProps { /** Full URL for the tool (for href attribute) */ @@ -26,20 +27,16 @@ export function useToolNavigation(): { // Click handler that maintains SPA behavior const onClick = (e: React.MouseEvent) => { - // Check if it's a special click (ctrl+click, etc.) - if (e.metaKey || e.ctrlKey || e.shiftKey) { - return; // Let browser handle it via href - } + handleUnlessSpecialClick(e, () => { + // Handle external links normally + if (tool.link) { + window.open(tool.link, '_blank', 'noopener,noreferrer'); + return; + } - // Handle external links normally - if (tool.link) { - window.open(tool.link, '_blank', 'noopener,noreferrer'); - return; - } - - // For regular clicks, prevent default and use SPA navigation - e.preventDefault(); - handleToolSelect(toolId); + // Use SPA navigation for internal tools + handleToolSelect(toolId); + }); }; return { href, onClick }; diff --git a/frontend/src/utils/clickHandlers.ts b/frontend/src/utils/clickHandlers.ts new file mode 100644 index 000000000..0c70cfca8 --- /dev/null +++ b/frontend/src/utils/clickHandlers.ts @@ -0,0 +1,31 @@ +/** + * Utility functions for handling click events in navigation components + */ + +/** + * Determines if a click event is a "special" click that should use browser's default navigation + * instead of SPA navigation. Special clicks include: + * - Ctrl+click (or Cmd+click on Mac) + * - Shift+click + * - Middle mouse button click + */ +export function isSpecialClick(e: React.MouseEvent): boolean { + return e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1; +} + +/** + * Handles a click event for SPA navigation, but allows special clicks to use browser defaults + * + * @param e - The click event + * @param handleClick - Function to execute for regular clicks (SPA navigation) + * @returns true if the event was handled as a special click, false if it was handled as regular click + */ +export function handleUnlessSpecialClick(e: React.MouseEvent, handleClick: () => void): boolean { + if (isSpecialClick(e)) { + return true; // Let browser handle via href + } + + e.preventDefault(); + handleClick(); + return false; +} \ No newline at end of file