Refine compact workspace header layout
This commit is contained in:
@@ -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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user