From f7a4aaa766a33041d6d3c11e69af3922d0e329f5 Mon Sep 17 00:00:00 2001 From: Connor Yoh Date: Mon, 25 Aug 2025 13:30:08 +0100 Subject: [PATCH] Drop downs appear correctly --- .../tools/automate/ToolSelector.tsx | 176 +++++++++++------- .../tools/toolPicker/ToolSearch.tsx | 15 +- 2 files changed, 117 insertions(+), 74 deletions(-) diff --git a/frontend/src/components/tools/automate/ToolSelector.tsx b/frontend/src/components/tools/automate/ToolSelector.tsx index 80b68b0a4..5d6094b75 100644 --- a/frontend/src/components/tools/automate/ToolSelector.tsx +++ b/frontend/src/components/tools/automate/ToolSelector.tsx @@ -1,6 +1,6 @@ -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Menu, Stack, Text, ScrollArea } from '@mantine/core'; +import { Stack, Text, ScrollArea } from '@mantine/core'; import { ToolRegistryEntry } from '../../../data/toolsTaxonomy'; import { useToolSections } from '../../../hooks/useToolSections'; import { renderToolButtons } from '../shared/renderToolButtons'; @@ -24,6 +24,8 @@ export default function ToolSelector({ const { t } = useTranslation(); const [opened, setOpened] = useState(false); const [searchTerm, setSearchTerm] = useState(''); + const [shouldAutoFocus, setShouldAutoFocus] = useState(false); + const containerRef = useRef(null); // Filter out excluded tools (like 'automate' itself) const baseFilteredTools = useMemo(() => { @@ -66,13 +68,20 @@ export default function ToolSelector({ } if (!sections || sections.length === 0) { + // If no sections, create a simple group from filtered tools + if (baseFilteredTools.length > 0) { + return [{ + name: 'All Tools', + tools: baseFilteredTools.map(([key, tool]) => ({ key, ...tool })) + }]; + } return []; } // Find the "all" section which contains all tools without duplicates const allSection = sections.find(s => (s as any).key === 'all'); return allSection?.subcategories || []; - }, [isSearching, searchGroups, sections]); + }, [isSearching, searchGroups, sections, baseFilteredTools]); const handleToolSelect = useCallback((toolKey: string) => { onSelect(toolKey); @@ -88,8 +97,25 @@ export default function ToolSelector({ const handleSearchFocus = () => { setOpened(true); + setShouldAutoFocus(true); // Request auto-focus for the input }; + // Handle click outside to close dropdown + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setOpened(false); + setSearchTerm(''); + } + }; + + if (opened) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [opened]); + + const handleSearchChange = (value: string) => { setSearchTerm(value); if (!opened) { @@ -97,6 +123,14 @@ export default function ToolSelector({ } }; + const handleInputFocus = () => { + if (!opened) { + setOpened(true); + } + // Clear auto-focus flag since input is now focused + setShouldAutoFocus(false); + }; + // Get display value for selected tool const getDisplayValue = () => { if (selectedValue && toolRegistry[selectedValue]) { @@ -106,77 +140,75 @@ export default function ToolSelector({ }; return ( -
- { - setOpened(isOpen); - // Clear search term when menu closes to show proper display - if (!isOpen) { - setSearchTerm(''); - } - }} - closeOnClickOutside={true} - closeOnEscape={true} - position="bottom-start" - offset={4} - withinPortal={false} - trapFocus={false} - shadow="sm" - transitionProps={{ duration: 0 }} - > - -
- {selectedValue && toolRegistry[selectedValue] && !opened ? ( - // Show selected tool in AutomationEntry style when tool is selected and not searching -
-
-
- {toolRegistry[selectedValue].icon} -
- - {toolRegistry[selectedValue].name} - -
+
+ {/* Always show the target - either selected tool or search input */} +
+ {selectedValue && toolRegistry[selectedValue] && !opened ? ( + // Show selected tool in AutomationEntry style when tool is selected and dropdown closed +
+
+
+ {toolRegistry[selectedValue].icon}
- ) : ( - // Show search input when no tool selected or actively searching - - )} -
- - - - - - {displayGroups.length === 0 ? ( - - {isSearching - ? t('tools.noSearchResults', 'No tools found') - : t('tools.noTools', 'No tools available') - } + + {toolRegistry[selectedValue].name} - ) : ( - renderedTools - )} - - - -
+
+ + ) : ( + // Show search input when no tool selected OR when dropdown is opened + + )} + + + {/* Custom dropdown */} + {opened && ( +
+ + + {displayGroups.length === 0 ? ( + + {isSearching + ? t('tools.noSearchResults', 'No tools found') + : t('tools.noTools', 'No tools available') + } + + ) : ( + renderedTools + )} + + +
+ )} ); } diff --git a/frontend/src/components/tools/toolPicker/ToolSearch.tsx b/frontend/src/components/tools/toolPicker/ToolSearch.tsx index c17784a52..b4b7b9319 100644 --- a/frontend/src/components/tools/toolPicker/ToolSearch.tsx +++ b/frontend/src/components/tools/toolPicker/ToolSearch.tsx @@ -15,6 +15,7 @@ interface ToolSearchProps { placeholder?: string; hideIcon?: boolean; onFocus?: () => void; + autoFocus?: boolean; } const ToolSearch = ({ @@ -26,7 +27,8 @@ const ToolSearch = ({ selectedToolKey, placeholder, hideIcon = false, - onFocus + onFocus, + autoFocus = false }: ToolSearchProps) => { const { t } = useTranslation(); const [dropdownOpen, setDropdownOpen] = useState(false); @@ -67,6 +69,15 @@ const ToolSearch = ({ return () => document.removeEventListener('mousedown', handleClickOutside); }, []); + // Auto-focus the input when requested + useEffect(() => { + if (autoFocus && searchRef.current) { + setTimeout(() => { + searchRef.current?.focus(); + }, 10); + } + }, [autoFocus]); + const searchInput = (
search} autoComplete="off" - + onFocus={onFocus} />
);