import { useCallback } from 'react'; import { Folder, ChevronDown, MoreVertical, Palette, Monitor, Moon, Sun, Undo2, Redo2, RotateCcw, Trash2, Search, LogOut, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { formatShortcut, type ThemeMode, useAppStore } from '@/store/app-store'; import { initializeProject } from '@/lib/project-init'; import type { Project } from '@/lib/electron'; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuLabel, } from '@/components/ui/dropdown-menu'; import { DndContext, closestCenter } from '@dnd-kit/core'; import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { SortableProjectItem, ThemeMenuItem } from './'; import { PROJECT_DARK_THEMES, PROJECT_LIGHT_THEMES, THEME_SUBMENU_CONSTANTS } from '../constants'; import { useProjectPicker, useDragAndDrop, useProjectTheme } from '../hooks'; import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts'; /** * Props for the ProjectSelectorWithOptions component. * Defines the interface for the project selector dropdown with additional options menu. */ interface ProjectSelectorWithOptionsProps { /** Whether the sidebar is currently expanded */ sidebarOpen: boolean; /** Whether the project picker dropdown is currently open */ isProjectPickerOpen: boolean; /** Callback to control the project picker dropdown open state */ setIsProjectPickerOpen: (value: boolean | ((prev: boolean) => boolean)) => void; /** Callback to show the delete project confirmation dialog */ setShowDeleteProjectDialog: (show: boolean) => void; /** Callback to show the remove from automaker confirmation dialog */ setShowRemoveFromAutomakerDialog: (show: boolean) => void; } /** * A project selector component with search, drag-and-drop reordering, and options menu. * * Features: * - Searchable dropdown for quick project switching * - Drag-and-drop reordering of projects * - Project-specific theme selection with live preview * - Project history navigation (previous/next) * - Option to move project to trash * * The component uses viewport-aware positioning via THEME_SUBMENU_CONSTANTS * for consistent submenu behavior across the application. * * @param props - Component props * @returns The rendered project selector or null if sidebar is closed or no projects exist */ export function ProjectSelectorWithOptions({ sidebarOpen, isProjectPickerOpen, setIsProjectPickerOpen, setShowDeleteProjectDialog, setShowRemoveFromAutomakerDialog, }: ProjectSelectorWithOptionsProps) { const { projects, currentProject, projectHistory, setCurrentProject, reorderProjects, cyclePrevProject, cycleNextProject, clearProjectHistory, } = useAppStore(); const shortcuts = useKeyboardShortcutsConfig(); // Wrap setCurrentProject to ensure .automaker is initialized before switching const setCurrentProjectWithInit = useCallback( async (p: Project) => { try { // Ensure .automaker directory structure exists before switching await initializeProject(p.path); } catch (error) { console.error('Failed to initialize project during switch:', error); // Continue with switch even if initialization fails - // the project may already be initialized } setCurrentProject(p); }, [setCurrentProject] ); const { projectSearchQuery, setProjectSearchQuery, selectedProjectIndex, projectSearchInputRef, scrollContainerRef, filteredProjects, } = useProjectPicker({ projects, currentProject, isProjectPickerOpen, setIsProjectPickerOpen, setCurrentProject: setCurrentProjectWithInit, }); const { sensors, handleDragEnd } = useDragAndDrop({ projects, reorderProjects }); const { globalTheme, setProjectTheme, setPreviewTheme, handlePreviewEnter, handlePreviewLeave } = useProjectTheme(); const handleSelectProject = useCallback( async (p: Project) => { await setCurrentProjectWithInit(p); setIsProjectPickerOpen(false); }, [setCurrentProjectWithInit, setIsProjectPickerOpen] ); if (!sidebarOpen || projects.length === 0) { return null; } return (
{/* Search input */}
setProjectSearchQuery(e.target.value)} className={cn( 'w-full h-8 pl-8 pr-3 text-sm rounded-lg', 'border border-border bg-background/50', 'text-foreground placeholder:text-muted-foreground', 'focus:outline-none focus:ring-1 focus:ring-brand-500/30 focus:border-brand-500/50', 'transition-all duration-200' )} data-testid="project-search-input" />
{filteredProjects.length === 0 ? (
No projects found
) : ( p.id)} strategy={verticalListSortingStrategy} >
{filteredProjects.map((project, index) => ( ))}
)} {/* Keyboard hint */}

↑↓ navigate{' '} |{' '} select{' '} |{' '} esc close

{/* Project Options Menu */} {currentProject && ( { // Clear preview theme when the menu closes if (!open) { setPreviewTheme(null); } }} > {/* Project Theme Submenu */} Project Theme {currentProject.theme && ( {currentProject.theme} )} { // Clear preview theme when leaving the dropdown setPreviewTheme(null); }} > { if (currentProject) { setPreviewTheme(null); // Only set project theme - don't change global theme // The UI uses getEffectiveTheme() which handles: previewTheme ?? projectTheme ?? globalTheme setProjectTheme( currentProject.id, value === '' ? null : (value as ThemeMode) ); } }} >
handlePreviewEnter(globalTheme)} onPointerLeave={() => setPreviewTheme(null)} > Use Global ({globalTheme})
{/* Two Column Layout */} {/* Max height with scroll to ensure all themes are visible when menu is near screen edge */}
{/* Dark Themes Column */}
Dark
{PROJECT_DARK_THEMES.map((option) => ( ))}
{/* Light Themes Column */}
Light
{PROJECT_LIGHT_THEMES.map((option) => ( ))}
{/* Project History Section */} {projectHistory.length > 1 && ( <> Project History Previous {formatShortcut(shortcuts.cyclePrevProject, true)} Next {formatShortcut(shortcuts.cycleNextProject, true)} Clear history )} {/* Remove / Trash Section */} setShowRemoveFromAutomakerDialog(true)} className="text-muted-foreground focus:text-foreground" data-testid="remove-from-automaker" > Remove from Automaker setShowDeleteProjectDialog(true)} className="text-destructive focus:text-destructive focus:bg-destructive/10" data-testid="move-project-to-trash" > Move to Trash
)}
); }