mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site): wrap workspace topbar controls on narrow viewports (#27313)
On mobile-width viewports the workspace page grew to ~1080px wide (390px viewport), pushing the schedule controls, status indicator, and action buttons off-screen. Users had to zoom out or scroll horizontally to reach Stop/Restart/Favorite/Share. The topbar header and the right-side controls group used non-wrapping flex rows, and every child is a `whitespace-nowrap` button, so the row's minimum width (~725px for the controls alone) forced the document wider than the viewport. ## Changes - `WorkspaceTopbar`: allow the topbar header and the right controls group to wrap; right controls stay right-aligned when they wrap onto their own row. - `WorkspaceActions`: allow the button row to wrap. - `TopbarData`: merge a caller-provided `className` (no existing caller passed one); the workspace breadcrumbs use it to wrap on very narrow screens. ## Verification Compared before/after with agent-browser on Storybook stories (`Workspace: Running`, `WorkspaceTopbar: Outdated / WithQuotaWithOrgs / Dormant / ConnectedWithMaxDeadline`): | Viewport | Before `scrollWidth` | After `scrollWidth` | |---|---|---| | 320 | 1080 (overflow) | 305 (fits) | | 390 | 1080 (overflow) | 375 (fits) | | 768 | 1080 (overflow) | 753 (fits) | | 1024 | 1080 (overflow) | 1009 (fits) | | 1440 | 1425 (fits) | 1425, pixel-identical (0% diff) | At widths >= 1280 the layout is unchanged (0% pixel diff at 1440 across the stories above). Between ~1080-1230px the controls now wrap onto a second right-aligned row instead of squeezing label text onto two lines inside a single row. Validation: `biome check`, `tsc --noEmit`, and `pnpm test:storybook` for `Workspace.stories.tsx` + `WorkspaceTopbar.stories.tsx` (36 passed). > This PR was prepared by Mux, an AI coding agent, on Mike's behalf.
This commit is contained in:
@@ -44,8 +44,16 @@ export const TopbarButton: React.FC<ButtonProps> = ({ ...props }) => {
|
||||
return <Button variant="outline" size="sm" {...props} />;
|
||||
};
|
||||
|
||||
export const TopbarData: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
||||
return <div {...props} className="flex gap-2 items-center justify-center" />;
|
||||
export const TopbarData: FC<HTMLAttributes<HTMLDivElement>> = ({
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn("flex gap-2 items-center justify-center", className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const TopbarDivider: FC<
|
||||
|
||||
@@ -163,7 +163,10 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2" data-testid="workspace-actions">
|
||||
<div
|
||||
className="flex flex-wrap items-center justify-end gap-2"
|
||||
data-testid="workspace-actions"
|
||||
>
|
||||
{/* Restarting must be handled separately, because it otherwise would appear as stopping */}
|
||||
{isUpdating
|
||||
? buttonMapping.updating
|
||||
|
||||
@@ -117,7 +117,7 @@ export const WorkspaceTopbar: FC<WorkspaceTopbarProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<Topbar className="[grid-area:topbar]">
|
||||
<Topbar className="[grid-area:topbar] flex-wrap gap-y-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TopbarIconButton asChild>
|
||||
@@ -130,7 +130,7 @@ export const WorkspaceTopbar: FC<WorkspaceTopbarProps> = ({
|
||||
</Tooltip>
|
||||
|
||||
<div className="flex items-center gap-y-6 gap-x-2 flex-wrap px-3 py-2 mr-auto">
|
||||
<TopbarData>
|
||||
<TopbarData className="flex-wrap">
|
||||
<OwnerBreadcrumb
|
||||
ownerName={workspace.owner_name}
|
||||
ownerAvatarUrl={workspace.owner_avatar_url}
|
||||
@@ -217,7 +217,7 @@ export const WorkspaceTopbar: FC<WorkspaceTopbarProps> = ({
|
||||
</div>
|
||||
|
||||
{!isImmutable && (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-wrap grow items-center justify-end gap-x-4 gap-y-2 min-h-12">
|
||||
<WorkspaceScheduleControls
|
||||
workspace={workspace}
|
||||
template={template}
|
||||
|
||||
Reference in New Issue
Block a user