authorgravatar for 97427849+flamboh@users.noreply.github.comoliver <97427849+flamboh@users.noreply.github.com> 2026-10-07 22:45:07-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-08 02:45:07-03:00
loged27eb724b4d7963b59de8fd2e51d6b877004163
tree12122fd30b0c21b424a48fc1340cda7961950e6c
parente803242d936af3e052757d59ab32bc7c4fcc2390
signature Signed by PGP key B5690EEEBB952194

fix(web): PR code toolbar no longer overlaps in narrow panels (#15561)


1 files changed, 115 insertions(+), 63 deletions(-)

apps/web/src/components/pullRequest/PullRequestCodeTab.tsx+115-63
......@@ -19,6 +19,7 @@ import {
1919 MessageSquareOffIcon,
2020 PilcrowIcon,
2121 Rows3Icon,
22 Settings2Icon,
2223 TextWrapIcon,
2324 TriangleAlertIcon,
2425} from "lucide-react";
......@@ -69,10 +70,12 @@ import { Checkbox } from "../ui/checkbox";
6970import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible";
7071import {
7172 DropdownMenu,
73 DropdownMenuCheckboxItem,
7274 DropdownMenuContent,
7375 DropdownMenuItem,
7476 DropdownMenuRadioGroup,
7577 DropdownMenuRadioItem,
78 DropdownMenuSeparator,
7679 DropdownMenuTrigger,
7780} from "../ui/menu";
7881import { toastManager } from "../ui/toast";
......@@ -1075,8 +1078,13 @@ function PullRequestCodeTab({
10751078 }
10761079 }, [commit, onSelectedCommitChange, selectedCommit]);
10771080 const scopeLabel = selectedCommit ? selectedCommit.messageHeadline : "All commits";
1081 const changeIgnoreWhitespace = (next: boolean) => {
1082 setIgnoreWhitespace(next);
1083 setDraft(null);
1084 setSelectedLines(null);
1085 };
10781086 const toolbar = (
1079 <div className="flex h-10 min-h-10 shrink-0 items-center justify-between gap-2 border-b border-border/60 bg-background px-4 text-xs text-muted-foreground">
1087 <div className="@container flex h-10 min-h-10 shrink-0 items-center justify-between gap-2 border-b border-border/60 bg-background px-4 text-xs text-muted-foreground">
10801088 <div className="flex min-w-0 flex-1 items-center gap-3">
10811089 {/* A host that reports no commits has nothing to scope by, and a dropdown whose only
10821090 entry is the scope already showing is a control that does nothing. */}
......@@ -1133,7 +1141,7 @@ function PullRequestCodeTab({
11331141 ) : null}
11341142 {/* One count, and the caveats as icons that carry their own words. Spelled out they
11351143 competed for a strip this narrow and every one of them truncated to nothing. */}
1136 <PullRequestMetaLine className="shrink-0">
1144 <PullRequestMetaLine>
11371145 <span className="shrink-0 tabular-nums">
11381146 {files.length} {files.length === 1 ? "file" : "files"}
11391147 {nextCursor === null ? "" : "+"}
......@@ -1224,30 +1232,113 @@ function PullRequestCodeTab({
12241232 </PullRequestMetaLine>
12251233 </div>
12261234 <div className="flex shrink-0 items-center gap-1">
1227 <Tooltip>
1228 <TooltipTrigger
1229 render={
1230 <Toggle
1231 aria-label={
1232 ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"
1235 <DropdownMenu>
1236 <Tooltip>
1237 <TooltipTrigger
1238 render={
1239 <DropdownMenuTrigger
1240 render={<Button size="icon-sm" variant="ghost" />}
1241 className="@lg:hidden"
1242 aria-label="Diff options"
1243 />
1244 }
1245 >
1246 <Settings2Icon className="size-3.5" />
1247 </TooltipTrigger>
1248 <TooltipPopup side="top">Diff options</TooltipPopup>
1249 </Tooltip>
1250 <DropdownMenuContent align="end">
1251 <DropdownMenuCheckboxItem
1252 checked={ignoreWhitespace}
1253 onCheckedChange={(checked) => changeIgnoreWhitespace(checked)}
1254 >
1255 Hide whitespace changes
1256 </DropdownMenuCheckboxItem>
1257 <DropdownMenuCheckboxItem
1258 checked={wordWrap}
1259 onCheckedChange={(checked) => setWordWrap(checked)}
1260 >
1261 Wrap lines
1262 </DropdownMenuCheckboxItem>
1263 <DropdownMenuSeparator />
1264 <DropdownMenuRadioGroup
1265 value={diffLayout}
1266 onValueChange={(value) => {
1267 if (value === "stacked" || value === "split") {
1268 updateClientSettings({ diffLayout: value });
12331269 }
1234 variant="ghost"
1235 size="sm"
1236 pressed={ignoreWhitespace}
1237 onPressedChange={(pressed) => {
1238 setIgnoreWhitespace(Boolean(pressed));
1239 setDraft(null);
1240 setSelectedLines(null);
1241 }}
1242 />
1243 }
1270 }}
1271 >
1272 <DropdownMenuRadioItem value="stacked" closeOnClick>
1273 Stacked
1274 </DropdownMenuRadioItem>
1275 <DropdownMenuRadioItem value="split" closeOnClick>
1276 Split
1277 </DropdownMenuRadioItem>
1278 </DropdownMenuRadioGroup>
1279 </DropdownMenuContent>
1280 </DropdownMenu>
1281 <div className="hidden items-center gap-1 @lg:flex">
1282 <Tooltip>
1283 <TooltipTrigger
1284 render={
1285 <Toggle
1286 aria-label={
1287 ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"
1288 }
1289 variant="ghost"
1290 size="sm"
1291 pressed={ignoreWhitespace}
1292 onPressedChange={(pressed) => changeIgnoreWhitespace(Boolean(pressed))}
1293 />
1294 }
1295 >
1296 <PilcrowIcon className="size-3.5" />
1297 </TooltipTrigger>
1298 <TooltipPopup side="top">
1299 {ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}
1300 </TooltipPopup>
1301 </Tooltip>
1302 <ToggleGroup
1303 aria-label="Diff layout"
1304 className="shrink-0"
1305 variant="segmented"
1306 value={[diffLayout]}
1307 onValueChange={(value) => {
1308 const next = value[0];
1309 if (next === "stacked" || next === "split") {
1310 updateClientSettings({ diffLayout: next });
1311 }
1312 }}
12441313 >
1245 <PilcrowIcon className="size-3.5" />
1246 </TooltipTrigger>
1247 <TooltipPopup side="top">
1248 {ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}
1249 </TooltipPopup>
1250 </Tooltip>
1314 <Toggle aria-label="Stacked diff view" value="stacked">
1315 <Rows3Icon className="size-3.5" />
1316 </Toggle>
1317 <Toggle aria-label="Split diff view" value="split">
1318 <Columns2Icon className="size-3.5" />
1319 </Toggle>
1320 </ToggleGroup>
1321 <Tooltip>
1322 <TooltipTrigger
1323 render={
1324 <Toggle
1325 aria-label={wordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"}
1326 variant="ghost"
1327 size="sm"
1328 pressed={wordWrap}
1329 onPressedChange={(pressed) => {
1330 setWordWrap(Boolean(pressed));
1331 }}
1332 />
1333 }
1334 >
1335 <TextWrapIcon className="size-3.5" />
1336 </TooltipTrigger>
1337 <TooltipPopup side="top">
1338 {wordWrap ? "Disable line wrapping" : "Enable line wrapping"}
1339 </TooltipPopup>
1340 </Tooltip>
1341 </div>
12511342 {fileKeys.length > 0 ? (
12521343 <Tooltip>
12531344 <TooltipTrigger
......@@ -1271,45 +1362,6 @@ function PullRequestCodeTab({
12711362 </TooltipPopup>
12721363 </Tooltip>
12731364 ) : null}
1274 <ToggleGroup
1275 aria-label="Diff layout"
1276 className="shrink-0"
1277 variant="segmented"
1278 value={[diffLayout]}
1279 onValueChange={(value) => {
1280 const next = value[0];
1281 if (next === "stacked" || next === "split") {
1282 updateClientSettings({ diffLayout: next });
1283 }
1284 }}
1285 >
1286 <Toggle aria-label="Stacked diff view" value="stacked">
1287 <Rows3Icon className="size-3.5" />
1288 </Toggle>
1289 <Toggle aria-label="Split diff view" value="split">
1290 <Columns2Icon className="size-3.5" />
1291 </Toggle>
1292 </ToggleGroup>
1293 <Tooltip>
1294 <TooltipTrigger
1295 render={
1296 <Toggle
1297 aria-label={wordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"}
1298 variant="ghost"
1299 size="sm"
1300 pressed={wordWrap}
1301 onPressedChange={(pressed) => {
1302 setWordWrap(Boolean(pressed));
1303 }}
1304 />
1305 }
1306 >
1307 <TextWrapIcon className="size-3.5" />
1308 </TooltipTrigger>
1309 <TooltipPopup side="top">
1310 {wordWrap ? "Disable line wrapping" : "Enable line wrapping"}
1311 </TooltipPopup>
1312 </Tooltip>
13131365 {fileKeys.length > 0 ? (
13141366 <Tooltip>
13151367 <TooltipTrigger