feat(sidebar): preview workspace board on card drag start with trigger cue (#5777)

This commit is contained in:
Neil 2026-06-19 00:19:41 -07:00 committed by GitHub
parent 692d8d0124
commit 238476f441
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 38 additions and 63 deletions

View File

@ -1426,6 +1426,29 @@
opacity: 0.42;
}
/* Why: while a card drag previews the board, pulse the toolbar trigger with the
same ring the drawer uses so it reads as another way to open the board. */
@keyframes workspace-board-trigger-preview-pulse {
0% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--sidebar-ring) 55%, transparent);
}
70%,
100% {
box-shadow: 0 0 0 5px color-mix(in srgb, var(--sidebar-ring) 0%, transparent);
}
}
[data-workspace-board-trigger][data-workspace-board-preview='true'] {
animation: workspace-board-trigger-preview-pulse 1.4s ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
[data-workspace-board-trigger][data-workspace-board-preview='true'] {
animation: none;
}
}
@keyframes workspace-kanban-sheet-in {
from {
clip-path: inset(0 100% 0 0);

View File

@ -13,11 +13,13 @@ const WORKSPACE_BOARD_MOVED_HINT_DURATION_MS = 12000
type SidebarToolbarProps = {
workspaceBoardOpen: boolean
workspaceBoardDragPreviewOpen?: boolean
onWorkspaceBoardToggle: () => void
}
const SidebarToolbar = React.memo(function SidebarToolbar({
workspaceBoardOpen,
workspaceBoardDragPreviewOpen = false,
onWorkspaceBoardToggle
}: SidebarToolbarProps) {
const [workspaceBoardMovedHintOpen, setWorkspaceBoardMovedHintOpen] = React.useState(false)
@ -69,7 +71,11 @@ const SidebarToolbar = React.memo(function SidebarToolbar({
<Tooltip open={workspaceBoardMovedHintOpen ? true : undefined}>
<TooltipTrigger asChild>
<Button
variant={workspaceBoardOpen ? 'secondary' : 'ghost'}
// Why: previewing the board from a card drag lights up the
// trigger so it's clear the drag is another way to open it.
variant={
workspaceBoardOpen || workspaceBoardDragPreviewOpen ? 'secondary' : 'ghost'
}
size="icon-xs"
type="button"
aria-label={translate(
@ -78,6 +84,7 @@ const SidebarToolbar = React.memo(function SidebarToolbar({
)}
aria-pressed={workspaceBoardOpen}
data-workspace-board-trigger=""
data-workspace-board-preview={workspaceBoardDragPreviewOpen ? 'true' : undefined}
onClick={handleWorkspaceBoardClick}
className="text-muted-foreground"
>

View File

@ -159,7 +159,6 @@ import {
setSidebarPointerDragDocumentStyles,
updateSidebarDragPreviewPosition
} from './worktree-sidebar-pointer-drag-dom'
import { shouldStartWorkspaceBoardDragPreview } from './workspace-board-drag-preview-intent'
import {
getWorktreeSidebarDragAutoscroll,
getWorktreeSidebarDragRectsForGroup,
@ -2180,24 +2179,16 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
clearWorktreeDrag()
return
}
const previewSidebarContainer = scrollRef.current
// Why: reveal the companion board preview the moment a card drag begins so the
// user sees the drop target on the right and can choose whether to aim for it,
// rather than discovering it only after dragging into the sidebar edge.
if (
!drag.workspaceBoardDragPreviewRequested &&
!workspaceBoardOpen &&
previewSidebarContainer
!hasWorkspaceKanbanSidebarDropBoard()
) {
const sidebarRect = previewSidebarContainer.getBoundingClientRect()
if (
shouldStartWorkspaceBoardDragPreview({
pointerX: drag.currentX,
startX: drag.startX,
sidebarRight: sidebarRect.right
}) &&
!hasWorkspaceKanbanSidebarDropBoard()
) {
drag.workspaceBoardDragPreviewRequested = true
onWorkspaceBoardDragPreviewStart()
}
drag.workspaceBoardDragPreviewRequested = true
onWorkspaceBoardDragPreviewStart()
}
const boardTarget = updateWorkspaceKanbanSidebarDropTargetVisual({
x: drag.currentX,

View File

@ -153,6 +153,7 @@ function Sidebar({
{/* Fixed bottom toolbar */}
<SidebarToolbar
workspaceBoardOpen={workspaceBoardOpen}
workspaceBoardDragPreviewOpen={workspaceBoardDragPreviewOpen}
onWorkspaceBoardToggle={toggleWorkspaceBoard}
/>
</>

View File

@ -1,34 +0,0 @@
import { describe, expect, it } from 'vitest'
import { shouldStartWorkspaceBoardDragPreview } from './workspace-board-drag-preview-intent'
describe('workspace board drag preview intent', () => {
it('does not start the expensive board preview for small sidebar reorder drags', () => {
expect(
shouldStartWorkspaceBoardDragPreview({
pointerX: 132,
startX: 96,
sidebarRight: 285
})
).toBe(false)
})
it('starts the board preview after a rightward drag reaches the sidebar edge zone', () => {
expect(
shouldStartWorkspaceBoardDragPreview({
pointerX: 252,
startX: 96,
sidebarRight: 285
})
).toBe(true)
})
it('ignores edge-zone drags that did not move right enough to signal board intent', () => {
expect(
shouldStartWorkspaceBoardDragPreview({
pointerX: 252,
startX: 242,
sidebarRight: 285
})
).toBe(false)
})
})

View File

@ -1,13 +0,0 @@
const WORKSPACE_BOARD_DRAG_PREVIEW_EDGE_ZONE_PX = 48
const WORKSPACE_BOARD_DRAG_PREVIEW_MIN_RIGHTWARD_PX = 16
export function shouldStartWorkspaceBoardDragPreview(args: {
pointerX: number
startX: number
sidebarRight: number
}): boolean {
return (
args.pointerX >= args.sidebarRight - WORKSPACE_BOARD_DRAG_PREVIEW_EDGE_ZONE_PX &&
args.pointerX - args.startX >= WORKSPACE_BOARD_DRAG_PREVIEW_MIN_RIGHTWARD_PX
)
}