mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: keep agents desktop layout at 200% zoom (#24699)
Fixes layout issues on the agents empty state page. 1. At 200% zoom on a 1440 px desktop, the CSS viewport shrinks to 720 px, which was below the previous `md:` breakpoint (768 px) and collapsed the page into the mobile stack. Switching the page shell and shell-level controls to the `sm:` breakpoint (640 px) keeps the sidebar and chat area side-by-side at common zoom levels while preserving the mobile stack for real phone viewports. 2. The empty state stays bottom-aligned on mobile and centered on the desktop branch, with tighter spacing so the chat input sits closer to the bottom of the screen at 200% zoom. 3. The inner stack gap shrinks from `gap-4` (16 px) to `gap-2` (8 px) and the footer paragraph drops its `mt-1`, tightening the space around the organization selector, the chat input, and the "Introductory access to Coder Agents through September 2026" line. 4. Sidebar header/footer controls, the page header, the chat top bar, and the plan-mode badge now use the same `sm:` desktop breakpoint as the page shell. A collapsed sidebar can be expanded again at 640 to 767 px. Dropdown full-width CSS (`@media (max-width: 767px)`) and the `isBelowMdViewport` helper are intentionally left at 768 px. Those govern dropdown UX rather than page layout, and the chat pane is still narrow at 640 to 767 px after the sidebar is visible. The page is in desktop mode in that range while dropdowns stay full-width. <img width="1460" height="858" alt="Screenshot 2026-04-30 at 23 03 48" src="https://github.com/user-attachments/assets/62072432-6edf-4bf5-9a7f-88fd69f89602" /> <img width="1460" height="856" alt="Screenshot 2026-04-30 at 23 03 57" src="https://github.com/user-attachments/assets/76d94673-ac45-4a50-9c6b-3cfeffa1d6c7" /> Regression coverage in Storybook: - `AgentsPageView.stories.tsx > EmptyStateZoom200Desktop` pins a new 720 px Chromatic viewport and asserts the rendered layout is horizontal, the sidebar is left of the main panel, and the sidebar header/footer controls are visible. - `AgentsPageView.stories.tsx > CollapsedSidebarZoom200Desktop` pins the same 720 px viewport and asserts the expand-sidebar control is visible when the sidebar is collapsed. - `AgentCreateForm.stories.tsx > OrgPickerTightSpacing` measures the vertical gap between the org selector row and the chat-input composer and expects it to stay below 16 px. --- Generated by Coder Agents.
This commit is contained in:
@@ -50,6 +50,19 @@ export const parameters: Parameters = {
|
||||
},
|
||||
type: "mobile",
|
||||
},
|
||||
// Approximates a 1440x900 desktop viewed at 200% browser zoom,
|
||||
// which collapses the CSS viewport to 720x450. Used by stories
|
||||
// that verify the desktop layout still renders at common zoom
|
||||
// levels. Below the Tailwind sm: breakpoint (640 px), the
|
||||
// AgentsPage collapses into the mobile stack, so 720 px stays
|
||||
// on the desktop branch.
|
||||
desktopZoom200: {
|
||||
name: "Desktop @ 200% zoom (720x450)",
|
||||
styles: {
|
||||
height: "450px",
|
||||
width: "720px",
|
||||
},
|
||||
},
|
||||
terminal: {
|
||||
name: "Terminal",
|
||||
styles: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import dayjs from "dayjs";
|
||||
import { type ComponentProps, useState } from "react";
|
||||
import { Navigate } from "react-router";
|
||||
import { Navigate, useOutletContext } from "react-router";
|
||||
import {
|
||||
expect,
|
||||
fn,
|
||||
@@ -35,8 +35,9 @@ import AgentSettingsInstructionsPage from "./AgentSettingsInstructionsPage";
|
||||
import AgentSettingsLifecyclePage from "./AgentSettingsLifecyclePage";
|
||||
import AgentSettingsPage from "./AgentSettingsPage";
|
||||
import AgentSettingsSpendPage from "./AgentSettingsSpendPage";
|
||||
import { AgentsPageView } from "./AgentsPageView";
|
||||
import { type AgentsOutletContext, AgentsPageView } from "./AgentsPageView";
|
||||
import type { ModelSelectorOption } from "./components/ChatElements";
|
||||
import { ChatTopBar } from "./components/ChatTopBar";
|
||||
|
||||
const defaultModelConfigID = "model-config-1";
|
||||
|
||||
@@ -213,6 +214,32 @@ const agentsRouting = {
|
||||
],
|
||||
};
|
||||
|
||||
const AgentTopBarRouteElement = () => {
|
||||
const { isSidebarCollapsed, onToggleSidebarCollapsed } =
|
||||
useOutletContext<AgentsOutletContext>();
|
||||
return (
|
||||
<ChatTopBar
|
||||
chatTitle="Collapsed sidebar agent"
|
||||
panel={{ showSidebarPanel: false, onToggleSidebar: fn() }}
|
||||
onArchiveAgent={fn()}
|
||||
onArchiveAndDeleteWorkspace={fn()}
|
||||
onRegenerateTitle={fn()}
|
||||
onUnarchiveAgent={fn()}
|
||||
isSidebarCollapsed={isSidebarCollapsed}
|
||||
onToggleSidebarCollapsed={onToggleSidebarCollapsed}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const agentsWithChatTopBarRouting = {
|
||||
...agentsRouting,
|
||||
children: agentsRouting.children.map((route) =>
|
||||
"path" in route && route.path === ":agentId"
|
||||
? { ...route, element: <AgentTopBarRouteElement /> }
|
||||
: route,
|
||||
),
|
||||
};
|
||||
|
||||
const defaultArgs: ComponentProps<typeof AgentsPageView> = {
|
||||
agentId: undefined,
|
||||
chatList: [],
|
||||
@@ -497,6 +524,92 @@ export const WithToolbarEndContent: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const EmptyStateZoom200Desktop: Story = {
|
||||
parameters: {
|
||||
viewport: { defaultViewport: "desktopZoom200" },
|
||||
chromatic: { viewports: [720] },
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const layout = await canvas.findByTestId("agents-page-layout");
|
||||
const sidebar = await canvas.findByTestId("agents-sidebar-panel");
|
||||
const main = await canvas.findByTestId("agents-main-panel");
|
||||
|
||||
await waitFor(() => {
|
||||
const layoutStyles = getComputedStyle(layout);
|
||||
const sidebarStyles = getComputedStyle(sidebar);
|
||||
const mainStyles = getComputedStyle(main);
|
||||
const sidebarRect = sidebar.getBoundingClientRect();
|
||||
const mainRect = main.getBoundingClientRect();
|
||||
|
||||
expect(layoutStyles.flexDirection).toBe("row");
|
||||
expect(sidebarStyles.display).not.toBe("none");
|
||||
expect(mainStyles.display).toBe("flex");
|
||||
expect(sidebarRect.width).toBeGreaterThan(0);
|
||||
expect(mainRect.width).toBeGreaterThan(0);
|
||||
expect(sidebarRect.left).toBeLessThan(mainRect.left);
|
||||
expect(sidebarRect.right).toBeLessThanOrEqual(mainRect.left + 1);
|
||||
});
|
||||
|
||||
await expect(canvas.getByRole("link", { name: "Settings" })).toBeVisible();
|
||||
await expect(canvas.getByRole("link", { name: "New Agent" })).toBeVisible();
|
||||
await expect(
|
||||
canvas.getByRole("button", { name: "Collapse sidebar" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
canvas.getByRole("button", { name: /TestUser/ }),
|
||||
).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const CollapsedSidebarZoom200Desktop: Story = {
|
||||
args: {
|
||||
isSidebarCollapsed: true,
|
||||
},
|
||||
parameters: {
|
||||
viewport: { defaultViewport: "desktopZoom200" },
|
||||
chromatic: { viewports: [720] },
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const expandButton = await canvas.findByRole("button", {
|
||||
name: "Expand sidebar",
|
||||
});
|
||||
|
||||
await expect(expandButton).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const CollapsedSidebarZoom200DesktopWithAgent: Story = {
|
||||
args: {
|
||||
agentId: "chat-1",
|
||||
isSidebarCollapsed: true,
|
||||
chatList: [
|
||||
buildChat({
|
||||
id: "chat-1",
|
||||
title: "Collapsed sidebar agent",
|
||||
updated_at: todayTimestamp,
|
||||
}),
|
||||
],
|
||||
},
|
||||
parameters: {
|
||||
viewport: { defaultViewport: "desktopZoom200" },
|
||||
chromatic: { viewports: [720] },
|
||||
reactRouter: reactRouterParameters({
|
||||
location: { path: "/agents/chat-1" },
|
||||
routing: agentsWithChatTopBarRouting,
|
||||
}),
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const expandButton = await canvas.findByRole("button", {
|
||||
name: "Expand sidebar",
|
||||
});
|
||||
|
||||
await expect(expandButton).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const CreatingAgent: Story = {
|
||||
args: {
|
||||
isCreating: true,
|
||||
|
||||
@@ -156,17 +156,21 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-surface-primary md:flex-row">
|
||||
<div
|
||||
data-testid="agents-page-layout"
|
||||
className="flex h-full min-h-0 flex-col overflow-hidden bg-surface-primary sm:flex-row"
|
||||
>
|
||||
<title>{pageTitle("Agents")}</title>
|
||||
<div
|
||||
data-testid="agents-sidebar-panel"
|
||||
className={cn(
|
||||
"md:h-full md:w-[320px] md:min-h-0 md:border-b-0",
|
||||
"sm:h-full sm:w-[320px] sm:min-h-0 sm:border-b-0",
|
||||
agentId
|
||||
? "hidden md:block shrink-0 h-[42dvh] min-h-[240px] border-b border-border-default"
|
||||
? "hidden sm:block shrink-0 h-[42dvh] min-h-[240px] border-b border-border-default"
|
||||
: isSettingsDetail || isAnalytics
|
||||
? "hidden md:block shrink-0"
|
||||
: "order-2 md:order-none flex-1 min-h-0 border-b border-border-default md:flex-none md:border-t-0 md:border-b-0",
|
||||
isSidebarCollapsed && "md:hidden",
|
||||
? "hidden sm:block shrink-0"
|
||||
: "order-2 sm:order-none flex-1 min-h-0 border-b border-border-default sm:flex-none sm:border-t-0 sm:border-b-0",
|
||||
isSidebarCollapsed && "sm:hidden",
|
||||
)}
|
||||
>
|
||||
<AgentsSidebar
|
||||
@@ -201,13 +205,14 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-testid="agents-main-panel"
|
||||
className={cn(
|
||||
"min-h-0 min-w-0 flex-1 flex-col bg-surface-primary",
|
||||
isSettingsIndex ? "hidden md:flex" : "flex",
|
||||
isSettingsIndex ? "hidden sm:flex" : "flex",
|
||||
!agentId &&
|
||||
!isSettingsDetail &&
|
||||
sidebarView.panel === "chats" &&
|
||||
"contents md:flex md:flex-1 md:flex-col",
|
||||
"contents sm:flex sm:flex-1 sm:flex-col",
|
||||
)}
|
||||
>
|
||||
<Outlet context={outletContextValue} />
|
||||
|
||||
@@ -652,6 +652,10 @@ export const PlanningIndicator: Story = {
|
||||
planModeEnabled: true,
|
||||
onPlanModeToggle: fn(),
|
||||
},
|
||||
parameters: {
|
||||
viewport: { defaultViewport: "desktopZoom200" },
|
||||
chromatic: { viewports: [720] },
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(canvas.getByText("Planning")).toBeVisible();
|
||||
|
||||
@@ -736,8 +736,9 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
|
||||
)}
|
||||
<div
|
||||
ref={setComposerElement}
|
||||
data-testid="chat-composer"
|
||||
className={cn(
|
||||
"rounded-2xl border border-border-default/80 bg-surface-secondary md:bg-surface-secondary/45 p-1 shadow-sm has-[textarea:focus]:ring-2 has-[textarea:focus]:ring-content-link/40",
|
||||
"rounded-2xl border border-border-default/80 bg-surface-secondary sm:bg-surface-secondary/45 p-1 shadow-sm has-[textarea:focus]:ring-2 has-[textarea:focus]:ring-content-link/40",
|
||||
isDragging && "ring-2 ring-content-link/40",
|
||||
isEditingHistoryMessage &&
|
||||
"shadow-[0_0_0_2px_hsla(var(--border-warning),0.6)]",
|
||||
@@ -1096,7 +1097,7 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
|
||||
/>
|
||||
)}
|
||||
{planModeEnabled && (
|
||||
<span className="hidden shrink-0 items-center gap-1 rounded-full bg-surface-secondary px-2 py-0.5 text-xs font-medium text-content-secondary md:inline-flex">
|
||||
<span className="hidden shrink-0 items-center gap-1 rounded-full bg-surface-secondary px-2 py-0.5 text-xs font-medium text-content-secondary sm:inline-flex">
|
||||
<PencilIcon className="size-3" />
|
||||
Planning
|
||||
{onPlanModeToggle && (
|
||||
@@ -1115,7 +1116,7 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
|
||||
* when there's no overflow but still occupies
|
||||
* layout space, preventing measurement flicker. */}
|
||||
{workspace && workspaceAgent && chatId && (
|
||||
<span className="ml-1 md:ml-0">
|
||||
<span className="ml-1 sm:ml-0">
|
||||
<WorkspacePill
|
||||
workspace={workspace}
|
||||
agent={workspaceAgent}
|
||||
|
||||
@@ -595,6 +595,30 @@ export const WithOrganizationPicker: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const OrgPickerTightSpacing: Story = {
|
||||
parameters: {
|
||||
showOrganizations: true,
|
||||
organizations: [MockDefaultOrganization, MockOrganization2],
|
||||
queries: [
|
||||
{
|
||||
key: permittedOrgsKey,
|
||||
data: [MockDefaultOrganization, MockOrganization2],
|
||||
},
|
||||
],
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const orgTrigger = await canvas.findByTestId("compact-org-selector");
|
||||
const composer = await canvas.findByTestId("chat-composer");
|
||||
|
||||
const orgRect = orgTrigger.getBoundingClientRect();
|
||||
const composerRect = composer.getBoundingClientRect();
|
||||
const gap = composerRect.top - orgRect.bottom;
|
||||
expect(gap).toBeGreaterThanOrEqual(0);
|
||||
expect(gap).toBeLessThan(16);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Standalone story for the org-change confirmation dialog. Renders
|
||||
* the ConfirmDialog directly in its open state, following the same
|
||||
|
||||
@@ -448,8 +448,8 @@ export const AgentCreateForm: FC<AgentCreateFormProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="order-last flex min-h-0 flex-none items-end justify-center overflow-auto p-4 pb-4 md:order-none md:h-full md:flex-1 md:items-center md:pt-12">
|
||||
<div className="mx-auto flex w-full max-w-3xl flex-col gap-4">
|
||||
<div className="order-last flex min-h-0 flex-none items-end justify-center overflow-auto px-4 pb-4 sm:order-none sm:h-full sm:flex-1 sm:items-center">
|
||||
<div className="mx-auto flex w-full max-w-3xl flex-col gap-2">
|
||||
{isForbidden ? (
|
||||
<ChatAccessDeniedAlert />
|
||||
) : createError ? (
|
||||
@@ -534,7 +534,7 @@ export const AgentCreateForm: FC<AgentCreateFormProps> = ({
|
||||
{modelSelectorHelp}
|
||||
</div>
|
||||
) : null}
|
||||
<p className="mt-1 text-center text-xs text-content-secondary/50">
|
||||
<p className="text-center text-xs text-content-secondary/50">
|
||||
<a
|
||||
href={docs("/ai-coder/agents")}
|
||||
target="_blank"
|
||||
|
||||
@@ -44,7 +44,7 @@ const createMatchMediaController = (initialDesktop: boolean) => {
|
||||
const dispatch = (): void => {
|
||||
const event = {
|
||||
matches: desktop,
|
||||
media: "(min-width: 768px)",
|
||||
media: "(min-width: 640px)",
|
||||
} as MediaQueryListEvent;
|
||||
for (const listener of listeners) {
|
||||
listener(event);
|
||||
@@ -52,7 +52,7 @@ const createMatchMediaController = (initialDesktop: boolean) => {
|
||||
};
|
||||
|
||||
const matchMedia = ((query: string): MediaQueryList => {
|
||||
const isDesktopQuery = /\(\s*min-width\s*:\s*768px\s*\)/.test(query);
|
||||
const isDesktopQuery = /\(\s*min-width\s*:\s*640px\s*\)/.test(query);
|
||||
return {
|
||||
matches: isDesktopQuery ? desktop : false,
|
||||
media: query,
|
||||
|
||||
@@ -63,11 +63,11 @@ export const AgentPageHeader: FC<AgentPageHeaderProps> = ({
|
||||
const chimeEnabled = controlledChimeEnabled ?? internalChimeEnabled;
|
||||
const webPush = controlledWebPush ?? internalWebPush;
|
||||
const [isDesktop, setIsDesktop] = useState<boolean>(() => {
|
||||
return window.matchMedia("(min-width: 768px)").matches;
|
||||
return window.matchMedia("(min-width: 640px)").matches;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia("(min-width: 768px)");
|
||||
const mediaQuery = window.matchMedia("(min-width: 640px)");
|
||||
const onMediaChange = (event: MediaQueryListEvent) => {
|
||||
setIsDesktop(event.matches);
|
||||
};
|
||||
@@ -115,21 +115,21 @@ export const AgentPageHeader: FC<AgentPageHeaderProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="order-first flex shrink-0 items-center gap-2 pl-4 pr-2 pt-3 pb-0.5 md:order-none md:px-4 md:py-0.5">
|
||||
<div className="order-first flex shrink-0 items-center gap-2 pl-4 pr-2 pt-3 pb-0.5 sm:order-none sm:px-4 sm:py-0.5">
|
||||
{mobileBack ? (
|
||||
<Button
|
||||
asChild
|
||||
variant="subtle"
|
||||
size="icon"
|
||||
aria-label={mobileBack.label}
|
||||
className="h-7 w-7 shrink-0 md:hidden"
|
||||
className="h-7 w-7 shrink-0 sm:hidden"
|
||||
>
|
||||
<Link to={mobileBack.to}>
|
||||
<ArrowLeftIcon />
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<div className="inline-flex shrink-0 items-center gap-2 md:hidden">
|
||||
<div className="inline-flex shrink-0 items-center gap-2 sm:hidden">
|
||||
<NavLink to="/workspaces" className="inline-flex">
|
||||
<ProductLogo className="size-6" />
|
||||
</NavLink>
|
||||
@@ -142,14 +142,14 @@ export const AgentPageHeader: FC<AgentPageHeaderProps> = ({
|
||||
size="icon"
|
||||
onClick={onExpandSidebar}
|
||||
aria-label="Expand sidebar"
|
||||
className="hidden h-7 w-7 min-w-0 shrink-0 md:inline-flex"
|
||||
className="hidden h-7 w-7 min-w-0 shrink-0 sm:inline-flex"
|
||||
>
|
||||
<PanelLeftIcon />
|
||||
</Button>
|
||||
)}
|
||||
<div className="min-w-0 flex-1" />
|
||||
{children && isDesktop && (
|
||||
<div className="hidden items-center gap-2 md:flex">{children}</div>
|
||||
<div className="hidden items-center gap-2 sm:flex">{children}</div>
|
||||
)}
|
||||
{/* Mobile: meatball menu with all actions */}
|
||||
{!mobileBack && !isDesktop && (
|
||||
@@ -159,7 +159,7 @@ export const AgentPageHeader: FC<AgentPageHeaderProps> = ({
|
||||
variant="subtle"
|
||||
size="icon"
|
||||
aria-label="More options"
|
||||
className="h-7 w-7 text-content-secondary hover:text-content-primary md:hidden"
|
||||
className="h-7 w-7 text-content-secondary hover:text-content-primary sm:hidden"
|
||||
>
|
||||
<EllipsisIcon />
|
||||
</Button>
|
||||
|
||||
@@ -29,11 +29,11 @@ function getRightPanelState(): { open: boolean; width: number } {
|
||||
* immediately instead of a fullscreen spinner.
|
||||
*/
|
||||
export const AgentsPageSkeleton: FC = () => (
|
||||
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-surface-primary md:flex-row">
|
||||
<div className="order-2 md:order-none flex-1 min-h-0 border-t border-border-default md:flex-none md:border-t-0 md:h-full md:w-[320px] md:min-h-0 md:border-b-0">
|
||||
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-surface-primary sm:flex-row">
|
||||
<div className="order-2 sm:order-none flex-1 min-h-0 border-t border-border-default sm:flex-none sm:border-t-0 sm:h-full sm:w-[320px] sm:min-h-0 sm:border-b-0">
|
||||
<div className="relative flex h-full w-full min-h-0 border-0 border-r border-solid overflow-hidden">
|
||||
<div className="absolute inset-0 flex flex-col">
|
||||
<div className="hidden border-b border-border-default px-2 pb-3 pt-1.5 md:block">
|
||||
<div className="hidden border-b border-border-default px-2 pb-3 pt-1.5 sm:block">
|
||||
<div className="mb-2.5 flex items-center justify-between">
|
||||
<Skeleton className="h-6 w-6 rounded" />
|
||||
<div className="flex items-center gap-0.5 -mr-1.5">
|
||||
@@ -67,7 +67,7 @@ export const AgentsPageSkeleton: FC = () => (
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col bg-surface-primary order-1 md:order-none" />
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col bg-surface-primary order-1 sm:order-none" />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export const ChatTopBar: FC<ChatTopBarProps> = ({
|
||||
asChild
|
||||
variant="subtle"
|
||||
size="icon"
|
||||
className="inline-flex h-7 w-7 min-w-0 shrink-0 md:hidden"
|
||||
className="inline-flex h-7 w-7 min-w-0 shrink-0 sm:hidden"
|
||||
>
|
||||
<Link
|
||||
to={{ pathname: "/agents", search: location.search }}
|
||||
@@ -103,7 +103,7 @@ export const ChatTopBar: FC<ChatTopBarProps> = ({
|
||||
size="icon"
|
||||
onClick={onToggleSidebarCollapsed}
|
||||
aria-label="Expand sidebar"
|
||||
className="hidden h-7 w-7 min-w-0 shrink-0 md:inline-flex"
|
||||
className="hidden h-7 w-7 min-w-0 shrink-0 sm:inline-flex"
|
||||
>
|
||||
<PanelLeftIcon />
|
||||
</Button>
|
||||
@@ -173,10 +173,10 @@ export const ChatTopBar: FC<ChatTopBarProps> = ({
|
||||
draft={prDraft}
|
||||
className="!size-3.5 shrink-0"
|
||||
/>
|
||||
<span className="truncate max-w-[120px] hidden md:inline">
|
||||
<span className="truncate max-w-[120px] hidden sm:inline">
|
||||
{prTitle || (prNumberMatch ? `#${prNumberMatch}` : "PR")}
|
||||
</span>
|
||||
<span className="md:hidden">
|
||||
<span className="sm:hidden">
|
||||
{prNumberMatch ? prNumberMatch : "PR"}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -1079,13 +1079,13 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
|
||||
{/* ── Panel 1: Chats ── */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 flex flex-col md:transition-transform md:duration-200 md:ease-in-out",
|
||||
"absolute inset-0 flex flex-col sm:transition-transform sm:duration-200 sm:ease-in-out",
|
||||
isSettingsPanel && "-translate-x-full",
|
||||
)}
|
||||
aria-hidden={isSettingsPanel}
|
||||
inert={isSettingsPanel ? true : undefined}
|
||||
>
|
||||
<div className="hidden border-b border-border-default px-2 pb-3 pt-1.5 md:block">
|
||||
<div className="hidden border-b border-border-default px-2 pb-3 pt-1.5 sm:block">
|
||||
<div className="mb-2.5 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<NavLink to="/workspaces" className="inline-flex">
|
||||
@@ -1140,10 +1140,10 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
|
||||
viewportClassName={cn(
|
||||
"[mask-image:linear-gradient(to_bottom,transparent_0,black_20px,black_calc(100%-20px),transparent_100%)]",
|
||||
"[-webkit-mask-image:linear-gradient(to_bottom,transparent_0,black_20px,black_calc(100%-20px),transparent_100%)]",
|
||||
"md:[mask-image:none] md:[-webkit-mask-image:none]",
|
||||
"sm:[mask-image:none] sm:[-webkit-mask-image:none]",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-2 px-2 py-3 md:px-2">
|
||||
<div className="flex flex-col gap-2 px-2 py-3">
|
||||
{loadError ? (
|
||||
<div className="space-y-3 px-1">
|
||||
<ErrorAlert error={loadError} />
|
||||
@@ -1284,7 +1284,7 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<div className="hidden border-0 border-t border-solid md:block">
|
||||
<div className="hidden border-0 border-t border-solid sm:block">
|
||||
<div className="flex items-stretch">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -1325,14 +1325,14 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
|
||||
{/* ── Panel 2: Sub-navigation (Settings) ── */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 flex flex-col md:transition-transform md:duration-200 md:ease-in-out",
|
||||
"absolute inset-0 flex flex-col sm:transition-transform sm:duration-200 sm:ease-in-out",
|
||||
!isSettingsPanel && "translate-x-full",
|
||||
)}
|
||||
aria-hidden={!isSettingsPanel}
|
||||
inert={!isSettingsPanel ? true : undefined}
|
||||
>
|
||||
{/* Back header */}
|
||||
<div className="border-b border-border-default px-2 pb-2 pt-3 md:py-2">
|
||||
<div className="border-b border-border-default px-2 pb-2 pt-3 sm: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}
|
||||
@@ -1371,7 +1371,7 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
|
||||
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"
|
||||
className="relative z-10 hidden h-7 w-7 min-w-0 text-content-secondary hover:text-content-primary sm:inline-flex"
|
||||
>
|
||||
<PanelLeftCloseIcon />
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user