Simplify WorkspaceSummary card hover UI with opacity fade and single action button (Vibe Kanban) (#2059)

* All changes are complete. Here's a summary of what was changed:

## Summary of Changes

### `WorkspaceSummary.tsx`
1. **Title truncation**: Replaced `truncate` class with CSS mask gradient that fades to transparent (no ellipsis dots)
2. **Simplified hover actions**: Removed pin and archive buttons, keeping only the "more options" (ListIcon) button
3. **Removed sliding animation**: The action button now uses simple opacity transition (`opacity-0 group-hover:opacity-100`) instead of the sliding animation
4. **Removed unused code**: `handleArchive`, `handlePin` functions and `ArchiveIcon` import removed
5. **Removed unused props**: `onArchive` and `onPin` props removed from interface

### `WorkspacesSidebar.tsx`
- Removed `onArchiveWorkspace` and `onPinWorkspace` props from interface and destructuring

### `WorkspacesSidebarContainer.tsx`
- Removed `handleArchiveWorkspace` and `handlePinWorkspace` callbacks
- Removed unused imports (`useCallback`, `useActions`, `Actions`)

All type checks pass. The pin and archive functionality is still accessible through the command bar (more options button).

* Cleanup script changes for workspace 262a1c1d-87ae-40f5-a36d-f43758d18fed

* Done. Changed `ListIcon` to `DotsThreeIcon` with `weight="bold"` for the more options button.

* Done. Changed the icon size from `size-icon-xs` to `size-5` (20px) while keeping the bold weight.

* Done. Added the `ArchiveWorkspace` action to the left side of the navbar in `NavbarActionGroups.left`.
This commit is contained in:
Louis Knight-Webb
2026-01-15 10:53:12 +00:00
committed by GitHub
parent 5ca8b9852a
commit 4a6e556ed2
4 changed files with 29 additions and 94 deletions

View File

@@ -965,7 +965,7 @@ export type NavbarItem = ActionDefinition | typeof NavbarDivider;
// Navbar action groups define which actions appear in each section // Navbar action groups define which actions appear in each section
export const NavbarActionGroups = { export const NavbarActionGroups = {
left: [Actions.OpenInOldUI] as ActionDefinition[], left: [Actions.ArchiveWorkspace, Actions.OpenInOldUI] as ActionDefinition[],
right: [ right: [
Actions.ToggleDiffViewMode, Actions.ToggleDiffViewMode,
Actions.ToggleAllDiffs, Actions.ToggleAllDiffs,

View File

@@ -1,6 +1,5 @@
import { useState, useCallback, useMemo } from 'react'; import { useState, useMemo } from 'react';
import { useWorkspaceContext } from '@/contexts/WorkspaceContext'; import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
import { useActions } from '@/contexts/ActionsContext';
import { useScratch } from '@/hooks/useScratch'; import { useScratch } from '@/hooks/useScratch';
import { ScratchType, type DraftWorkspaceData } from 'shared/types'; import { ScratchType, type DraftWorkspaceData } from 'shared/types';
import { splitMessageToTitleDescription } from '@/utils/string'; import { splitMessageToTitleDescription } from '@/utils/string';
@@ -9,7 +8,6 @@ import {
usePersistedExpanded, usePersistedExpanded,
} from '@/stores/useUiPreferencesStore'; } from '@/stores/useUiPreferencesStore';
import { WorkspacesSidebar } from '@/components/ui-new/views/WorkspacesSidebar'; import { WorkspacesSidebar } from '@/components/ui-new/views/WorkspacesSidebar';
import { Actions } from '@/components/ui-new/actions';
// Fixed UUID for the universal workspace draft (same as in useCreateModeState.ts) // Fixed UUID for the universal workspace draft (same as in useCreateModeState.ts)
const DRAFT_WORKSPACE_ID = '00000000-0000-0000-0000-000000000001'; const DRAFT_WORKSPACE_ID = '00000000-0000-0000-0000-000000000001';
@@ -50,23 +48,6 @@ export function WorkspacesSidebarContainer() {
return title || 'New Workspace'; return title || 'New Workspace';
}, [draftScratch]); }, [draftScratch]);
// Action handlers for sidebar workspace actions
const { executeAction } = useActions();
const handleArchiveWorkspace = useCallback(
(workspaceId: string) => {
executeAction(Actions.ArchiveWorkspace, workspaceId);
},
[executeAction]
);
const handlePinWorkspace = useCallback(
(workspaceId: string) => {
executeAction(Actions.PinWorkspace, workspaceId);
},
[executeAction]
);
return ( return (
<WorkspacesSidebar <WorkspacesSidebar
workspaces={activeWorkspaces} workspaces={activeWorkspaces}
@@ -76,8 +57,6 @@ export function WorkspacesSidebarContainer() {
searchQuery={searchQuery} searchQuery={searchQuery}
onSearchChange={setSearchQuery} onSearchChange={setSearchQuery}
onAddWorkspace={navigateToCreate} onAddWorkspace={navigateToCreate}
onArchiveWorkspace={handleArchiveWorkspace}
onPinWorkspace={handlePinWorkspace}
isCreateMode={isCreateMode} isCreateMode={isCreateMode}
draftTitle={persistedDraftTitle} draftTitle={persistedDraftTitle}
onSelectCreate={navigateToCreate} onSelectCreate={navigateToCreate}

View File

@@ -6,8 +6,7 @@ import {
FileIcon, FileIcon,
CircleIcon, CircleIcon,
GitPullRequestIcon, GitPullRequestIcon,
ArchiveIcon, DotsThreeIcon,
ListIcon,
} from '@phosphor-icons/react'; } from '@phosphor-icons/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@@ -31,8 +30,6 @@ interface WorkspaceSummaryProps {
latestProcessStatus?: 'running' | 'completed' | 'failed' | 'killed'; latestProcessStatus?: 'running' | 'completed' | 'failed' | 'killed';
prStatus?: 'open' | 'merged' | 'closed' | 'unknown'; prStatus?: 'open' | 'merged' | 'closed' | 'unknown';
onClick?: () => void; onClick?: () => void;
onArchive?: () => void;
onPin?: () => void;
className?: string; className?: string;
summary?: boolean; summary?: boolean;
/** Whether this is a draft workspace (shows "Draft" instead of elapsed time) */ /** Whether this is a draft workspace (shows "Draft" instead of elapsed time) */
@@ -55,8 +52,6 @@ export function WorkspaceSummary({
latestProcessStatus, latestProcessStatus,
prStatus, prStatus,
onClick, onClick,
onArchive,
onPin,
className, className,
summary = false, summary = false,
isDraft = false, isDraft = false,
@@ -74,16 +69,6 @@ export function WorkspaceSummary({
}); });
}; };
const handleArchive = (e: React.MouseEvent) => {
e.stopPropagation();
onArchive?.();
};
const handlePin = (e: React.MouseEvent) => {
e.stopPropagation();
onPin?.();
};
return ( return (
<div <div
className={cn( className={cn(
@@ -108,7 +93,18 @@ export function WorkspaceSummary({
: 'text-low opacity-60 hover:opacity-100 hover:text-normal' : 'text-low opacity-60 hover:opacity-100 hover:text-normal'
)} )}
> >
<div className={cn('truncate pr-double', !summary && 'text-normal')}> <div
className={cn(
'overflow-hidden whitespace-nowrap pr-double',
!summary && 'text-normal'
)}
style={{
maskImage:
'linear-gradient(to right, black calc(100% - 24px), transparent 100%)',
WebkitMaskImage:
'linear-gradient(to right, black calc(100% - 24px), transparent 100%)',
}}
>
{name} {name}
</div> </div>
{(!summary || isActive) && ( {(!summary || isActive) && (
@@ -204,53 +200,21 @@ export function WorkspaceSummary({
)} )}
</button> </button>
{/* Right-side hover zone for action overlay */} {/* Right-side hover action - more options only */}
{workspaceId && ( {workspaceId && (
<div className="absolute right-0 top-0 bottom-0 w-16 group/actions"> <div className="absolute right-0 top-0 bottom-0 flex items-center opacity-0 group-hover:opacity-100">
{/* Sliding action overlay - only appears when hovering this zone */} {/* Gradient fade from transparent to background */}
<div <div className="h-full w-6 pointer-events-none bg-gradient-to-r from-transparent to-secondary" />
className={cn( {/* Single action button */}
'absolute right-0 top-0 bottom-0 flex items-center', <div className="flex items-center pr-base h-full bg-secondary">
'translate-x-full group-hover/actions:translate-x-0', <button
'transition-transform duration-150 ease-out' onClick={handleOpenCommandBar}
)} onPointerDown={(e) => e.stopPropagation()}
> className="p-1.5 rounded-sm text-low hover:text-normal hover:bg-tertiary"
{/* Gradient fade from transparent to pill background */} title={t('workspaces.more')}
<div className="h-full w-6 pointer-events-none bg-gradient-to-r from-transparent to-secondary" /> >
{/* Action pill */} <DotsThreeIcon className="size-5" weight="bold" />
<div className="flex items-center gap-0.5 pr-base h-full bg-secondary"> </button>
<button
onClick={handlePin}
onPointerDown={(e) => e.stopPropagation()}
className={cn(
'p-1.5 rounded-sm transition-colors duration-100',
'hover:bg-tertiary',
isPinned ? 'text-brand' : 'text-low hover:text-normal'
)}
title={isPinned ? t('workspaces.unpin') : t('workspaces.pin')}
>
<PushPinIcon
className="size-icon-xs"
weight={isPinned ? 'fill' : 'regular'}
/>
</button>
<button
onClick={handleArchive}
onPointerDown={(e) => e.stopPropagation()}
className="p-1.5 rounded-sm text-low hover:text-normal hover:bg-tertiary transition-colors duration-100"
title={t('workspaces.archive')}
>
<ArchiveIcon className="size-icon-xs" />
</button>
<button
onClick={handleOpenCommandBar}
onPointerDown={(e) => e.stopPropagation()}
className="p-1.5 rounded-sm text-low hover:text-normal hover:bg-tertiary transition-colors duration-100"
title={t('workspaces.more')}
>
<ListIcon className="size-icon-xs" />
</button>
</div>
</div> </div>
</div> </div>
)} )}

View File

@@ -11,8 +11,6 @@ interface WorkspacesSidebarProps {
selectedWorkspaceId: string | null; selectedWorkspaceId: string | null;
onSelectWorkspace: (id: string) => void; onSelectWorkspace: (id: string) => void;
onAddWorkspace?: () => void; onAddWorkspace?: () => void;
onArchiveWorkspace?: (id: string) => void;
onPinWorkspace?: (id: string) => void;
searchQuery: string; searchQuery: string;
onSearchChange: (value: string) => void; onSearchChange: (value: string) => void;
/** Whether we're in create mode */ /** Whether we're in create mode */
@@ -33,8 +31,6 @@ export function WorkspacesSidebar({
selectedWorkspaceId, selectedWorkspaceId,
onSelectWorkspace, onSelectWorkspace,
onAddWorkspace, onAddWorkspace,
onArchiveWorkspace,
onPinWorkspace,
searchQuery, searchQuery,
onSearchChange, onSearchChange,
isCreateMode = false, isCreateMode = false,
@@ -107,8 +103,6 @@ export function WorkspacesSidebar({
latestProcessStatus={workspace.latestProcessStatus} latestProcessStatus={workspace.latestProcessStatus}
prStatus={workspace.prStatus} prStatus={workspace.prStatus}
onClick={() => onSelectWorkspace(workspace.id)} onClick={() => onSelectWorkspace(workspace.id)}
onArchive={() => onArchiveWorkspace?.(workspace.id)}
onPin={() => onPinWorkspace?.(workspace.id)}
/> />
)) ))
)} )}
@@ -145,8 +139,6 @@ export function WorkspacesSidebar({
latestProcessStatus={workspace.latestProcessStatus} latestProcessStatus={workspace.latestProcessStatus}
prStatus={workspace.prStatus} prStatus={workspace.prStatus}
onClick={() => onSelectWorkspace(workspace.id)} onClick={() => onSelectWorkspace(workspace.id)}
onArchive={() => onArchiveWorkspace?.(workspace.id)}
onPin={() => onPinWorkspace?.(workspace.id)}
/> />
))} ))}
</div> </div>