| author | |
| committer | |
| log | 07f8027d9a542f72b12db927caceebb2c9c72aa7 |
| tree | 22131031c7f6700663e4e90ab289250527501dc3 |
| parent | cf251c3bd00ff0e0cc90a78b20c043cf58f330ad |
| signature | Signed by PGP key B5690EEEBB952194 |
12 files changed, 167 insertions(+), 194 deletions(-)
apps/web/src/components/ChatView.tsx+6-15| ... | ... | @@ -170,7 +170,6 @@ import { |
| 170 | 170 | WifiOffIcon, |
| 171 | 171 | } from "lucide-react"; |
| 172 | 172 | import { cn, randomHex } from "~/lib/utils"; |
| 173 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar"; | |
| 174 | 173 | import { stackedThreadToast, toastManager } from "./ui/toast"; |
| 175 | 174 | import { decodeProjectScriptKeybindingRule } from "~/lib/projectScriptKeybindings"; |
| 176 | 175 | import { type NewProjectScriptInput } from "./ProjectScriptsControl"; |
| ... | ... | @@ -259,6 +258,7 @@ import { ChatHeader } from "./chat/ChatHeader"; |
| 259 | 258 | import { PanelLayoutControls, RightPanelMaximizeControl } from "./chat/PanelLayoutControls"; |
| 260 | 259 | import { type ExpandedImagePreview } from "./chat/ExpandedImagePreview"; |
| 261 | 260 | import { NoActiveThreadState } from "./NoActiveThreadState"; |
| 261 | import { WorkspacePageHeader } from "./WorkspacePageHeader"; | |
| 262 | 262 | import { |
| 263 | 263 | resolveEffectiveEnvMode, |
| 264 | 264 | resolveLocalCheckoutBranchMismatch, |
| ... | ... | @@ -6240,20 +6240,11 @@ function ChatViewContent(props: ChatViewProps) { |
| 6240 | 6240 | data-chat-column-maximized-away={rightPanelMaximized ? "true" : "false"} |
| 6241 | 6241 | > |
| 6242 | 6242 | {/* Top bar */} |
| 6243 | <header | |
| 6243 | <WorkspacePageHeader | |
| 6244 | 6244 | data-chat-header |
| 6245 | className={cn( | |
| 6246 | "bg-background transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none", | |
| 6247 | isElectron | |
| 6248 | ? cn( | |
| 6249 | "drag-region relative flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 sm:px-5", | |
| 6250 | reserveTitleBarControlInset && | |
| 6251 | !inlineRightPanelOwnsTitleBar && | |
| 6252 | "wco:pr-[var(--workspace-native-controls-inset)]", | |
| 6253 | ) | |
| 6254 | : "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)]", | |
| 6255 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | |
| 6256 | )} | |
| 6245 | electron={isElectron} | |
| 6246 | reserveNativeControls={reserveTitleBarControlInset && !inlineRightPanelOwnsTitleBar} | |
| 6247 | className="relative bg-background" | |
| 6257 | 6248 | > |
| 6258 | 6249 | {!rightPanelOpen ? panelLayoutControls : null} |
| 6259 | 6250 | <ChatHeader |
| ... | ... | @@ -6284,7 +6275,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 6284 | 6275 | onUpdateProjectScript={updateProjectScript} |
| 6285 | 6276 | onDeleteProjectScript={deleteProjectScript} |
| 6286 | 6277 | /> |
| 6287 | </header> | |
| 6278 | </WorkspacePageHeader> | |
| 6288 | 6279 | |
| 6289 | 6280 | <ThreadErrorBanner |
| 6290 | 6281 | error={visibleThreadError} |
apps/web/src/components/NoActiveThreadState.tsx+4-15| ... | ... | @@ -1,26 +1,15 @@ |
| 1 | 1 | import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "./ui/empty"; |
| 2 | 2 | import { SidebarInset } from "./ui/sidebar"; |
| 3 | 3 | import { isElectron } from "../env"; |
| 4 | import { cn } from "~/lib/utils"; | |
| 5 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar"; | |
| 4 | import { WorkspacePageHeader } from "./WorkspacePageHeader"; | |
| 6 | 5 | |
| 7 | 6 | export function NoActiveThreadState() { |
| 8 | 7 | return ( |
| 9 | 8 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground"> |
| 10 | 9 | <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background"> |
| 11 | <header | |
| 12 | className={cn( | |
| 13 | "border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | |
| 14 | isElectron | |
| 15 | ? "drag-region flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center" | |
| 16 | : "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center", | |
| 17 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | |
| 18 | )} | |
| 19 | > | |
| 10 | <WorkspacePageHeader electron={isElectron} className="border-b border-border"> | |
| 20 | 11 | {isElectron ? ( |
| 21 | <span className="text-xs text-muted-foreground/50 wco:pr-[var(--workspace-native-controls-inset)]"> | |
| 22 | No active thread | |
| 23 | </span> | |
| 12 | <span className="text-xs text-muted-foreground/50">No active thread</span> | |
| 24 | 13 | ) : ( |
| 25 | 14 | <div className="flex items-center gap-2"> |
| 26 | 15 | <span className="text-sm font-medium text-foreground md:text-muted-foreground/60"> |
| ... | ... | @@ -28,7 +17,7 @@ export function NoActiveThreadState() { |
| 28 | 17 | </span> |
| 29 | 18 | </div> |
| 30 | 19 | )} |
| 31 | </header> | |
| 20 | </WorkspacePageHeader> | |
| 32 | 21 | |
| 33 | 22 | <Empty className="flex-1"> |
| 34 | 23 | <div className="w-full max-w-lg px-8 py-12"> |
apps/web/src/components/WorkspacePageHeader.tsx created+28| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | import type { ComponentPropsWithoutRef } from "react"; | |
| 2 | ||
| 3 | import { cn } from "../lib/utils"; | |
| 4 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "../workspaceTitlebar"; | |
| 5 | ||
| 6 | /** Shared workspace top-bar geometry. */ | |
| 7 | export function WorkspacePageHeader({ | |
| 8 | electron = false, | |
| 9 | reserveNativeControls = electron, | |
| 10 | className, | |
| 11 | ...props | |
| 12 | }: ComponentPropsWithoutRef<"header"> & { | |
| 13 | readonly electron?: boolean; | |
| 14 | readonly reserveNativeControls?: boolean; | |
| 15 | }) { | |
| 16 | return ( | |
| 17 | <header | |
| 18 | className={cn( | |
| 19 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center gap-3 pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)]", | |
| 20 | electron && "drag-region", | |
| 21 | reserveNativeControls && "wco:pr-[var(--workspace-native-controls-inset)]", | |
| 22 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | |
| 23 | className, | |
| 24 | )} | |
| 25 | {...props} | |
| 26 | /> | |
| 27 | ); | |
| 28 | } |
apps/web/src/components/settings/DiagnosticsSettings.tsx+1-1| ... | ... | @@ -992,7 +992,7 @@ export function DiagnosticsSettingsPanel() { |
| 992 | 992 | : false; |
| 993 | 993 | |
| 994 | 994 | return ( |
| 995 | <SettingsPageContainer className="max-w-6xl gap-10"> | |
| 995 | <SettingsPageContainer width="expanded" className="gap-10"> | |
| 996 | 996 | <ResourceTelemetryDiagnostics /> |
| 997 | 997 | |
| 998 | 998 | <SettingsSection |
apps/web/src/components/settings/KeybindingsSettings.tsx+1-1| ... | ... | @@ -1195,7 +1195,7 @@ export function KeybindingsSettingsPanel() { |
| 1195 | 1195 | ); |
| 1196 | 1196 | |
| 1197 | 1197 | return ( |
| 1198 | <SettingsPageContainer className="max-w-5xl"> | |
| 1198 | <SettingsPageContainer width="wide"> | |
| 1199 | 1199 | <SettingsSection |
| 1200 | 1200 | {...searchableSetting("keybindings")} |
| 1201 | 1201 | headerAction={ |
apps/web/src/components/settings/ProjectSettingsPanel.tsx+4-22| ... | ... | @@ -81,8 +81,6 @@ import { |
| 81 | 81 | type NewProjectScriptInput, |
| 82 | 82 | type ProjectScriptEditorRequest, |
| 83 | 83 | } from "../projectScriptEditor"; |
| 84 | import { cn } from "../../lib/utils"; | |
| 85 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "../../workspaceTitlebar"; | |
| 86 | 84 | import { Button } from "../ui/button"; |
| 87 | 85 | import { Input } from "../ui/input"; |
| 88 | 86 | import { |
| ... | ... | @@ -103,6 +101,7 @@ import { |
| 103 | 101 | WorkspaceBreadcrumbItem, |
| 104 | 102 | WorkspaceBreadcrumbSeparator, |
| 105 | 103 | } from "../WorkspaceBreadcrumb"; |
| 104 | import { WorkspacePageHeader } from "../WorkspacePageHeader"; | |
| 106 | 105 | import { |
| 107 | 106 | SettingResetButton, |
| 108 | 107 | SettingsPageContainer, |
| ... | ... | @@ -175,26 +174,9 @@ export function ProjectSettingsPage({ projectKey }: { projectKey: string }) { |
| 175 | 174 | return ( |
| 176 | 175 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground isolate"> |
| 177 | 176 | <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background text-foreground"> |
| 178 | {!isElectron && ( | |
| 179 | <header | |
| 180 | className={cn( | |
| 181 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | |
| 182 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | |
| 183 | )} | |
| 184 | > | |
| 185 | <ProjectSettingsBreadcrumb projectKey={projectKey} /> | |
| 186 | </header> | |
| 187 | )} | |
| 188 | {isElectron && ( | |
| 189 | <div | |
| 190 | className={cn( | |
| 191 | "drag-region flex h-[52px] shrink-0 items-center px-5 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none wco:h-[env(titlebar-area-height)] wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]", | |
| 192 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | |
| 193 | )} | |
| 194 | > | |
| 195 | <ProjectSettingsBreadcrumb projectKey={projectKey} /> | |
| 196 | </div> | |
| 197 | )} | |
| 177 | <WorkspacePageHeader electron={isElectron}> | |
| 178 | <ProjectSettingsBreadcrumb projectKey={projectKey} /> | |
| 179 | </WorkspacePageHeader> | |
| 198 | 180 | <ProjectSettingsPanel projectKey={projectKey} /> |
| 199 | 181 | </div> |
| 200 | 182 | </SidebarInset> |
apps/web/src/components/settings/SettingsSidebarNav.tsx+5-22| ... | ... | @@ -9,7 +9,6 @@ import { |
| 9 | 9 | } from "react"; |
| 10 | 10 | import { |
| 11 | 11 | ArchiveIcon, |
| 12 | ArrowLeftIcon, | |
| 13 | 12 | BlocksIcon, |
| 14 | 13 | BotIcon, |
| 15 | 14 | GitBranchIcon, |
| ... | ... | @@ -20,7 +19,7 @@ import { |
| 20 | 19 | Settings2Icon, |
| 21 | 20 | XIcon, |
| 22 | 21 | } from "lucide-react"; |
| 23 | import { useCanGoBack, useLocation, useNavigate } from "@tanstack/react-router"; | |
| 22 | import { useLocation, useNavigate } from "@tanstack/react-router"; | |
| 24 | 23 | |
| 25 | 24 | import { Button } from "../ui/button"; |
| 26 | 25 | import { Input } from "../ui/input"; |
| ... | ... | @@ -35,6 +34,7 @@ import { |
| 35 | 34 | useSidebar, |
| 36 | 35 | } from "../ui/sidebar"; |
| 37 | 36 | import { T3ConnectSidebarAvatar, T3ConnectSidebarSignIn } from "../clerk/T3ConnectSidebarSignIn"; |
| 37 | import { SidebarUtilityMenu } from "../sidebar/SidebarChrome"; | |
| 38 | 38 | import { scrollToSettingsTarget } from "./settingsLayout"; |
| 39 | 39 | import { |
| 40 | 40 | searchSettings, |
| ... | ... | @@ -74,7 +74,6 @@ function SettingsSectionIcon({ to }: { to: SettingsPath }) { |
| 74 | 74 | export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 75 | 75 | const navigate = useNavigate(); |
| 76 | 76 | const currentHash = useLocation({ select: (location) => location.hash }); |
| 77 | const canGoBack = useCanGoBack(); | |
| 78 | 77 | const { isMobile, setOpenMobile, open, setOpen } = useSidebar(); |
| 79 | 78 | const searchInputRef = useRef<HTMLInputElement>(null); |
| 80 | 79 | const [query, setQuery] = useState(""); |
| ... | ... | @@ -178,17 +177,6 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 178 | 177 | }, |
| 179 | 178 | [activeResultIndex, clearSearch, handleSearchResultClick, isSearching, results], |
| 180 | 179 | ); |
| 181 | const handleBackClick = useCallback(() => { | |
| 182 | if (isMobile) { | |
| 183 | setOpenMobile(false); | |
| 184 | } | |
| 185 | if (canGoBack) { | |
| 186 | window.history.back(); | |
| 187 | return; | |
| 188 | } | |
| 189 | void navigate({ to: "/" }); | |
| 190 | }, [canGoBack, isMobile, navigate, setOpenMobile]); | |
| 191 | ||
| 192 | 180 | return ( |
| 193 | 181 | <> |
| 194 | 182 | <SidebarContent className="overflow-x-hidden"> |
| ... | ... | @@ -298,14 +286,9 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 298 | 286 | <SidebarFooter className="p-[var(--sidebar-content-inset)]"> |
| 299 | 287 | <T3ConnectSidebarSignIn /> |
| 300 | 288 | <div className="flex items-center gap-1"> |
| 301 | <SidebarMenu className="min-w-0 flex-1"> | |
| 302 | <SidebarMenuItem> | |
| 303 | <SidebarMenuButton onClick={handleBackClick}> | |
| 304 | <ArrowLeftIcon /> | |
| 305 | <span>Back</span> | |
| 306 | </SidebarMenuButton> | |
| 307 | </SidebarMenuItem> | |
| 308 | </SidebarMenu> | |
| 289 | <div className="min-w-0 flex-1"> | |
| 290 | <SidebarUtilityMenu /> | |
| 291 | </div> | |
| 309 | 292 | <T3ConnectSidebarAvatar /> |
| 310 | 293 | </div> |
| 311 | 294 | </SidebarFooter> |
apps/web/src/components/settings/settingsLayout.tsx+18-2| ... | ... | @@ -15,6 +15,14 @@ import { cn } from "../../lib/utils"; |
| 15 | 15 | import { Button } from "../ui/button"; |
| 16 | 16 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 17 | 17 | |
| 18 | type SettingsPageWidth = "readable" | "wide" | "expanded"; | |
| 19 | ||
| 20 | const SETTINGS_PAGE_WIDTH_CLASS: Record<SettingsPageWidth, string> = { | |
| 21 | readable: "max-w-4xl", | |
| 22 | wide: "max-w-5xl", | |
| 23 | expanded: "max-w-6xl", | |
| 24 | }; | |
| 25 | ||
| 18 | 26 | interface SettingsSearchTargetContextValue { |
| 19 | 27 | readonly targetId: string | null; |
| 20 | 28 | readonly onTargetHandled: () => void; |
| ... | ... | @@ -233,9 +241,11 @@ export function SettingResetButton({ |
| 233 | 241 | export function SettingsPageContainer({ |
| 234 | 242 | children, |
| 235 | 243 | className, |
| 244 | width = "readable", | |
| 236 | 245 | }: { |
| 237 | 246 | children: ReactNode; |
| 238 | 247 | className?: string; |
| 248 | width?: SettingsPageWidth; | |
| 239 | 249 | }) { |
| 240 | 250 | const navigate = useNavigate(); |
| 241 | 251 | const hash = useLocation({ select: (location) => location.hash }); |
| ... | ... | @@ -247,10 +257,16 @@ export function SettingsPageContainer({ |
| 247 | 257 | return ( |
| 248 | 258 | <SettingsSearchTargetProvider targetId={targetId} onTargetHandled={clearTargetHash}> |
| 249 | 259 | <div |
| 250 | className="topbar-scroll-fade scrollbar-gutter-both flex-1 overflow-y-auto px-4 pt-10 pb-7 sm:px-8 sm:pt-12 sm:pb-10" | |
| 260 | className="topbar-scroll-fade scrollbar-gutter-both flex-1 overflow-y-auto [--topbar-scroll-fade-height:1.5rem] sm:[--topbar-scroll-fade-height:1.5rem]" | |
| 251 | 261 | data-settings-page-scroll |
| 252 | 262 | > |
| 253 | <div className={cn("mx-auto flex w-full max-w-4xl flex-col gap-12", className)}> | |
| 263 | <div | |
| 264 | className={cn( | |
| 265 | "mx-auto flex w-full flex-col gap-12 px-5 pt-6 pb-12 sm:px-6", | |
| 266 | SETTINGS_PAGE_WIDTH_CLASS[width], | |
| 267 | className, | |
| 268 | )} | |
| 269 | > | |
| 254 | 270 | {children} |
| 255 | 271 | </div> |
| 256 | 272 | </div> |
apps/web/src/components/sidebar/SidebarChrome.tsx+78-68| ... | ... | @@ -4,8 +4,9 @@ import { |
| 4 | 4 | GitPullRequestIcon, |
| 5 | 5 | SettingsIcon, |
| 6 | 6 | } from "lucide-react"; |
| 7 | import type { ReactNode } from "react"; | |
| 7 | 8 | import { memo, useCallback } from "react"; |
| 8 | import { Link, useLocation, useNavigate } from "@tanstack/react-router"; | |
| 9 | import { Link, useCanGoBack, useLocation, useNavigate } from "@tanstack/react-router"; | |
| 9 | 10 | |
| 10 | 11 | import { useEnvironmentIdentificationMode } from "../../hooks/useSettings"; |
| 11 | 12 | import { cn } from "../../lib/utils"; |
| ... | ... | @@ -117,16 +118,44 @@ function T3Wordmark() { |
| 117 | 118 | ); |
| 118 | 119 | } |
| 119 | 120 | |
| 120 | export const SidebarChromeFooter = memo(function SidebarChromeFooter() { | |
| 121 | function SidebarUtilityItem({ | |
| 122 | icon, | |
| 123 | label, | |
| 124 | onClick, | |
| 125 | }: { | |
| 126 | icon: ReactNode; | |
| 127 | label: string; | |
| 128 | onClick: () => void; | |
| 129 | }) { | |
| 130 | return ( | |
| 131 | <SidebarMenuItem className="shrink-0"> | |
| 132 | <Tooltip> | |
| 133 | <TooltipTrigger | |
| 134 | render={ | |
| 135 | <SidebarMenuButton aria-label={label} onClick={onClick} size="icon"> | |
| 136 | {icon} | |
| 137 | </SidebarMenuButton> | |
| 138 | } | |
| 139 | /> | |
| 140 | <TooltipPopup side="top">{label}</TooltipPopup> | |
| 141 | </Tooltip> | |
| 142 | </SidebarMenuItem> | |
| 143 | ); | |
| 144 | } | |
| 145 | ||
| 146 | export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { | |
| 121 | 147 | const navigate = useNavigate(); |
| 148 | const canGoBack = useCanGoBack(); | |
| 122 | 149 | const { isMobile, setOpenMobile } = useSidebar(); |
| 123 | 150 | const currentFooterPage = useLocation({ |
| 124 | 151 | select: (location) => |
| 125 | location.pathname === "/usage" | |
| 126 | ? "usage" | |
| 127 | : location.pathname === "/pull-requests" | |
| 128 | ? "pull-requests" | |
| 129 | : null, | |
| 152 | /^\/settings(?:\/|$)/.test(location.pathname) | |
| 153 | ? "settings" | |
| 154 | : location.pathname === "/usage" | |
| 155 | ? "usage" | |
| 156 | : location.pathname === "/pull-requests" | |
| 157 | ? "pull-requests" | |
| 158 | : null, | |
| 130 | 159 | }); |
| 131 | 160 | const { environments } = useEnvironments(); |
| 132 | 161 | // The page reads every connected server, so one of them offering pull requests is enough for |
| ... | ... | @@ -157,73 +186,54 @@ export const SidebarChromeFooter = memo(function SidebarChromeFooter() { |
| 157 | 186 | |
| 158 | 187 | const handleBackClick = useCallback(() => { |
| 159 | 188 | closeMobileSidebar(); |
| 189 | if (canGoBack) { | |
| 190 | window.history.back(); | |
| 191 | return; | |
| 192 | } | |
| 160 | 193 | void navigate({ to: "/" }); |
| 161 | }, [closeMobileSidebar, navigate]); | |
| 194 | }, [canGoBack, closeMobileSidebar, navigate]); | |
| 162 | 195 | |
| 196 | return ( | |
| 197 | <SidebarMenu className="flex-row items-center"> | |
| 198 | {currentFooterPage ? ( | |
| 199 | <SidebarMenuItem className="min-w-0 flex-1"> | |
| 200 | <SidebarMenuButton onClick={handleBackClick}> | |
| 201 | <ArrowLeftIcon /> | |
| 202 | <span>Back</span> | |
| 203 | </SidebarMenuButton> | |
| 204 | </SidebarMenuItem> | |
| 205 | ) : ( | |
| 206 | <> | |
| 207 | <SidebarUtilityItem | |
| 208 | icon={<SettingsIcon />} | |
| 209 | label="Settings" | |
| 210 | onClick={handleSettingsClick} | |
| 211 | /> | |
| 212 | {pullRequestsSupported ? ( | |
| 213 | <SidebarUtilityItem | |
| 214 | icon={<GitPullRequestIcon />} | |
| 215 | label="Pull Requests" | |
| 216 | onClick={handlePullRequestsClick} | |
| 217 | /> | |
| 218 | ) : null} | |
| 219 | <SidebarUtilityItem | |
| 220 | icon={<ChartNoAxesColumnIcon />} | |
| 221 | label="Usage" | |
| 222 | onClick={handleUsageClick} | |
| 223 | /> | |
| 224 | </> | |
| 225 | )} | |
| 226 | <SidebarUpdatePill /> | |
| 227 | </SidebarMenu> | |
| 228 | ); | |
| 229 | }); | |
| 230 | ||
| 231 | export const SidebarChromeFooter = memo(function SidebarChromeFooter() { | |
| 163 | 232 | return ( |
| 164 | 233 | <SidebarFooter className="p-[var(--sidebar-content-inset)]"> |
| 165 | 234 | <SidebarProviderUpdatePill /> |
| 166 | 235 | <SidebarUpdateArchitectureWarning /> |
| 167 | <SidebarMenu className="flex-row items-center"> | |
| 168 | {currentFooterPage ? ( | |
| 169 | <SidebarMenuItem className="min-w-0 flex-1"> | |
| 170 | <SidebarMenuButton onClick={handleBackClick}> | |
| 171 | <ArrowLeftIcon /> | |
| 172 | <span>Back</span> | |
| 173 | </SidebarMenuButton> | |
| 174 | </SidebarMenuItem> | |
| 175 | ) : ( | |
| 176 | <> | |
| 177 | <SidebarMenuItem className="shrink-0"> | |
| 178 | <Tooltip> | |
| 179 | <TooltipTrigger | |
| 180 | render={ | |
| 181 | <SidebarMenuButton | |
| 182 | aria-label="Settings" | |
| 183 | onClick={handleSettingsClick} | |
| 184 | size="icon" | |
| 185 | > | |
| 186 | <SettingsIcon /> | |
| 187 | </SidebarMenuButton> | |
| 188 | } | |
| 189 | /> | |
| 190 | <TooltipPopup side="top">Settings</TooltipPopup> | |
| 191 | </Tooltip> | |
| 192 | </SidebarMenuItem> | |
| 193 | {pullRequestsSupported ? ( | |
| 194 | <SidebarMenuItem className="shrink-0"> | |
| 195 | <Tooltip> | |
| 196 | <TooltipTrigger | |
| 197 | render={ | |
| 198 | <SidebarMenuButton | |
| 199 | aria-label="Pull Requests" | |
| 200 | onClick={handlePullRequestsClick} | |
| 201 | size="icon" | |
| 202 | > | |
| 203 | <GitPullRequestIcon /> | |
| 204 | </SidebarMenuButton> | |
| 205 | } | |
| 206 | /> | |
| 207 | <TooltipPopup side="top">Pull Requests</TooltipPopup> | |
| 208 | </Tooltip> | |
| 209 | </SidebarMenuItem> | |
| 210 | ) : null} | |
| 211 | <SidebarMenuItem className="shrink-0"> | |
| 212 | <Tooltip> | |
| 213 | <TooltipTrigger | |
| 214 | render={ | |
| 215 | <SidebarMenuButton aria-label="Usage" onClick={handleUsageClick} size="icon"> | |
| 216 | <ChartNoAxesColumnIcon /> | |
| 217 | </SidebarMenuButton> | |
| 218 | } | |
| 219 | /> | |
| 220 | <TooltipPopup side="top">Usage</TooltipPopup> | |
| 221 | </Tooltip> | |
| 222 | </SidebarMenuItem> | |
| 223 | </> | |
| 224 | )} | |
| 225 | <SidebarUpdatePill /> | |
| 226 | </SidebarMenu> | |
| 236 | <SidebarUtilityMenu /> | |
| 227 | 237 | </SidebarFooter> |
| 228 | 238 | ); |
| 229 | 239 | }); |
apps/web/src/routes/-chatIndexTitlebar.test.ts+9-3| ... | ... | @@ -15,9 +15,15 @@ describe("hosted static onboarding header", () => { |
| 15 | 15 | |
| 16 | 16 | const onboardingHeader = routeSource.slice(onboardingStart, onboardingEnd); |
| 17 | 17 | |
| 18 | expect(onboardingHeader).toContain("h-[var(--workspace-topbar-height)]"); | |
| 19 | expect(onboardingHeader).toContain("min-h-[var(--workspace-topbar-height)]"); | |
| 20 | expect(onboardingHeader).toContain("COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS"); | |
| 18 | expect(onboardingHeader).toContain("<WorkspacePageHeader"); | |
| 21 | 19 | expect(onboardingHeader).not.toMatch(/(?:^|\s)(?:[\w-]+:)*py-/); |
| 20 | ||
| 21 | const headerSource = NodeFS.readFileSync( | |
| 22 | new URL("../components/WorkspacePageHeader.tsx", import.meta.url), | |
| 23 | "utf8", | |
| 24 | ); | |
| 25 | expect(headerSource).toContain("h-[var(--workspace-topbar-height)]"); | |
| 26 | expect(headerSource).toContain("min-h-[var(--workspace-topbar-height)]"); | |
| 27 | expect(headerSource).toContain("COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS"); | |
| 22 | 28 | }); |
| 23 | 29 | }); |
apps/web/src/routes/_chat.index.tsx+3-9| ... | ... | @@ -8,6 +8,7 @@ import { sortScopedProjectsForSidebar } from "../components/Sidebar.logic"; |
| 8 | 8 | import { Button } from "../components/ui/button"; |
| 9 | 9 | import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "../components/ui/empty"; |
| 10 | 10 | import { SidebarInset } from "../components/ui/sidebar"; |
| 11 | import { WorkspacePageHeader } from "../components/WorkspacePageHeader"; | |
| 11 | 12 | import { useNewThreadHandler } from "../hooks/useHandleNewThread"; |
| 12 | 13 | import { |
| 13 | 14 | useAllEnvironmentShellsBootstrapped, |
| ... | ... | @@ -17,8 +18,6 @@ import { |
| 17 | 18 | import { useEnvironments } from "../state/environments"; |
| 18 | 19 | import { APP_DISPLAY_NAME } from "~/branding"; |
| 19 | 20 | import { hasCloudPublicConfig } from "~/cloud/publicConfig"; |
| 20 | import { cn } from "~/lib/utils"; | |
| 21 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar"; | |
| 22 | 21 | |
| 23 | 22 | function ChatIndexRouteView() { |
| 24 | 23 | const { authGateState } = Route.useRouteContext(); |
| ... | ... | @@ -143,18 +142,13 @@ function HostedStaticOnboardingState() { |
| 143 | 142 | return ( |
| 144 | 143 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground"> |
| 145 | 144 | <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background"> |
| 146 | <header | |
| 147 | className={cn( | |
| 148 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | |
| 149 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | |
| 150 | )} | |
| 151 | > | |
| 145 | <WorkspacePageHeader className="border-b border-border"> | |
| 152 | 146 | <div className="flex items-center gap-2"> |
| 153 | 147 | <span className="text-sm font-medium text-foreground md:text-muted-foreground/60"> |
| 154 | 148 | {APP_DISPLAY_NAME} |
| 155 | 149 | </span> |
| 156 | 150 | </div> |
| 157 | </header> | |
| 151 | </WorkspacePageHeader> | |
| 158 | 152 | |
| 159 | 153 | <Empty className="flex-1"> |
| 160 | 154 | <div className="w-full max-w-xl rounded-3xl border border-border/55 bg-card/20 px-8 py-12 shadow-sm/5"> |
apps/web/src/routes/settings.tsx+10-36| ... | ... | @@ -13,9 +13,8 @@ import { useSettingsRestore } from "../components/settings/SettingsPanels"; |
| 13 | 13 | import { SettingsBreadcrumb } from "../components/settings/SettingsBreadcrumb"; |
| 14 | 14 | import { Button } from "../components/ui/button"; |
| 15 | 15 | import { SidebarInset } from "../components/ui/sidebar"; |
| 16 | import { WorkspacePageHeader } from "../components/WorkspacePageHeader"; | |
| 16 | 17 | import { isElectron } from "../env"; |
| 17 | import { cn } from "~/lib/utils"; | |
| 18 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar"; | |
| 19 | 18 | |
| 20 | 19 | function RestoreDefaultsButton({ onRestored }: { onRestored: () => void }) { |
| 21 | 20 | const { changedSettingLabels, restoreDefaults } = useSettingsRestore(onRestored); |
| ... | ... | @@ -72,41 +71,16 @@ function SettingsContentLayout() { |
| 72 | 71 | return ( |
| 73 | 72 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground isolate"> |
| 74 | 73 | <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background text-foreground"> |
| 75 | {!isElectron && ( | |
| 76 | <header | |
| 77 | className={cn( | |
| 78 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | |
| 79 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | |
| 80 | )} | |
| 81 | > | |
| 82 | <div className="flex w-full items-center gap-2"> | |
| 83 | <SettingsBreadcrumb pathname={location.pathname} /> | |
| 84 | {showRestoreDefaults ? ( | |
| 85 | <div className="ms-auto flex items-center gap-2"> | |
| 86 | <RestoreDefaultsButton onRestored={handleRestored} /> | |
| 87 | </div> | |
| 88 | ) : null} | |
| 89 | </div> | |
| 90 | </header> | |
| 91 | )} | |
| 92 | ||
| 93 | {isElectron && ( | |
| 94 | <div | |
| 95 | className={cn( | |
| 96 | "drag-region flex h-[52px] shrink-0 items-center px-5 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none wco:h-[env(titlebar-area-height)] wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]", | |
| 97 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | |
| 98 | )} | |
| 99 | > | |
| 100 | <div className="flex w-full items-center gap-2"> | |
| 101 | <SettingsBreadcrumb pathname={location.pathname} /> | |
| 102 | {showRestoreDefaults ? ( | |
| 103 | <div className="ms-auto flex items-center gap-2"> | |
| 104 | <RestoreDefaultsButton onRestored={handleRestored} /> | |
| 105 | </div> | |
| 106 | ) : null} | |
| 107 | </div> | |
| 74 | <WorkspacePageHeader electron={isElectron}> | |
| 75 | <div className="flex w-full items-center gap-3"> | |
| 76 | <SettingsBreadcrumb pathname={location.pathname} /> | |
| 77 | {showRestoreDefaults ? ( | |
| 78 | <div className="ms-auto flex items-center gap-2"> | |
| 79 | <RestoreDefaultsButton onRestored={handleRestored} /> | |
| 80 | </div> | |
| 81 | ) : null} | |
| 108 | 82 | </div> |
| 109 | )} | |
| 83 | </WorkspacePageHeader> | |
| 110 | 84 | |
| 111 | 85 | <div key={restoreSignal} className="min-h-0 flex flex-1 flex-col"> |
| 112 | 86 | <Outlet /> |