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:
committed by
GitHub
parent
5ca8b9852a
commit
4a6e556ed2
@@ -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,
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user