auto-claude: subtask-9-3 - Add loading states to pop-out buttons during window creation
- Added Loader2 spinner icon to pop-out buttons while windows are being created - Buttons are now disabled during window creation to prevent duplicate requests - Updated SortableProjectTab to accept isLoading prop and show spinner - ProjectTabBar now passes loading state from window-store to tabs - Sidebar pop-out buttons show spinner and are disabled while loading - Added i18n keys for "Creating window..." in both English and French - Loading state tracked via window-store's loadingWindows Set Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 4.5
parent
5542b7db71
commit
6d3ab81136
@@ -30,7 +30,7 @@ export function ProjectTabBar({
|
||||
onSettingsClick
|
||||
}: ProjectTabBarProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const { isProjectPoppedOut, setWindowLoading, addPoppedOutProject } = useWindowStore();
|
||||
const { isProjectPoppedOut, setWindowLoading, addPoppedOutProject, isWindowLoading } = useWindowStore();
|
||||
|
||||
// Handler for popping out a project into a new window
|
||||
const handlePopOutProject = async (projectId: string) => {
|
||||
@@ -148,6 +148,7 @@ export function ProjectTabBar({
|
||||
onSettingsClick={isActiveTab ? onSettingsClick : undefined}
|
||||
onPopOutClick={isActiveTab ? () => handlePopOutProject(project.id) : undefined}
|
||||
isPoppedOut={isProjectPoppedOut(project.id)}
|
||||
isLoading={isWindowLoading(project.id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
Wrench,
|
||||
PanelLeft,
|
||||
PanelLeftClose,
|
||||
ExternalLink
|
||||
ExternalLink,
|
||||
Loader2
|
||||
} from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { ScrollArea } from './ui/scroll-area';
|
||||
@@ -113,7 +114,7 @@ export function Sidebar({
|
||||
const projects = useProjectStore((state) => state.projects);
|
||||
const selectedProjectId = useProjectStore((state) => state.selectedProjectId);
|
||||
const settings = useSettingsStore((state) => state.settings);
|
||||
const { isViewPoppedOut, setWindowLoading, addPoppedOutView } = useWindowStore();
|
||||
const { isViewPoppedOut, setWindowLoading, addPoppedOutView, isWindowLoading, getViewKey } = useWindowStore();
|
||||
|
||||
const [showAddProjectModal, setShowAddProjectModal] = useState(false);
|
||||
const [showInitDialog, setShowInitDialog] = useState(false);
|
||||
@@ -338,6 +339,8 @@ export function Sidebar({
|
||||
const isActive = activeView === item.id;
|
||||
const Icon = item.icon;
|
||||
const isPoppedOut = selectedProjectId ? isViewPoppedOut(selectedProjectId, item.id) : false;
|
||||
const viewKey = selectedProjectId ? getViewKey(selectedProjectId, item.id) : '';
|
||||
const isLoading = viewKey ? isWindowLoading(viewKey) : false;
|
||||
|
||||
const button = (
|
||||
<div
|
||||
@@ -379,26 +382,31 @@ export function Sidebar({
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!isPoppedOut) {
|
||||
if (!isPoppedOut && !isLoading) {
|
||||
handlePopOutView(item.id, e);
|
||||
}
|
||||
}}
|
||||
disabled={isPoppedOut}
|
||||
disabled={isPoppedOut || isLoading}
|
||||
aria-label={isPoppedOut ? t('navigation:viewPoppedOut') : t('navigation:popOutView')}
|
||||
className={cn(
|
||||
'h-5 w-5 p-0 rounded flex items-center justify-center',
|
||||
'text-muted-foreground hover:text-foreground',
|
||||
'hover:bg-muted/50 transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
|
||||
isPoppedOut ? 'opacity-100 text-primary' : 'opacity-0 group-hover:opacity-100',
|
||||
isPoppedOut && 'cursor-not-allowed'
|
||||
isPoppedOut || isLoading ? 'opacity-100' : 'opacity-0 group-hover:opacity-100',
|
||||
isPoppedOut && 'text-primary',
|
||||
(isPoppedOut || isLoading) && 'cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
{isLoading ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<span>{isPoppedOut ? t('navigation:tooltips.viewPoppedOut') : t('navigation:tooltips.popOutView')}</span>
|
||||
<span>{isLoading ? t('navigation:tooltips.popOutLoading') : isPoppedOut ? t('navigation:tooltips.viewPoppedOut') : t('navigation:tooltips.popOutView')}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Settings2, ExternalLink } from 'lucide-react';
|
||||
import { Settings2, ExternalLink, Loader2 } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||
import type { Project } from '../../shared/types';
|
||||
@@ -17,6 +17,7 @@ interface SortableProjectTabProps {
|
||||
onSettingsClick?: () => void;
|
||||
onPopOutClick?: () => void;
|
||||
isPoppedOut?: boolean;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
// Detect if running on macOS for keyboard shortcut display
|
||||
@@ -32,7 +33,8 @@ export function SortableProjectTab({
|
||||
onClose,
|
||||
onSettingsClick,
|
||||
onPopOutClick,
|
||||
isPoppedOut = false
|
||||
isPoppedOut = false,
|
||||
isLoading = false
|
||||
}: SortableProjectTabProps) {
|
||||
const { t } = useTranslation('common');
|
||||
// Build tooltip with keyboard shortcut hint (only for tabs 1-9)
|
||||
@@ -138,22 +140,26 @@ export function SortableProjectTab({
|
||||
'text-muted-foreground hover:text-foreground',
|
||||
'hover:bg-muted/50 transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
|
||||
isPoppedOut && 'opacity-50 cursor-not-allowed'
|
||||
(isPoppedOut || isLoading) && 'opacity-50 cursor-not-allowed'
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!isPoppedOut) {
|
||||
if (!isPoppedOut && !isLoading) {
|
||||
onPopOutClick();
|
||||
}
|
||||
}}
|
||||
disabled={isPoppedOut}
|
||||
disabled={isPoppedOut || isLoading}
|
||||
aria-label={t('projectTab.popOutAriaLabel')}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
||||
{isLoading ? (
|
||||
<Loader2 className="h-3 w-3 sm:h-3.5 sm:w-3.5 animate-spin" />
|
||||
) : (
|
||||
<ExternalLink className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<span>{t('projectTab.popOut')}</span>
|
||||
<span>{isLoading ? t('projectTab.popOutLoading') : t('projectTab.popOut')}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"addProjectAriaLabel": "Add project",
|
||||
"popOut": "Pop out project",
|
||||
"popOutAriaLabel": "Pop out project into separate window",
|
||||
"popOutLoading": "Creating window...",
|
||||
"poppedOutBadge": "Popped Out"
|
||||
},
|
||||
"accessibility": {
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"settings": "Application Settings",
|
||||
"help": "Help & Feedback",
|
||||
"popOutView": "Open this view in a separate window",
|
||||
"viewPoppedOut": "This view is opened in a separate window"
|
||||
"viewPoppedOut": "This view is opened in a separate window",
|
||||
"popOutLoading": "Creating window..."
|
||||
},
|
||||
"messages": {
|
||||
"initializeToCreateTasks": "Initialize Auto Claude to create tasks"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"addProjectAriaLabel": "Ajouter un projet",
|
||||
"popOut": "Ouvrir dans une fenêtre séparée",
|
||||
"popOutAriaLabel": "Ouvrir le projet dans une fenêtre séparée",
|
||||
"popOutLoading": "Création de la fenêtre...",
|
||||
"poppedOutBadge": "Détaché"
|
||||
},
|
||||
"accessibility": {
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"settings": "Paramètres de l'application",
|
||||
"help": "Aide & Feedback",
|
||||
"popOutView": "Ouvrir cette vue dans une fenêtre séparée",
|
||||
"viewPoppedOut": "Cette vue est ouverte dans une fenêtre séparée"
|
||||
"viewPoppedOut": "Cette vue est ouverte dans une fenêtre séparée",
|
||||
"popOutLoading": "Création de la fenêtre..."
|
||||
},
|
||||
"messages": {
|
||||
"initializeToCreateTasks": "Initialisez Auto Claude pour créer des tâches"
|
||||
|
||||
Reference in New Issue
Block a user