import { SearchIcon } from "lucide-react";
import { PullRequestStackPopover } from "./PullRequestStackPopover";
import { memo, type RefCallback } from "react";
import { cn } from "~/lib/utils";
import { getSourceControlPresentationForKind } from "~/sourceControlPresentation";
import { formatRelativeTimeLabel } from "~/timestampFormat";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { PullRequestChecksPopover } from "./PullRequestChecksPopover";
import { pullRequestLabelColor, type EnvironmentPullRequestEntry } from "./pullRequestList.logic";
import { openOnHostLabel, showPullRequestLinkContextMenu } from "./pullRequestLinkContextMenu";
import {
PullRequestActorLabel,
PullRequestDiffStat,
PullRequestMetaLine,
PullRequestApprovalGlyph,
PullRequestStateGlyph,
} from "./pullRequestPresentation";
/**
* Each slot past the first only appears once the meta line is wide enough to hold it, so a
* narrow row shows one label and a "+N" while a wide one spreads out up to three. The "+N"
* rides on whichever pill is the last visible one, and is hidden as soon as the next slot shows.
*/
const LABEL_SLOTS = [
{ pill: "", overflow: "@xl/pr-row-meta:hidden" },
{ pill: "hidden @xl/pr-row-meta:inline-flex", overflow: "@3xl/pr-row-meta:hidden" },
{ pill: "hidden @3xl/pr-row-meta:inline-flex", overflow: "" },
] as const;
function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry["labels"] }) {
if (labels.length === 0) return null;
return (
{LABEL_SLOTS.map((slot, index) => {
const label = labels[index];
if (!label) return null;
const dot = pullRequestLabelColor(label.color);
const remaining = labels.length - index - 1;
return (
{label.name}
{remaining > 0 ? (
+{remaining}
) : null}
);
})}
);
}
export type PullRequestRowTarget = Pick<
EnvironmentPullRequestEntry,
"environmentId" | "projectId" | "host" | "repository" | "number"
>;
function PullRequestRowImpl({
entry,
selected,
showProjectTitle,
showProvider,
environmentLabel,
matchedElsewhere,
statsKey,
statsRef,
onSelect,
}: {
entry: EnvironmentPullRequestEntry;
selected: boolean;
showProjectTitle: boolean;
/** Only when the list spans more than one host, where the repository alone is ambiguous. */
showProvider: boolean;
/** Names the server this row was read from, where the list spans more than one. */
environmentLabel?: string;
/**
* A search found this, but in something the row does not show — a description, a comment, a
* commit message. Saying so is the difference between a result and an apparently random row.
*/
matchedElsewhere?: boolean;
/** Used by the list's shared visibility observer to defer optional line-count reads. */
statsKey?: string;
statsRef?: RefCallback;
onSelect: (entry: PullRequestRowTarget) => void;
}) {
const { Icon, providerName } = getSourceControlPresentationForKind(entry.provider);
return (
);
}
/**
* Memoized: the list re-renders on every keystroke of a search and every status poll, and a
* row whose entry, selection and match state are unchanged has nothing new to say. Effective
* because the route hands it a stable `onSelect`.
*/
export const PullRequestRow = memo(PullRequestRowImpl);