fix(ui): sync provider quick switch highlight with scroll target

This commit is contained in:
Supra4E8C
2026-01-31 17:12:59 +08:00
parent 460519ed00
commit c6fabcb6bc

View File

@@ -28,6 +28,8 @@ const PROVIDERS: ProviderNavItem[] = [
{ id: 'openai', label: 'OpenAI', getIcon: (theme) => (theme === 'dark' ? iconOpenaiDark : iconOpenaiLight) }, { id: 'openai', label: 'OpenAI', getIcon: (theme) => (theme === 'dark' ? iconOpenaiDark : iconOpenaiLight) },
]; ];
const HEADER_OFFSET = 24;
export function ProviderNav() { export function ProviderNav() {
const resolvedTheme = useThemeStore((state) => state.resolvedTheme); const resolvedTheme = useThemeStore((state) => state.resolvedTheme);
const [activeProvider, setActiveProvider] = useState<ProviderId | null>(null); const [activeProvider, setActiveProvider] = useState<ProviderId | null>(null);
@@ -45,37 +47,27 @@ export function ProviderNav() {
if (!container) return; if (!container) return;
const containerRect = container.getBoundingClientRect(); const containerRect = container.getBoundingClientRect();
const triggerPoint = containerRect.top + containerRect.height * 0.3; const activationLine = containerRect.top + HEADER_OFFSET + 1;
let currentActive: ProviderId | null = null; let currentActive: ProviderId | null = null;
let closestAbove: { id: ProviderId; top: number } | null = null;
for (const provider of PROVIDERS) { for (const provider of PROVIDERS) {
const element = document.getElementById(`provider-${provider.id}`); const element = document.getElementById(`provider-${provider.id}`);
if (element) { if (!element) continue;
const rect = element.getBoundingClientRect();
const elementTop = rect.top;
const elementBottom = rect.bottom;
// Check if triggerPoint is within this element's bounds const rect = element.getBoundingClientRect();
if (triggerPoint >= elementTop && triggerPoint < elementBottom) { if (rect.top <= activationLine) {
currentActive = provider.id; currentActive = provider.id;
break; continue;
}
// Track the element whose top is closest to (but not exceeding) triggerPoint
// This handles short cards where triggerPoint may have passed the bottom
if (elementTop <= triggerPoint) {
if (!closestAbove || elementTop > closestAbove.top) {
closestAbove = { id: provider.id, top: elementTop };
}
}
} }
if (currentActive) break;
} }
// If no element contains triggerPoint, use the closest one above it if (!currentActive) {
if (!currentActive && closestAbove) { const firstVisible = PROVIDERS.find((provider) =>
currentActive = closestAbove.id; document.getElementById(`provider-${provider.id}`)
);
currentActive = firstVisible?.id ?? null;
} }
setActiveProvider(currentActive); setActiveProvider(currentActive);
@@ -86,6 +78,7 @@ export function ProviderNav() {
if (!container) return; if (!container) return;
container.addEventListener('scroll', handleScroll, { passive: true }); container.addEventListener('scroll', handleScroll, { passive: true });
handleScroll();
return () => container.removeEventListener('scroll', handleScroll); return () => container.removeEventListener('scroll', handleScroll);
}, [handleScroll, getScrollContainer]); }, [handleScroll, getScrollContainer]);
@@ -96,9 +89,9 @@ export function ProviderNav() {
const containerRect = container.getBoundingClientRect(); const containerRect = container.getBoundingClientRect();
const elementRect = element.getBoundingClientRect(); const elementRect = element.getBoundingClientRect();
const headerOffset = 24; const scrollTop = container.scrollTop + (elementRect.top - containerRect.top) - HEADER_OFFSET;
const scrollTop = container.scrollTop + (elementRect.top - containerRect.top) - headerOffset;
setActiveProvider(providerId);
container.scrollTo({ container.scrollTo({
top: scrollTop, top: scrollTop,
behavior: 'smooth', behavior: 'smooth',