_code.tsx

  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}