Skip to content

Commit a9e22f0

Browse files
roomote[bot]roomotedaniel-lxs
authored
fix: center active mode in selector dropdown on open (#7883)
Co-authored-by: Roo Code <[email protected]> Co-authored-by: daniel-lxs <[email protected]>
1 parent ad85518 commit a9e22f0

File tree

1 file changed

+58
-25
lines changed

1 file changed

+58
-25
lines changed

webview-ui/src/components/chat/ModeSelector.tsx

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export const ModeSelector = ({
4444
const [open, setOpen] = React.useState(false)
4545
const [searchValue, setSearchValue] = React.useState("")
4646
const searchInputRef = React.useRef<HTMLInputElement>(null)
47+
const selectedItemRef = React.useRef<HTMLDivElement>(null)
48+
const scrollContainerRef = React.useRef<HTMLDivElement>(null)
4749
const portalContainer = useRooPortal("roo-portal")
4850
const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState()
4951
const { t } = useAppTranslation()
@@ -149,10 +151,37 @@ export const ModeSelector = ({
149151
[trackModeSelectorOpened],
150152
)
151153

152-
// Auto-focus search input when popover opens.
154+
// Auto-focus search input and scroll to selected item when popover opens.
153155
React.useEffect(() => {
154-
if (open && searchInputRef.current) {
155-
searchInputRef.current.focus()
156+
if (open) {
157+
// Focus search input
158+
if (searchInputRef.current) {
159+
searchInputRef.current.focus()
160+
}
161+
162+
requestAnimationFrame(() => {
163+
if (selectedItemRef.current && scrollContainerRef.current) {
164+
const container = scrollContainerRef.current
165+
const item = selectedItemRef.current
166+
167+
// Calculate positions
168+
const containerHeight = container.clientHeight
169+
const itemTop = item.offsetTop
170+
const itemHeight = item.offsetHeight
171+
172+
// Center the item in the container
173+
const scrollPosition = itemTop - containerHeight / 2 + itemHeight / 2
174+
175+
// Ensure we don't scroll past boundaries
176+
const maxScroll = container.scrollHeight - containerHeight
177+
const finalScrollPosition = Math.min(Math.max(0, scrollPosition), maxScroll)
178+
179+
container.scrollTo({
180+
top: finalScrollPosition,
181+
behavior: "instant",
182+
})
183+
}
184+
})
156185
}
157186
}, [open])
158187

@@ -223,36 +252,40 @@ export const ModeSelector = ({
223252
)}
224253

225254
{/* Mode List */}
226-
<div className="max-h-[300px] overflow-y-auto">
255+
<div ref={scrollContainerRef} className="max-h-[300px] overflow-y-auto">
227256
{filteredModes.length === 0 && searchValue ? (
228257
<div className="py-2 px-3 text-sm text-vscode-foreground/70">
229258
{t("chat:modeSelector.noResults")}
230259
</div>
231260
) : (
232261
<div className="py-1">
233-
{filteredModes.map((mode) => (
234-
<div
235-
key={mode.slug}
236-
onClick={() => handleSelect(mode.slug)}
237-
className={cn(
238-
"px-3 py-1.5 text-sm cursor-pointer flex items-center",
239-
"hover:bg-vscode-list-hoverBackground",
240-
mode.slug === value
241-
? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
242-
: "",
243-
)}
244-
data-testid="mode-selector-item">
245-
<div className="flex-1 min-w-0">
246-
<div className="font-bold truncate">{mode.name}</div>
247-
{mode.description && (
248-
<div className="text-xs text-vscode-descriptionForeground truncate">
249-
{mode.description}
250-
</div>
262+
{filteredModes.map((mode) => {
263+
const isSelected = mode.slug === value
264+
return (
265+
<div
266+
key={mode.slug}
267+
ref={isSelected ? selectedItemRef : null}
268+
onClick={() => handleSelect(mode.slug)}
269+
className={cn(
270+
"px-3 py-1.5 text-sm cursor-pointer flex items-center",
271+
"hover:bg-vscode-list-hoverBackground",
272+
isSelected
273+
? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
274+
: "",
251275
)}
276+
data-testid="mode-selector-item">
277+
<div className="flex-1 min-w-0">
278+
<div className="font-bold truncate">{mode.name}</div>
279+
{mode.description && (
280+
<div className="text-xs text-vscode-descriptionForeground truncate">
281+
{mode.description}
282+
</div>
283+
)}
284+
</div>
285+
{isSelected && <Check className="ml-auto size-4 p-0.5" />}
252286
</div>
253-
{mode.slug === value && <Check className="ml-auto size-4 p-0.5" />}
254-
</div>
255-
))}
287+
)
288+
})}
256289
</div>
257290
)}
258291
</div>

0 commit comments

Comments
 (0)