Files
Agent-System-Frontend/src/components/TopBar.tsx
T
weichen 411a79d9e2 Deliver a conversation-first progressive workbench with safe live state
Why: make material, background work, confirmations and files accessible to nontechnical users across desktop and mobile.

Evidence: Dev SELF receipts for Frontend tsc, 190 tests, knip baseline, build, nine fixture browser cases and two real HTTP/browser smoke cases using MockRunner.

Impact: existing API and permissions remain authoritative; stale events/queries and duplicate effects are guarded, while one file preview and reading state are preserved.

Refs: work-companion-ui design r0.2 and plan r1 U2-U5; UI01-UI13.
2026-10-12 16:05:13 +00:00

53 lines
2.7 KiB
TypeScript

// Project identity lives in context; the top bar keeps work/files reachable when panes close.
import { BrandMark, Icon, pendingCount } from '@ui'
import { store, useStore } from '../store'
import type { WorkbenchPane } from '../workbenchLayout'
interface TopBarProps {
compact?: boolean
activePane?: WorkbenchPane
showReturn?: boolean
onContext?: () => void
onConversation?: () => void
onWork?: () => void
onFiles?: () => void
}
export function TopBar({ compact = false, activePane = 'conversation', showReturn = false,
onContext = store.toggleLeft, onConversation,
onWork = () => { store.openInbox(); store.setUI({ inboxFilter: 'all', previewExpanded: false }) }, onFiles = store.openFiles,
}: TopBarProps = {}) {
const pending = useStore(s => pendingCount(s.cards) + pendingCount(s.projectCards))
const ui = useStore(s => s.ui)
const returnVisible = showReturn || compact && activePane !== 'conversation'
return (
<header className={`topbar companion-topbar${compact ? ' compact' : ''}`}>
<div className="topbar-identity"><BrandMark size={22} /><span className="topbar-name">工作助手</span></div>
<span className="topbar-spacer" />
{returnVisible && <button className="shell-return" aria-label="返回对话" onClick={onConversation}>
<Icon name="chevron-right" className="shell-back-icon" size={14} /><span>返回对话</span>
</button>}
<nav className="shell-navigation" aria-label="工作台导航">
{compact && <>
<button className="shell-nav-button" aria-label="打开上下文" aria-pressed={activePane === 'context'} onClick={onContext}>
<Icon name="panel-left" size={15} /><span>上下文</span>
</button>
<button className="shell-nav-button" aria-label="打开对话" aria-pressed={activePane === 'conversation'} onClick={onConversation}>
<Icon name="message" size={15} /><span>对话</span>
</button>
</>}
<button className="shell-nav-button" aria-label="打开工作"
aria-pressed={compact ? activePane === 'work' : !ui.rightCollapsed && ui.rightTab === 'work'}
title={pending > 0 ? `${pending} 项待处理` : '工作'} onClick={onWork}>
<Icon name="inbox" size={15} /><span>工作</span>
{pending > 0 && <span className="shell-pending" data-testid="topbar-pending">{pending > 99 ? '99+' : pending}</span>}
</button>
<button className="shell-nav-button" aria-label="打开文件"
aria-pressed={compact ? activePane === 'files' : !ui.rightCollapsed && ui.rightTab === 'files'} onClick={onFiles}>
<Icon name="folder" size={15} /><span>文件</span>
</button>
</nav>
</header>
)
}