返回 DeepSeek-Reasonix
TopicbarRegion.tsx
根目录 / desktop / frontend / src / app-shell / TopicbarRegion.tsx
1 import type { ReactNode } from "react";
2 import { PanelLeft } from "lucide-react";
3 import logoSymbol from "../assets/logo-symbol.svg";
4 import { Tooltip } from "../components/Tooltip";
5 import { WorktreeBadge } from "../components/WorktreeBadge";
6
7 export type TopicbarView = {
8 automationReturn: boolean; automationReturnLabel: string;
9 chromeHidden: boolean;
10 /** Windows leads the bar with the app mark; macOS leads with the traffic lights. */
11 brand: boolean;
12 sidebar: { title: string; blocked: boolean; pressed: boolean; collapsed: boolean };
13 title: {
14 text: string; hover: string; renameLabel: string; editing: boolean;
15 draft: string; editSize?: number; canRename: boolean; workspaceLabel?: string;
16 };
17 subtitle: {
18 visible: boolean; title: string; worktreeTabId?: string;
19 mergeLabel: string; mergeTooltip: string; sourcePlatform?: string; sourceLabel?: string;
20 };
21 };
22 type Commands = {
23 openAutomation(): void;
24 toggleSidebar(): void;
25 setTitleDraft(value: string): void;
26 commitRename(): void | Promise<void>;
27 cancelRename(): void;
28 startRename(): void;
29 openWorktree(tabId: string): void;
30 };
31
32 /** Presentation only: consume display values separately from stable commands. */
33 export function TopicbarRegion({ view, commands, children }: {
34 view: TopicbarView; commands: Commands; children: ReactNode;
35 }) {
36 const { sidebar, title, subtitle } = view;
37 return <header className="topicbar">
38 {view.brand && <div className="topicbar__brand">
39 <img src={logoSymbol} alt="Reasonix" className="topicbar__brand-logo" draggable={false} />
40 </div>}
41 {view.automationReturn && <button className="btn btn--small" type="button" onClick={event => {
42 event.currentTarget.focus({ preventScroll: true });
43 commands.openAutomation();
44 }}>{view.automationReturnLabel}</button>}
45 {view.chromeHidden && <Tooltip label={sidebar.title}>
46 <button className={[
47 "topicbar__chrome-btn", sidebar.blocked ? "topicbar__chrome-btn--blocked" : "",
48 sidebar.pressed ? "topicbar__chrome-btn--pressed" : "",
49 ].filter(Boolean).join(" ")} type="button"
50 onClick={sidebar.blocked ? undefined : commands.toggleSidebar}
51 aria-label={sidebar.title} aria-pressed={!sidebar.collapsed} aria-disabled={sidebar.blocked}>
52 <PanelLeft size={15} />
53 </button>
54 </Tooltip>}
55 <div className="topicbar__identity">
56 <div className="topicbar__title-row">
57 {title.editing ? <div className="topicbar__title-edit">
58 <input autoFocus className="topicbar__title-input" aria-label={title.renameLabel}
59 size={title.editSize} value={title.draft}
60 onChange={event => commands.setTitleDraft(event.target.value)}
61 onFocus={event => event.currentTarget.select()}
62 onKeyDown={event => {
63 if (event.key === "Enter") { event.preventDefault(); void commands.commitRename(); }
64 if (event.key === "Escape") { event.preventDefault(); commands.cancelRename(); }
65 }} onBlur={() => void commands.commitRename()} />
66 </div> : title.canRename ? <h1 title={title.hover}>
67 <button className="topicbar__title-button" type="button" onClick={commands.startRename} aria-label={title.renameLabel}>
68 {title.text}
69 </button>
70 </h1> : <h1 title={title.hover}>{title.text}</h1>}
71 {title.workspaceLabel && <span className="topicbar__workspace-label" title={title.workspaceLabel}>{title.workspaceLabel}</span>}
72 </div>
73 {subtitle.visible && <div className="topicbar__subtitle" title={subtitle.title}>
74 {subtitle.worktreeTabId && <WorktreeBadge size={11} />}
75 {subtitle.worktreeTabId && <button type="button" className="topicbar__worktree-btn"
76 onClick={() => commands.openWorktree(subtitle.worktreeTabId!)} title={subtitle.mergeTooltip}>
77 <span>{subtitle.mergeLabel}</span>
78 </button>}
79 {subtitle.sourcePlatform && <span className={`topicbar__source-chip topicbar__source-chip--${subtitle.sourcePlatform}`}>{subtitle.sourceLabel}</span>}
80 </div>}
81 </div>
82 <div className="topicbar__spacer" />
83 {children}
84 </header>;
85 }
86
86 lines Plain Text