1// Pathless layout for the code browser. Reads preloaded refs from the
2// $repo context and renders the shared header (breadcrumb + ref selector
3// + history toggle). Child routes (tree, blob, commits) render in Outlet.
4
5import { useReadQuery } from "@apollo/client/react";
6import {
7 createFileRoute,
8 Outlet,
9 useMatchRoute,
10 useNavigate,
11 useParams,
12} from "@tanstack/react-router";
13import { GitCommit } from "lucide-react";
14
15import type { GitRef } from "@/__generated__/graphql";
16import { CodeBreadcrumb } from "@/components/code/CodeBreadcrumb";
17import { RefSelector } from "@/components/code/RefSelector";
18import { ButtonLink } from "@/components/ui/button-link";
19import { Skeleton } from "@/components/ui/skeleton";
20
21export const Route = createFileRoute("/$repo/_code")({
22 component: CodeLayout,
23 pendingComponent: CodeLayoutSkeleton,
24});
25
26function CodeLayout() {
27 const { repo } = Route.useParams();
28 const { ref: repoRef, refsRef } = Route.useRouteContext();
29 const { data: refsData } = useReadQuery(refsRef);
30 const refs: GitRef[] = refsData?.repository?.refs?.nodes ?? [];
31 const repoName = refsData?.repository?.name ?? repoRef ?? "default-repo";
32
33 // Read child route params (ref and splat path) via loose useParams
34 const allParams = useParams({ strict: false }) as {
35 ref?: string;
36 _splat?: string;
37 };
38 const currentRef = allParams.ref ?? "";
39 const currentPath = allParams._splat ?? "";
40
41 const matchRoute = useMatchRoute();
42 const isCommitsView = !!matchRoute({
43 to: "/$repo/commits/$ref",
44 params: { repo, ref: currentRef },
45 fuzzy: true,
46 });
47
48 const navigate = useNavigate();
49
50 function handleRefSelect(ref: GitRef) {
51 void navigate({
52 to: "/$repo/tree/$ref/$",
53 params: { repo, ref: ref.shortName, _splat: "" },
54 });
55 }
56
57 return (
58 <div className="space-y-4">
59 <div className="flex flex-wrap items-center justify-between gap-3">
60 <CodeBreadcrumb
61 repoName={repoName}
62 currentRef={currentRef}
63 path={currentPath}
64 repo={repo}
65 />
66 <div className="flex items-center gap-2">
67 {isCommitsView ? (
68 <ButtonLink
69 to="/$repo/tree/$ref/$"
70 params={{ repo, ref: currentRef, _splat: currentPath }}
71 variant="secondary"
72 size="sm"
73 >
74 <GitCommit className="size-3.5" />
75 History
76 </ButtonLink>
77 ) : (
78 <ButtonLink
79 to="/$repo/commits/$ref"
80 params={{ repo, ref: currentRef }}
81 variant="outline"
82 size="sm"
83 >
84 <GitCommit className="size-3.5" />
85 History
86 </ButtonLink>
87 )}
88 <RefSelector refs={refs} currentRef={currentRef} onSelect={handleRefSelect} />
89 </div>
90 </div>
91
92 <Outlet />
93 </div>
94 );
95}
96
97function CodeLayoutSkeleton() {
98 return (
99 <div className="space-y-4">
100 <div className="flex items-center justify-between">
101 <Skeleton className="h-5 w-48" />
102 <Skeleton className="h-8 w-28" />
103 </div>
104 <div className="divide-border border-border divide-y rounded-md border">
105 {Array.from({ length: 8 }).map((_, i) => (
106 <div key={i} className="flex items-center gap-3 px-4 py-2">
107 <Skeleton className="size-4 rounded-sm" />
108 <Skeleton className="h-4 w-32" />
109 </div>
110 ))}
111 </div>
112 </div>
113 );
114}