fix: restore wrapper View for sidebar hover stability
The wrapper View with onPointerEnter/onPointerLeave is needed to keep hover state stable when the mouse moves from the row to the kebab dropdown (which opens in an overlay outside the Pressable). Without it, the Pressable fires onHoverOut and the kebab disappears. On native, isNative makes controls always-visible so this is web-only behavior which is correct (onPointerEnter works on web).
This commit is contained in:
@@ -851,8 +851,31 @@ function ProjectHeaderRow({
|
|||||||
|
|
||||||
if (menuController) {
|
if (menuController) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuTrigger
|
<View onPointerEnter={() => setIsHovered(true)} onPointerLeave={() => setIsHovered(false)}>
|
||||||
enabledOnMobile={false}
|
<ContextMenuTrigger
|
||||||
|
enabledOnMobile={false}
|
||||||
|
style={({ pressed }) => [
|
||||||
|
styles.projectRow,
|
||||||
|
isDragging && styles.projectRowDragging,
|
||||||
|
selected && styles.sidebarRowSelected,
|
||||||
|
isHovered && styles.projectRowHovered,
|
||||||
|
pressed && styles.projectRowPressed,
|
||||||
|
]}
|
||||||
|
onPressIn={interaction.handlePressIn}
|
||||||
|
onTouchMove={interaction.handleTouchMove}
|
||||||
|
onPressOut={interaction.handlePressOut}
|
||||||
|
onPress={handlePress}
|
||||||
|
testID={`sidebar-project-row-${project.projectKey}`}
|
||||||
|
>
|
||||||
|
{rowChildren}
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View onPointerEnter={() => setIsHovered(true)} onPointerLeave={() => setIsHovered(false)}>
|
||||||
|
<Pressable
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
styles.projectRow,
|
styles.projectRow,
|
||||||
isDragging && styles.projectRowDragging,
|
isDragging && styles.projectRowDragging,
|
||||||
@@ -860,8 +883,6 @@ function ProjectHeaderRow({
|
|||||||
isHovered && styles.projectRowHovered,
|
isHovered && styles.projectRowHovered,
|
||||||
pressed && styles.projectRowPressed,
|
pressed && styles.projectRowPressed,
|
||||||
]}
|
]}
|
||||||
onHoverIn={() => setIsHovered(true)}
|
|
||||||
onHoverOut={() => setIsHovered(false)}
|
|
||||||
onPressIn={interaction.handlePressIn}
|
onPressIn={interaction.handlePressIn}
|
||||||
onTouchMove={interaction.handleTouchMove}
|
onTouchMove={interaction.handleTouchMove}
|
||||||
onPressOut={interaction.handlePressOut}
|
onPressOut={interaction.handlePressOut}
|
||||||
@@ -869,29 +890,8 @@ function ProjectHeaderRow({
|
|||||||
testID={`sidebar-project-row-${project.projectKey}`}
|
testID={`sidebar-project-row-${project.projectKey}`}
|
||||||
>
|
>
|
||||||
{rowChildren}
|
{rowChildren}
|
||||||
</ContextMenuTrigger>
|
</Pressable>
|
||||||
);
|
</View>
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
style={({ pressed }) => [
|
|
||||||
styles.projectRow,
|
|
||||||
isDragging && styles.projectRowDragging,
|
|
||||||
selected && styles.sidebarRowSelected,
|
|
||||||
isHovered && styles.projectRowHovered,
|
|
||||||
pressed && styles.projectRowPressed,
|
|
||||||
]}
|
|
||||||
onHoverIn={() => setIsHovered(true)}
|
|
||||||
onHoverOut={() => setIsHovered(false)}
|
|
||||||
onPressIn={interaction.handlePressIn}
|
|
||||||
onTouchMove={interaction.handleTouchMove}
|
|
||||||
onPressOut={interaction.handlePressOut}
|
|
||||||
onPress={handlePress}
|
|
||||||
testID={`sidebar-project-row-${project.projectKey}`}
|
|
||||||
>
|
|
||||||
{rowChildren}
|
|
||||||
</Pressable>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -937,118 +937,121 @@ function WorkspaceRowInner({
|
|||||||
}, [interaction.didLongPressRef, onPress]);
|
}, [interaction.didLongPressRef, onPress]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<View
|
||||||
disabled={isArchiving}
|
style={styles.workspaceRowContainer}
|
||||||
style={({ pressed }) => [
|
onPointerEnter={() => setIsHovered(true)}
|
||||||
styles.workspaceRowContainer,
|
onPointerLeave={() => setIsHovered(false)}
|
||||||
styles.workspaceRow,
|
|
||||||
isDragging && styles.workspaceRowDragging,
|
|
||||||
selected && styles.sidebarRowSelected,
|
|
||||||
isHovered && styles.workspaceRowHovered,
|
|
||||||
pressed && styles.workspaceRowPressed,
|
|
||||||
]}
|
|
||||||
onHoverIn={() => setIsHovered(true)}
|
|
||||||
onHoverOut={() => setIsHovered(false)}
|
|
||||||
onPressIn={interaction.handlePressIn}
|
|
||||||
onTouchMove={interaction.handleTouchMove}
|
|
||||||
onPressOut={interaction.handlePressOut}
|
|
||||||
onPress={handlePress}
|
|
||||||
testID={`sidebar-workspace-row-${workspace.workspaceKey}`}
|
|
||||||
>
|
>
|
||||||
<View style={styles.workspaceRowMain}>
|
<Pressable
|
||||||
<View
|
disabled={isArchiving}
|
||||||
{...(dragHandleProps?.attributes as any)}
|
style={({ pressed }) => [
|
||||||
{...(dragHandleProps?.listeners as any)}
|
styles.workspaceRow,
|
||||||
ref={dragHandleProps?.setActivatorNodeRef as any}
|
isDragging && styles.workspaceRowDragging,
|
||||||
style={styles.workspaceRowLeft}
|
selected && styles.sidebarRowSelected,
|
||||||
>
|
isHovered && styles.workspaceRowHovered,
|
||||||
<WorkspaceStatusIndicator
|
pressed && styles.workspaceRowPressed,
|
||||||
bucket={workspace.statusBucket}
|
]}
|
||||||
workspaceKind={workspace.workspaceKind}
|
onPressIn={interaction.handlePressIn}
|
||||||
loading={isArchiving || isCreating}
|
onTouchMove={interaction.handleTouchMove}
|
||||||
/>
|
onPressOut={interaction.handlePressOut}
|
||||||
<Text
|
onPress={handlePress}
|
||||||
style={[
|
testID={`sidebar-workspace-row-${workspace.workspaceKey}`}
|
||||||
styles.workspaceBranchText,
|
>
|
||||||
isHovered && styles.workspaceBranchTextHovered,
|
<View style={styles.workspaceRowMain}>
|
||||||
isCreating && styles.workspaceBranchTextCreating,
|
<View
|
||||||
]}
|
{...(dragHandleProps?.attributes as any)}
|
||||||
numberOfLines={1}
|
{...(dragHandleProps?.listeners as any)}
|
||||||
|
ref={dragHandleProps?.setActivatorNodeRef as any}
|
||||||
|
style={styles.workspaceRowLeft}
|
||||||
>
|
>
|
||||||
{workspace.name}
|
<WorkspaceStatusIndicator
|
||||||
</Text>
|
bucket={workspace.statusBucket}
|
||||||
</View>
|
workspaceKind={workspace.workspaceKind}
|
||||||
<View style={styles.workspaceRowRight}>
|
loading={isArchiving || isCreating}
|
||||||
{isCreating ? <Text style={styles.workspaceCreatingText}>Creating...</Text> : null}
|
/>
|
||||||
{onArchive && (isHovered || platformIsNative || isCompact) ? (
|
<Text
|
||||||
<DropdownMenu>
|
style={[
|
||||||
<DropdownMenuTrigger
|
styles.workspaceBranchText,
|
||||||
hitSlop={8}
|
isHovered && styles.workspaceBranchTextHovered,
|
||||||
style={({ hovered = false }) => [
|
isCreating && styles.workspaceBranchTextCreating,
|
||||||
styles.kebabButton,
|
]}
|
||||||
hovered && styles.kebabButtonHovered,
|
numberOfLines={1}
|
||||||
]}
|
>
|
||||||
accessibilityRole="button"
|
{workspace.name}
|
||||||
accessibilityLabel="Workspace actions"
|
</Text>
|
||||||
testID={`sidebar-workspace-kebab-${workspace.workspaceKey}`}
|
</View>
|
||||||
>
|
<View style={styles.workspaceRowRight}>
|
||||||
{({ hovered }) => (
|
{isCreating ? <Text style={styles.workspaceCreatingText}>Creating...</Text> : null}
|
||||||
<MoreVertical
|
{onArchive && (isHovered || platformIsNative || isCompact) ? (
|
||||||
size={14}
|
<DropdownMenu>
|
||||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
<DropdownMenuTrigger
|
||||||
/>
|
hitSlop={8}
|
||||||
)}
|
style={({ hovered = false }) => [
|
||||||
</DropdownMenuTrigger>
|
styles.kebabButton,
|
||||||
<DropdownMenuContent align="end" width={260}>
|
hovered && styles.kebabButtonHovered,
|
||||||
{onCopyPath ? (
|
]}
|
||||||
<DropdownMenuItem
|
accessibilityRole="button"
|
||||||
testID={`sidebar-workspace-menu-copy-path-${workspace.workspaceKey}`}
|
accessibilityLabel="Workspace actions"
|
||||||
leading={<Copy size={14} color={theme.colors.foregroundMuted} />}
|
testID={`sidebar-workspace-kebab-${workspace.workspaceKey}`}
|
||||||
onSelect={onCopyPath}
|
|
||||||
>
|
|
||||||
Copy path
|
|
||||||
</DropdownMenuItem>
|
|
||||||
) : null}
|
|
||||||
{onCopyBranchName ? (
|
|
||||||
<DropdownMenuItem
|
|
||||||
testID={`sidebar-workspace-menu-copy-branch-name-${workspace.workspaceKey}`}
|
|
||||||
leading={<Copy size={14} color={theme.colors.foregroundMuted} />}
|
|
||||||
onSelect={onCopyBranchName}
|
|
||||||
>
|
|
||||||
Copy branch name
|
|
||||||
</DropdownMenuItem>
|
|
||||||
) : null}
|
|
||||||
<DropdownMenuItem
|
|
||||||
testID={`sidebar-workspace-menu-archive-${workspace.workspaceKey}`}
|
|
||||||
leading={<Archive size={14} color={theme.colors.foregroundMuted} />}
|
|
||||||
trailing={archiveShortcutKeys ? <Shortcut chord={archiveShortcutKeys} /> : null}
|
|
||||||
status={archiveStatus}
|
|
||||||
pendingLabel={archivePendingLabel}
|
|
||||||
onSelect={onArchive}
|
|
||||||
>
|
>
|
||||||
{archiveLabel ?? "Archive"}
|
{({ hovered }) => (
|
||||||
</DropdownMenuItem>
|
<MoreVertical
|
||||||
</DropdownMenuContent>
|
size={14}
|
||||||
</DropdownMenu>
|
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||||
) : workspace.diffStat ? (
|
/>
|
||||||
<View style={styles.diffStatRow}>
|
)}
|
||||||
<Text style={styles.diffStatAdditions}>+{workspace.diffStat.additions}</Text>
|
</DropdownMenuTrigger>
|
||||||
<Text style={styles.diffStatDeletions}>-{workspace.diffStat.deletions}</Text>
|
<DropdownMenuContent align="end" width={260}>
|
||||||
</View>
|
{onCopyPath ? (
|
||||||
) : null}
|
<DropdownMenuItem
|
||||||
{showShortcutBadge && shortcutNumber !== null ? (
|
testID={`sidebar-workspace-menu-copy-path-${workspace.workspaceKey}`}
|
||||||
<View style={styles.shortcutBadge}>
|
leading={<Copy size={14} color={theme.colors.foregroundMuted} />}
|
||||||
<Text style={styles.shortcutBadgeText}>{shortcutNumber}</Text>
|
onSelect={onCopyPath}
|
||||||
</View>
|
>
|
||||||
) : null}
|
Copy path
|
||||||
|
</DropdownMenuItem>
|
||||||
|
) : null}
|
||||||
|
{onCopyBranchName ? (
|
||||||
|
<DropdownMenuItem
|
||||||
|
testID={`sidebar-workspace-menu-copy-branch-name-${workspace.workspaceKey}`}
|
||||||
|
leading={<Copy size={14} color={theme.colors.foregroundMuted} />}
|
||||||
|
onSelect={onCopyBranchName}
|
||||||
|
>
|
||||||
|
Copy branch name
|
||||||
|
</DropdownMenuItem>
|
||||||
|
) : null}
|
||||||
|
<DropdownMenuItem
|
||||||
|
testID={`sidebar-workspace-menu-archive-${workspace.workspaceKey}`}
|
||||||
|
leading={<Archive size={14} color={theme.colors.foregroundMuted} />}
|
||||||
|
trailing={archiveShortcutKeys ? <Shortcut chord={archiveShortcutKeys} /> : null}
|
||||||
|
status={archiveStatus}
|
||||||
|
pendingLabel={archivePendingLabel}
|
||||||
|
onSelect={onArchive}
|
||||||
|
>
|
||||||
|
{archiveLabel ?? "Archive"}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
) : workspace.diffStat ? (
|
||||||
|
<View style={styles.diffStatRow}>
|
||||||
|
<Text style={styles.diffStatAdditions}>+{workspace.diffStat.additions}</Text>
|
||||||
|
<Text style={styles.diffStatDeletions}>-{workspace.diffStat.deletions}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{showShortcutBadge && shortcutNumber !== null ? (
|
||||||
|
<View style={styles.shortcutBadge}>
|
||||||
|
<Text style={styles.shortcutBadgeText}>{shortcutNumber}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
{prHint ? (
|
||||||
{prHint ? (
|
<View style={styles.workspacePrBadgeRow}>
|
||||||
<View style={styles.workspacePrBadgeRow}>
|
<WorkspacePrBadge hint={prHint} />
|
||||||
<WorkspacePrBadge hint={prHint} />
|
</View>
|
||||||
</View>
|
) : null}
|
||||||
) : null}
|
</Pressable>
|
||||||
</Pressable>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user