Fix workspace tab drag hit area
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user