mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 20:03:37 +00:00
refactor: convert settings page to separate view panels
- Replace scroll-based navigation with view switching - Add useSettingsView hook for managing active panel state - Extract Audio section into its own component - Remove scroll-mt-6 classes and IDs from section components - Update navigation config to reflect current sections - Create barrel export for settings-view hooks This improves performance by only rendering the active section instead of all sections in a single scrollable container. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Key,
|
||||
Palette,
|
||||
@@ -11,12 +10,13 @@ import {
|
||||
Trash2,
|
||||
Settings2,
|
||||
Volume2,
|
||||
VolumeX,
|
||||
} from "lucide-react";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
import { useCliStatus } from "./settings-view/hooks/use-cli-status";
|
||||
import { useScrollTracking } from "@/hooks/use-scroll-tracking";
|
||||
import {
|
||||
useCliStatus,
|
||||
useSettingsView,
|
||||
type SettingsViewId,
|
||||
} from "./settings-view/hooks";
|
||||
import { SettingsHeader } from "./settings-view/components/settings-header";
|
||||
import { KeyboardMapDialog } from "./settings-view/components/keyboard-map-dialog";
|
||||
import { DeleteProjectDialog } from "./settings-view/components/delete-project-dialog";
|
||||
@@ -24,6 +24,7 @@ import { SettingsNavigation } from "./settings-view/components/settings-navigati
|
||||
import { ApiKeysSection } from "./settings-view/api-keys/api-keys-section";
|
||||
import { ClaudeCliStatus } from "./settings-view/cli-status/claude-cli-status";
|
||||
import { AppearanceSection } from "./settings-view/appearance/appearance-section";
|
||||
import { AudioSection } from "./settings-view/audio/audio-section";
|
||||
import { KeyboardShortcutsSection } from "./settings-view/keyboard-shortcuts/keyboard-shortcuts-section";
|
||||
import { FeatureDefaultsSection } from "./settings-view/feature-defaults/feature-defaults-section";
|
||||
import { DangerZoneSection } from "./settings-view/danger-zone/danger-zone-section";
|
||||
@@ -91,23 +92,72 @@ export function SettingsView() {
|
||||
};
|
||||
|
||||
// Use CLI status hook
|
||||
const {
|
||||
claudeCliStatus,
|
||||
isCheckingClaudeCli,
|
||||
handleRefreshClaudeCli,
|
||||
} = useCliStatus();
|
||||
const { claudeCliStatus, isCheckingClaudeCli, handleRefreshClaudeCli } =
|
||||
useCliStatus();
|
||||
|
||||
// Use scroll tracking hook
|
||||
const { activeSection, scrollToSection, scrollContainerRef } =
|
||||
useScrollTracking({
|
||||
items: NAV_ITEMS,
|
||||
filterFn: (item) => item.id !== "danger" || !!currentProject,
|
||||
initialSection: "api-keys",
|
||||
});
|
||||
// Use settings view navigation hook
|
||||
const { activeView, navigateTo } = useSettingsView();
|
||||
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [showKeyboardMapDialog, setShowKeyboardMapDialog] = useState(false);
|
||||
|
||||
// Render the active section based on current view
|
||||
const renderActiveSection = () => {
|
||||
switch (activeView) {
|
||||
case "api-keys":
|
||||
return <ApiKeysSection />;
|
||||
case "claude":
|
||||
return (
|
||||
<ClaudeCliStatus
|
||||
status={claudeCliStatus}
|
||||
isChecking={isCheckingClaudeCli}
|
||||
onRefresh={handleRefreshClaudeCli}
|
||||
/>
|
||||
);
|
||||
case "appearance":
|
||||
return (
|
||||
<AppearanceSection
|
||||
effectiveTheme={effectiveTheme}
|
||||
currentProject={settingsProject}
|
||||
onThemeChange={handleSetTheme}
|
||||
/>
|
||||
);
|
||||
case "keyboard":
|
||||
return (
|
||||
<KeyboardShortcutsSection
|
||||
onOpenKeyboardMap={() => setShowKeyboardMapDialog(true)}
|
||||
/>
|
||||
);
|
||||
case "audio":
|
||||
return (
|
||||
<AudioSection
|
||||
muteDoneSound={muteDoneSound}
|
||||
onMuteDoneSoundChange={setMuteDoneSound}
|
||||
/>
|
||||
);
|
||||
case "defaults":
|
||||
return (
|
||||
<FeatureDefaultsSection
|
||||
showProfilesOnly={showProfilesOnly}
|
||||
defaultSkipTests={defaultSkipTests}
|
||||
useWorktrees={useWorktrees}
|
||||
onShowProfilesOnlyChange={setShowProfilesOnly}
|
||||
onDefaultSkipTestsChange={setDefaultSkipTests}
|
||||
onUseWorktreesChange={setUseWorktrees}
|
||||
/>
|
||||
);
|
||||
case "danger":
|
||||
return (
|
||||
<DangerZoneSection
|
||||
project={settingsProject}
|
||||
onDeleteClick={() => setShowDeleteDialog(true)}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <ApiKeysSection />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex-1 flex flex-col overflow-hidden content-bg"
|
||||
@@ -118,107 +168,17 @@ export function SettingsView() {
|
||||
|
||||
{/* Content Area with Sidebar */}
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
{/* Sticky Side Navigation */}
|
||||
{/* Side Navigation - No longer scrolls, just switches views */}
|
||||
<SettingsNavigation
|
||||
navItems={NAV_ITEMS}
|
||||
activeSection={activeSection}
|
||||
activeSection={activeView}
|
||||
currentProject={currentProject}
|
||||
onNavigate={scrollToSection}
|
||||
onNavigate={(id) => navigateTo(id as SettingsViewId)}
|
||||
/>
|
||||
|
||||
{/* Scrollable Content */}
|
||||
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto p-8">
|
||||
<div className="max-w-4xl mx-auto space-y-6 pb-96">
|
||||
{/* API Keys Section */}
|
||||
<ApiKeysSection />
|
||||
|
||||
{/* Claude CLI Status Section */}
|
||||
{claudeCliStatus && (
|
||||
<ClaudeCliStatus
|
||||
status={claudeCliStatus}
|
||||
isChecking={isCheckingClaudeCli}
|
||||
onRefresh={handleRefreshClaudeCli}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Appearance Section */}
|
||||
<AppearanceSection
|
||||
effectiveTheme={effectiveTheme}
|
||||
currentProject={settingsProject}
|
||||
onThemeChange={handleSetTheme}
|
||||
/>
|
||||
|
||||
|
||||
{/* Keyboard Shortcuts Section */}
|
||||
<KeyboardShortcutsSection
|
||||
onOpenKeyboardMap={() => setShowKeyboardMapDialog(true)}
|
||||
/>
|
||||
|
||||
{/* Audio Section */}
|
||||
<div
|
||||
id="audio"
|
||||
className="rounded-2xl border border-border/50 bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl shadow-sm shadow-black/5 overflow-hidden scroll-mt-6"
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
||||
<Volume2 className="w-5 h-5 text-brand-500" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">
|
||||
Audio
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Configure audio and notification settings.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
{/* Mute Done Sound Setting */}
|
||||
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||
<Checkbox
|
||||
id="mute-done-sound"
|
||||
checked={muteDoneSound}
|
||||
onCheckedChange={(checked) =>
|
||||
setMuteDoneSound(checked === true)
|
||||
}
|
||||
className="mt-1"
|
||||
data-testid="mute-done-sound-checkbox"
|
||||
/>
|
||||
<div className="space-y-1.5">
|
||||
<Label
|
||||
htmlFor="mute-done-sound"
|
||||
className="text-foreground cursor-pointer font-medium flex items-center gap-2"
|
||||
>
|
||||
<VolumeX className="w-4 h-4 text-brand-500" />
|
||||
Mute notification sound when agents complete
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
When enabled, disables the "ding" sound that
|
||||
plays when an agent completes a feature. The feature
|
||||
will still move to the completed column, but without
|
||||
audio notification.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature Defaults Section */}
|
||||
<FeatureDefaultsSection
|
||||
showProfilesOnly={showProfilesOnly}
|
||||
defaultSkipTests={defaultSkipTests}
|
||||
useWorktrees={useWorktrees}
|
||||
onShowProfilesOnlyChange={setShowProfilesOnly}
|
||||
onDefaultSkipTestsChange={setDefaultSkipTests}
|
||||
onUseWorktreesChange={setUseWorktrees}
|
||||
/>
|
||||
|
||||
{/* Danger Zone Section - Only show when a project is selected */}
|
||||
<DangerZoneSection
|
||||
project={settingsProject}
|
||||
onDeleteClick={() => setShowDeleteDialog(true)}
|
||||
/>
|
||||
</div>
|
||||
{/* Content Panel - Shows only the active section */}
|
||||
<div className="flex-1 overflow-y-auto p-8">
|
||||
<div className="max-w-4xl mx-auto">{renderActiveSection()}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -58,9 +58,8 @@ export function ApiKeysSection() {
|
||||
|
||||
return (
|
||||
<div
|
||||
id="api-keys"
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden scroll-mt-6",
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
|
||||
@@ -18,9 +18,8 @@ export function AppearanceSection({
|
||||
}: AppearanceSectionProps) {
|
||||
return (
|
||||
<div
|
||||
id="appearance"
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden scroll-mt-6",
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Volume2, VolumeX } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface AudioSectionProps {
|
||||
muteDoneSound: boolean;
|
||||
onMuteDoneSoundChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function AudioSection({
|
||||
muteDoneSound,
|
||||
onMuteDoneSoundChange,
|
||||
}: AudioSectionProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
||||
<Volume2 className="w-5 h-5 text-brand-500" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">
|
||||
Audio
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Configure audio and notification settings.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||
<Checkbox
|
||||
id="mute-done-sound"
|
||||
checked={muteDoneSound}
|
||||
onCheckedChange={(checked) => onMuteDoneSoundChange(checked === true)}
|
||||
className="mt-1"
|
||||
data-testid="mute-done-sound-checkbox"
|
||||
/>
|
||||
<div className="space-y-1.5">
|
||||
<Label
|
||||
htmlFor="mute-done-sound"
|
||||
className="text-foreground cursor-pointer font-medium flex items-center gap-2"
|
||||
>
|
||||
<VolumeX className="w-4 h-4 text-brand-500" />
|
||||
Mute notification sound when agents complete
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
When enabled, disables the "ding" sound that plays when
|
||||
an agent completes a feature. The feature will still move to the
|
||||
completed column, but without audio notification.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,9 +23,8 @@ export function ClaudeCliStatus({
|
||||
|
||||
return (
|
||||
<div
|
||||
id="claude"
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden scroll-mt-6",
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
Key,
|
||||
Terminal,
|
||||
Palette,
|
||||
LayoutGrid,
|
||||
Settings2,
|
||||
Volume2,
|
||||
FlaskConical,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
@@ -20,8 +20,8 @@ export const NAV_ITEMS: NavigationItem[] = [
|
||||
{ id: "api-keys", label: "API Keys", icon: Key },
|
||||
{ id: "claude", label: "Claude", icon: Terminal },
|
||||
{ id: "appearance", label: "Appearance", icon: Palette },
|
||||
{ id: "kanban", label: "Kanban Display", icon: LayoutGrid },
|
||||
{ id: "keyboard", label: "Keyboard Shortcuts", icon: Settings2 },
|
||||
{ id: "audio", label: "Audio", icon: Volume2 },
|
||||
{ id: "defaults", label: "Feature Defaults", icon: FlaskConical },
|
||||
{ id: "danger", label: "Danger Zone", icon: Trash2 },
|
||||
];
|
||||
|
||||
@@ -16,9 +16,8 @@ export function DangerZoneSection({
|
||||
|
||||
return (
|
||||
<div
|
||||
id="danger"
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden scroll-mt-6",
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-destructive/30",
|
||||
"bg-gradient-to-br from-destructive/5 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-destructive/5"
|
||||
|
||||
@@ -22,9 +22,8 @@ export function FeatureDefaultsSection({
|
||||
}: FeatureDefaultsSectionProps) {
|
||||
return (
|
||||
<div
|
||||
id="defaults"
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden scroll-mt-6",
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { useCliStatus } from "./use-cli-status";
|
||||
export { useSettingsView, type SettingsViewId } from "./use-settings-view";
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
export type SettingsViewId =
|
||||
| "api-keys"
|
||||
| "claude"
|
||||
| "appearance"
|
||||
| "keyboard"
|
||||
| "audio"
|
||||
| "defaults"
|
||||
| "danger";
|
||||
|
||||
interface UseSettingsViewOptions {
|
||||
initialView?: SettingsViewId;
|
||||
}
|
||||
|
||||
export function useSettingsView({
|
||||
initialView = "api-keys",
|
||||
}: UseSettingsViewOptions = {}) {
|
||||
const [activeView, setActiveView] = useState<SettingsViewId>(initialView);
|
||||
|
||||
const navigateTo = useCallback((viewId: SettingsViewId) => {
|
||||
setActiveView(viewId);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
activeView,
|
||||
navigateTo,
|
||||
};
|
||||
}
|
||||
@@ -11,9 +11,8 @@ export function KeyboardShortcutsSection({
|
||||
}: KeyboardShortcutsSectionProps) {
|
||||
return (
|
||||
<div
|
||||
id="keyboard"
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden scroll-mt-6",
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
|
||||
Reference in New Issue
Block a user