diff --git a/packages/app/src/components/git-actions-split-button.tsx b/packages/app/src/components/git-actions-split-button.tsx index f510424ca..234d789d3 100644 --- a/packages/app/src/components/git-actions-split-button.tsx +++ b/packages/app/src/components/git-actions-split-button.tsx @@ -16,18 +16,25 @@ import type { GitAction, GitActions } from "@/components/git-actions-policy"; interface GitActionsSplitButtonProps { gitActions: GitActions; + hideLabels?: boolean; } -export function GitActionsSplitButton({ gitActions }: GitActionsSplitButtonProps) { +export function GitActionsSplitButton({ + gitActions, + hideLabels, +}: GitActionsSplitButtonProps) { const { theme } = useUnistyles(); const toast = useToast(); const archiveShortcutKeys = useShortcutKeys("archive-worktree"); - const getActionDisplayLabel = useCallback((action: GitAction): string => { - if (action.status === "pending") return action.pendingLabel; - if (action.status === "success") return action.successLabel; - return action.label; - }, []); + const getActionDisplayLabel = useCallback( + (action: GitAction): string => { + if (action.status === "pending") return action.pendingLabel; + if (action.status === "success") return action.successLabel; + return action.label; + }, + [], + ); const handleActionSelect = useCallback( (action: GitAction) => { @@ -68,9 +75,11 @@ export function GitActionsSplitButton({ gitActions }: GitActionsSplitButtonProps ) : ( {gitActions.primary.icon} - - {getActionDisplayLabel(gitActions.primary)} - + {!hideLabels && ( + + {getActionDisplayLabel(gitActions.primary)} + + )} )} diff --git a/packages/app/src/components/headers/screen-header.tsx b/packages/app/src/components/headers/screen-header.tsx index f516934e9..cc5b3b58b 100644 --- a/packages/app/src/components/headers/screen-header.tsx +++ b/packages/app/src/components/headers/screen-header.tsx @@ -1,4 +1,5 @@ import type { ReactNode } from "react"; +import type { LayoutChangeEvent } from "react-native"; import { View, type StyleProp, type ViewStyle } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; @@ -17,6 +18,7 @@ interface ScreenHeaderProps { leftStyle?: StyleProp; rightStyle?: StyleProp; borderless?: boolean; + onRowLayout?: (event: LayoutChangeEvent) => void; } /** @@ -29,6 +31,7 @@ export function ScreenHeader({ leftStyle, rightStyle, borderless, + onRowLayout, }: ScreenHeaderProps) { const { theme } = useUnistyles(); const insets = useSafeAreaInsets(); @@ -42,6 +45,7 @@ export function ScreenHeader({ ({ flexDirection: "row", alignItems: "center", gap: theme.spacing[2], + minWidth: 0, }, right: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], + flexShrink: 0, }, borderless: { borderBottomColor: "transparent", diff --git a/packages/app/src/components/headers/screen-title.tsx b/packages/app/src/components/headers/screen-title.tsx index 57a60f60d..de8b98449 100644 --- a/packages/app/src/components/headers/screen-title.tsx +++ b/packages/app/src/components/headers/screen-title.tsx @@ -25,6 +25,7 @@ export function ScreenTitle({ children, numberOfLines = 1, testID, style }: Scre const styles = StyleSheet.create((theme) => ({ text: { flexShrink: 1, + minWidth: 0, fontSize: theme.fontSize.base, fontWeight: { xs: "400", diff --git a/packages/app/src/hooks/use-container-width.ts b/packages/app/src/hooks/use-container-width.ts new file mode 100644 index 000000000..35bb1b7b6 --- /dev/null +++ b/packages/app/src/hooks/use-container-width.ts @@ -0,0 +1,18 @@ +import { useCallback, useState } from "react"; +import type { LayoutChangeEvent } from "react-native"; + +/** + * Tracks the width of a container via onLayout. + */ +export function useContainerWidth(): { + onLayout: (e: LayoutChangeEvent) => void; + width: number; +} { + const [width, setWidth] = useState(0); + return { + onLayout: useCallback((e: LayoutChangeEvent) => { + setWidth(e.nativeEvent.layout.width); + }, []), + width, + }; +} diff --git a/packages/app/src/screens/workspace/workspace-git-actions.tsx b/packages/app/src/screens/workspace/workspace-git-actions.tsx index 86fb15d9c..b30fe7bf4 100644 --- a/packages/app/src/screens/workspace/workspace-git-actions.tsx +++ b/packages/app/src/screens/workspace/workspace-git-actions.tsx @@ -15,9 +15,14 @@ import { useGitActions } from "@/hooks/use-git-actions"; interface WorkspaceGitActionsProps { serverId: string; cwd: string; + hideLabels?: boolean; } -export function WorkspaceGitActions({ serverId, cwd }: WorkspaceGitActionsProps) { +export function WorkspaceGitActions({ + serverId, + cwd, + hideLabels, +}: WorkspaceGitActionsProps) { const { theme } = useUnistyles(); const icons = useMemo( @@ -40,5 +45,5 @@ export function WorkspaceGitActions({ serverId, cwd }: WorkspaceGitActionsProps) return null; } - return ; + return ; } diff --git a/packages/app/src/screens/workspace/workspace-open-in-editor-button.tsx b/packages/app/src/screens/workspace/workspace-open-in-editor-button.tsx index 836c2469f..a414175df 100644 --- a/packages/app/src/screens/workspace/workspace-open-in-editor-button.tsx +++ b/packages/app/src/screens/workspace/workspace-open-in-editor-button.tsx @@ -20,9 +20,14 @@ import { isWeb } from "@/constants/platform"; interface WorkspaceOpenInEditorButtonProps { serverId: string; cwd: string; + hideLabels?: boolean; } -export function WorkspaceOpenInEditorButton({ serverId, cwd }: WorkspaceOpenInEditorButtonProps) { +export function WorkspaceOpenInEditorButton({ + serverId, + cwd, + hideLabels, +}: WorkspaceOpenInEditorButtonProps) { const { theme } = useUnistyles(); const toast = useToast(); const client = useHostRuntimeClient(serverId); @@ -127,7 +132,7 @@ export function WorkspaceOpenInEditorButton({ serverId, cwd }: WorkspaceOpenInEd size={16} color={theme.colors.foregroundMuted} /> - Open + {!hideLabels && Open} )} @@ -196,7 +201,14 @@ const styles = StyleSheet.create((theme) => ({ }, splitButtonPrimary: { paddingLeft: theme.spacing[3], - paddingRight: 10, + paddingRight: theme.spacing[3], + paddingVertical: theme.spacing[1], + justifyContent: "center", + position: "relative", + }, + splitButtonPrimaryIconOnly: { + paddingLeft: theme.spacing[2], + paddingRight: theme.spacing[2], paddingVertical: theme.spacing[1], justifyContent: "center", position: "relative", diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index a37667ef5..3b8ab6e4e 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -117,6 +117,7 @@ import { findAdjacentPane } from "@/utils/split-navigation"; import { isAbsolutePath } from "@/utils/path"; import { useIsCompactFormFactor, supportsDesktopPaneSplits } from "@/constants/layout"; import { isWeb, isNative } from "@/constants/platform"; +import { useContainerWidth } from "@/hooks/use-container-width"; const TERMINALS_QUERY_STALE_TIME = 5_000; const WORKSPACE_SETUP_AUTO_OPEN_WINDOW_MS = 30_000; @@ -939,6 +940,8 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) ); const pendingByDraftId = useCreateFlowStore((state) => state.pendingByDraftId); const { closingTabIds, closeTab } = useCloseTabs(); + const { onLayout: onHeaderLayout, width: headerWidth } = useContainerWidth(); + const showCompactButtonLabels = headerWidth < 700; const closeWorkspaceTabWithCleanup = useCallback( function closeWorkspaceTabWithCleanup(input: { tabId: string; @@ -2044,6 +2047,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) {(!isFocusModeEnabled || isMobile) && ( @@ -2151,12 +2155,14 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) serverId={normalizedServerId} workspaceId={normalizedWorkspaceId} scripts={workspaceDescriptor.scripts} + hideLabels={showCompactButtonLabels} /> ) : null} {!isMobile ? ( ) : null} {!isMobile && isGitCheckout ? ( @@ -2164,6 +2170,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) @@ -2413,9 +2420,11 @@ const styles = StyleSheet.create((theme) => ({ flexDirection: "row", alignItems: "center", gap: theme.spacing[2], + overflow: "hidden", }, headerTitleTextGroup: { minWidth: 0, + overflow: "hidden", flexShrink: 1, flexGrow: { xs: 1, @@ -2442,6 +2451,8 @@ const styles = StyleSheet.create((theme) => ({ md: theme.fontSize.base, }, flexShrink: 1, + minWidth: 0, + maxWidth: "60%", }, headerTitleSkeleton: { width: 190, diff --git a/packages/app/src/screens/workspace/workspace-scripts-button.tsx b/packages/app/src/screens/workspace/workspace-scripts-button.tsx index 054a619fe..512413b70 100644 --- a/packages/app/src/screens/workspace/workspace-scripts-button.tsx +++ b/packages/app/src/screens/workspace/workspace-scripts-button.tsx @@ -41,12 +41,14 @@ interface WorkspaceScriptsButtonProps { serverId: string; workspaceId: string; scripts: WorkspaceDescriptor["scripts"]; + hideLabels?: boolean; } export function WorkspaceScriptsButton({ serverId, workspaceId, scripts, + hideLabels, }: WorkspaceScriptsButtonProps): ReactElement | null { const { theme } = useUnistyles(); const toast = useToast(); @@ -98,7 +100,7 @@ export function WorkspaceScriptsButton({ } fill="transparent" /> - Scripts + {!hideLabels && Scripts} @@ -268,6 +270,11 @@ const styles = StyleSheet.create((theme) => ({ paddingVertical: theme.spacing[1], justifyContent: "center", }, + splitButtonPrimaryIconOnly: { + paddingHorizontal: theme.spacing[2], + paddingVertical: theme.spacing[1], + justifyContent: "center", + }, splitButtonPrimaryHovered: { backgroundColor: theme.colors.surface2, },