1import { ArrowLeftIcon } from "lucide-react";
2import { memo, useCallback } from "react";
3import { useLocation } from "@tanstack/react-router";
4
5import { useEnvironmentIdentificationMode } from "../../hooks/useSettings";
6import { cn } from "../../lib/utils";
7import {
8 resolveEnvironmentIdentificationPillLabel,
9 useEnvironmentStageLabel,
10} from "../SidebarStageBackdrop";
11import { Badge } from "../ui/badge";
12import {
13 SidebarFooter,
14 SidebarMenu,
15 SidebarMenuButton,
16 SidebarMenuItem,
17 SidebarTrigger,
18 useSidebar,
19} from "../ui/sidebar";
20import { isSidebarUtilityPage, useNavigateToMainApp } from "./mainAppLocation";
21import { SidebarThreadUndoNotice } from "./SidebarThreadUndoNotice";
22import { NewWorkWorkspaceButton } from "./NewWorkWorkspaceButton";
23import { AppMenu } from "./AppMenu";
24import { SidebarMergebotButton } from "./SidebarMergebotButton";
25import { SidebarUsageButton } from "./SidebarUsageButton";
26import { SidebarDeploymentsButton } from "./SidebarDeploymentsButton";
27
28export const SidebarChromeHeader = memo(function SidebarChromeHeader({
29 isElectron,
30}: {
31 isElectron: boolean;
32}) {
33 const stageLabel = useEnvironmentStageLabel();
34 const environmentIdentificationMode = useEnvironmentIdentificationMode();
35 const pillLabel =
36 environmentIdentificationMode === "pill"
37 ? resolveEnvironmentIdentificationPillLabel(stageLabel)
38 : null;
39
40 return (
41 // The titlebar row, not a padded SidebarHeader: it aligns to the window controls.
42 // Its height already spaces the first group below, so it absorbs that group's top inset.
43 <div
44 className={cn(
45 "relative -mb-(--sidebar-content-inset) flex h-[var(--workspace-topbar-height)] shrink-0 flex-row items-center pr-(--sidebar-content-inset)",
46 isElectron && "drag-region",
47 )}
48 >
49 {/* Match the fixed control's top inset inside the sheet's safe area. */}
50 <div className="relative top-[calc(var(--workspace-controls-top)-env(safe-area-inset-top))] z-10 flex min-w-0 flex-1 items-center gap-2">
51 {/* Match the fixed control's inset and border compensation. */}
52 <div className="ml-[calc(var(--workspace-controls-left)+1px-env(safe-area-inset-left))] flex shrink-0 items-center md:hidden">
53 <div className="flex items-center [-webkit-app-region:no-drag]">
54 <AppMenu />
55 </div>
56 <SidebarTrigger aria-label="Toggle main sidebar" />
57 </div>
58 {pillLabel ? (
59 <div className="ml-[var(--workspace-titlebar-content-left)] max-md:ml-0 flex h-7 min-w-0 items-center">
60 <Badge data-environment-identification="pill" size="sm" variant="secondary">
61 {pillLabel}
62 </Badge>
63 </div>
64 ) : null}
65 <div className="ml-auto flex shrink-0 items-center [-webkit-app-region:no-drag]">
66 <NewWorkWorkspaceButton />
67 </div>
68 </div>
69 </div>
70 );
71});
72
73export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() {
74 const navigateToMainApp = useNavigateToMainApp();
75 const { isMobile, setOpenMobile } = useSidebar();
76 const isOnUtilityPage = useLocation({
77 select: (location) => isSidebarUtilityPage(location.pathname),
78 });
79 const closeMobileSidebar = useCallback(() => {
80 if (isMobile) {
81 setOpenMobile(false);
82 }
83 }, [isMobile, setOpenMobile]);
84 const handleBackClick = useCallback(() => {
85 closeMobileSidebar();
86 void navigateToMainApp();
87 }, [closeMobileSidebar, navigateToMainApp]);
88
89 return (
90 <SidebarMenu className="flex-row items-center">
91 {isOnUtilityPage ? (
92 <SidebarMenuItem className="min-w-0 flex-1">
93 <SidebarMenuButton onClick={handleBackClick}>
94 <ArrowLeftIcon />
95 <span>Back</span>
96 </SidebarMenuButton>
97 </SidebarMenuItem>
98 ) : (
99 <SidebarMergebotButton />
100 )}
101 </SidebarMenu>
102 );
103});
104
105export const SidebarChromeFooter = memo(function SidebarChromeFooter() {
106 return (
107 <SidebarFooter>
108 <SidebarThreadUndoNotice />
109 <div className="flex items-start gap-1">
110 <div className="min-w-0 flex-1">
111 <SidebarUsageButton />
112 </div>
113 <div className="shrink-0 pt-1">
114 <SidebarMenu>
115 <SidebarDeploymentsButton />
116 </SidebarMenu>
117 </div>
118 </div>
119 <SidebarUtilityMenu />
120 </SidebarFooter>
121 );
122});