Skip to content

Commit 1f5ee43

Browse files
fix: preserve scroll position when switching tabs in settings (#7587)
* fix: preserve scroll position when switching tabs in settings * ui(settings): restore scroll synchronously to prevent flicker; dx(ui): name TabContent for clearer DevTools --------- Co-authored-by: Daniel Riccio <[email protected]>
1 parent c25cfde commit 1f5ee43

File tree

2 files changed

+20
-6
lines changed

2 files changed

+20
-6
lines changed

webview-ui/src/components/common/Tab.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const TabHeader = ({ className, children, ...props }: TabProps) => (
1717
</div>
1818
)
1919

20-
export const TabContent = ({ className, children, ...props }: TabProps) => {
20+
export const TabContent = forwardRef<HTMLDivElement, TabProps>(({ className, children, ...props }, ref) => {
2121
const { renderContext } = useExtensionState()
2222

2323
const onWheel = useCallback(
@@ -40,11 +40,12 @@ export const TabContent = ({ className, children, ...props }: TabProps) => {
4040
)
4141

4242
return (
43-
<div className={cn("flex-1 overflow-auto p-5", className)} onWheel={onWheel} {...props}>
43+
<div ref={ref} className={cn("flex-1 overflow-auto p-5", className)} onWheel={onWheel} {...props}>
4444
{children}
4545
</div>
4646
)
47-
}
47+
})
48+
TabContent.displayName = "TabContent"
4849

4950
export const TabList = forwardRef<
5051
HTMLDivElement,

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
112112
: "providers",
113113
)
114114

115+
const scrollPositions = useRef<Record<SectionName, number>>(
116+
Object.fromEntries(sectionNames.map((s) => [s, 0])) as Record<SectionName, number>,
117+
)
118+
const contentRef = useRef<HTMLDivElement | null>(null)
119+
115120
const prevApiConfigName = useRef(currentApiConfigName)
116121
const confirmDialogHandler = useRef<() => void>()
117122

@@ -398,12 +403,20 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
398403
// Handle tab changes with unsaved changes check
399404
const handleTabChange = useCallback(
400405
(newTab: SectionName) => {
401-
// Directly switch tab without checking for unsaved changes
406+
if (contentRef.current) {
407+
scrollPositions.current[activeTab] = contentRef.current.scrollTop
408+
}
402409
setActiveTab(newTab)
403410
},
404-
[], // No dependency on isChangeDetected needed anymore
411+
[activeTab],
405412
)
406413

414+
useLayoutEffect(() => {
415+
if (contentRef.current) {
416+
contentRef.current.scrollTop = scrollPositions.current[activeTab] ?? 0
417+
}
418+
}, [activeTab])
419+
407420
// Store direct DOM element refs for each tab
408421
const tabRefs = useRef<Record<SectionName, HTMLButtonElement | null>>(
409422
Object.fromEntries(sectionNames.map((name) => [name, null])) as Record<SectionName, HTMLButtonElement | null>,
@@ -579,7 +592,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
579592
</TabList>
580593

581594
{/* Content area */}
582-
<TabContent className="p-0 flex-1 overflow-auto">
595+
<TabContent ref={contentRef} className="p-0 flex-1 overflow-auto">
583596
{/* Providers Section */}
584597
{activeTab === "providers" && (
585598
<div>

0 commit comments

Comments
 (0)