mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-05 09:33:07 +00:00
refactor(ProjectSelector): improve project selection logic and UI/UX
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Folder,
|
Folder,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@@ -47,7 +48,6 @@ export function ProjectSelectorWithOptions({
|
|||||||
setIsProjectPickerOpen,
|
setIsProjectPickerOpen,
|
||||||
setShowDeleteProjectDialog,
|
setShowDeleteProjectDialog,
|
||||||
}: ProjectSelectorWithOptionsProps) {
|
}: ProjectSelectorWithOptionsProps) {
|
||||||
// Get data from store
|
|
||||||
const {
|
const {
|
||||||
projects,
|
projects,
|
||||||
currentProject,
|
currentProject,
|
||||||
@@ -59,7 +59,6 @@ export function ProjectSelectorWithOptions({
|
|||||||
clearProjectHistory,
|
clearProjectHistory,
|
||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
|
|
||||||
// Get keyboard shortcuts
|
|
||||||
const shortcuts = useKeyboardShortcutsConfig();
|
const shortcuts = useKeyboardShortcutsConfig();
|
||||||
const {
|
const {
|
||||||
projectSearchQuery,
|
projectSearchQuery,
|
||||||
@@ -75,10 +74,16 @@ export function ProjectSelectorWithOptions({
|
|||||||
setCurrentProject,
|
setCurrentProject,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Drag-and-drop handlers
|
|
||||||
const { sensors, handleDragEnd } = useDragAndDrop({ projects, reorderProjects });
|
const { sensors, handleDragEnd } = useDragAndDrop({ projects, reorderProjects });
|
||||||
|
|
||||||
// Theme management
|
const handleProjectSelect = useCallback(
|
||||||
|
(project: (typeof projects)[number]) => {
|
||||||
|
setCurrentProject(project);
|
||||||
|
setIsProjectPickerOpen(false);
|
||||||
|
},
|
||||||
|
[setCurrentProject, setIsProjectPickerOpen]
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
globalTheme,
|
globalTheme,
|
||||||
setTheme,
|
setTheme,
|
||||||
@@ -107,7 +112,6 @@ export function ProjectSelectorWithOptions({
|
|||||||
'shadow-sm shadow-black/5',
|
'shadow-sm shadow-black/5',
|
||||||
'text-foreground titlebar-no-drag min-w-0',
|
'text-foreground titlebar-no-drag min-w-0',
|
||||||
'transition-all duration-200 ease-out',
|
'transition-all duration-200 ease-out',
|
||||||
'hover:scale-[1.01] active:scale-[0.99]',
|
|
||||||
isProjectPickerOpen &&
|
isProjectPickerOpen &&
|
||||||
'from-brand-500/10 to-brand-600/5 border-brand-500/30 ring-2 ring-brand-500/20 shadow-lg shadow-brand-500/5'
|
'from-brand-500/10 to-brand-600/5 border-brand-500/30 ring-2 ring-brand-500/20 shadow-lg shadow-brand-500/5'
|
||||||
)}
|
)}
|
||||||
@@ -140,7 +144,7 @@ export function ProjectSelectorWithOptions({
|
|||||||
align="start"
|
align="start"
|
||||||
data-testid="project-picker-dropdown"
|
data-testid="project-picker-dropdown"
|
||||||
>
|
>
|
||||||
{/* Search input for type-ahead filtering */}
|
{/* Search input */}
|
||||||
<div className="px-1 pb-2">
|
<div className="px-1 pb-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||||
@@ -151,10 +155,10 @@ export function ProjectSelectorWithOptions({
|
|||||||
value={projectSearchQuery}
|
value={projectSearchQuery}
|
||||||
onChange={(e) => setProjectSearchQuery(e.target.value)}
|
onChange={(e) => setProjectSearchQuery(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full h-9 pl-8 pr-3 text-sm rounded-lg',
|
'w-full h-8 pl-8 pr-3 text-sm rounded-lg',
|
||||||
'border border-border bg-background/50',
|
'border border-border bg-background/50',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:outline-none focus:ring-2 focus:ring-brand-500/30 focus:border-brand-500/50',
|
'focus:outline-none focus:ring-1 focus:ring-brand-500/30 focus:border-brand-500/50',
|
||||||
'transition-all duration-200'
|
'transition-all duration-200'
|
||||||
)}
|
)}
|
||||||
data-testid="project-search-input"
|
data-testid="project-search-input"
|
||||||
@@ -176,17 +180,14 @@ export function ProjectSelectorWithOptions({
|
|||||||
items={filteredProjects.map((p) => p.id)}
|
items={filteredProjects.map((p) => p.id)}
|
||||||
strategy={verticalListSortingStrategy}
|
strategy={verticalListSortingStrategy}
|
||||||
>
|
>
|
||||||
<div className="space-y-0.5 max-h-64 overflow-y-auto">
|
<div className="space-y-0.5 max-h-64 overflow-y-auto overflow-x-hidden">
|
||||||
{filteredProjects.map((project, index) => (
|
{filteredProjects.map((project, index) => (
|
||||||
<SortableProjectItem
|
<SortableProjectItem
|
||||||
key={project.id}
|
key={project.id}
|
||||||
project={project}
|
project={project}
|
||||||
currentProjectId={currentProject?.id}
|
currentProjectId={currentProject?.id}
|
||||||
isHighlighted={index === selectedProjectIndex}
|
isHighlighted={index === selectedProjectIndex}
|
||||||
onSelect={(p) => {
|
onSelect={handleProjectSelect}
|
||||||
setCurrentProject(p);
|
|
||||||
setIsProjectPickerOpen(false);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -197,9 +198,9 @@ export function ProjectSelectorWithOptions({
|
|||||||
{/* Keyboard hint */}
|
{/* Keyboard hint */}
|
||||||
<div className="px-2 pt-2 mt-1.5 border-t border-border/50">
|
<div className="px-2 pt-2 mt-1.5 border-t border-border/50">
|
||||||
<p className="text-[10px] text-muted-foreground text-center tracking-wide">
|
<p className="text-[10px] text-muted-foreground text-center tracking-wide">
|
||||||
<span className="text-foreground/60">arrow</span> navigate{' '}
|
<span className="text-foreground/60">↑↓</span> navigate{' '}
|
||||||
<span className="mx-1 text-foreground/30">|</span>{' '}
|
<span className="mx-1 text-foreground/30">|</span>{' '}
|
||||||
<span className="text-foreground/60">enter</span> select{' '}
|
<span className="text-foreground/60">↵</span> select{' '}
|
||||||
<span className="mx-1 text-foreground/30">|</span>{' '}
|
<span className="mx-1 text-foreground/30">|</span>{' '}
|
||||||
<span className="text-foreground/60">esc</span> close
|
<span className="text-foreground/60">esc</span> close
|
||||||
</p>
|
</p>
|
||||||
@@ -207,7 +208,7 @@ export function ProjectSelectorWithOptions({
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
{/* Project Options Menu - theme and history */}
|
{/* Project Options Menu */}
|
||||||
{currentProject && (
|
{currentProject && (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
@@ -224,8 +225,7 @@ export function ProjectSelectorWithOptions({
|
|||||||
'text-muted-foreground hover:text-foreground',
|
'text-muted-foreground hover:text-foreground',
|
||||||
'bg-transparent hover:bg-accent/60',
|
'bg-transparent hover:bg-accent/60',
|
||||||
'border border-border/50 hover:border-border',
|
'border border-border/50 hover:border-border',
|
||||||
'transition-all duration-200 ease-out titlebar-no-drag',
|
'transition-all duration-200 ease-out titlebar-no-drag'
|
||||||
'hover:scale-[1.02] active:scale-[0.98]'
|
|
||||||
)}
|
)}
|
||||||
title="Project options"
|
title="Project options"
|
||||||
data-testid="project-options-menu"
|
data-testid="project-options-menu"
|
||||||
@@ -253,7 +253,6 @@ export function ProjectSelectorWithOptions({
|
|||||||
setPreviewTheme(null);
|
setPreviewTheme(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Use Global Option */}
|
|
||||||
<DropdownMenuRadioGroup
|
<DropdownMenuRadioGroup
|
||||||
value={currentProject.theme || ''}
|
value={currentProject.theme || ''}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
@@ -329,7 +328,7 @@ export function ProjectSelectorWithOptions({
|
|||||||
</DropdownMenuSubContent>
|
</DropdownMenuSubContent>
|
||||||
</DropdownMenuSub>
|
</DropdownMenuSub>
|
||||||
|
|
||||||
{/* Project History Section - only show when there's history */}
|
{/* Project History Section */}
|
||||||
{projectHistory.length > 1 && (
|
{projectHistory.length > 1 && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|||||||
@@ -45,7 +45,12 @@ export function SortableProjectItem({
|
|||||||
|
|
||||||
{/* Project content - clickable area */}
|
{/* Project content - clickable area */}
|
||||||
<div className="flex items-center gap-2.5 flex-1 min-w-0" onClick={() => onSelect(project)}>
|
<div className="flex items-center gap-2.5 flex-1 min-w-0" onClick={() => onSelect(project)}>
|
||||||
<Folder className="h-4 w-4 shrink-0 text-muted-foreground" />
|
<Folder
|
||||||
|
className={cn(
|
||||||
|
'h-4 w-4 shrink-0',
|
||||||
|
currentProjectId === project.id ? 'text-brand-500' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<span className="flex-1 truncate text-sm font-medium">{project.name}</span>
|
<span className="flex-1 truncate text-sm font-medium">{project.name}</span>
|
||||||
{currentProjectId === project.id && <Check className="h-4 w-4 text-brand-500 shrink-0" />}
|
{currentProjectId === project.id && <Check className="h-4 w-4 text-brand-500 shrink-0" />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user