diff --git a/packages/app/src/components/workspace-hover-card.tsx b/packages/app/src/components/workspace-hover-card.tsx index 2cabce0b6..0a833dfc6 100644 --- a/packages/app/src/components/workspace-hover-card.tsx +++ b/packages/app/src/components/workspace-hover-card.tsx @@ -12,7 +12,14 @@ import { Dimensions, Text, View } from "react-native"; import { useTranslation } from "react-i18next"; import { FadeIn, FadeOut } from "react-native-reanimated"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; -import { CircleCheck, CircleDot, CircleX, ExternalLink, GitBranch } from "lucide-react-native"; +import { + CircleCheck, + CircleDot, + CircleX, + ExternalLink, + Folder, + GitBranch, +} from "lucide-react-native"; import { GitHubIcon } from "@/components/icons/github-icon"; import type { Theme } from "@/styles/theme"; import { DiffStat } from "@/components/diff-stat"; @@ -22,6 +29,7 @@ import { useBottomSheetModalInternal } from "@gorhom/bottom-sheet"; import type { SidebarWorkspaceEntry } from "@/hooks/use-sidebar-workspaces-list"; import type { PrHint } from "@/git/use-pr-status-query"; import { openExternalUrl } from "@/utils/open-external-url"; +import { shortenPath } from "@/utils/shorten-path"; import { PrBadge } from "@/components/sidebar-workspace-list"; import { useHoverSafeZone } from "@/hooks/use-hover-safe-zone"; import { useIsCompactFormFactor } from "@/constants/layout"; @@ -113,8 +121,6 @@ function WorkspaceHoverCardDesktop({ const graceTimerRef = useRef | null>(null); const triggerHoveredRef = useRef(false); - const hasContent = prHint !== null || !!workspace.diffStat; - const clearGraceTimer = useCallback(() => { if (graceTimerRef.current) { clearTimeout(graceTimerRef.current); @@ -133,10 +139,10 @@ function WorkspaceHoverCardDesktop({ const handleTriggerEnter = useCallback(() => { triggerHoveredRef.current = true; clearGraceTimer(); - if (!isDragging && hasContent) { + if (!isDragging) { setOpen(true); } - }, [clearGraceTimer, isDragging, hasContent]); + }, [clearGraceTimer, isDragging]); const handleTriggerLeave = useCallback(() => { triggerHoveredRef.current = false; @@ -162,19 +168,6 @@ function WorkspaceHoverCardDesktop({ } }, [isDragging, clearGraceTimer]); - // When content becomes available while trigger is already hovered, open the card. - useEffect(() => { - if (!hasContent) { - clearGraceTimer(); - setOpen(false); - return; - } - if (isDragging) return; - if (triggerHoveredRef.current) { - setOpen(true); - } - }, [clearGraceTimer, hasContent, isDragging]); - // Cleanup on unmount useEffect(() => { return () => { @@ -190,7 +183,7 @@ function WorkspaceHoverCardDesktop({ onPointerLeave={handleTriggerLeave} > {children} - {open && hasContent ? ( + {open ? ( ; }): ReactElement | null { const { t } = useTranslation(); + const cwdDisplay = shortenPath(workspace.workspaceDirectory); const bottomSheetInternal = useBottomSheetModalInternal(true); const [triggerRect, setTriggerRect] = useState(null); const [contentSize, setContentSize] = useState<{ width: number; height: number } | null>(null); @@ -286,11 +280,11 @@ function WorkspaceHoverCardContent({ {workspace.name} - {workspace.currentBranch && workspace.currentBranch !== workspace.name ? ( - + {workspace.currentBranch ? ( + @@ -298,6 +292,14 @@ function WorkspaceHoverCardContent({ ) : null} + {cwdDisplay ? ( + + + + {cwdDisplay} + + + ) : null} {prHint || workspace.diffStat ? ( {workspace.diffStat ? ( @@ -322,6 +324,7 @@ function WorkspaceHoverCardContent({ } const ThemedGitBranch = withUnistyles(GitBranch); +const ThemedFolder = withUnistyles(Folder); const ThemedExternalLink = withUnistyles(ExternalLink); const ThemedGitHubIcon = withUnistyles(GitHubIcon); const ThemedCircleCheck = withUnistyles(CircleCheck); @@ -484,14 +487,14 @@ const styles = StyleSheet.create((theme) => ({ paddingHorizontal: theme.spacing[3], paddingBottom: theme.spacing[2], }, - cardBranchRow: { + cardInfoRow: { flexDirection: "row", alignItems: "center", gap: theme.spacing[1.5], paddingHorizontal: theme.spacing[3], paddingBottom: theme.spacing[2], }, - cardBranchText: { + cardInfoText: { flex: 1, minWidth: 0, color: theme.colors.foregroundMuted, diff --git a/packages/app/src/utils/shorten-path.test.ts b/packages/app/src/utils/shorten-path.test.ts new file mode 100644 index 000000000..7b8536458 --- /dev/null +++ b/packages/app/src/utils/shorten-path.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { shortenPath } from "./shorten-path"; + +describe("shortenPath", () => { + it("shortens a macOS home directory path", () => { + expect(shortenPath("/Users/moboudra/dev/paseo")).toBe("~/dev/paseo"); + }); + + it("shortens a Linux home directory path", () => { + expect(shortenPath("/home/moboudra/dev/paseo")).toBe("~/dev/paseo"); + }); + + it("leaves non-home absolute paths unchanged", () => { + expect(shortenPath("/var/www/app")).toBe("/var/www/app"); + }); + + it("leaves Windows paths unchanged", () => { + expect(shortenPath("C:\\Users\\moboudra\\dev\\paseo")).toBe("C:\\Users\\moboudra\\dev\\paseo"); + }); + + it("returns an empty string for null or undefined", () => { + expect(shortenPath(null)).toBe(""); + expect(shortenPath(undefined)).toBe(""); + }); + + it("returns an empty string for an empty string", () => { + expect(shortenPath("")).toBe(""); + }); +});