Files
automaker/apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts
Stefan de Vogelaere 5436b18f70 refactor: move Project Settings below Tools section in sidebar
- Remove Project Settings from Project section
- Add Project Settings as standalone section below Tools/GitHub
- Use empty label for visual separation without header
- Add horizontal separator line above sections without labels
- Rename to "Project Settings" for clarity
- Keep "Global Settings" at bottom of sidebar
2026-01-16 23:27:53 +01:00

302 lines
7.3 KiB
TypeScript

import { useMemo, useState, useEffect } from 'react';
import type { NavigateOptions } from '@tanstack/react-router';
import {
FileText,
LayoutGrid,
Bot,
BookOpen,
Terminal,
CircleDot,
GitPullRequest,
Lightbulb,
Brain,
Network,
Settings,
} from 'lucide-react';
import type { NavSection, NavItem } from '../types';
import type { KeyboardShortcut } from '@/hooks/use-keyboard-shortcuts';
import type { Project } from '@/lib/electron';
import { getElectronAPI } from '@/lib/electron';
interface UseNavigationProps {
shortcuts: {
toggleSidebar: string;
openProject: string;
projectPicker: string;
cyclePrevProject: string;
cycleNextProject: string;
spec: string;
context: string;
memory: string;
board: string;
graph: string;
agent: string;
terminal: string;
settings: string;
projectSettings: string;
ideation: string;
githubIssues: string;
githubPrs: string;
};
hideSpecEditor: boolean;
hideContext: boolean;
hideTerminal: boolean;
currentProject: Project | null;
projects: Project[];
projectHistory: string[];
navigate: (opts: NavigateOptions) => void;
toggleSidebar: () => void;
handleOpenFolder: () => void;
cyclePrevProject: () => void;
cycleNextProject: () => void;
/** Count of unviewed validations to show on GitHub Issues nav item */
unviewedValidationsCount?: number;
/** Whether spec generation is currently running for the current project */
isSpecGenerating?: boolean;
}
export function useNavigation({
shortcuts,
hideSpecEditor,
hideContext,
hideTerminal,
currentProject,
projects,
projectHistory,
navigate,
toggleSidebar,
handleOpenFolder,
cyclePrevProject,
cycleNextProject,
unviewedValidationsCount,
isSpecGenerating,
}: UseNavigationProps) {
// Track if current project has a GitHub remote
const [hasGitHubRemote, setHasGitHubRemote] = useState(false);
useEffect(() => {
async function checkGitHubRemote() {
if (!currentProject?.path) {
setHasGitHubRemote(false);
return;
}
try {
const api = getElectronAPI();
if (api.github) {
const result = await api.github.checkRemote(currentProject.path);
setHasGitHubRemote(result.success && result.hasGitHubRemote === true);
}
} catch {
setHasGitHubRemote(false);
}
}
checkGitHubRemote();
}, [currentProject?.path]);
// Build navigation sections
const navSections: NavSection[] = useMemo(() => {
const allToolsItems: NavItem[] = [
{
id: 'ideation',
label: 'Ideation',
icon: Lightbulb,
shortcut: shortcuts.ideation,
},
{
id: 'spec',
label: 'Spec Editor',
icon: FileText,
shortcut: shortcuts.spec,
isLoading: isSpecGenerating,
},
{
id: 'context',
label: 'Context',
icon: BookOpen,
shortcut: shortcuts.context,
},
{
id: 'memory',
label: 'Memory',
icon: Brain,
shortcut: shortcuts.memory,
},
];
// Filter out hidden items
const visibleToolsItems = allToolsItems.filter((item) => {
if (item.id === 'spec' && hideSpecEditor) {
return false;
}
if (item.id === 'context' && hideContext) {
return false;
}
return true;
});
// Build project items - Terminal is conditionally included
const projectItems: NavItem[] = [
{
id: 'board',
label: 'Kanban Board',
icon: LayoutGrid,
shortcut: shortcuts.board,
},
{
id: 'graph',
label: 'Graph View',
icon: Network,
shortcut: shortcuts.graph,
},
{
id: 'agent',
label: 'Agent Runner',
icon: Bot,
shortcut: shortcuts.agent,
},
];
// Add Terminal to Project section if not hidden
if (!hideTerminal) {
projectItems.push({
id: 'terminal',
label: 'Terminal',
icon: Terminal,
shortcut: shortcuts.terminal,
});
}
const sections: NavSection[] = [
{
label: 'Project',
items: projectItems,
},
{
label: 'Tools',
items: visibleToolsItems,
},
];
// Add GitHub section if project has a GitHub remote
if (hasGitHubRemote) {
sections.push({
label: 'GitHub',
items: [
{
id: 'github-issues',
label: 'Issues',
icon: CircleDot,
shortcut: shortcuts.githubIssues,
count: unviewedValidationsCount,
},
{
id: 'github-prs',
label: 'Pull Requests',
icon: GitPullRequest,
shortcut: shortcuts.githubPrs,
},
],
});
}
// Add Project Settings as a standalone section (no label for visual separation)
sections.push({
label: '',
items: [
{
id: 'project-settings',
label: 'Project Settings',
icon: Settings,
shortcut: shortcuts.projectSettings,
},
],
});
return sections;
}, [
shortcuts,
hideSpecEditor,
hideContext,
hideTerminal,
hasGitHubRemote,
unviewedValidationsCount,
isSpecGenerating,
]);
// Build keyboard shortcuts for navigation
const navigationShortcuts: KeyboardShortcut[] = useMemo(() => {
const shortcutsList: KeyboardShortcut[] = [];
// Sidebar toggle shortcut - always available
shortcutsList.push({
key: shortcuts.toggleSidebar,
action: () => toggleSidebar(),
description: 'Toggle sidebar',
});
// Open project shortcut - opens the folder selection dialog directly
shortcutsList.push({
key: shortcuts.openProject,
action: () => handleOpenFolder(),
description: 'Open folder selection dialog',
});
// Project cycling shortcuts - only when we have project history
if (projectHistory.length > 1) {
shortcutsList.push({
key: shortcuts.cyclePrevProject,
action: () => cyclePrevProject(),
description: 'Cycle to previous project (MRU)',
});
shortcutsList.push({
key: shortcuts.cycleNextProject,
action: () => cycleNextProject(),
description: 'Cycle to next project (LRU)',
});
}
// Only enable nav shortcuts if there's a current project
if (currentProject) {
navSections.forEach((section) => {
section.items.forEach((item) => {
if (item.shortcut) {
shortcutsList.push({
key: item.shortcut,
// Cast to router path type; ids are constrained to known routes
action: () => navigate({ to: `/${item.id}` as unknown as '/' }),
description: `Navigate to ${item.label}`,
});
}
});
});
// Add global settings shortcut
shortcutsList.push({
key: shortcuts.settings,
action: () => navigate({ to: '/settings' }),
description: 'Navigate to Global Settings',
});
}
return shortcutsList;
}, [
shortcuts,
currentProject,
navigate,
toggleSidebar,
projects.length,
handleOpenFolder,
projectHistory.length,
cyclePrevProject,
cycleNextProject,
navSections,
]);
return {
navSections,
navigationShortcuts,
};
}