import React, { useCallback, useMemo } from "react"; import { View } from "react-native"; import { useRouter, type Href } from "expo-router"; import { useTranslation } from "react-i18next"; import { Copy, Ellipsis, Globe, Import as ImportIcon, Settings, SquarePen, } from "lucide-react-native"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; import type { TerminalProfile } from "@getpaseo/protocol/messages"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { buttonControlHeight } from "@/components/ui/control-geometry"; import { TerminalProfileIcon } from "@/components/terminal-profile-icon"; import { useDaemonConfig } from "@/hooks/use-daemon-config"; import { getTerminalProfileIcon, resolveTerminalProfiles, } from "@getpaseo/protocol/terminal-profiles"; import { buildSettingsHostSectionRoute } from "@/utils/host-routes"; import type { Theme } from "@/styles/theme"; const ThemedEllipsis = withUnistyles(Ellipsis); const ThemedCopy = withUnistyles(Copy); const ThemedSquarePen = withUnistyles(SquarePen); const ThemedGlobe = withUnistyles(Globe); const ThemedImport = withUnistyles(ImportIcon); const ThemedSettings = withUnistyles(Settings); const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground }); const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted }); const MENU_NEW_AGENT_ICON = ; const MENU_NEW_BROWSER_ICON = ; const MENU_NEW_TERMINAL_ICON = ; const MENU_IMPORT_ICON = ; const MENU_COPY_ICON = ; const MENU_SETTINGS_ICON = ; /** * The compact header buttons draw at 32pt, under the 44pt touch minimum, and sit flush against * each other — so the slop can only grow vertically. Widening it would put two buttons' slop over * the same pixels, which is a worse miss than a small target. */ const COMPACT_HEADER_BUTTON_HIT_SLOP = { top: 8, bottom: 8 } as const; /** * Actions that belong to the workspace itself rather than to a tab. Both header menus render them, * from the same callbacks, so the two surfaces can't drift. */ export interface WorkspaceHeaderWorkspaceActions { currentBranchName: string | null; showWorkspaceSetup: boolean; importAgentDisabled: boolean; copyPathDisabled: boolean; onOpenImportSheet: () => void; onCopyWorkspacePath: () => void; onCopyBranchName: () => void; onOpenSetupTab: () => void; } function WorkspaceHeaderWorkspaceActionItems({ currentBranchName, showWorkspaceSetup, importAgentDisabled, copyPathDisabled, onOpenImportSheet, onCopyWorkspacePath, onCopyBranchName, onOpenSetupTab, }: WorkspaceHeaderWorkspaceActions) { const { t } = useTranslation(); return ( <> {t("workspace.header.actions.copyPath")} {currentBranchName ? ( {t("workspace.header.actions.copyBranchName")} ) : null} {t("workspace.header.actions.importSession")} {showWorkspaceSetup ? ( <> {t("workspace.header.actions.showSetup")} ) : null} ); } function WorkspaceHeaderMenuTriggerIcon({ hovered, open }: { hovered: boolean; open: boolean }) { const colorMapping = hovered || open ? foregroundColorMapping : mutedColorMapping; return ; } function renderTriggerIcon({ hovered, open }: { hovered: boolean; open: boolean }) { return ; } /** * Wide layouts make tabs from the tab strip's `+` menu, so this one carries workspace actions only. */ export function WorkspaceHeaderMenuDesktop(props: WorkspaceHeaderWorkspaceActions) { const { t } = useTranslation(); return ( {renderTriggerIcon} ); } interface HeaderMenuProfileItemProps { profile: TerminalProfile; disabled: boolean; onCreateTerminalWithProfile: (profile: TerminalProfile) => void; } function HeaderMenuProfileItem({ profile, disabled, onCreateTerminalWithProfile, }: HeaderMenuProfileItemProps) { const handleSelect = useCallback(() => { onCreateTerminalWithProfile(profile); }, [onCreateTerminalWithProfile, profile]); const leading = useMemo( () => ( ), [profile], ); return ( {profile.name} ); } export interface WorkspaceHeaderMenuMobileProps extends WorkspaceHeaderWorkspaceActions { normalizedServerId: string; showCreateBrowserTab: boolean; createTerminalDisabled: boolean; onCreateDraftTab: () => void; onCreateTerminal: () => void; onCreateTerminalWithProfile: (profile: TerminalProfile) => void; onCreateBrowser: () => void; } /** * Compact layouts have no tab strip to launch from, so new tabs live here alongside the workspace * actions. */ export function WorkspaceHeaderMenuMobile({ normalizedServerId, showCreateBrowserTab, createTerminalDisabled, onCreateDraftTab, onCreateTerminal, onCreateTerminalWithProfile, onCreateBrowser, ...workspaceActions }: WorkspaceHeaderMenuMobileProps) { const { t } = useTranslation(); const router = useRouter(); const { config } = useDaemonConfig(normalizedServerId); const profiles = useMemo( () => resolveTerminalProfiles(config?.terminalProfiles), [config?.terminalProfiles], ); const handleEditProfiles = useCallback(() => { router.push(buildSettingsHostSectionRoute(normalizedServerId, "terminals") as Href); }, [normalizedServerId, router]); return ( {renderTriggerIcon} {t("workspace.header.actions.newAgent")} {showCreateBrowserTab ? ( {t("workspace.header.actions.newBrowser")} ) : null} {t("workspace.tabs.actions.terminalProfilesMenu")} {t("workspace.header.actions.newTerminal")} {profiles.map((profile) => ( ))} {t("workspace.tabs.actions.editTerminalProfiles")} ); } const styles = StyleSheet.create((theme) => ({ headerActionButton: { paddingVertical: theme.spacing[2], paddingHorizontal: theme.spacing[2], borderRadius: theme.borderRadius.lg, }, compactHeaderActionButton: { width: { xs: theme.spacing[8], md: buttonControlHeight.xs, }, height: { xs: theme.spacing[8], md: buttonControlHeight.xs, }, padding: 0, borderRadius: theme.borderRadius.lg, alignItems: "center", justifyContent: "center", }, headerMenuProfileIconWrapper: { width: 16, height: 16, }, }));