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,
},
}));