mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
refactor(settings): extract scroll tracking into custom hook
- Create hooks/use-scroll-tracking.ts for scroll-based navigation - Move scroll position tracking logic and useEffect to hook - Move scrollToSection callback to hook - Update settings-view.tsx to use new useScrollTracking hook - Remove useState, useEffect, useRef, useCallback imports (no longer needed) - Reduce settings-view.tsx by ~60 lines - Improve code organization and testability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useRef, useCallback } from "react";
|
import { useState } from "react";
|
||||||
import { useAppStore } from "@/store/app-store";
|
import { useAppStore } from "@/store/app-store";
|
||||||
import { useSetupStore } from "@/store/setup-store";
|
import { useSetupStore } from "@/store/setup-store";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
import { KeyboardMap, ShortcutReferencePanel } from "@/components/ui/keyboard-map";
|
import { KeyboardMap, ShortcutReferencePanel } from "@/components/ui/keyboard-map";
|
||||||
// Import custom hooks
|
// Import custom hooks
|
||||||
import { useCliStatus } from "./settings-view/hooks/use-cli-status";
|
import { useCliStatus } from "./settings-view/hooks/use-cli-status";
|
||||||
|
import { useScrollTracking } from "./settings-view/hooks/use-scroll-tracking";
|
||||||
// Import extracted sections
|
// Import extracted sections
|
||||||
import { ApiKeysSection } from "./settings-view/api-keys/api-keys-section";
|
import { ApiKeysSection } from "./settings-view/api-keys/api-keys-section";
|
||||||
import { ClaudeCliStatus } from "./settings-view/cli-status/claude-cli-status";
|
import { ClaudeCliStatus } from "./settings-view/cli-status/claude-cli-status";
|
||||||
@@ -95,72 +96,12 @@ export function SettingsView() {
|
|||||||
handleRefreshCodexCli,
|
handleRefreshCodexCli,
|
||||||
} = useCliStatus();
|
} = useCliStatus();
|
||||||
|
|
||||||
const [activeSection, setActiveSection] = useState("api-keys");
|
// Use scroll tracking hook
|
||||||
|
const { activeSection, scrollToSection, scrollContainerRef } =
|
||||||
|
useScrollTracking(NAV_ITEMS, currentProject);
|
||||||
|
|
||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
const [showKeyboardMapDialog, setShowKeyboardMapDialog] = useState(false);
|
const [showKeyboardMapDialog, setShowKeyboardMapDialog] = useState(false);
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// Track scroll position to highlight active nav item
|
|
||||||
useEffect(() => {
|
|
||||||
const container = scrollContainerRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
const sections = NAV_ITEMS.filter(
|
|
||||||
(item) => item.id !== "danger" || currentProject
|
|
||||||
)
|
|
||||||
.map((item) => ({
|
|
||||||
id: item.id,
|
|
||||||
element: document.getElementById(item.id),
|
|
||||||
}))
|
|
||||||
.filter((s) => s.element);
|
|
||||||
|
|
||||||
const containerRect = container.getBoundingClientRect();
|
|
||||||
const scrollTop = container.scrollTop;
|
|
||||||
const scrollHeight = container.scrollHeight;
|
|
||||||
const clientHeight = container.clientHeight;
|
|
||||||
|
|
||||||
// Check if scrolled to bottom (within a small threshold)
|
|
||||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
|
|
||||||
|
|
||||||
if (isAtBottom && sections.length > 0) {
|
|
||||||
// If at bottom, highlight the last visible section
|
|
||||||
setActiveSection(sections[sections.length - 1].id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = sections.length - 1; i >= 0; i--) {
|
|
||||||
const section = sections[i];
|
|
||||||
if (section.element) {
|
|
||||||
const rect = section.element.getBoundingClientRect();
|
|
||||||
const relativeTop = rect.top - containerRect.top + scrollTop;
|
|
||||||
if (scrollTop >= relativeTop - 100) {
|
|
||||||
setActiveSection(section.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
container.addEventListener("scroll", handleScroll);
|
|
||||||
return () => container.removeEventListener("scroll", handleScroll);
|
|
||||||
}, [currentProject]);
|
|
||||||
|
|
||||||
const scrollToSection = useCallback((sectionId: string) => {
|
|
||||||
const element = document.getElementById(sectionId);
|
|
||||||
if (element && scrollContainerRef.current) {
|
|
||||||
const container = scrollContainerRef.current;
|
|
||||||
const containerRect = container.getBoundingClientRect();
|
|
||||||
const elementRect = element.getBoundingClientRect();
|
|
||||||
const relativeTop =
|
|
||||||
elementRect.top - containerRect.top + container.scrollTop;
|
|
||||||
|
|
||||||
container.scrollTo({
|
|
||||||
top: relativeTop - 24,
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { useState, useEffect, useRef, useCallback } from "react";
|
||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
import type { Project } from "@/store/app-store";
|
||||||
|
|
||||||
|
interface NavigationItem {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for managing scroll-based navigation tracking
|
||||||
|
* Automatically highlights the active section based on scroll position
|
||||||
|
* and provides smooth scrolling to sections
|
||||||
|
*/
|
||||||
|
export function useScrollTracking(
|
||||||
|
navItems: NavigationItem[],
|
||||||
|
currentProject: Project | null
|
||||||
|
) {
|
||||||
|
const [activeSection, setActiveSection] = useState("api-keys");
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Track scroll position to highlight active nav item
|
||||||
|
useEffect(() => {
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const sections = navItems
|
||||||
|
.filter((item) => item.id !== "danger" || currentProject)
|
||||||
|
.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
element: document.getElementById(item.id),
|
||||||
|
}))
|
||||||
|
.filter((s) => s.element);
|
||||||
|
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const scrollTop = container.scrollTop;
|
||||||
|
const scrollHeight = container.scrollHeight;
|
||||||
|
const clientHeight = container.clientHeight;
|
||||||
|
|
||||||
|
// Check if scrolled to bottom (within a small threshold)
|
||||||
|
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
|
||||||
|
|
||||||
|
if (isAtBottom && sections.length > 0) {
|
||||||
|
// If at bottom, highlight the last visible section
|
||||||
|
setActiveSection(sections[sections.length - 1].id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = sections.length - 1; i >= 0; i--) {
|
||||||
|
const section = sections[i];
|
||||||
|
if (section.element) {
|
||||||
|
const rect = section.element.getBoundingClientRect();
|
||||||
|
const relativeTop = rect.top - containerRect.top + scrollTop;
|
||||||
|
if (scrollTop >= relativeTop - 100) {
|
||||||
|
setActiveSection(section.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
container.addEventListener("scroll", handleScroll);
|
||||||
|
return () => container.removeEventListener("scroll", handleScroll);
|
||||||
|
}, [currentProject, navItems]);
|
||||||
|
|
||||||
|
// Scroll to a specific section with smooth animation
|
||||||
|
const scrollToSection = useCallback((sectionId: string) => {
|
||||||
|
const element = document.getElementById(sectionId);
|
||||||
|
if (element && scrollContainerRef.current) {
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const elementRect = element.getBoundingClientRect();
|
||||||
|
const relativeTop =
|
||||||
|
elementRect.top - containerRect.top + container.scrollTop;
|
||||||
|
|
||||||
|
container.scrollTo({
|
||||||
|
top: relativeTop - 24,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeSection,
|
||||||
|
scrollToSection,
|
||||||
|
scrollContainerRef,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user