fix: center active mode in selector dropdown on open (#7883)

Co-authored-by: Roo Code <roomote@roocode.com>
Co-authored-by: daniel-lxs <ricciodaniel98@gmail.com>
This commit is contained in:
roomote[bot] 2025-09-11 15:16:43 -04:00 committed by GitHub
parent ad8551868e
commit a9e22f0df0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -44,6 +44,8 @@ export const ModeSelector = ({
const [open, setOpen] = React.useState(false)
const [searchValue, setSearchValue] = React.useState("")
const searchInputRef = React.useRef<HTMLInputElement>(null)
const selectedItemRef = React.useRef<HTMLDivElement>(null)
const scrollContainerRef = React.useRef<HTMLDivElement>(null)
const portalContainer = useRooPortal("roo-portal")
const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState()
const { t } = useAppTranslation()
@ -149,10 +151,37 @@ export const ModeSelector = ({
[trackModeSelectorOpened],
)
// Auto-focus search input when popover opens.
// Auto-focus search input and scroll to selected item when popover opens.
React.useEffect(() => {
if (open && searchInputRef.current) {
searchInputRef.current.focus()
if (open) {
// Focus search input
if (searchInputRef.current) {
searchInputRef.current.focus()
}
requestAnimationFrame(() => {
if (selectedItemRef.current && scrollContainerRef.current) {
const container = scrollContainerRef.current
const item = selectedItemRef.current
// Calculate positions
const containerHeight = container.clientHeight
const itemTop = item.offsetTop
const itemHeight = item.offsetHeight
// Center the item in the container
const scrollPosition = itemTop - containerHeight / 2 + itemHeight / 2
// Ensure we don't scroll past boundaries
const maxScroll = container.scrollHeight - containerHeight
const finalScrollPosition = Math.min(Math.max(0, scrollPosition), maxScroll)
container.scrollTo({
top: finalScrollPosition,
behavior: "instant",
})
}
})
}
}, [open])
@ -223,36 +252,40 @@ export const ModeSelector = ({
)}
{/* Mode List */}
<div className="max-h-[300px] overflow-y-auto">
<div ref={scrollContainerRef} className="max-h-[300px] overflow-y-auto">
{filteredModes.length === 0 && searchValue ? (
<div className="py-2 px-3 text-sm text-vscode-foreground/70">
{t("chat:modeSelector.noResults")}
</div>
) : (
<div className="py-1">
{filteredModes.map((mode) => (
<div
key={mode.slug}
onClick={() => handleSelect(mode.slug)}
className={cn(
"px-3 py-1.5 text-sm cursor-pointer flex items-center",
"hover:bg-vscode-list-hoverBackground",
mode.slug === value
? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
: "",
)}
data-testid="mode-selector-item">
<div className="flex-1 min-w-0">
<div className="font-bold truncate">{mode.name}</div>
{mode.description && (
<div className="text-xs text-vscode-descriptionForeground truncate">
{mode.description}
</div>
{filteredModes.map((mode) => {
const isSelected = mode.slug === value
return (
<div
key={mode.slug}
ref={isSelected ? selectedItemRef : null}
onClick={() => handleSelect(mode.slug)}
className={cn(
"px-3 py-1.5 text-sm cursor-pointer flex items-center",
"hover:bg-vscode-list-hoverBackground",
isSelected
? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
: "",
)}
data-testid="mode-selector-item">
<div className="flex-1 min-w-0">
<div className="font-bold truncate">{mode.name}</div>
{mode.description && (
<div className="text-xs text-vscode-descriptionForeground truncate">
{mode.description}
</div>
)}
</div>
{isSelected && <Check className="ml-auto size-4 p-0.5" />}
</div>
{mode.slug === value && <Check className="ml-auto size-4 p-0.5" />}
</div>
))}
)
})}
</div>
)}
</div>