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 {...@@ -19,6 +19,7 @@ import {
19 MessageSquareOffIcon,19 MessageSquareOffIcon,
20 PilcrowIcon,20 PilcrowIcon,
21 Rows3Icon,21 Rows3Icon,
22 Settings2Icon,
22 TextWrapIcon,23 TextWrapIcon,
23 TriangleAlertIcon,24 TriangleAlertIcon,
24} from "lucide-react";25} from "lucide-react";
...@@ -69,10 +70,12 @@ import { Checkbox } from "../ui/checkbox";...@@ -69,10 +70,12 @@ import { Checkbox } from "../ui/checkbox";
69import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible";70import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible";
70import {71import {
71 DropdownMenu,72 DropdownMenu,
73 DropdownMenuCheckboxItem,
72 DropdownMenuContent,74 DropdownMenuContent,
73 DropdownMenuItem,75 DropdownMenuItem,
74 DropdownMenuRadioGroup,76 DropdownMenuRadioGroup,
75 DropdownMenuRadioItem,77 DropdownMenuRadioItem,
78 DropdownMenuSeparator,
76 DropdownMenuTrigger,79 DropdownMenuTrigger,
77} from "../ui/menu";80} from "../ui/menu";
78import { toastManager } from "../ui/toast";81import { toastManager } from "../ui/toast";
...@@ -1075,8 +1078,13 @@ function PullRequestCodeTab({...@@ -1075,8 +1078,13 @@ function PullRequestCodeTab({
1075 }1078 }
1076 }, [commit, onSelectedCommitChange, selectedCommit]);1079 }, [commit, onSelectedCommitChange, selectedCommit]);
1077 const scopeLabel = selectedCommit ? selectedCommit.messageHeadline : "All commits";1080 const scopeLabel = selectedCommit ? selectedCommit.messageHeadline : "All commits";
1081 const changeIgnoreWhitespace = (next: boolean) => {
1082 setIgnoreWhitespace(next);
1083 setDraft(null);
1084 setSelectedLines(null);
1085 };
1078 const toolbar = (1086 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">
1080 <div className="flex min-w-0 flex-1 items-center gap-3">1088 <div className="flex min-w-0 flex-1 items-center gap-3">
1081 {/* A host that reports no commits has nothing to scope by, and a dropdown whose only1089 {/* A host that reports no commits has nothing to scope by, and a dropdown whose only
1082 entry is the scope already showing is a control that does nothing. */}1090 entry is the scope already showing is a control that does nothing. */}
...@@ -1133,7 +1141,7 @@ function PullRequestCodeTab({...@@ -1133,7 +1141,7 @@ function PullRequestCodeTab({
1133 ) : null}1141 ) : null}
1134 {/* One count, and the caveats as icons that carry their own words. Spelled out they1142 {/* One count, and the caveats as icons that carry their own words. Spelled out they
1135 competed for a strip this narrow and every one of them truncated to nothing. */}1143 competed for a strip this narrow and every one of them truncated to nothing. */}
1136 <PullRequestMetaLine className="shrink-0">1144 <PullRequestMetaLine>
1137 <span className="shrink-0 tabular-nums">1145 <span className="shrink-0 tabular-nums">
1138 {files.length} {files.length === 1 ? "file" : "files"}1146 {files.length} {files.length === 1 ? "file" : "files"}
1139 {nextCursor === null ? "" : "+"}1147 {nextCursor === null ? "" : "+"}
...@@ -1224,30 +1232,113 @@ function PullRequestCodeTab({...@@ -1224,30 +1232,113 @@ function PullRequestCodeTab({
1224 </PullRequestMetaLine>1232 </PullRequestMetaLine>
1225 </div>1233 </div>
1226 <div className="flex shrink-0 items-center gap-1">1234 <div className="flex shrink-0 items-center gap-1">
1227 <Tooltip>1235 <DropdownMenu>
1228 <TooltipTrigger1236 <Tooltip>
1229 render={1237 <TooltipTrigger
1230 <Toggle1238 render={
1231 aria-label={1239 <DropdownMenuTrigger
1232 ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"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 });
1233 }1269 }
1234 variant="ghost"1270 }}
1235 size="sm"1271 >
1236 pressed={ignoreWhitespace}1272 <DropdownMenuRadioItem value="stacked" closeOnClick>
1237 onPressedChange={(pressed) => {1273 Stacked
1238 setIgnoreWhitespace(Boolean(pressed));1274 </DropdownMenuRadioItem>
1239 setDraft(null);1275 <DropdownMenuRadioItem value="split" closeOnClick>
1240 setSelectedLines(null);1276 Split
1241 }}1277 </DropdownMenuRadioItem>
1242 />1278 </DropdownMenuRadioGroup>
1243 }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 }}
1244 >1313 >
1245 <PilcrowIcon className="size-3.5" />1314 <Toggle aria-label="Stacked diff view" value="stacked">
1246 </TooltipTrigger>1315 <Rows3Icon className="size-3.5" />
1247 <TooltipPopup side="top">1316 </Toggle>
1248 {ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}1317 <Toggle aria-label="Split diff view" value="split">
1249 </TooltipPopup>1318 <Columns2Icon className="size-3.5" />
1250 </Tooltip>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>
1251 {fileKeys.length > 0 ? (1342 {fileKeys.length > 0 ? (
1252 <Tooltip>1343 <Tooltip>
1253 <TooltipTrigger1344 <TooltipTrigger
...@@ -1271,45 +1362,6 @@ function PullRequestCodeTab({...@@ -1271,45 +1362,6 @@ function PullRequestCodeTab({
1271 </TooltipPopup>1362 </TooltipPopup>
1272 </Tooltip>1363 </Tooltip>
1273 ) : null}1364 ) : 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>
1313 {fileKeys.length > 0 ? (1365 {fileKeys.length > 0 ? (
1314 <Tooltip>1366 <Tooltip>
1315 <TooltipTrigger1367 <TooltipTrigger