| 1 | import { PanelLeft, PanelRight, Search } from "lucide-react"; |
| 2 | import { TabBar } from "./TabBar"; |
| 3 | import type { TabMeta } from "../lib/types"; |
| 4 | import { useT } from "../lib/i18n"; |
| 5 | |
| 6 | type DesktopPlatform = "darwin" | "windows" | "linux"; |
| 7 | |
| 8 | interface AppChromeProps { |
| 9 | platform: DesktopPlatform; |
| 10 | browserPreviewChrome: boolean; |
| 11 | workbenchChrome?: boolean; |
| 12 | tabs: TabMeta[]; |
| 13 | activeTabId?: string; |
| 14 | revealActiveSignal: number; |
| 15 | commandCompact: boolean; |
| 16 | sidebarTogglePressed: boolean; |
| 17 | sidebarExpandBlocked: boolean; |
| 18 | sidebarCollapsed: boolean; |
| 19 | sidebarToggleTitle: string; |
| 20 | workspacePanelMaximized: boolean; |
| 21 | workspacePanelRenderable: boolean; |
| 22 | workspaceTogglePressed: boolean; |
| 23 | workspacePanelLabel: string; |
| 24 | onToggleSidebar: () => void; |
| 25 | onToggleWorkspacePanel: () => void; |
| 26 | onTabChange: (tabId: string) => void; |
| 27 | onTabClose: (tabId: string) => void; |
| 28 | onTabsClose: (tabIds: string[], nextActiveTabId?: string) => void; |
| 29 | onTabsReorder: (tabIds: string[]) => void; |
| 30 | onNewTab: () => void; |
| 31 | onOpenPalette: () => void; |
| 32 | } |
| 33 | |
| 34 | export function AppChrome({ |
| 35 | platform, |
| 36 | browserPreviewChrome, |
| 37 | workbenchChrome = false, |
| 38 | tabs, |
| 39 | activeTabId, |
| 40 | revealActiveSignal, |
| 41 | commandCompact, |
| 42 | sidebarTogglePressed, |
| 43 | sidebarExpandBlocked, |
| 44 | sidebarCollapsed, |
| 45 | sidebarToggleTitle, |
| 46 | workspacePanelMaximized, |
| 47 | workspacePanelRenderable, |
| 48 | workspaceTogglePressed, |
| 49 | workspacePanelLabel, |
| 50 | onToggleSidebar, |
| 51 | onToggleWorkspacePanel, |
| 52 | onTabChange, |
| 53 | onTabClose, |
| 54 | onTabsClose, |
| 55 | onTabsReorder, |
| 56 | onNewTab, |
| 57 | onOpenPalette, |
| 58 | }: AppChromeProps) { |
| 59 | const t = useT(); |
| 60 | const darwinChrome = platform === "darwin"; |
| 61 | const titlebarDragRail = darwinChrome || platform === "windows"; |
| 62 | const chromeClassName = [ |
| 63 | "app-chrome", |
| 64 | "app-chrome--tabs", |
| 65 | darwinChrome ? "app-chrome--darwin-tabs" : "app-chrome--native-tabs", |
| 66 | workbenchChrome ? "app-chrome--workbench" : "", |
| 67 | !darwinChrome ? "app-chrome--identityless" : "", |
| 68 | `app-chrome--platform-${platform}`, |
| 69 | ].filter(Boolean).join(" "); |
| 70 | const tabBar = ( |
| 71 | <TabBar |
| 72 | tabs={tabs} |
| 73 | activeTabId={activeTabId} |
| 74 | revealActiveSignal={revealActiveSignal} |
| 75 | onTabChange={onTabChange} |
| 76 | onTabClose={onTabClose} |
| 77 | onTabsClose={onTabsClose} |
| 78 | onTabsReorder={onTabsReorder} |
| 79 | onNewTab={onNewTab} |
| 80 | onOpenPalette={undefined} |
| 81 | commandCompact={commandCompact} |
| 82 | /> |
| 83 | ); |
| 84 | |
| 85 | return ( |
| 86 | <header className={chromeClassName}> |
| 87 | {browserPreviewChrome && darwinChrome && ( |
| 88 | <div className="app-chrome__traffic" aria-hidden="true"> |
| 89 | <span /> |
| 90 | <span /> |
| 91 | <span /> |
| 92 | </div> |
| 93 | )} |
| 94 | {titlebarDragRail && <span className="app-chrome__drag-rail" aria-hidden="true" />} |
| 95 | <button |
| 96 | className={[ |
| 97 | "app-chrome__panel-toggle", |
| 98 | "app-chrome__panel-toggle--left", |
| 99 | sidebarTogglePressed ? "app-chrome__panel-toggle--pressed" : "", |
| 100 | sidebarExpandBlocked ? "app-chrome__panel-toggle--blocked" : "", |
| 101 | ].filter(Boolean).join(" ")} |
| 102 | type="button" |
| 103 | onClick={sidebarExpandBlocked ? undefined : onToggleSidebar} |
| 104 | aria-label={sidebarToggleTitle} |
| 105 | aria-pressed={!sidebarCollapsed} |
| 106 | aria-disabled={sidebarExpandBlocked} |
| 107 | > |
| 108 | <PanelLeft size={16} /> |
| 109 | </button> |
| 110 | {workbenchChrome && ( |
| 111 | <button |
| 112 | className="app-chrome__workbench-search" |
| 113 | type="button" |
| 114 | onClick={onOpenPalette} |
| 115 | aria-label={t("palette.placeholder")} |
| 116 | > |
| 117 | <Search size={18} /> |
| 118 | </button> |
| 119 | )} |
| 120 | |
| 121 | {workbenchChrome ? ( |
| 122 | <span className="app-chrome__spacer" aria-hidden="true" /> |
| 123 | ) : darwinChrome ? ( |
| 124 | <> |
| 125 | <div className="app-chrome__tab-strip app-chrome__tab-strip--darwin"> |
| 126 | {tabBar} |
| 127 | </div> |
| 128 | <div |
| 129 | className={[ |
| 130 | "app-chrome__tools", |
| 131 | "app-chrome__tools--fixed", |
| 132 | workspaceTogglePressed ? "app-chrome__tools--workspace-pressed" : "", |
| 133 | ].filter(Boolean).join(" ")} |
| 134 | aria-label={t("tabBar.commandSearch")} |
| 135 | > |
| 136 | <button |
| 137 | className={[ |
| 138 | "tabbar__command", |
| 139 | "tabbar__command--compact", |
| 140 | "app-chrome__command", |
| 141 | ].filter(Boolean).join(" ")} |
| 142 | type="button" |
| 143 | onClick={onOpenPalette} |
| 144 | aria-label={t("palette.placeholder")} |
| 145 | title={t("palette.placeholder")} |
| 146 | > |
| 147 | <Search size={16} className="tabbar__command-icon" /> |
| 148 | </button> |
| 149 | </div> |
| 150 | </> |
| 151 | ) : ( |
| 152 | <> |
| 153 | <div className="app-chrome__tab-strip app-chrome__tab-strip--native"> |
| 154 | {tabBar} |
| 155 | </div> |
| 156 | <div |
| 157 | className={[ |
| 158 | "app-chrome__tools", |
| 159 | workspaceTogglePressed ? "app-chrome__tools--workspace-pressed" : "", |
| 160 | ].filter(Boolean).join(" ")} |
| 161 | aria-label={t("tabBar.commandSearch")} |
| 162 | > |
| 163 | <button |
| 164 | className={[ |
| 165 | "tabbar__command", |
| 166 | "tabbar__command--compact", |
| 167 | "app-chrome__command", |
| 168 | ].filter(Boolean).join(" ")} |
| 169 | type="button" |
| 170 | onClick={onOpenPalette} |
| 171 | aria-label={t("palette.placeholder")} |
| 172 | title={t("palette.placeholder")} |
| 173 | > |
| 174 | <Search size={16} className="tabbar__command-icon" /> |
| 175 | </button> |
| 176 | </div> |
| 177 | </> |
| 178 | )} |
| 179 | |
| 180 | {!workspacePanelMaximized && ( |
| 181 | <button |
| 182 | className={[ |
| 183 | "app-chrome__panel-toggle", |
| 184 | "app-chrome__panel-toggle--right", |
| 185 | workspacePanelRenderable ? "app-chrome__panel-toggle--active" : "", |
| 186 | workspaceTogglePressed ? "app-chrome__panel-toggle--pressed" : "", |
| 187 | ].filter(Boolean).join(" ")} |
| 188 | type="button" |
| 189 | onClick={onToggleWorkspacePanel} |
| 190 | aria-label={workspacePanelLabel} |
| 191 | aria-pressed={workspacePanelRenderable} |
| 192 | > |
| 193 | <PanelRight size={16} /> |
| 194 | </button> |
| 195 | )} |
| 196 | </header> |
| 197 | ); |
| 198 | } |
| 199 |