/** * MoreMenu - Dropdown menu behind a "..." icon for less-frequent toolbar actions. * * Groups: Teams, Settings, Extensions, Search, Schedules, Docs, Export (session-only), Analyze (session-only). * Closes on outside click or Escape. */ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { isElectronMode } from '@renderer/api'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { useStore } from '@renderer/store'; import { triggerDownload } from '@renderer/utils/sessionExporter'; import { formatShortcut } from '@renderer/utils/stringUtils'; import { Activity, BookOpen, Braces, Calendar, FileText, MoreHorizontal, Puzzle, Search, Settings, Type, Users, } from 'lucide-react'; import { useShallow } from 'zustand/react/shallow'; import type { SessionDetail } from '@renderer/types/data'; import type { Tab } from '@renderer/types/tabs'; import type { ExportFormat } from '@renderer/utils/sessionExporter'; const DOCS_URL = 'https://777genius.github.io/agent-teams-ai/docs/'; interface MoreMenuProps { activeTab: Tab | undefined; activeTabSessionDetail: SessionDetail | null; activeTabId: string | null; } interface MenuItem { id: string; label: string; icon: React.ComponentType<{ className?: string }>; shortcut?: string; onClick: () => void; } export const MoreMenu = ({ activeTab, activeTabSessionDetail, activeTabId, }: Readonly): React.JSX.Element => { const [isOpen, setIsOpen] = useState(false); const [buttonHover, setButtonHover] = useState(false); const [hoveredId, setHoveredId] = useState(null); const containerRef = useRef(null); const { openCommandPalette, openExtensionsTab, openSessionReport, openSchedulesTab, openSettingsTab, openTeamsTab, } = useStore( useShallow((s) => ({ openCommandPalette: () => s.openCommandPalette(), openExtensionsTab: () => s.openExtensionsTab(), openSessionReport: (tabId: string) => s.openSessionReport(tabId), openSchedulesTab: () => s.openSchedulesTab(), openSettingsTab: () => s.openSettingsTab(), openTeamsTab: () => s.openTeamsTab(), })) ); // Close on outside click useEffect(() => { if (!isOpen) return; const handleClickOutside = (event: MouseEvent): void => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [isOpen]); // Close on Escape useEffect(() => { if (!isOpen) return; const handleEscape = (event: KeyboardEvent): void => { if (event.key === 'Escape') { setIsOpen(false); } }; document.addEventListener('keydown', handleEscape); return () => document.removeEventListener('keydown', handleEscape); }, [isOpen]); const handleExport = useCallback( (format: ExportFormat) => { if (activeTabSessionDetail) { triggerDownload(activeTabSessionDetail, format); } setIsOpen(false); }, [activeTabSessionDetail] ); const handleOpenDocs = useCallback(async () => { if (isElectronMode()) { await window.electronAPI.openExternal(DOCS_URL); } else { window.open(DOCS_URL, '_blank', 'noopener,noreferrer'); } setIsOpen(false); }, []); const isSessionWithData = activeTab?.type === 'session' && activeTabSessionDetail != null; // Build menu sections const topItems: MenuItem[] = [ { id: 'teams', label: 'Teams', icon: Users, onClick: () => { openTeamsTab(); setIsOpen(false); }, }, { id: 'settings', label: 'Settings', icon: Settings, onClick: () => { openSettingsTab(); setIsOpen(false); }, }, { id: 'extensions', label: 'Extensions', icon: Puzzle, onClick: () => { openExtensionsTab(); setIsOpen(false); }, }, { id: 'search', label: 'Search', icon: Search, shortcut: formatShortcut('K'), onClick: () => { openCommandPalette(); setIsOpen(false); }, }, { id: 'schedules', label: 'Schedules', icon: Calendar, onClick: () => { openSchedulesTab(); setIsOpen(false); }, }, { id: 'docs', label: 'Docs', icon: BookOpen, onClick: () => { void handleOpenDocs(); }, }, ]; const sessionItems: MenuItem[] = isSessionWithData ? [ { id: 'export-md', label: 'Export as Markdown', icon: FileText, shortcut: '.md', onClick: () => handleExport('markdown'), }, { id: 'export-json', label: 'Export as JSON', icon: Braces, shortcut: '.json', onClick: () => handleExport('json'), }, { id: 'export-txt', label: 'Export as Plain Text', icon: Type, shortcut: '.txt', onClick: () => handleExport('plaintext'), }, { id: 'analyze', label: 'Analyze Session', icon: Activity, onClick: () => { if (activeTabId) openSessionReport(activeTabId); setIsOpen(false); }, }, ] : []; const renderItem = (item: MenuItem): React.JSX.Element => ( ); const separator = (
); return (
{/* Trigger button */} More actions {/* Dropdown menu */} {isOpen && (
{topItems.map(renderItem)} {sessionItems.length > 0 && ( <> {separator} {sessionItems.map(renderItem)} )}
)}
); };