From 6a865da4f1e43aedd260589e276825ede3a5685d Mon Sep 17 00:00:00 2001 From: Felix <24791380+vcfgv@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:40:56 +0800 Subject: [PATCH] feat(setup): enhance provider selection with dropdown menu and keyboard accessibility (#71) --- src/pages/Setup/index.tsx | 134 +++++++++++++++++++++++++++++++------- 1 file changed, 110 insertions(+), 24 deletions(-) diff --git a/src/pages/Setup/index.tsx b/src/pages/Setup/index.tsx index e0f575a71..d2137b05d 100644 --- a/src/pages/Setup/index.tsx +++ b/src/pages/Setup/index.tsx @@ -689,6 +689,8 @@ function ProviderContent({ const [selectedProviderConfigId, setSelectedProviderConfigId] = useState(null); const [baseUrl, setBaseUrl] = useState(''); const [modelId, setModelId] = useState(''); + const [providerMenuOpen, setProviderMenuOpen] = useState(false); + const providerMenuRef = useRef(null); // On mount, try to restore previously configured provider useEffect(() => { @@ -764,7 +766,33 @@ function ProviderContent({ return () => { cancelled = true; }; }, [onApiKeyChange, selectedProvider, providers]); + useEffect(() => { + if (!providerMenuOpen) return; + + const handlePointerDown = (event: MouseEvent) => { + if (providerMenuRef.current && !providerMenuRef.current.contains(event.target as Node)) { + setProviderMenuOpen(false); + } + }; + + const handleEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setProviderMenuOpen(false); + } + }; + + document.addEventListener('mousedown', handlePointerDown); + document.addEventListener('keydown', handleEscape); + return () => { + document.removeEventListener('mousedown', handlePointerDown); + document.removeEventListener('keydown', handleEscape); + }; + }, [providerMenuOpen]); + const selectedProviderData = providers.find((p) => p.id === selectedProvider); + const selectedProviderIconUrl = selectedProviderData + ? getProviderIconUrl(selectedProviderData.id) + : undefined; const showBaseUrlField = selectedProviderData?.showBaseUrl ?? false; const showModelIdField = selectedProviderData?.showModelId ?? false; const requiresKey = selectedProviderData?.requiresApiKey ?? false; @@ -855,37 +883,95 @@ function ProviderContent({ && (requiresKey ? apiKey.length > 0 : true) && (showModelIdField ? modelId.trim().length > 0 : true); + const handleSelectProvider = (providerId: string) => { + onSelectProvider(providerId); + setSelectedProviderConfigId(null); + onConfiguredChange(false); + onApiKeyChange(''); + setKeyValid(null); + setProviderMenuOpen(false); + }; + return (
{/* Provider selector — dropdown */}
- -
- - +
+ {selectedProvider && selectedProviderData ? ( + selectedProviderIconUrl ? ( + {selectedProviderData.name} + ) : ( + {selectedProviderData.icon} + ) + ) : ( + + )} + + {selectedProviderData + ? `${selectedProviderData.name}${selectedProviderData.model ? ` — ${selectedProviderData.model}` : ''}` + : t('provider.selectPlaceholder')} + +
+ + + + {providerMenuOpen && ( +
+ {providers.map((p) => { + const iconUrl = getProviderIconUrl(p.id); + const isSelected = selectedProvider === p.id; + + return ( + + ); + })} +
+ )}