fix(site): improve mobile layout for settings and analytics (#23460)

This commit is contained in:
Danielle Maywood
2026-03-23 22:00:23 +00:00
committed by GitHub
parent 53350377b3
commit c344d7c00e
6 changed files with 117 additions and 42 deletions
@@ -36,7 +36,7 @@ const AgentAnalyticsPage: FC<AgentAnalyticsPageProps> = ({ now }) => {
return (
<>
<AgentPageHeader />
<AgentPageHeader mobileBack={{ to: "/agents", label: "Agents" }} />
<AgentAnalyticsPageView
summary={summaryQuery.data}
isLoading={summaryQuery.isLoading}
@@ -10,7 +10,11 @@ const AgentSettingsPage: FC = () => {
const isAgentsAdmin = permissions.editDeploymentConfig;
return (
<>
<AgentPageHeader />
<AgentPageHeader
mobileBack={
section ? { to: "/agents/settings", label: "Settings" } : undefined
}
/>
<AgentSettingsPageView
activeSection={section ?? "behavior"}
canManageChatModelConfigs={isAgentsAdmin}
+14 -3
View File
@@ -92,6 +92,14 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
const location = useLocation();
const sidebarView = sidebarViewFromPath(location.pathname);
// Mobile can't fit the sidebar nav and content side by side,
// so we show one or the other depending on the route depth.
const isSettingsIndex =
sidebarView.panel === "settings" && !sidebarView.section;
const isSettingsDetail =
sidebarView.panel === "settings" && Boolean(sidebarView.section);
const isAnalytics = sidebarView.panel === "analytics";
// The sidebar expects plain string error messages, but the outlet
// context now carries structured ChatDetailError objects.
const sidebarChatErrorReasons = Object.fromEntries(
@@ -121,7 +129,9 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
"md:h-full md:w-[320px] md:min-h-0 md:border-b-0",
agentId
? "hidden md:block shrink-0 h-[42dvh] min-h-[240px] border-b border-border-default"
: "order-2 md:order-none flex-1 min-h-0 border-t border-border-default md:flex-none md:border-t-0",
: isSettingsDetail || isAnalytics
? "hidden md:block shrink-0"
: "order-2 md:order-none flex-1 min-h-0 border-t border-border-default md:flex-none md:border-t-0",
isSidebarCollapsed && "md:hidden",
)}
>
@@ -150,11 +160,12 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
isAdmin={isAgentsAdmin}
/>
</div>
<div
className={cn(
"flex min-h-0 min-w-0 flex-1 flex-col bg-surface-primary",
"min-h-0 min-w-0 flex-1 flex-col bg-surface-primary",
isSettingsIndex ? "hidden md:flex" : "flex",
!agentId &&
!isSettingsDetail &&
sidebarView.panel === "chats" &&
"order-1 md:order-none flex-none md:flex-1",
)}
@@ -275,7 +275,7 @@ export const AgentDetailView: FC<AgentDetailViewProps> = ({
/>
</div>
</ScrollAnchoredContainer>
<div className="shrink-0 overflow-y-auto px-4 [scrollbar-gutter:stable] [scrollbar-width:thin]">
<div className="shrink-0 overflow-y-auto px-4 pb-4 md:pb-0 [scrollbar-gutter:stable] [scrollbar-width:thin]">
<AgentDetailInput
store={store}
compressionThreshold={compressionThreshold}
@@ -414,7 +414,7 @@ export const AgentDetailLoadingView: FC<AgentDetailLoadingViewProps> = ({
</div>
</div>
</div>
<div className="shrink-0 overflow-y-auto px-4 [scrollbar-gutter:stable] [scrollbar-width:thin]">
<div className="shrink-0 overflow-y-auto px-4 pb-4 md:pb-0 [scrollbar-gutter:stable] [scrollbar-width:thin]">
<AgentChatInput
onSend={() => {}}
initialValue=""
@@ -1,31 +1,56 @@
import { Button } from "components/Button/Button";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { CoderIcon } from "components/Icons/CoderIcon";
import { PanelLeftIcon } from "lucide-react";
import {
BarChart3Icon,
ChevronLeftIcon,
PanelLeftIcon,
SettingsIcon,
} from "lucide-react";
import { useDashboard } from "modules/dashboard/useDashboard";
import type { FC, ReactNode } from "react";
import { NavLink, useOutletContext } from "react-router";
import { Link, NavLink, useLocation, useOutletContext } from "react-router";
import { cn } from "utils/cn";
import type { AgentsOutletContext } from "../AgentsPageView";
import { sidebarViewFromPath } from "./Sidebar/AgentsSidebar";
interface AgentPageHeaderProps {
children?: ReactNode;
/** When set, shows a back link on mobile instead of the logo
* and hides the settings/analytics nav buttons. */
mobileBack?: { to: string; label: string };
}
export const AgentPageHeader: FC<AgentPageHeaderProps> = ({ children }) => {
export const AgentPageHeader: FC<AgentPageHeaderProps> = ({
children,
mobileBack,
}) => {
const { isSidebarCollapsed, onExpandSidebar } =
useOutletContext<AgentsOutletContext>();
const { appearance } = useDashboard();
const logoUrl = appearance.logo_url;
const location = useLocation();
const sidebarView = sidebarViewFromPath(location.pathname);
return (
<div className="flex shrink-0 items-center gap-2 px-4 py-0.5">
<NavLink to="/workspaces" className="inline-flex shrink-0 md:hidden">
{logoUrl ? (
<ExternalImage className="h-6" src={logoUrl} alt="Logo" />
) : (
<CoderIcon className="h-6 w-6 fill-content-primary" />
)}
</NavLink>
<div className="flex shrink-0 items-center gap-2 px-4 pt-3 pb-0.5 md:py-0.5">
{mobileBack ? (
<Link
to={mobileBack.to}
className="inline-flex shrink-0 items-center gap-1 text-sm text-content-secondary no-underline hover:text-content-primary md:hidden"
>
<ChevronLeftIcon className="h-4 w-4" />
{mobileBack.label}
</Link>
) : (
<NavLink to="/workspaces" className="inline-flex shrink-0 md:hidden">
{logoUrl ? (
<ExternalImage className="h-6" src={logoUrl} alt="Logo" />
) : (
<CoderIcon className="h-6 w-6 fill-content-primary" />
)}
</NavLink>
)}
{isSidebarCollapsed && (
<Button
variant="subtle"
@@ -38,6 +63,40 @@ export const AgentPageHeader: FC<AgentPageHeaderProps> = ({ children }) => {
</Button>
)}
<div className="min-w-0 flex-1" />
{/* Mobile-only nav buttons mirroring the sidebar toolbar
* which is hidden below the md breakpoint. */}
{!mobileBack && (
<div className="flex items-center gap-0.5 md:hidden">
<Button
asChild
variant="subtle"
size="icon"
aria-label="Settings"
className={cn(
"h-7 w-7 min-w-0 text-content-secondary hover:text-content-primary",
sidebarView.panel === "settings" && "text-content-primary",
)}
>
<Link to="/agents/settings" state={{ from: location.pathname }}>
<SettingsIcon />
</Link>
</Button>
<Button
asChild
variant="subtle"
size="icon"
aria-label="Analytics"
className={cn(
"h-7 w-7 min-w-0 text-content-secondary hover:text-content-primary",
sidebarView.panel === "analytics" && "text-content-primary",
)}
>
<Link to="/agents/analytics">
<BarChart3Icon />
</Link>
</Button>
</div>
)}
{children && <div className="flex items-center gap-2">{children}</div>}
</div>
);
@@ -74,7 +74,7 @@ import { UsageIndicator } from "../UsageIndicator";
type SidebarView =
| { panel: "chats" }
| { panel: "settings"; section: string }
| { panel: "settings"; section: string | undefined }
| { panel: "analytics" };
/**
@@ -86,7 +86,7 @@ export function sidebarViewFromPath(pathname: string): SidebarView {
}
const settingsMatch = pathname.match(/^\/agents\/settings(?:\/([^/]+))?/);
if (settingsMatch) {
return { panel: "settings", section: settingsMatch[1] || "behavior" };
return { panel: "settings", section: settingsMatch[1] };
}
return { panel: "chats" };
}
@@ -689,7 +689,7 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
{/* ── Panel 1: Chats ── */}
<div
className={cn(
"absolute inset-0 flex flex-col transition-transform duration-200 ease-in-out",
"absolute inset-0 flex flex-col md:transition-transform md:duration-200 md:ease-in-out",
sidebarView.panel === "settings" && "-translate-x-full",
)}
aria-hidden={sidebarView.panel === "settings"}
@@ -923,21 +923,24 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
{/* ── Panel 2: Sub-navigation (Settings) ── */}{" "}
<div
className={cn(
"absolute inset-0 flex flex-col transition-transform duration-200 ease-in-out",
"absolute inset-0 flex flex-col md:transition-transform md:duration-200 md:ease-in-out",
sidebarView.panel !== "settings" && "translate-x-full",
)}
aria-hidden={sidebarView.panel !== "settings"}
inert={sidebarView.panel !== "settings" ? true : undefined}
>
{/* Back header */}
<div className="hidden border-b border-border-default px-2 py-2 md:block">
<div className="flex items-center justify-between">
<div className="border-b border-border-default px-2 pb-2 pt-3 md:py-2">
<div className="relative flex items-center">
<span className="pointer-events-none absolute inset-0 flex items-center justify-center text-sm font-medium text-content-primary">
{subNavTitle}
</span>
<Button
asChild
variant="subtle"
size="icon"
aria-label="Back to chats"
className="h-7 w-7 min-w-0 text-content-secondary hover:text-content-primary"
className="relative z-10 h-7 w-7 min-w-0 text-content-secondary hover:text-content-primary"
>
<Link
to={(location.state as { from?: string })?.from || "/agents"}
@@ -945,22 +948,18 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
<ChevronLeftIcon />
</Link>
</Button>
<span className="text-sm font-medium text-content-primary">
{subNavTitle}
</span>
<div className="flex items-center gap-0.5 -mr-1.5">
{onCollapse && (
<Button
variant="subtle"
size="icon"
onClick={onCollapse}
aria-label="Collapse sidebar"
className="h-7 w-7 min-w-0 text-content-secondary hover:text-content-primary"
>
<PanelLeftCloseIcon />
</Button>
)}
</div>
<div className="flex-1" />
{onCollapse && (
<Button
variant="subtle"
size="icon"
onClick={onCollapse}
aria-label="Collapse sidebar"
className="relative z-10 hidden h-7 w-7 min-w-0 text-content-secondary hover:text-content-primary md:inline-flex"
>
<PanelLeftCloseIcon />
</Button>
)}
</div>
</div>
{/* Sub-navigation items */}
@@ -969,7 +968,9 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
<SettingsNavItem
icon={UserIcon}
label="Behavior"
active={sidebarView.section === "behavior"}
active={
!sidebarView.section || sidebarView.section === "behavior"
}
to="/agents/settings/behavior"
state={location.state}
/>