Refine compact workspace header layout

This commit is contained in:
Mohamed Boudra
2026-04-14 11:34:50 +07:00
parent c59a979446
commit 9c0b5616fe
2 changed files with 62 additions and 14 deletions
@@ -4,6 +4,7 @@ import { useQueryClient } from "@tanstack/react-query";
import { ChevronDown, GitBranch } from "lucide-react-native"; import { ChevronDown, GitBranch } from "lucide-react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox"; import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
import { useIsCompactFormFactor } from "@/constants/layout";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime"; import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import { useToast } from "@/contexts/toast-context"; import { useToast } from "@/contexts/toast-context";
import { useBranchSwitcher } from "@/hooks/use-branch-switcher"; import { useBranchSwitcher } from "@/hooks/use-branch-switcher";
@@ -24,6 +25,7 @@ export function BranchSwitcher({
isGitCheckout, isGitCheckout,
}: BranchSwitcherProps) { }: BranchSwitcherProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const isCompact = useIsCompactFormFactor();
const anchorRef = useRef<View>(null); const anchorRef = useRef<View>(null);
const client = useHostRuntimeClient(serverId); const client = useHostRuntimeClient(serverId);
const isConnected = useHostRuntimeIsConnected(serverId); const isConnected = useHostRuntimeIsConnected(serverId);
@@ -65,7 +67,7 @@ export function BranchSwitcher({
<Text testID="workspace-header-title" style={styles.headerTitle} numberOfLines={1}> <Text testID="workspace-header-title" style={styles.headerTitle} numberOfLines={1}>
{title} {title}
</Text> </Text>
<ChevronDown size={12} color={theme.colors.foregroundMuted} /> {!isCompact ? <ChevronDown size={12} color={theme.colors.foregroundMuted} /> : null}
</Pressable> </Pressable>
<Combobox <Combobox
options={branchOptions} options={branchOptions}
@@ -111,6 +113,10 @@ const styles = StyleSheet.create((theme) => ({
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
gap: theme.spacing[1], gap: theme.spacing[1],
marginLeft: {
xs: -theme.spacing[2],
md: 0,
},
paddingVertical: theme.spacing[1], paddingVertical: theme.spacing[1],
paddingHorizontal: theme.spacing[2], paddingHorizontal: theme.spacing[2],
borderRadius: theme.borderRadius.md, borderRadius: theme.borderRadius.md,
@@ -141,6 +141,18 @@ function decodeSegment(value: string): string {
} }
} }
function areHeaderLabelsEquivalent(
a: string | null | undefined,
b: string | null | undefined,
): boolean {
const normalizedA = trimNonEmpty(a)?.toLocaleLowerCase();
const normalizedB = trimNonEmpty(b)?.toLocaleLowerCase();
if (!normalizedA || !normalizedB) {
return false;
}
return normalizedA === normalizedB;
}
function getFallbackTabOptionLabel(tab: WorkspaceTabDescriptor): string { function getFallbackTabOptionLabel(tab: WorkspaceTabDescriptor): string {
if (tab.target.kind === "draft") { if (tab.target.kind === "draft") {
return "New Agent"; return "New Agent";
@@ -747,6 +759,12 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
? resolveWorkspaceHeader({ workspace: workspaceDescriptor }) ? resolveWorkspaceHeader({ workspace: workspaceDescriptor })
: null; : null;
const isWorkspaceHeaderLoading = workspaceHeader === null; const isWorkspaceHeaderLoading = workspaceHeader === null;
const workspaceHeaderTitle = workspaceHeader?.title ?? "";
const workspaceHeaderSubtitle = workspaceHeader?.subtitle ?? "";
const shouldShowWorkspaceHeaderSubtitle = !areHeaderLabelsEquivalent(
workspaceHeaderTitle,
workspaceHeaderSubtitle,
);
const isGitCheckout = checkoutQuery.data?.isGit ?? false; const isGitCheckout = checkoutQuery.data?.isGit ?? false;
const currentBranchName = const currentBranchName =
@@ -1949,27 +1967,29 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
<SidebarMenuToggle /> <SidebarMenuToggle />
<View style={styles.headerTitleContainer}> <View style={styles.headerTitleContainer}>
{isWorkspaceHeaderLoading ? ( {isWorkspaceHeaderLoading ? (
<> <View style={styles.headerTitleTextGroup}>
<View style={styles.headerTitleSkeleton} /> <View style={styles.headerTitleSkeleton} />
<View style={styles.headerProjectTitleSkeleton} /> <View style={styles.headerProjectTitleSkeleton} />
</> </View>
) : ( ) : (
<> <View style={styles.headerTitleTextGroup}>
<BranchSwitcher <BranchSwitcher
currentBranchName={currentBranchName} currentBranchName={currentBranchName}
title={workspaceHeader.title} title={workspaceHeaderTitle}
serverId={normalizedServerId} serverId={normalizedServerId}
workspaceId={normalizedWorkspaceId} workspaceId={normalizedWorkspaceId}
isGitCheckout={isGitCheckout} isGitCheckout={isGitCheckout}
/> />
<Text {shouldShowWorkspaceHeaderSubtitle ? (
testID="workspace-header-subtitle" <Text
style={styles.headerProjectTitle} testID="workspace-header-subtitle"
numberOfLines={1} style={styles.headerProjectTitle}
> numberOfLines={1}
{workspaceHeader.subtitle} >
</Text> {workspaceHeaderSubtitle}
</> </Text>
) : null}
</View>
)} )}
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger <DropdownMenuTrigger
@@ -2291,15 +2311,37 @@ const styles = StyleSheet.create((theme) => ({
flexShrink: 1, flexShrink: 1,
}, },
headerTitleContainer: { headerTitleContainer: {
flex: 1,
flexShrink: 1, flexShrink: 1,
minWidth: 0, minWidth: 0,
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
gap: theme.spacing[2], gap: theme.spacing[2],
}, },
headerTitleTextGroup: {
minWidth: 0,
flexShrink: 1,
flexGrow: 1,
flexDirection: {
xs: "column",
md: "row",
},
alignItems: {
xs: "flex-start",
md: "center",
},
justifyContent: "center",
gap: {
xs: 0,
md: theme.spacing[2],
},
},
headerProjectTitle: { headerProjectTitle: {
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.base, fontSize: {
xs: theme.fontSize.sm,
md: theme.fontSize.base,
},
flexShrink: 1, flexShrink: 1,
}, },
headerTitleSkeleton: { headerTitleSkeleton: {