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