Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 47 additions & 18 deletions apps/web/src/components/BranchToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,29 +186,58 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({

if (isLocked) {
return (
<span
className="inline-flex h-7 min-w-0 max-w-[48%] flex-initial items-center justify-start gap-1 rounded-md border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"
data-composer-context-control
>
{triggerContent}
</span>
<Tooltip>
<TooltipTrigger
render={<span tabIndex={0} />}
className="inline-flex h-7 min-w-0 max-w-[48%] flex-initial items-center justify-start gap-1 rounded-md border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"
data-composer-context-control
aria-label={
showEnvironmentIndicator
? `Run on ${autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"}; Workspace ${workspaceLabel}`
: `Workspace ${workspaceLabel}`
}
>
{triggerContent}
</TooltipTrigger>
<TooltipPopup>
{showEnvironmentIndicator
? `${autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"} · ${workspaceLabel}`
: workspaceLabel}
</TooltipPopup>
</Tooltip>
);
}

return (
<Menu>
<MenuTrigger
render={<Button variant="ghost" size="xs" />}
className="min-w-0 max-w-[48%] flex-initial justify-start font-normal text-muted-foreground/70 text-xs! hover:text-foreground/80"
data-composer-context-control
data-composer-shortcut={[
showEnvironmentPicker && !envLocked ? "composer.host" : "",
!envModeLocked ? "composer.workspace" : "",
].join(" ")}
>
{triggerContent}
<ChevronDownIcon className="size-3 shrink-0 opacity-50" />
</MenuTrigger>
<Tooltip>
<TooltipTrigger
render={
<MenuTrigger
render={<Button variant="ghost" size="xs" />}
className="min-w-0 max-w-[48%] flex-initial justify-start font-normal text-muted-foreground/70 text-xs! hover:text-foreground/80"
data-composer-context-control
aria-label={
showEnvironmentIndicator
? `Run on ${autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"}; Workspace ${workspaceLabel}`
: `Workspace ${workspaceLabel}`
}
data-composer-shortcut={[
showEnvironmentPicker && !envLocked ? "composer.host" : "",
!envModeLocked ? "composer.workspace" : "",
].join(" ")}
/>
}
>
{triggerContent}
<ChevronDownIcon className="size-3 shrink-0 opacity-50" />
</TooltipTrigger>
<TooltipPopup>
{showEnvironmentIndicator
? `${autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"} · ${workspaceLabel}`
: workspaceLabel}
</TooltipPopup>
</Tooltip>
<MenuPopup align="start" side="top" className="w-64" {...composerFloatingLayerProps}>
{showEnvironmentPicker && availableEnvironments && onEnvironmentChange ? (
<>
Expand Down
39 changes: 39 additions & 0 deletions apps/web/src/components/BranchToolbarContextTooltips.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { EnvironmentId, ProjectId } from "@t3tools/contracts";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vite-plus/test";

import { BranchToolbarEnvironmentSelector } from "./BranchToolbarEnvironmentSelector";
import { BranchToolbarEnvModeSelector } from "./BranchToolbarEnvModeSelector";

describe("locked composer context labels", () => {
it("keeps clipped environment and workspace labels keyboard reachable", () => {
const environment = renderToStaticMarkup(
<BranchToolbarEnvironmentSelector
envLocked
environmentId={EnvironmentId.make("remote")}
availableEnvironments={[
{
environmentId: EnvironmentId.make("remote"),
projectId: ProjectId.make("project"),
label: "Remote build host",
machine: "server",
isPrimary: false,
},
]}
/>,
);
const workspace = renderToStaticMarkup(
<BranchToolbarEnvModeSelector
envLocked
effectiveEnvMode="local"
activeWorktreePath="/projects/pylon/worktrees/task"
onEnvModeChange={() => {}}
/>,
);

expect(environment).toContain('tabindex="0"');
expect(environment).toContain('aria-label="Run on Remote build host"');
expect(workspace).toContain('tabindex="0"');
expect(workspace).toContain('aria-label="Workspace');
});
});
106 changes: 66 additions & 40 deletions apps/web/src/components/BranchToolbarEnvModeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
SelectTrigger,
SelectValue,
} from "./ui/select";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";

const PREVIOUS_WORKTREE_SELECT_VALUE = "previous-worktree";

Expand Down Expand Up @@ -51,27 +52,36 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe

if (envLocked) {
return (
<span
className="inline-flex h-7 min-w-0 items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"
data-composer-context-control
>
{activeWorktreePath ? (
<FolderGitIcon className="size-3 shrink-0" />
) : (
<FolderIcon className="size-3 shrink-0" />
)}
<span
data-composer-label
className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
<Tooltip>
<TooltipTrigger
render={
<span
tabIndex={0}
aria-label={`Workspace ${resolveLockedWorkspaceLabel(activeWorktreePath)}`}
/>
}
className="inline-flex h-7 min-w-0 items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"
data-composer-context-control
>
{activeWorktreePath ? (
<FolderGitIcon className="size-3 shrink-0" />
) : (
<FolderIcon className="size-3 shrink-0" />
)}
<span
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
data-composer-label
className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
>
{resolveLockedWorkspaceLabel(activeWorktreePath)}
<span
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
>
{resolveLockedWorkspaceLabel(activeWorktreePath)}
</span>
</span>
</span>
</span>
</TooltipTrigger>
<TooltipPopup>{resolveLockedWorkspaceLabel(activeWorktreePath)}</TooltipPopup>
</Tooltip>
);
}

Expand All @@ -88,33 +98,49 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
}}
items={envModeItems}
>
<SelectTrigger
variant="ghost"
size="xs"
className="min-w-0 shrink font-normal text-xs!"
aria-label="Workspace"
data-composer-shortcut="composer.workspace"
data-composer-context-control
>
{effectiveEnvMode === "worktree" ? (
<FolderGit2Icon className="size-3" />
) : activeWorktreePath ? (
<FolderGitIcon className="size-3" />
) : (
<FolderIcon className="size-3" />
)}
<span
data-composer-label
className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
<Tooltip>
<TooltipTrigger
render={
<SelectTrigger
variant="ghost"
size="xs"
className="min-w-0 shrink font-normal text-xs!"
aria-label="Workspace"
aria-description={
effectiveEnvMode === "worktree"
? resolveEnvModeLabel("worktree")
: resolveCurrentWorkspaceLabel(activeWorktreePath)
}
data-composer-shortcut="composer.workspace"
data-composer-context-control
/>
}
>
{effectiveEnvMode === "worktree" ? (
<FolderGit2Icon className="size-3" />
) : activeWorktreePath ? (
<FolderGitIcon className="size-3" />
) : (
<FolderIcon className="size-3" />
)}
<span
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
data-composer-label
className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
>
<SelectValue />
<span
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
>
<SelectValue />
</span>
</span>
</span>
</SelectTrigger>
</TooltipTrigger>
<TooltipPopup>
{effectiveEnvMode === "worktree"
? resolveEnvModeLabel("worktree")
: resolveCurrentWorkspaceLabel(activeWorktreePath)}
</TooltipPopup>
</Tooltip>
<SelectPopup alignItemWithTrigger={false} {...composerFloatingLayerProps}>
<SelectGroup>
<SelectGroupLabel>Workspace</SelectGroupLabel>
Expand Down
95 changes: 55 additions & 40 deletions apps/web/src/components/BranchToolbarEnvironmentSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
SelectTrigger,
SelectValue,
} from "./ui/select";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";

interface BranchToolbarEnvironmentSelectorProps {
autoEnvironmentLabel?: string | undefined;
Expand Down Expand Up @@ -58,26 +59,32 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
// only thing in the strip.
if (envLocked || onEnvironmentChange === undefined) {
return (
<span
className="inline-flex h-7 min-w-0 max-w-full items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"
data-composer-context-control
>
<EnvironmentMachineIcon
kind={activeEnvironment?.machine ?? "server"}
className="size-3 shrink-0"
/>
<span
data-composer-label
className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
<Tooltip>
<TooltipTrigger
render={
<span tabIndex={0} aria-label={`Run on ${activeEnvironment?.label ?? "Run on"}`} />
}
className="inline-flex h-7 min-w-0 max-w-full items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"
data-composer-context-control
>
<EnvironmentMachineIcon
kind={activeEnvironment?.machine ?? "server"}
className="size-3 shrink-0"
/>
<span
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
data-composer-label
className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
>
{activeEnvironment?.label ?? "Run on"}
<span
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
>
{activeEnvironment?.label ?? "Run on"}
</span>
</span>
</span>
</span>
</TooltipTrigger>
<TooltipPopup>{activeEnvironment?.label ?? "Run on"}</TooltipPopup>
</Tooltip>
);
}

Expand All @@ -90,34 +97,42 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
}
items={environmentItems}
>
<SelectTrigger
variant="ghost"
size="xs"
className="min-w-0 max-w-full font-normal text-xs!"
aria-label="Run on"
data-composer-shortcut="composer.host"
data-composer-context-control
>
{autoEnvironmentLabel ? (
<ScaleIcon className="size-3 shrink-0" aria-hidden="true" />
) : (
<EnvironmentMachineIcon
kind={activeEnvironment?.machine ?? "server"}
className="size-3 shrink-0"
/>
)}
<span
data-composer-label
className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
<Tooltip>
<TooltipTrigger
render={
<SelectTrigger
variant="ghost"
size="xs"
className="min-w-0 max-w-full font-normal text-xs!"
aria-label="Run on"
aria-description={autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"}
data-composer-shortcut="composer.host"
data-composer-context-control
/>
}
>
{autoEnvironmentLabel ? (
<ScaleIcon className="size-3 shrink-0" aria-hidden="true" />
) : (
<EnvironmentMachineIcon
kind={activeEnvironment?.machine ?? "server"}
className="size-3 shrink-0"
/>
)}
<span
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
data-composer-label
className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
>
<SelectValue />
<span
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
>
<SelectValue />
</span>
</span>
</span>
</SelectTrigger>
</TooltipTrigger>
<TooltipPopup>{autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"}</TooltipPopup>
</Tooltip>
<SelectPopup alignItemWithTrigger={false} {...composerFloatingLayerProps}>
<SelectGroup>
<SelectGroupLabel>Run on</SelectGroupLabel>
Expand Down
Loading