Skip to content

Commit f9b4912

Browse files
committed
feat: implement scroll-to-setting functionality with UI improvements
- Add scroll-to-setting feature for settings search results - Add data-setting-id attributes to all settings components - Implement scrollToSetting utility with smooth scrolling and highlight animation - Update SettingsView to handle scroll after tab navigation - Add CSS highlight animation with fade-out effect - Improve search dropdown UI with right margin spacing - Add comprehensive test coverage for scroll functionality - All tests passing (123 tests)
1 parent 2539c01 commit f9b4912

14 files changed

+204
-62
lines changed

webview-ui/src/components/settings/About.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const About = ({ telemetrySetting, setTelemetrySetting, className, ...pro
3838
</SectionHeader>
3939

4040
<Section>
41-
<div>
41+
<div data-setting-id="telemetrySetting">
4242
<VSCodeCheckbox
4343
checked={telemetrySetting === "enabled"}
4444
onChange={(e: any) => {

webview-ui/src/components/settings/AutoApproveSettings.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export const AutoApproveSettings = ({
164164
<span className="codicon codicon-eye" />
165165
<div>{t("settings:autoApprove.readOnly.label")}</div>
166166
</div>
167-
<div>
167+
<div data-setting-id="alwaysAllowReadOnlyOutsideWorkspace">
168168
<VSCodeCheckbox
169169
checked={alwaysAllowReadOnlyOutsideWorkspace}
170170
onChange={(e: any) =>
@@ -188,7 +188,7 @@ export const AutoApproveSettings = ({
188188
<span className="codicon codicon-edit" />
189189
<div>{t("settings:autoApprove.write.label")}</div>
190190
</div>
191-
<div>
191+
<div data-setting-id="alwaysAllowWriteOutsideWorkspace">
192192
<VSCodeCheckbox
193193
checked={alwaysAllowWriteOutsideWorkspace}
194194
onChange={(e: any) =>
@@ -203,7 +203,7 @@ export const AutoApproveSettings = ({
203203
{t("settings:autoApprove.write.outsideWorkspace.description")}
204204
</div>
205205
</div>
206-
<div>
206+
<div data-setting-id="alwaysAllowWriteProtected">
207207
<VSCodeCheckbox
208208
checked={alwaysAllowWriteProtected}
209209
onChange={(e: any) =>
@@ -216,7 +216,7 @@ export const AutoApproveSettings = ({
216216
{t("settings:autoApprove.write.protected.description")}
217217
</div>
218218
</div>
219-
<div>
219+
<div data-setting-id="writeDelayMs">
220220
<div className="flex items-center gap-2">
221221
<Slider
222222
min={0}
@@ -241,7 +241,7 @@ export const AutoApproveSettings = ({
241241
<span className="codicon codicon-refresh" />
242242
<div>{t("settings:autoApprove.retry.label")}</div>
243243
</div>
244-
<div>
244+
<div data-setting-id="requestDelaySeconds">
245245
<div className="flex items-center gap-2">
246246
<Slider
247247
min={5}
@@ -266,7 +266,7 @@ export const AutoApproveSettings = ({
266266
<span className="codicon codicon-question" />
267267
<div>{t("settings:autoApprove.followupQuestions.label")}</div>
268268
</div>
269-
<div>
269+
<div data-setting-id="followupAutoApproveTimeoutMs">
270270
<div className="flex items-center gap-2">
271271
<Slider
272272
min={1000}
@@ -294,7 +294,7 @@ export const AutoApproveSettings = ({
294294
<div>{t("settings:autoApprove.execute.label")}</div>
295295
</div>
296296

297-
<div>
297+
<div data-setting-id="allowedCommands">
298298
<label className="block font-medium mb-1" data-testid="allowed-commands-heading">
299299
{t("settings:autoApprove.execute.allowedCommands")}
300300
</label>
@@ -342,7 +342,7 @@ export const AutoApproveSettings = ({
342342
</div>
343343

344344
{/* Denied Commands Section */}
345-
<div className="mt-6">
345+
<div className="mt-6" data-setting-id="deniedCommands">
346346
<label className="block font-medium mb-1" data-testid="denied-commands-heading">
347347
{t("settings:autoApprove.execute.deniedCommands")}
348348
</label>

webview-ui/src/components/settings/AutoApproveToggle.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export const AutoApproveToggle = ({ onToggle, ...props }: AutoApproveToggleProps
124124
aria-label={t(labelKey)}
125125
aria-pressed={!!props[key]}
126126
data-testid={testId}
127+
data-setting-id={key}
127128
className={cn(" aspect-square h-[80px]", !props[key] && "opacity-50")}>
128129
<span className={cn("flex flex-col items-center gap-1")}>
129130
<span className={`codicon codicon-${icon}`} />

webview-ui/src/components/settings/BrowserSettings.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export const BrowserSettings = ({
107107
</SectionHeader>
108108

109109
<Section>
110-
<div>
110+
<div data-setting-id="browserToolEnabled">
111111
<VSCodeCheckbox
112112
checked={browserToolEnabled}
113113
onChange={(e: any) => setCachedStateField("browserToolEnabled", e.target.checked)}>
@@ -126,7 +126,7 @@ export const BrowserSettings = ({
126126

127127
{browserToolEnabled && (
128128
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
129-
<div>
129+
<div data-setting-id="browserViewportSize">
130130
<label className="block font-medium mb-1">{t("settings:browser.viewport.label")}</label>
131131
<Select
132132
value={browserViewportSize}
@@ -149,7 +149,7 @@ export const BrowserSettings = ({
149149
</div>
150150
</div>
151151

152-
<div>
152+
<div data-setting-id="screenshotQuality">
153153
<label className="block font-medium mb-1">
154154
{t("settings:browser.screenshotQuality.label")}
155155
</label>
@@ -168,7 +168,7 @@ export const BrowserSettings = ({
168168
</div>
169169
</div>
170170

171-
<div>
171+
<div data-setting-id="remoteBrowserEnabled">
172172
<VSCodeCheckbox
173173
checked={remoteBrowserEnabled}
174174
onChange={(e: any) => {
@@ -189,7 +189,7 @@ export const BrowserSettings = ({
189189

190190
{remoteBrowserEnabled && (
191191
<>
192-
<div className="flex items-center gap-2">
192+
<div className="flex items-center gap-2" data-setting-id="remoteBrowserHost">
193193
<VSCodeTextField
194194
value={remoteBrowserHost ?? ""}
195195
onChange={(e: any) =>

webview-ui/src/components/settings/CheckpointSettings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const CheckpointSettings = ({ enableCheckpoints, setCachedStateField, ...
2626
</SectionHeader>
2727

2828
<Section>
29-
<div>
29+
<div data-setting-id="enableCheckpoints">
3030
<VSCodeCheckbox
3131
checked={enableCheckpoints}
3232
onChange={(e: any) => {

webview-ui/src/components/settings/ContextManagementSettings.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export const ContextManagementSettings = ({
8989
</SectionHeader>
9090

9191
<Section>
92-
<div>
92+
<div data-setting-id="maxOpenTabsContext">
9393
<span className="block font-medium mb-1">{t("settings:contextManagement.openTabs.label")}</span>
9494
<div className="flex items-center gap-2">
9595
<Slider
@@ -107,7 +107,7 @@ export const ContextManagementSettings = ({
107107
</div>
108108
</div>
109109

110-
<div>
110+
<div data-setting-id="maxWorkspaceFiles">
111111
<span className="block font-medium mb-1">
112112
{t("settings:contextManagement.workspaceFiles.label")}
113113
</span>
@@ -127,7 +127,7 @@ export const ContextManagementSettings = ({
127127
</div>
128128
</div>
129129

130-
<div>
130+
<div data-setting-id="maxConcurrentFileReads">
131131
<span className="block font-medium mb-1">
132132
{t("settings:contextManagement.maxConcurrentFileReads.label")}
133133
</span>
@@ -147,7 +147,7 @@ export const ContextManagementSettings = ({
147147
</div>
148148
</div>
149149

150-
<div>
150+
<div data-setting-id="showRooIgnoredFiles">
151151
<VSCodeCheckbox
152152
checked={showRooIgnoredFiles}
153153
onChange={(e: any) => setCachedStateField("showRooIgnoredFiles", e.target.checked)}
@@ -161,7 +161,7 @@ export const ContextManagementSettings = ({
161161
</div>
162162
</div>
163163

164-
<div>
164+
<div data-setting-id="maxReadFileLine">
165165
<div className="flex flex-col gap-2">
166166
<span className="font-medium">{t("settings:contextManagement.maxReadFile.label")}</span>
167167
<div className="flex items-center gap-4">
@@ -198,19 +198,21 @@ export const ContextManagementSettings = ({
198198
</div>
199199
</Section>
200200
<Section className="pt-2">
201-
<VSCodeCheckbox
202-
checked={autoCondenseContext}
203-
onChange={(e: any) => setCachedStateField("autoCondenseContext", e.target.checked)}
204-
data-testid="auto-condense-context-checkbox">
205-
<span className="font-medium">{t("settings:contextManagement.autoCondenseContext.name")}</span>
206-
</VSCodeCheckbox>
201+
<div data-setting-id="autoCondenseContext">
202+
<VSCodeCheckbox
203+
checked={autoCondenseContext}
204+
onChange={(e: any) => setCachedStateField("autoCondenseContext", e.target.checked)}
205+
data-testid="auto-condense-context-checkbox">
206+
<span className="font-medium">{t("settings:contextManagement.autoCondenseContext.name")}</span>
207+
</VSCodeCheckbox>
208+
</div>
207209
{autoCondenseContext && (
208210
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
209211
<div className="flex items-center gap-4 font-bold">
210212
<FoldVertical size={16} />
211213
<div>{t("settings:contextManagement.condensingThreshold.label")}</div>
212214
</div>
213-
<div>
215+
<div data-setting-id="condensingApiConfigId">
214216
<Select
215217
value={selectedThresholdProfile || "default"}
216218
onValueChange={(value) => {
@@ -255,7 +257,7 @@ export const ContextManagementSettings = ({
255257
</div>
256258

257259
{/* Threshold Slider */}
258-
<div>
260+
<div data-setting-id="autoCondenseContextPercent">
259261
<div className="flex items-center gap-2">
260262
<Slider
261263
min={10}

webview-ui/src/components/settings/LanguageSettings.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,25 @@ export const LanguageSettings = ({ language, setCachedStateField, className, ...
3131
</SectionHeader>
3232

3333
<Section>
34-
<Select value={language} onValueChange={(value) => setCachedStateField("language", value as Language)}>
35-
<SelectTrigger className="w-full">
36-
<SelectValue placeholder={t("settings:common.select")} />
37-
</SelectTrigger>
38-
<SelectContent>
39-
<SelectGroup>
40-
{Object.entries(LANGUAGES).map(([code, name]) => (
41-
<SelectItem key={code} value={code}>
42-
{name}
43-
<span className="text-muted-foreground">({code})</span>
44-
</SelectItem>
45-
))}
46-
</SelectGroup>
47-
</SelectContent>
48-
</Select>
34+
<div data-setting-id="language">
35+
<Select
36+
value={language}
37+
onValueChange={(value) => setCachedStateField("language", value as Language)}>
38+
<SelectTrigger className="w-full">
39+
<SelectValue placeholder={t("settings:common.select")} />
40+
</SelectTrigger>
41+
<SelectContent>
42+
<SelectGroup>
43+
{Object.entries(LANGUAGES).map(([code, name]) => (
44+
<SelectItem key={code} value={code}>
45+
{name}
46+
<span className="text-muted-foreground">({code})</span>
47+
</SelectItem>
48+
))}
49+
</SelectGroup>
50+
</SelectContent>
51+
</Select>
52+
</div>
4953
</Section>
5054
</div>
5155
)

webview-ui/src/components/settings/NotificationSettings.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const NotificationSettings = ({
3535
</SectionHeader>
3636

3737
<Section>
38-
<div>
38+
<div data-setting-id="ttsEnabled">
3939
<VSCodeCheckbox
4040
checked={ttsEnabled}
4141
onChange={(e: any) => setCachedStateField("ttsEnabled", e.target.checked)}
@@ -49,7 +49,7 @@ export const NotificationSettings = ({
4949

5050
{ttsEnabled && (
5151
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
52-
<div>
52+
<div data-setting-id="ttsSpeed">
5353
<label className="block font-medium mb-1">
5454
{t("settings:notifications.tts.speedLabel")}
5555
</label>
@@ -68,7 +68,7 @@ export const NotificationSettings = ({
6868
</div>
6969
)}
7070

71-
<div>
71+
<div data-setting-id="soundEnabled">
7272
<VSCodeCheckbox
7373
checked={soundEnabled}
7474
onChange={(e: any) => setCachedStateField("soundEnabled", e.target.checked)}
@@ -82,7 +82,7 @@ export const NotificationSettings = ({
8282

8383
{soundEnabled && (
8484
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
85-
<div>
85+
<div data-setting-id="soundVolume">
8686
<label className="block font-medium mb-1">
8787
{t("settings:notifications.sound.volumeLabel")}
8888
</label>

webview-ui/src/components/settings/SettingsSearchDropdown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ export const SettingsSearchDropdown: React.FC<SettingsSearchDropdownProps> = ({
195195
<PopoverContent
196196
align="start"
197197
sideOffset={4}
198-
className="w-96 p-0 max-h-96 overflow-hidden"
198+
className="w-96 p-0 max-h-96 overflow-hidden mr-2"
199199
onOpenAutoFocus={(e) => e.preventDefault()}>
200200
<div ref={dropdownRef} className="overflow-y-auto max-h-96">
201201
{searchResults.length === 0 ? (

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import { Section } from "./Section"
6767
import PromptsSettings from "./PromptsSettings"
6868
import { cn } from "@/lib/utils"
6969
import { SettingsSearchDropdown } from "./SettingsSearchDropdown"
70+
import { scrollToSetting } from "./searchUtils"
7071

7172
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
7273
export const settingsTabList =
@@ -459,9 +460,12 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
459460
}, [scrollToActiveTab])
460461

461462
// Handle search selection
462-
const handleSearchSelection = useCallback((sectionId: string, _settingId: string) => {
463+
const handleSearchSelection = useCallback((sectionId: string, settingId: string) => {
463464
setActiveTab(sectionId as SectionName)
464-
// TODO: In the future, we could also highlight the specific setting
465+
// Use setTimeout to ensure tab content is rendered before scrolling
466+
setTimeout(() => {
467+
scrollToSetting(settingId)
468+
}, 100)
465469
}, [])
466470

467471
return (

0 commit comments

Comments
 (0)