Skip to content
128 linesCodeBlameRaw
1import { ArrowDown, ArrowUp, GitBranch, GitPullRequest, Globe } from "lucide-react";
2import { Link } from "react-router";
3
4import type { Pull } from "@g1t/contracts";
5
6import type { Drift } from "../lib/branches";
7import type { CommitPerson } from "../lib/commit-people";
8import { CommitAvatars, CommitNames } from "./commit-person";
9import { CheckBadge } from "./checks";
10import { type ChecksSource, CommitChecksBadge } from "./commit-checks";
11import { host } from "./deploy";
12import { TimeAgo } from "./ui";
13import { Hint } from "./ui/hint";
14
15function drift({ ahead, behind }: Drift, main: string): string {
16 return `${ahead} ${ahead === 1 ? "commit" : "commits"} ahead of ${main}, ${behind} behind`;
17}
18
19export type ActiveBranch = {
20 name: string;
21 commit: { hash: string; message: string; author: CommitPerson; coAuthors: CommitPerson[]; at: string } | null;
22 /** Null when the two histories were not read far enough to meet. */
23 drift: Drift | null;
24 pull: { number: number; title: string; checkStatus: Pull["checkStatus"]; draft: boolean } | null;
25 preview: string | null;
26};
27
28/** Branches other than the default, newest first, with how far each has moved, its head's checks and what is open on it. */
29export function ActiveBranches({
30 branches,
31 base,
32 main,
33 checks,
34}: {
35 branches: ActiveBranch[];
36 base: string;
37 main: string;
38 /** The checks on each branch's head, streamed in. */
39 checks?: ChecksSource;
40}) {
41 return (
42 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
43 {branches.map((branch) => (
44 <li key={branch.name} className="flex flex-wrap items-start gap-x-3 gap-y-1.5 px-4 py-3">
45 <GitBranch size={15} className="mt-0.5 shrink-0 text-faint" />
46 <span className="min-w-0 grow basis-48">
47 <Link
48 to={`${base}/tree/${encodeURIComponent(branch.name)}`}
49 className="block truncate font-mono text-[0.8125rem] font-medium hover:text-accent"
50 >
51 {branch.name}
52 </Link>
53 {branch.commit && (
54 <span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-muted">
55 <CommitAvatars commit={branch.commit} size={14} max={2} />
56 <span className="shrink-0">
57 <CommitNames commit={branch.commit} className="hover:text-fg" />
58 </span>
59 <span className="text-faint">·</span>
60 <Hint label={branch.commit.message}>
61 <Link to={`${base}/commit/${branch.commit.hash}`} className="min-w-0 truncate hover:text-fg">
62 {branch.commit.message}
63 </Link>
64 </Hint>
65 <CommitChecksBadge checks={checks} sha={branch.commit.hash} className="size-5" />
66 <span className="shrink-0 text-faint">
67 · <TimeAgo at={branch.commit.at} />
68 </span>
69 </span>
70 )}
71 </span>
72 <span className="ml-6.5 flex shrink-0 flex-wrap items-center gap-2 text-xs sm:ml-0">
73 {branch.drift && (
74 <Hint label={drift(branch.drift, main)}>
75 <span className="inline-flex items-center gap-1.5 rounded-md border border-line px-1.5 py-0.5 font-mono tabular-nums text-muted">
76 <span className="sr-only">{drift(branch.drift, main)}</span>
77 <span className="inline-flex items-center gap-0.5" aria-hidden>
78 <ArrowUp size={11} className={branch.drift.ahead > 0 ? "text-success" : "text-faint"} />
79 {branch.drift.ahead}
80 </span>
81 <span className="inline-flex items-center gap-0.5" aria-hidden>
82 <ArrowDown size={11} className={branch.drift.behind > 0 ? "text-warn" : "text-faint"} />
83 {branch.drift.behind}
84 </span>
85 </span>
86 </Hint>
87 )}
88 {branch.pull ? (
89 <Hint label={branch.pull.title}>
90 <Link
91 to={`${base}/pull/${branch.pull.number}`}
92 className="inline-flex items-center gap-1.5 rounded-md border border-line px-1.5 py-0.5 text-muted hover:border-line-strong hover:text-fg"
93 >
94 <GitPullRequest size={12} className={branch.pull.draft ? "text-faint" : "text-success"} />#{branch.pull.number}
95 <span className="sr-only">{branch.pull.title}</span>
96 <CheckBadge status={branch.pull.checkStatus} />
97 </Link>
98 </Hint>
99 ) : branch.drift?.ahead === 0 ? (
100 // Nothing here that the default branch lacks: no pull request to open.
101 <Hint label={`Every commit on ${branch.name} is already on ${main}.`}>
102 <span className="px-1.5 py-0.5 text-faint">Nothing to merge</span>
103 </Hint>
104 ) : (
105 <Link
106 to={`${base}/pulls/new?branch=${encodeURIComponent(branch.name)}`}
107 className="rounded-md px-1.5 py-0.5 text-faint hover:text-fg"
108 >
109 Open a pull request
110 </Link>
111 )}
112 {branch.preview && (
113 <Hint label={host(branch.preview)}>
114 <a
115 href={branch.preview}
116 className="inline-flex max-w-48 items-center gap-1 truncate rounded-md border border-line px-1.5 py-0.5 text-muted hover:border-line-strong hover:text-fg"
117 >
118 <Globe size={12} className="shrink-0 text-accent" />
119 Preview
120 </a>
121 </Hint>
122 )}
123 </span>
124 </li>
125 ))}
126 </ul>
127 );
128}