authorgravatar for saiansh2525@gmail.comYash Singh <saiansh2525@gmail.com> 2026-10-10 03:34:14-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-10 08:34:14+00:00
logc77a7b7eebd6ea8a3a63bbcaaa7c1707fe65c86e
treefc7f1ad3f6e5f714631d976b371b7715d4d7fc19
parentbd2346eda2e2c380d1844869c7fd16c279d2190f
signature Signed by PGP key B5690EEEBB952194

fix(web): align compact button touch targets (#17748)


9 files changed, 58 insertions(+), 27 deletions(-)

apps/web/src/components/BranchToolbarBranchSelector.tsx+5-1
......@@ -779,7 +779,11 @@ export function BranchToolbarBranchSelector({
779779 )}
780780 />
781781 <ComposerContextLabel displayMode={displayMode}>
782 <MiddleTruncate value={triggerLabel} className="w-full" />
782 <MiddleTruncate
783 value={triggerLabel}
784 variant={displayMode === "panel" ? "cap-aligned" : "default"}
785 className="w-full"
786 />
783787 </ComposerContextLabel>
784788 {displayMode !== "panel" ? (
785789 <ChevronDownIcon className="size-3 shrink-0 opacity-50" />
apps/web/src/components/ComposerContextLabel.tsx+3-1
......@@ -1,6 +1,7 @@
11import type { ReactNode } from "react";
22
33import { cn } from "../lib/utils";
4import { THREAD_DETAILS_PANEL_LABEL_CLASS } from "./chat/threadDetailsPanelStyles";
45
56/** Keeps text measurable while the composer's outer label box collapses. */
67export function ComposerContextLabel({
......@@ -16,7 +17,7 @@ export function ComposerContextLabel({
1617 className={cn(
1718 "min-w-0",
1819 displayMode === "panel"
19 ? "flex-1 truncate text-left"
20 ? "flex-1 overflow-x-clip overflow-y-visible text-left"
2021 : "max-w-[240px] group-data-[compact]/composer-context:max-w-0",
2122 )}
2223 >
......@@ -24,6 +25,7 @@ export function ComposerContextLabel({
2425 data-composer-label-motion
2526 className={cn(
2627 "block w-full min-w-0 truncate",
28 displayMode === "panel" && THREAD_DETAILS_PANEL_LABEL_CLASS,
2729 displayMode === "toolbar" &&
2830 "max-w-[240px] transition-opacity duration-180 ease-drawer group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none",
2931 )}
apps/web/src/components/GitActionsControl.tsx+23-18
......@@ -127,6 +127,7 @@ import { type DraftId, useComposerDraftStore } from "~/composerDraftStore";
127127import {
128128 THREAD_DETAILS_PANEL_CHEVRON_CLASS,
129129 THREAD_DETAILS_PANEL_ICON_CLASS,
130 THREAD_DETAILS_PANEL_LABEL_CLASS,
130131 THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS,
131132 THREAD_DETAILS_PANEL_SPLIT_SEPARATOR_CLASS,
132133} from "./chat/threadDetailsPanelStyles";
......@@ -441,23 +442,25 @@ function GitActionProgressButtonContent({
441442 aria-live="polite"
442443 className={cn(
443444 "grid min-w-0 flex-1 items-center",
444 // Pin the title row to the button's minimum content height (min-height
445 // minus vertical padding and border) so revealing the output row
446 // extends the button downward without re-centering — the title must
447 // not shift. No row gap: the collapsed output row must contribute zero
448 // height so the single-line running button matches the static button
449 // exactly. The panel column gap matches the static row's icon-to-label
450 // distance (gap-2.5 plus the label's ml-0.5). In the panel the elapsed
451 // counter renders outside the button (in the menu-chevron slot), so
452 // there is no trailing column.
445 // Pin the title row to the button's minimum content height so output
446 // expands below it. Panel controls leave 24px after padding and border.
447 // No row gap: collapsed output must not add height. The panel's elapsed
448 // counter uses the menu-chevron slot, so it has no trailing column.
453449 isPanel
454 ? "grid-cols-[auto_minmax(0,1fr)] grid-rows-[1.75rem] gap-x-3"
450 ? "grid-cols-[auto_minmax(0,1fr)] grid-rows-[1.5rem] gap-x-3"
455451 : "grid-cols-[auto_minmax(0,1fr)_auto] grid-rows-[1.25rem] gap-x-2.5 sm:grid-rows-[1rem]",
456452 )}
457453 role="status"
458454 >
459455 <Spinner aria-hidden="true" className="row-start-1 -mx-0.5 shrink-0" />
460 <p className="row-start-1 min-w-0 truncate text-left">{progress.status}</p>
456 <p
457 className={cn(
458 "row-start-1 min-w-0 truncate text-left",
459 isPanel && THREAD_DETAILS_PANEL_LABEL_CLASS,
460 )}
461 >
462 {progress.status}
463 </p>
461464 {!isPanel ? (
462465 <GitActionElapsedTime
463466 startedAtMs={progress.startedAtMs}
......@@ -496,11 +499,13 @@ function GitActionSuccessButtonContent({ success }: { success: InlineGitActionSu
496499 return (
497500 <div
498501 aria-live="polite"
499 className="grid min-w-0 flex-1 grid-cols-[auto_minmax(0,1fr)] grid-rows-[1.75rem] items-center gap-x-3"
502 className="grid min-w-0 flex-1 grid-cols-[auto_minmax(0,1fr)] grid-rows-[1.5rem] items-center gap-x-3"
500503 role="status"
501504 >
502505 <CheckIcon aria-hidden="true" className="size-3.5 shrink-0 text-success" />
503 <p className="min-w-0 truncate text-left">{success.title}</p>
506 <p className={cn("min-w-0 truncate text-left", THREAD_DETAILS_PANEL_LABEL_CLASS)}>
507 {success.title}
508 </p>
504509 <div
505510 className={cn(
506511 "col-start-2 grid min-w-0 transition-[grid-template-rows,opacity] duration-200 ease-out motion-reduce:transition-none",
......@@ -1704,7 +1709,7 @@ export default function GitActionsControl({
17041709 <PopoverTrigger
17051710 openOnHover
17061711 nativeButton={false}
1707 render={<span className="block w-max cursor-not-allowed" />}
1712 render={<span className="block w-full cursor-not-allowed" />}
17081713 >
17091714 <MenuItem
17101715 density={presentation === "menu" ? "touch" : "default"}
......@@ -1824,7 +1829,7 @@ export default function GitActionsControl({
18241829 onClick={initializeGit}
18251830 >
18261831 <GitBranchPlusIcon className="size-3.5" aria-hidden />
1827 <span className="ml-0.5">
1832 <span className={cn("ml-0.5", isPanel && THREAD_DETAILS_PANEL_LABEL_CLASS)}>
18281833 {initAction.isPending ? "Initializing..." : "Initialize Git"}
18291834 </span>
18301835 </ThreadDetailsControl>
......@@ -1882,7 +1887,7 @@ export default function GitActionsControl({
18821887 <span
18831888 className={cn(
18841889 "sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5",
1885 isPanel && "not-sr-only ml-0 truncate",
1890 isPanel && cn("not-sr-only ml-0 truncate", THREAD_DETAILS_PANEL_LABEL_CLASS),
18861891 )}
18871892 >
18881893 {quickAction.label}
......@@ -1909,7 +1914,7 @@ export default function GitActionsControl({
19091914 <span
19101915 className={cn(
19111916 "sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5",
1912 isPanel && "not-sr-only ml-0 truncate",
1917 isPanel && cn("not-sr-only ml-0 truncate", THREAD_DETAILS_PANEL_LABEL_CLASS),
19131918 )}
19141919 >
19151920 {quickAction.label}
......@@ -1979,7 +1984,7 @@ export default function GitActionsControl({
19791984 onClick={onOpenChanges}
19801985 >
19811986 <FileDiffIcon className={THREAD_DETAILS_PANEL_ICON_CLASS} aria-hidden />
1982 <span className="flex-1 text-left">Changes</span>
1987 <span className={cn("flex-1 text-left", THREAD_DETAILS_PANEL_LABEL_CLASS)}>Changes</span>
19831988 <span className="flex items-center gap-1 font-mono text-2xs tabular-nums">
19841989 <span className="text-success">+{changesTotals?.insertions ?? 0}</span>
19851990 <span className="text-destructive">-{changesTotals?.deletions ?? 0}</span>
apps/web/src/components/ProjectScriptsControl.tsx+6-3
......@@ -50,6 +50,7 @@ import { cn } from "~/lib/utils";
5050import {
5151 THREAD_DETAILS_PANEL_CHEVRON_CLASS,
5252 THREAD_DETAILS_PANEL_ICON_CLASS,
53 THREAD_DETAILS_PANEL_LABEL_CLASS,
5354 THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS,
5455 THREAD_DETAILS_PANEL_SPLIT_SEPARATOR_CLASS,
5556} from "./chat/threadDetailsPanelStyles";
......@@ -322,7 +323,7 @@ export default function ProjectScriptsControl({
322323 <span
323324 className={cn(
324325 "sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5",
325 isPanel && "not-sr-only ml-0 truncate",
326 isPanel && cn("not-sr-only ml-0 truncate", THREAD_DETAILS_PANEL_LABEL_CLASS),
326327 )}
327328 >
328329 {primaryScript.name}
......@@ -387,7 +388,9 @@ export default function ProjectScriptsControl({
387388 onClick={() => setActionsMenuOpen({ presentation, scripts: false, imports: true })}
388389 >
389390 <WrenchIcon className={THREAD_DETAILS_PANEL_ICON_CLASS} />
390 <span className="min-w-0 truncate">Actions</span>
391 <span className={cn("min-w-0 truncate", THREAD_DETAILS_PANEL_LABEL_CLASS)}>
392 Actions
393 </span>
391394 </ThreadDetailsControl>
392395 <span aria-hidden="true" className={THREAD_DETAILS_PANEL_SPLIT_SEPARATOR_CLASS} />
393396 <Menu
......@@ -456,7 +459,7 @@ export default function ProjectScriptsControl({
456459 <span
457460 className={cn(
458461 "sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5",
459 isPanel && "not-sr-only ml-0.5",
462 isPanel && cn("not-sr-only ml-0.5", THREAD_DETAILS_PANEL_LABEL_CLASS),
460463 )}
461464 >
462465 {isPanel ? "Add project script" : "Add action"}
apps/web/src/components/chat/ComposerPrimaryActions.tsx+1-1
......@@ -370,7 +370,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({
370370 <button
371371 type="button"
372372 className={cn(
373 "flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-full border px-2.5 text-xs tabular-nums outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-64 [&_svg]:pointer-events-none [&_svg]:size-3.5",
373 "flex h-9 shrink-0 cursor-pointer items-center gap-1.5 rounded-full border px-2.5 text-xs tabular-nums outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-64 sm:h-8 [&_svg]:pointer-events-none [&_svg]:size-3.5",
374374 keepFullHistory
375375 ? "border-border text-muted-foreground hover:text-foreground"
376376 : "border-warning/40 text-warning hover:bg-warning/8",
apps/web/src/components/chat/OpenInPicker.tsx+2-1
......@@ -64,6 +64,7 @@ import { useAtomCommand } from "~/state/use-atom-command";
6464import {
6565 THREAD_DETAILS_PANEL_CHEVRON_CLASS,
6666 THREAD_DETAILS_PANEL_ICON_CLASS,
67 THREAD_DETAILS_PANEL_LABEL_CLASS,
6768 THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS,
6869 THREAD_DETAILS_PANEL_SPLIT_SEPARATOR_CLASS,
6970} from "./threadDetailsPanelStyles";
......@@ -421,7 +422,7 @@ export const OpenInPicker = memo(function OpenInPicker({
421422 compact
422423 ? "sr-only"
423424 : "sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5",
424 isPanel && "not-sr-only ml-0 min-w-0 truncate",
425 isPanel && cn("not-sr-only ml-0 min-w-0 truncate", THREAD_DETAILS_PANEL_LABEL_CLASS),
425426 )}
426427 >
427428 {primaryLabel}
apps/web/src/components/chat/ThreadDetailsPrRow.tsx+4-1
......@@ -75,6 +75,7 @@ import { Button } from "../ui/button";
7575import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
7676import {
7777 THREAD_DETAILS_PANEL_ICON_CLASS,
78 THREAD_DETAILS_PANEL_LABEL_CLASS,
7879 THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS,
7980 THREAD_DETAILS_PANEL_SPLIT_SEPARATOR_CLASS,
8081} from "./threadDetailsPanelStyles";
......@@ -398,7 +399,9 @@ export function ThreadDetailsPrRow({
398399 }
399400 >
400401 {icon}
401 <span className="min-w-0 flex-1 truncate text-left">{text}</span>
402 <span className={cn("min-w-0 flex-1 truncate text-left", THREAD_DETAILS_PANEL_LABEL_CLASS)}>
403 {text}
404 </span>
402405 </TooltipTrigger>
403406 {rowTooltip}
404407 </Tooltip>
apps/web/src/components/chat/threadDetailsPanelStyles.ts+5
......@@ -14,6 +14,11 @@ const THREAD_DETAILS_PANEL_SPLIT_GROUP_SURFACE_CLASS = `${THREAD_DETAILS_PANEL_H
1414
1515export const THREAD_DETAILS_PANEL_ROW_CONTENT_CLASS = "gap-2.5 px-2.5 text-left";
1616
17// Center capitals with icons. Only clip horizontally so truncated labels retain
18// accents and descenders outside the trimmed text box.
19export const THREAD_DETAILS_PANEL_LABEL_CLASS =
20 "[text-box:trim-both_cap_alphabetic] overflow-x-clip overflow-y-visible";
21
1722const THREAD_DETAILS_PANEL_CONTROL_CLASS = `h-8 min-w-0 rounded-lg border-transparent ${THREAD_DETAILS_PANEL_ROW_CONTENT_CLASS} text-[13px] font-medium text-foreground/80`;
1823
1924export const THREAD_DETAILS_PANEL_ROW_CLASS = `${THREAD_DETAILS_PANEL_CONTROL_CLASS} w-full justify-start ${THREAD_DETAILS_PANEL_ROW_SURFACE_CLASS}`;
apps/web/src/components/ui/middle-truncate.tsx+9-1
......@@ -16,6 +16,7 @@ export function MiddleTruncate({
1616 value,
1717 tail,
1818 showTitle = true,
19 variant = "default",
1920 className,
2021 ...props
2122}: Omit<ComponentProps<"span">, "children"> & {
......@@ -24,12 +25,19 @@ export function MiddleTruncate({
2425 tail?: number;
2526 /** The full value on hover. Off where a tooltip already carries it. */
2627 showTitle?: boolean;
28 /** Align capital letters with adjacent icons while preserving accents and descenders. */
29 variant?: "default" | "cap-aligned";
2730}) {
2831 const split = splitForMiddleTruncate(value, tail);
2932 return (
3033 <span
3134 {...(showTitle ? { title: value } : {})}
32 className={cn("inline-flex min-w-0 max-w-full overflow-hidden whitespace-nowrap", className)}
35 className={cn(
36 "inline-flex min-w-0 max-w-full overflow-hidden whitespace-nowrap",
37 variant === "cap-aligned" &&
38 "overflow-x-clip overflow-y-visible [&>span]:[text-box:trim-both_cap_alphabetic] [&>span]:overflow-x-clip [&>span]:overflow-y-visible",
39 className,
40 )}
3341 {...props}
3442 >
3543 {split ? (