From fdfa6f3046855ff01ddc51eeb1c748a3513e83b7 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Wed, 15 Apr 2026 06:57:10 +0200 Subject: [PATCH] fix: remove shadow causing gradient rectangle and add keyboard navigation to quick prompt model picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove shadow-2xl shadow-black/50 from the ModelSelector dropdown. On transparent Electron windows (Windows), box-shadows paint onto the transparent canvas and create a visible rectangle behind the UI — the panel itself already avoided this with a comment explaining why. Add full arrow-key navigation to the model picker listbox: ArrowUp/Down cycle through options, Enter/Space selects, Home/End jump to extremes, Escape closes. The container auto-focuses on mount so keyboard input works immediately after opening via Tab+Enter. Mouse hover also syncs the focused index for consistent visual feedback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../components/quick-prompt/ModelSelector.tsx | 91 ++++++++++++++++--- 1 file changed, 78 insertions(+), 13 deletions(-) diff --git a/src/renderer/components/quick-prompt/ModelSelector.tsx b/src/renderer/components/quick-prompt/ModelSelector.tsx index 9011c30..f064669 100644 --- a/src/renderer/components/quick-prompt/ModelSelector.tsx +++ b/src/renderer/components/quick-prompt/ModelSelector.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useMemo } from 'react'; +import { useCallback, useEffect, useRef, useMemo, useState } from 'react'; import { Check, Brain } from 'lucide-react'; import { ProviderIcon } from '@renderer/components/ProviderIcons'; @@ -37,6 +37,55 @@ export function ModelSelector({ onClose, }: ModelSelectorProps) { const containerRef = useRef(null); + const flatModels = useMemo(() => [...models], [models]); + const initialIndex = flatModels.findIndex((m) => m.id === selectedModelId); + const [focusedIndex, setFocusedIndex] = useState(initialIndex >= 0 ? initialIndex : 0); + const optionRefs = useRef>(new Map()); + + // Scroll the focused option into view whenever it changes + useEffect(() => { + optionRefs.current.get(focusedIndex)?.scrollIntoView({ block: 'nearest' }); + }, [focusedIndex]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case 'ArrowDown': { + e.preventDefault(); + setFocusedIndex((prev) => (prev + 1) % flatModels.length); + break; + } + case 'ArrowUp': { + e.preventDefault(); + setFocusedIndex((prev) => (prev - 1 + flatModels.length) % flatModels.length); + break; + } + case 'Home': { + e.preventDefault(); + setFocusedIndex(0); + break; + } + case 'End': { + e.preventDefault(); + setFocusedIndex(flatModels.length - 1); + break; + } + case 'Enter': + case ' ': { + e.preventDefault(); + const model = flatModels[focusedIndex]; + if (model) onSelect(model); + break; + } + case 'Escape': { + e.stopPropagation(); + onClose(); + break; + } + } + }, + [flatModels, focusedIndex, onSelect, onClose], + ); useEffect(() => { const handleClickOutside = (e: MouseEvent) => { @@ -44,21 +93,18 @@ export function ModelSelector({ onClose(); } }; - const handleEscape = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - e.stopPropagation(); - onClose(); - } - }; document.addEventListener('mousedown', handleClickOutside); - document.addEventListener('keydown', handleEscape, true); return () => { document.removeEventListener('mousedown', handleClickOutside); - document.removeEventListener('keydown', handleEscape, true); }; }, [onClose]); + // Focus the container on mount so keyboard events are captured immediately + useEffect(() => { + containerRef.current?.focus(); + }, []); + const selectedModel = models.find((m) => m.id === selectedModelId); const supportedEfforts = selectedModel?.supportedReasoningEfforts; @@ -96,12 +142,19 @@ export function ModelSelector({ return result; }, [models]); + // Build a flat index for each model so we can map group-based rendering + // back to the flat focusedIndex. + let flatIndex = -1; + return (
{/* Model list — grouped by provider */}
@@ -121,7 +174,10 @@ export function ModelSelector({ {/* Models in this provider group */} {group.models.map((model) => { + flatIndex++; + const modelIndex = flatIndex; const isSelected = model.id === selectedModelId; + const isFocused = modelIndex === focusedIndex; const tierLabel = model.tier === 'premium' ? 'PRO' : model.tier === 'fast' ? 'FAST' : undefined; const tierColor = model.tier === 'premium' ? 'text-amber-400 bg-amber-400/10' @@ -132,15 +188,24 @@ export function ModelSelector({ return (