History

fix(web): prevent pull request metadata overlap (#8790)

Matthew Feroz committed 2 months agov0.0.37

Commit cefec321 parent 352710d3 files · +59 −29Browse files

Unified · Split

3 files
  1. apps/web/src/components/pullRequest/PullRequestRow.tsx +38 −16
  2. apps/web/src/components/pullRequest/pullRequestPresentation.tsx +3 −1
  3. apps/web/src/routes/_chat.pull-requests.tsx +18 −12
apps/web/src/components/pullRequest/PullRequestRow.tsx+38−16
@@ -1,3 +1,4 @@
1+import { SearchIcon } from "lucide-react";
12 import { memo } from "react";
23
34 import { cn } from "~/lib/utils";
@@ -45,7 +46,7 @@ function PullRequestRowImpl({ · 41 unmodified lines
4546 aria-current={selected ? "true" : undefined}
4647 onClick={() => onSelect(entry)}
4748 className={cn(
48− "grid w-full grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
49+ "grid w-full grid-cols-[auto_minmax(0,1fr)] items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
4950 // Offscreen rows are skipped for style, layout and paint: a long list costs what the
5051 // viewport shows, not what the pages have loaded. The intrinsic size keeps the
5152 // scrollbar honest while a row is skipped.
@@ -59,9 +60,30 @@ function PullRequestRowImpl({ · 7 unmodified lines
5960 mergeability={entry.mergeability}
6061 baseBranch={entry.baseBranch}
6162 />
62− <span className="min-w-0">
63− <span className="block truncate text-sm font-medium text-foreground">{entry.title}</span>
64− <PullRequestMetaLine className="mt-0.5 text-xs text-muted-foreground/70">
63+ <span className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-0.5">
64+ <span className="col-start-1 row-start-1 block truncate text-sm font-medium text-foreground">
65+ {entry.title}
66+ </span>
67+ <span className="col-start-2 row-start-1 justify-self-end whitespace-nowrap text-xs text-muted-foreground/70 tabular-nums">
68+ {formatRelativeTimeLabel(entry.updatedAt)}
69+ </span>
70+ <PullRequestMetaLine className="@container/pr-row-meta col-start-1 row-start-2 overflow-hidden text-xs text-muted-foreground/70">
71+ {matchedElsewhere ? (
72+ <Tooltip>
73+ <TooltipTrigger
74+ render={
75+ <span className="flex min-w-6 items-center gap-1 overflow-hidden rounded-full border border-border/60 px-1 text-[10px]" />
76+ }
77+ >
78+ <span className="sr-only">matched in the description</span>
79+ <SearchIcon aria-hidden className="size-3 shrink-0" />
80+ <span aria-hidden className="hidden truncate @xs/pr-row-meta:block">
81+ matched in the description
82+ </span>
83+ </TooltipTrigger>
84+ <TooltipPopup side="top">Matched in the description</TooltipPopup>
85+ </Tooltip>
86+ ) : null}
6587 <span className="flex shrink-0 items-center gap-1">
6688 {showProvider ? (
6789 <Tooltip>
@@ -89,15 +111,19 @@ function PullRequestRowImpl({ · 21 unmodified lines
89111 </span>
90112 {showProjectTitle ? <span className="truncate">{entry.repository}</span> : null}
91113 {environmentLabel ? (
92− <span className="max-w-32 shrink-0 truncate">{environmentLabel}</span>
114+ <span className="min-w-0 max-w-32 truncate">{environmentLabel}</span>
93115 ) : null}
94− <PullRequestActorLabel actor={entry.author} className="max-w-40 shrink-0" />
116+ <PullRequestActorLabel
117+ actor={entry.author}
118+ className="min-w-4 max-w-40"
119+ labelClassName="sr-only @xs/pr-row-meta:not-sr-only @xs/pr-row-meta:truncate"
120+ />
95121 {/* Only a verdict somebody has actually given: "review required" is the absence of
96122 one, and saying so on every unreviewed row would say nothing. */}
97123 {entry.reviewDecision === "approved" || entry.reviewDecision === "changes-requested" ? (
98124 <span
99125 className={cn(
100− "shrink-0",
126+ "min-w-0 truncate",
101127 entry.reviewDecision === "approved"
102128 ? "text-emerald-600/90 dark:text-emerald-400/80"
103129 : "text-amber-600/90 dark:text-amber-400/80",
@@ -117,16 +143,12 @@ function PullRequestRowImpl({ · 13 unmodified lines
117143 }}
118144 />
119145 )}
120− {matchedElsewhere ? (
121− <span className="shrink-0 rounded-full border border-border/60 px-1.5 text-[10px]">
122− matched in the description
123− </span>
124− ) : null}
125146 </PullRequestMetaLine>
126− </span>
127− <span className="flex shrink-0 flex-col items-end gap-0.5 text-xs text-muted-foreground/70 tabular-nums">
128− <span>{formatRelativeTimeLabel(entry.updatedAt)}</span>
129− <PullRequestDiffStat additions={entry.additions} deletions={entry.deletions} />
147+ <PullRequestDiffStat
148+ additions={entry.additions}
149+ deletions={entry.deletions}
150+ className="col-start-2 row-start-2 justify-self-end text-xs"
151+ />
130152 </span>
131153 </button>
132154 );
apps/web/src/components/pullRequest/pullRequestPresentation.tsx+3−1
@@ -339,17 +339,19 @@ export function PullRequestActorAvatar({ · 338 unmodified lines
339339 export function PullRequestActorLabel({
340340 actor,
341341 className,
342+ labelClassName,
342343 tooltip = true,
343344 }: {
344345 actor: PullRequestActor | null;
345346 className?: string;
347+ labelClassName?: string;
346348 tooltip?: boolean;
347349 }) {
348350 const login = actor?.login ?? "ghost";
349351 const label = (
350352 <>
351353 <PullRequestActorAvatar actor={actor} />
352− <span className="truncate">{login}</span>
354+ <span className={cn("truncate", labelClassName)}>{login}</span>
353355 </>
354356 );
355357 if (!tooltip) {
apps/web/src/routes/_chat.pull-requests.tsx+18−12
@@ -1628,11 +1628,13 @@ function CompactFilterMenu<Value extends string>({ · 1627 unmodified lines
16281628 value,
16291629 options,
16301630 onChange,
1631+ className,
16311632 }: {
16321633 label: string;
16331634 value: Value;
16341635 options: ReadonlyArray<PullRequestFilterOption<Value>>;
16351636 onChange: (value: Value) => void;
1637+ className?: string;
16361638 }) {
16371639 const current = options.find((option) => option.value === value) ?? options[0];
16381640 if (!current) return null;
@@ -1640,10 +1642,13 @@ function CompactFilterMenu<Value extends string>({ · 1 unmodified line
16401642 <Menu>
16411643 <MenuTrigger
16421644 aria-label={label}
1643− className="inline-flex h-7 shrink-0 items-center gap-1 rounded-md px-1.5 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
1645+ className={cn(
1646+ "inline-flex h-7 min-w-0 items-center gap-1 rounded-md px-1.5 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground",
1647+ className,
1648+ )}
16441649 >
1645− {current.label}
1646− <ChevronDownIcon aria-hidden className="size-3 text-muted-foreground/70" />
1650+ <span className="truncate">{current.label}</span>
1651+ <ChevronDownIcon aria-hidden className="size-3 shrink-0 text-muted-foreground/70" />
16471652 </MenuTrigger>
16481653 <MenuPopup align="start" side="bottom" className="min-w-40">
16491654 <MenuRadioGroup value={value} onValueChange={(next) => onChange(next as Value)}>
@@ -1721,7 +1726,7 @@ function ExpandableSearch({ · 71 unmodified lines
17211726 return (
17221727 <div
17231728 ref={containerRef}
1724− className="w-56 shrink-0"
1729+ className="w-56 min-w-24 shrink"
17251730 onFocus={() => onFocusWithin?.(true)}
17261731 onBlur={() => {
17271732 onFocusWithin?.(false);
@@ -1808,6 +1813,7 @@ function PullRequestsColumn({ · 80 unmodified lines
18081813 const inFlowSearchRef = useRef<HTMLDivElement | null>(null);
18091814 const [searchOpen, setSearchOpen] = useState(false);
18101815 const [searchFocusToken, setSearchFocusToken] = useState(0);
1816+ const searchExpanded = searchOpen || searchValue.length > 0;
18111817 // Mod+F belongs to this page's own search: the desktop shell binds no find-in-page, so the
18121818 // shortcut would otherwise do nothing. Condensed, it unfolds the topbar search; at the top,
18131819 // it focuses the in-flow bar and selects the query the way a find field would.
@@ -1860,20 +1866,20 @@ function PullRequestsColumn({ · 46 unmodified lines
18601866 >
18611867 {titlebarControls}
18621868 {condensed ? (
1863− <WorkspaceBreadcrumb ariaLabel="Pull request scope">
1864− {/* The page name remains the foreground anchor in both states; the live filters are
1865− its compact scope, grouped as the second crumb rather than pretending each menu
1866− is a separate page in the hierarchy. */}
1867− <WorkspaceBreadcrumbItem current>
1869+ <WorkspaceBreadcrumb ariaLabel="Pull request scope" className="overflow-hidden">
1870+ {/* An expanded search owns the scarce horizontal space. The page title stays
1871+ available to readers while the live filters remain available in both states. */}
1872+ <WorkspaceBreadcrumbItem current className={cn(searchExpanded && "sr-only")}>
18681873 <h1 className="truncate">Pull Requests</h1>
18691874 </WorkspaceBreadcrumbItem>
1870− <WorkspaceBreadcrumbSeparator />
1871− <WorkspaceBreadcrumbItem className="gap-1.5 overflow-hidden">
1875+ {searchExpanded ? null : <WorkspaceBreadcrumbSeparator />}
1876+ <WorkspaceBreadcrumbItem className="shrink gap-1.5">
18721877 <CompactFilterMenu
18731878 label="Filter by state"
18741879 value={state}
18751880 options={STATE_TABS}
18761881 onChange={onState}
1882+ className="shrink-0"
18771883 />
18781884 <CompactFilterMenu
18791885 label="Filter by involvement"
@@ -1900,7 +1906,7 @@ function PullRequestsColumn({ · 20 unmodified lines
19001906 )}
19011907 <div className="min-w-0 flex-1" />
19021908 {condensed ? (
1903− <div className="flex shrink-0 items-center gap-1.5">
1909+ <div className="flex shrink items-center gap-1.5">
19041910 <ExpandableSearch
19051911 searchInput={searchInput}
19061912 searchValue={searchValue}