import React, { useState, useRef, useCallback } from 'react'; import type { Agent } from '../hooks/useAgents'; import { useDismissablePopover } from '../hooks/useDismissablePopover'; import { getAgentSwitchSettings, saveAgentSwitchSettings, type AgentSwitchSettings } from '../utils/agentSwitch'; interface ApproveDropdownProps { onApprove: () => void; agents: Agent[]; disabled?: boolean; isLoading?: boolean; } function getSelectedLabel(setting: AgentSwitchSettings, agents: Agent[]): string | null { if (setting.switchTo === 'disabled') return null; if (setting.switchTo === 'custom' && setting.customName) { return setting.customName; } const match = agents.find(a => a.id.toLowerCase() === setting.switchTo.toLowerCase()); return match?.name ?? setting.switchTo; } function isSelected(agentId: string, setting: AgentSwitchSettings): boolean { if (setting.switchTo === 'custom') return false; if (setting.switchTo === 'disabled') return false; return agentId.toLowerCase() === setting.switchTo.toLowerCase(); } const Checkmark = () => ( ); export const ApproveDropdown: React.FC = ({ onApprove, agents, disabled = false, isLoading = false, }) => { const [setting, setSetting] = useState(() => getAgentSwitchSettings('plan')); const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); // Shared dismissal (outside pointerdown + Escape), active only while open — // the old always-on listeners were no-ops when closed. The hook consumes the // dismissing Escape so it cannot double as a host-ladder Escape. const dismiss = useCallback(() => setIsOpen(false), []); useDismissablePopover({ enabled: isOpen, ref: dropdownRef, onDismiss: dismiss, }); const handleSelect = (newSetting: AgentSwitchSettings) => { setSetting(newSetting); saveAgentSwitchSettings(newSetting); setIsOpen(false); }; const agentLabel = getSelectedLabel(setting, agents); const isNoSwitch = setting.switchTo === 'disabled'; const isCustom = setting.switchTo === 'custom'; const notFound = agentLabel && !isNoSwitch && !isCustom && !agents.some(a => a.id.toLowerCase() === setting.switchTo.toLowerCase()); const baseClasses = disabled ? 'opacity-50 cursor-not-allowed bg-muted text-muted-foreground' : 'bg-success text-success-foreground hover:opacity-90'; const handleApproveClick = () => { setIsOpen(false); onApprove(); }; return ( {/* Mobile: simple button */} {isLoading ? '...' : 'OK'} {/* Desktop: split button */} {isLoading ? 'Approving...' : ( agentLabel ? ( Approve → {agentLabel} {notFound && (?)} ) : 'Approve' )} setIsOpen(!isOpen)} disabled={disabled} className={`px-1.5 py-1 rounded-r-md border-l border-success-foreground/20 text-xs transition-all ${baseClasses}`} > {/* Dropdown */} {isOpen && ( Switch to agent {agents.map((agent) => { const selected = isSelected(agent.id, setting); return ( handleSelect({ switchTo: agent.id })} className={`w-full px-3 py-1.5 text-left text-xs transition-colors flex items-center gap-2 ${ selected ? 'text-primary bg-primary/10 font-medium' : 'text-popover-foreground hover:bg-muted' }`} > {selected && } {agent.name} ); })} {isCustom && setting.customName && ( setIsOpen(false)} className="w-full px-3 py-1.5 text-left text-xs transition-colors flex items-center gap-2 text-primary bg-primary/10 font-medium" > {setting.customName} (custom) )} handleSelect({ switchTo: 'disabled' })} className={`w-full px-3 py-1.5 text-left text-xs transition-colors flex items-center gap-2 ${ isNoSwitch ? 'text-primary bg-primary/10 font-medium' : 'text-popover-foreground hover:bg-muted' }`} > {isNoSwitch && } No switch )} ); };