Fix workspace tab drag hit area

This commit is contained in:
Mohamed Boudra
2026-03-18 21:11:09 +07:00
parent 82ab598426
commit 865e25b3a9
@@ -1,5 +1,5 @@
import { useCallback, useMemo, useState, type Dispatch, type SetStateAction } from "react"; import { useCallback, useMemo, useState, type Dispatch, type SetStateAction } from "react";
import { ActivityIndicator, Pressable, ScrollView, Text, View, type LayoutChangeEvent } from "react-native"; import { ActivityIndicator, Platform, Pressable, ScrollView, Text, View, type LayoutChangeEvent } from "react-native";
import { Columns2, Plus, Rows2, SquareTerminal, X } from "lucide-react-native"; import { Columns2, Plus, Rows2, SquareTerminal, X } from "lucide-react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { SortableInlineList } from "@/components/sortable-inline-list"; import { SortableInlineList } from "@/components/sortable-inline-list";
@@ -12,6 +12,7 @@ import {
} from "@/components/ui/context-menu"; } from "@/components/ui/context-menu";
import { Shortcut } from "@/components/ui/shortcut"; import { Shortcut } from "@/components/ui/shortcut";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
import { useWorkspaceTabLayout } from "@/screens/workspace/use-workspace-tab-layout"; import { useWorkspaceTabLayout } from "@/screens/workspace/use-workspace-tab-layout";
import { import {
useWorkspaceTabPresentation, useWorkspaceTabPresentation,
@@ -81,6 +82,7 @@ function getFallbackTabLabel(tab: WorkspaceTabDescriptor): string {
function TabChip({ function TabChip({
tab, tab,
isActive, isActive,
isDragging,
isFocused, isFocused,
resolvedTabWidth, resolvedTabWidth,
showLabel, showLabel,
@@ -98,6 +100,7 @@ function TabChip({
}: { }: {
tab: WorkspaceTabDescriptor; tab: WorkspaceTabDescriptor;
isActive: boolean; isActive: boolean;
isDragging: boolean;
isFocused: boolean; isFocused: boolean;
resolvedTabWidth: number; resolvedTabWidth: number;
showLabel: boolean; showLabel: boolean;
@@ -117,16 +120,34 @@ function TabChip({
const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab; const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab;
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const isHighlighted = isActive || hovered || isCloseHovered; const isHighlighted = isActive || hovered || isCloseHovered;
const closeButtonDragBlockers =
Platform.OS === "web"
? ({
onPointerDown: (event: { stopPropagation?: () => void }) => {
event.stopPropagation?.();
},
onMouseDown: (event: { stopPropagation?: () => void }) => {
event.stopPropagation?.();
},
} as const)
: undefined;
return ( return (
<ContextMenu key={tab.key}> <ContextMenu key={tab.key}>
<Tooltip delayDuration={400} enabledOnDesktop enabledOnMobile={false}> <Tooltip delayDuration={400} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger asChild triggerRefProp="triggerRef"> <TooltipTrigger asChild triggerRefProp="triggerRef">
<ContextMenuTrigger <ContextMenuTrigger
{...(dragHandleProps?.attributes as any)}
{...(dragHandleProps?.listeners as any)}
testID={`workspace-tab-${tab.key}`} testID={`workspace-tab-${tab.key}`}
triggerRef={dragHandleProps?.setActivatorNodeRef as any}
enabledOnMobile={false} enabledOnMobile={false}
style={({ hovered, pressed }) => [ style={({ hovered, pressed }) => [
styles.tab, styles.tab,
Platform.OS === "web" &&
({
cursor: isDragging ? "grabbing" : "grab",
} as const),
{ {
minWidth: resolvedTabWidth, minWidth: resolvedTabWidth,
width: resolvedTabWidth, width: resolvedTabWidth,
@@ -157,12 +178,7 @@ function TabChip({
]} ]}
/> />
)} )}
<View <View style={styles.tabHandle}>
{...(dragHandleProps?.attributes as any)}
{...(dragHandleProps?.listeners as any)}
ref={dragHandleProps?.setActivatorNodeRef}
style={styles.tabHandle}
>
<View style={styles.tabIcon}> <View style={styles.tabIcon}>
<WorkspaceTabIcon presentation={presentation} active={isHighlighted} /> <WorkspaceTabIcon presentation={presentation} active={isHighlighted} />
</View> </View>
@@ -193,8 +209,12 @@ function TabChip({
{showCloseButton ? ( {showCloseButton ? (
<Pressable <Pressable
{...(closeButtonDragBlockers as any)}
testID={closeButtonTestId} testID={closeButtonTestId}
disabled={isClosingTab} disabled={isClosingTab}
onPressIn={(event) => {
event.stopPropagation?.();
}}
onHoverIn={() => { onHoverIn={() => {
setHoveredTabKey(tab.key); setHoveredTabKey(tab.key);
setHoveredCloseTabKey(tab.key); setHoveredCloseTabKey(tab.key);
@@ -359,7 +379,7 @@ export function WorkspaceDesktopTabsRow({
}) })
: undefined : undefined
} }
renderItem={({ item, index, dragHandleProps }) => { renderItem={({ item, index, dragHandleProps, isActive }) => {
const shouldShowCloseButton = layout.closeButtonPolicy === "all"; const shouldShowCloseButton = layout.closeButtonPolicy === "all";
const layoutItem = layout.items[index] ?? null; const layoutItem = layout.items[index] ?? null;
const resolvedTabWidth = layoutItem?.width ?? 150; const resolvedTabWidth = layoutItem?.width ?? 150;
@@ -376,6 +396,7 @@ export function WorkspaceDesktopTabsRow({
key={`${item.tab.key}:${item.tab.kind}`} key={`${item.tab.key}:${item.tab.kind}`}
item={item} item={item}
isFocused={isFocused} isFocused={isFocused}
isDragging={isActive}
index={index} index={index}
tabCount={tabs.length} tabCount={tabs.length}
normalizedServerId={normalizedServerId} normalizedServerId={normalizedServerId}
@@ -482,6 +503,7 @@ export function WorkspaceDesktopTabsRow({
function ResolvedDesktopTabChip({ function ResolvedDesktopTabChip({
item, item,
isFocused, isFocused,
isDragging,
index, index,
tabCount, tabCount,
normalizedServerId, normalizedServerId,
@@ -504,6 +526,7 @@ function ResolvedDesktopTabChip({
}: { }: {
item: WorkspaceDesktopTabRowItem; item: WorkspaceDesktopTabRowItem;
isFocused: boolean; isFocused: boolean;
isDragging: boolean;
index: number; index: number;
tabCount: number; tabCount: number;
normalizedServerId: string; normalizedServerId: string;
@@ -565,6 +588,7 @@ function ResolvedDesktopTabChip({
<TabChip <TabChip
tab={item.tab} tab={item.tab}
isActive={item.isActive} isActive={item.isActive}
isDragging={isDragging}
isFocused={isFocused} isFocused={isFocused}
resolvedTabWidth={resolvedTabWidth} resolvedTabWidth={resolvedTabWidth}
showLabel={showLabel} showLabel={showLabel}
@@ -590,7 +614,7 @@ function ResolvedDesktopTabChip({
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
tabsContainer: { tabsContainer: {
minWidth: 0, minWidth: 0,
height: theme.spacing[2] * 2 + theme.iconSize.sm + theme.spacing[1], height: WORKSPACE_SECONDARY_HEADER_HEIGHT,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: theme.colors.border, borderBottomColor: theme.colors.border,
backgroundColor: theme.colors.surface0, backgroundColor: theme.colors.surface0,