"use client"; import { useId, useState, type KeyboardEvent } from "react"; import { IconGitPullRequest } from "@tabler/icons-react"; import { cn } from "@/lib/utils"; import { PRCIPopover } from "@/components/github/pr-ci-popover"; import { getPRStatusColor, pickDefaultPR } from "@/components/github/pr-task-icon"; import { usePRFeedbackBackgroundSync } from "@/hooks/domains/github/use-pr-ci-popover"; import type { TaskPR } from "@/lib/types/github"; /** * Renders nothing — just keeps one PR's feedback cache warm while the popover * is mounted, so switching tabs shows fresh data immediately. One instance per * PR (keyed by id) so the hook count stays stable as the list changes. */ function PRFeedbackWarmer({ pr }: { pr: TaskPR }) { usePRFeedbackBackgroundSync(pr); return null; } function prTabDomId(uid: string, pr: TaskPR): string { return `${uid}-pr-tab-${pr.repo}-${pr.pr_number}`; } function PRTab({ pr, uid, panelId, active, onSelect, }: { pr: TaskPR; uid: string; panelId: string; active: boolean; onSelect: (pr: TaskPR) => void; }) { return ( ); } /** * Multi-PR variant of the CI popover. A segmented header lists every PR linked * to the task (one chip per PR, coloured by its status); the body reuses the * single-PR PRCIPopover for whichever PR is selected. Defaults to the * worst-status open PR so problems surface first. */ export function MultiPRCIPopover({ prs, enabled, onOpenDetailPanel, }: { prs: TaskPR[]; enabled: boolean; onOpenDetailPanel?: (pr: TaskPR) => void; }) { // `overrideId` is only set when the user activates a tab. The displayed PR is // derived: honour the override while it still exists, otherwise fall back to // the worst-status PR. This keeps the selection valid as the list changes // (PR closed, new PR opened, task switch) without a setState-in-effect. const [overrideId, setOverrideId] = useState(null); // useId-scoped DOM ids so two mounted popovers (chip + topbar) can't collide. // The panel id is stable across tab switches so every tab's aria-controls // always references an element that exists in the DOM. const uid = useId(); const panelId = `${uid}-pr-ci-tabpanel`; const selected = prs.find((p) => p.id === overrideId) ?? pickDefaultPR(prs); if (!selected) return null; // ArrowLeft/ArrowRight move selection (and focus) within the tablist, // wrapping at the ends — selection follows focus, per the ARIA tabs pattern. const onTablistKeyDown = (e: KeyboardEvent) => { if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return; e.preventDefault(); const idx = prs.findIndex((p) => p.id === selected.id); const delta = e.key === "ArrowRight" ? 1 : -1; const next = prs[(idx + delta + prs.length) % prs.length]; setOverrideId(next.id); document.getElementById(prTabDomId(uid, next))?.focus(); }; return (
{prs .filter((p) => p.state === "open") .map((pr) => ( ))}
{prs.map((pr) => ( setOverrideId(p.id)} /> ))}
onOpenDetailPanel(selected) : undefined} />
); }