| 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 |