import { memo, useCallback, useEffect, useState, type MouseEvent } from "react";
import { styled, useStyletron } from "baseui";
import { GitPullRequest, GitPullRequestDraft } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { getFoundryTokens } from "../../styles/tokens";
import type { AgentKind, AgentTab } from "./view-model";
export interface ContextMenuItem {
label: string;
onClick: () => void;
}
export function useContextMenu() {
const [menu, setMenu] = useState<{ x: number; y: number; items: ContextMenuItem[] } | null>(null);
useEffect(() => {
if (!menu) {
return;
}
const close = () => setMenu(null);
window.addEventListener("click", close);
window.addEventListener("contextmenu", close);
return () => {
window.removeEventListener("click", close);
window.removeEventListener("contextmenu", close);
};
}, [menu]);
const open = useCallback((event: MouseEvent, items: ContextMenuItem[]) => {
event.preventDefault();
event.stopPropagation();
setMenu({ x: event.clientX, y: event.clientY, items });
}, []);
return { menu, open, close: useCallback(() => setMenu(null), []) };
}
export const ContextMenuOverlay = memo(function ContextMenuOverlay({
menu,
onClose,
}: {
menu: { x: number; y: number; items: ContextMenuItem[] };
onClose: () => void;
}) {
const [css] = useStyletron();
const t = useFoundryTokens();
return (
{menu.items.map((item, index) => (
{
item.onClick();
onClose();
}}
className={css({
padding: "8px 14px",
fontSize: "12px",
color: t.textPrimary,
cursor: "pointer",
":hover": { backgroundColor: t.interactiveHover },
})}
>
{item.label}
))}
);
});
export const SpinnerDot = memo(function SpinnerDot({ size = 10 }: { size?: number }) {
const t = useFoundryTokens();
return (
);
});
export const UnreadDot = memo(function UnreadDot() {
const t = useFoundryTokens();
return (
);
});
export const TaskIndicator = memo(function TaskIndicator({ isRunning, hasUnread, isDraft }: { isRunning: boolean; hasUnread: boolean; isDraft: boolean }) {
const t = useFoundryTokens();
if (isRunning) return ;
if (hasUnread) return ;
if (isDraft) return ;
return ;
});
const ClaudeIcon = memo(function ClaudeIcon({ size = 14 }: { size?: number }) {
return (
);
});
const OpenAIIcon = memo(function OpenAIIcon({ size = 14 }: { size?: number }) {
const t = useFoundryTokens();
return (
);
});
const CursorIcon = memo(function CursorIcon({ size = 14 }: { size?: number }) {
const t = useFoundryTokens();
return (
);
});
export const AgentIcon = memo(function AgentIcon({ agent, size = 14 }: { agent: AgentKind; size?: number }) {
switch (agent) {
case "Claude":
return ;
case "Codex":
return ;
case "Cursor":
return ;
}
});
export const TabAvatar = memo(function TabAvatar({ tab }: { tab: AgentTab }) {
if (tab.status === "running") return ;
if (tab.unread) return ;
return ;
});
export const Shell = styled("div", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
display: "flex",
height: "100dvh",
backgroundColor: t.surfaceSecondary,
overflow: "hidden",
};
});
export const SPanel = styled("section", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
minHeight: 0,
flex: 1,
display: "flex",
flexDirection: "column" as const,
backgroundColor: t.surfaceSecondary,
overflow: "hidden",
};
});
export const ScrollBody = styled("div", () => ({
minHeight: 0,
flex: 1,
position: "relative" as const,
overflowY: "auto" as const,
display: "flex",
flexDirection: "column" as const,
}));
export const HEADER_HEIGHT = "42px";
export const PROMPT_TEXTAREA_MIN_HEIGHT = 56;
export const PROMPT_TEXTAREA_MAX_HEIGHT = 100;
export const PanelHeaderBar = styled("div", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
display: "flex",
alignItems: "center",
minHeight: HEADER_HEIGHT,
maxHeight: HEADER_HEIGHT,
padding: "0 14px",
borderBottom: `1px solid ${t.borderDefault}`,
backgroundColor: t.surfaceTertiary,
gap: "8px",
flexShrink: 0,
position: "relative" as const,
zIndex: 9999,
};
});