From d835dbd5101cbdf27e6f18e436235ceaa10fd2ad Mon Sep 17 00:00:00 2001 From: Connor Yoh Date: Mon, 25 Aug 2025 14:37:32 +0100 Subject: [PATCH] Design improvemtns to automation customisation --- frontend/src/components/shared/TextInput.tsx | 6 +- .../components/tools/automate/ToolList.tsx | 116 ++++++++++-------- .../tools/automate/ToolSelector.tsx | 30 ++--- .../tools/toolPicker/ToolButton.tsx | 15 ++- .../tools/toolPicker/ToolPicker.css | 2 +- .../tools/toolPicker/ToolSearch.tsx | 66 +++++----- 6 files changed, 129 insertions(+), 106 deletions(-) diff --git a/frontend/src/components/shared/TextInput.tsx b/frontend/src/components/shared/TextInput.tsx index e44e8efb2..3b2ad8683 100644 --- a/frontend/src/components/shared/TextInput.tsx +++ b/frontend/src/components/shared/TextInput.tsx @@ -30,6 +30,8 @@ export interface TextInputProps { readOnly?: boolean; /** Accessibility label */ 'aria-label'?: string; + /** Focus event handler */ + onFocus?: () => void; } export const TextInput = forwardRef(({ @@ -45,6 +47,7 @@ export const TextInput = forwardRef(({ disabled = false, readOnly = false, 'aria-label': ariaLabel, + onFocus, ...props }, ref) => { const { colorScheme } = useMantineColorScheme(); @@ -62,7 +65,7 @@ export const TextInput = forwardRef(({ return (
{icon && ( - @@ -80,6 +83,7 @@ export const TextInput = forwardRef(({ disabled={disabled} readOnly={readOnly} aria-label={ariaLabel} + onFocus={onFocus} style={{ backgroundColor: colorScheme === 'dark' ? '#4B525A' : '#FFFFFF', color: colorScheme === 'dark' ? '#FFFFFF' : '#6B7382', diff --git a/frontend/src/components/tools/automate/ToolList.tsx b/frontend/src/components/tools/automate/ToolList.tsx index 8b24b5c17..b11140ac5 100644 --- a/frontend/src/components/tools/automate/ToolList.tsx +++ b/frontend/src/components/tools/automate/ToolList.tsx @@ -1,14 +1,13 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Text, Stack, Group, ActionIcon } from '@mantine/core'; -import DeleteIcon from '@mui/icons-material/Delete'; -import SettingsIcon from '@mui/icons-material/Settings'; -import CloseIcon from '@mui/icons-material/Close'; -import AddCircleOutline from '@mui/icons-material/AddCircleOutline'; -import { AutomationTool } from '../../../types/automation'; -import { ToolRegistryEntry } from '../../../data/toolsTaxonomy'; -import ToolSelector from './ToolSelector'; -import AutomationEntry from './AutomationEntry'; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Text, Stack, Group, ActionIcon } from "@mantine/core"; +import SettingsIcon from "@mui/icons-material/Settings"; +import CloseIcon from "@mui/icons-material/Close"; +import AddCircleOutline from "@mui/icons-material/AddCircleOutline"; +import { AutomationTool } from "../../../types/automation"; +import { ToolRegistryEntry } from "../../../data/toolsTaxonomy"; +import ToolSelector from "./ToolSelector"; +import AutomationEntry from "./AutomationEntry"; interface ToolListProps { tools: AutomationTool[]; @@ -29,35 +28,39 @@ export default function ToolList({ onToolConfigure, onToolAdd, getToolName, - getToolDefaultParameters + getToolDefaultParameters, }: ToolListProps) { const { t } = useTranslation(); const handleToolSelect = (index: number, newOperation: string) => { const defaultParams = getToolDefaultParameters(newOperation); - + onToolUpdate(index, { operation: newOperation, name: getToolName(newOperation), configured: false, - parameters: defaultParams + parameters: defaultParams, }); }; return (
- - {t('automate.creation.tools.selected', 'Selected Tools')} ({tools.length}) + + {t("automate.creation.tools.selected", "Selected Tools")} ({tools.length}) {tools.map((tool, index) => (
{/* Delete X in top right */} @@ -65,26 +68,26 @@ export default function ToolList({ variant="subtle" size="xs" onClick={() => onToolRemove(index)} - title={t('automate.creation.tools.remove', 'Remove tool')} + title={t("automate.creation.tools.remove", "Remove tool")} style={{ - position: 'absolute', - top: '4px', - right: '4px', + position: "absolute", + top: "4px", + right: "4px", zIndex: 1, - color: 'var(--mantine-color-gray-6)' + color: "var(--mantine-color-gray-6)", }} > - + -
+
{/* Tool Selection Dropdown with inline settings cog */}
handleToolSelect(index, newOperation)} - excludeTools={['automate']} + excludeTools={["automate"]} toolRegistry={toolRegistry} selectedValue={tool.operation} placeholder={tool.name} @@ -97,26 +100,37 @@ export default function ToolList({ variant="subtle" size="sm" onClick={() => onToolConfigure(index)} - title={t('automate.creation.tools.configure', 'Configure tool')} - style={{ color: 'var(--mantine-color-gray-6)' }} + title={t("automate.creation.tools.configure", "Configure tool")} + style={{ color: "var(--mantine-color-gray-6)" }} > )} - - {/* Configuration status underneath */} - {tool.operation && !tool.configured && ( - - {t('automate.creation.tools.notConfigured', "! Not Configured")} - - )}
- + {/* Configuration status underneath */} + {tool.operation && !tool.configured && ( +
+ + {t("automate.creation.tools.notConfigured", "! Not Configured")} + +
+ )} {index < tools.length - 1 && ( -
- +
+ + ↓ +
)} @@ -124,19 +138,23 @@ export default function ToolList({ {/* Arrow before Add Tool Button */} {tools.length > 0 && ( -
- +
+ + ↓ +
)} {/* Add Tool Button */} -
+
); -} \ No newline at end of file +} diff --git a/frontend/src/components/tools/automate/ToolSelector.tsx b/frontend/src/components/tools/automate/ToolSelector.tsx index 5d6094b75..4fb87548f 100644 --- a/frontend/src/components/tools/automate/ToolSelector.tsx +++ b/frontend/src/components/tools/automate/ToolSelector.tsx @@ -5,6 +5,7 @@ import { ToolRegistryEntry } from '../../../data/toolsTaxonomy'; import { useToolSections } from '../../../hooks/useToolSections'; import { renderToolButtons } from '../shared/renderToolButtons'; import ToolSearch from '../toolPicker/ToolSearch'; +import ToolButton from '../toolPicker/ToolButton'; interface ToolSelectorProps { onSelect: (toolKey: string) => void; @@ -72,7 +73,8 @@ export default function ToolSelector({ if (baseFilteredTools.length > 0) { return [{ name: 'All Tools', - tools: baseFilteredTools.map(([key, tool]) => ({ key, ...tool })) + subcategoryId: 'all' as any, + tools: baseFilteredTools.map(([key, tool]) => ({ id: key, tool })) }]; } return []; @@ -140,26 +142,15 @@ export default function ToolSelector({ }; return ( -
+
{/* 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} -
- - {toolRegistry[selectedValue].name} - -
+
+ {}} rounded={true}>
) : ( // Show search input when no tool selected OR when dropdown is opened @@ -167,14 +158,13 @@ export default function ToolSelector({ value={searchTerm} onChange={handleSearchChange} toolRegistry={filteredToolRegistry} - mode="filter" + mode="unstyled" placeholder={getDisplayValue()} hideIcon={true} onFocus={handleInputFocus} autoFocus={shouldAutoFocus} /> )} -
{/* Custom dropdown */} {opened && ( diff --git a/frontend/src/components/tools/toolPicker/ToolButton.tsx b/frontend/src/components/tools/toolPicker/ToolButton.tsx index 66bd9489e..185eed5ed 100644 --- a/frontend/src/components/tools/toolPicker/ToolButton.tsx +++ b/frontend/src/components/tools/toolPicker/ToolButton.tsx @@ -9,9 +9,10 @@ interface ToolButtonProps { tool: ToolRegistryEntry; isSelected: boolean; onSelect: (id: string) => void; + rounded?: boolean; } -const ToolButton: React.FC = ({ id, tool, isSelected, onSelect }) => { +const ToolButton: React.FC = ({ id, tool, isSelected, onSelect, rounded = false }) => { const handleClick = (id: string) => { if (tool.link) { // Open external link in new tab @@ -33,7 +34,17 @@ const ToolButton: React.FC = ({ id, tool, isSelected, onSelect fullWidth justify="flex-start" className="tool-button" - styles={{ root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)" } }} + styles={{ + root: { + borderRadius: rounded ? 'var(--mantine-radius-lg)' : 0, + color: "var(--tools-text-and-icon-color)", + ...(rounded && { + '&:hover': { + borderRadius: 'var(--mantine-radius-lg)', + } + }) + } + }} > void; toolRegistry: Readonly>; onToolSelect?: (toolId: string) => void; - mode: 'filter' | 'dropdown'; + mode: "filter" | "dropdown" | "unstyled"; selectedToolKey?: string | null; placeholder?: string; hideIcon?: boolean; @@ -23,12 +23,12 @@ const ToolSearch = ({ onChange, toolRegistry, onToolSelect, - mode = 'filter', + mode = "filter", selectedToolKey, placeholder, hideIcon = false, onFocus, - autoFocus = false + autoFocus = false, }: ToolSearchProps) => { const { t } = useTranslation(); const [dropdownOpen, setDropdownOpen] = useState(false); @@ -39,9 +39,10 @@ const ToolSearch = ({ if (!value.trim()) return []; return Object.entries(toolRegistry) .filter(([id, tool]) => { - if (mode === 'dropdown' && id === selectedToolKey) return false; - return tool.name.toLowerCase().includes(value.toLowerCase()) || - tool.description.toLowerCase().includes(value.toLowerCase()); + if (mode === "dropdown" && id === selectedToolKey) return false; + return ( + tool.name.toLowerCase().includes(value.toLowerCase()) || tool.description.toLowerCase().includes(value.toLowerCase()) + ); }) .slice(0, 6) .map(([id, tool]) => ({ id, tool })); @@ -49,7 +50,7 @@ const ToolSearch = ({ const handleSearchChange = (searchValue: string) => { onChange(searchValue); - if (mode === 'dropdown') { + if (mode === "dropdown") { setDropdownOpen(searchValue.trim().length > 0 && filteredTools.length > 0); } }; @@ -65,8 +66,8 @@ const ToolSearch = ({ setDropdownOpen(false); } }; - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); }, []); // Auto-focus the input when requested @@ -79,7 +80,6 @@ const ToolSearch = ({ }, [autoFocus]); const searchInput = ( -
search} autoComplete="off" + style={{padding: 0}} onFocus={onFocus} /> -
); - if (mode === 'filter') { + if (mode === "filter") { + return
{searchInput}
; + } + + if (mode === "unstyled") { return searchInput; } return ( -
+
{searchInput} {dropdownOpen && filteredTools.length > 0 && (
- + {filteredTools.map(({ id, tool }) => (
- } + leftSection={
{tool.icon}
} fullWidth justify="flex-start" style={{ - borderRadius: '6px', - color: 'var(--tools-text-and-icon-color)', - padding: '8px 12px' + borderRadius: "6px", + color: "var(--tools-text-and-icon-color)", + padding: "8px 12px", }} > -
+
{tool.name}
- + {tool.description}