1import type { ReactNode } from "react";
2
3import { isElectron } from "~/env";
4import { cn } from "~/lib/utils";
5
6import { Skeleton } from "./ui/skeleton";
7
8export type DiffPanelMode = "inline" | "sheet" | "sidebar" | "embedded";
9
10function getDiffPanelHeaderRowClassName(mode: DiffPanelMode) {
11 const shouldUseDragRegion = isElectron && mode !== "sheet" && mode !== "embedded";
12 return cn(
13 "flex items-center justify-between gap-2",
14 mode === "embedded" ? "px-2" : "px-4",
15 shouldUseDragRegion
16 ? "drag-region h-[var(--workspace-topbar-height)] border-b border-border wco:pr-(--workspace-native-controls-inset)"
17 : "flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background in-data-[preview-panel-mode=inline]:mb-3 in-data-[preview-panel-mode=inline]:h-7 in-data-[preview-panel-mode=inline]:min-h-7 in-data-[preview-panel-mode=inline]:border-b-transparent",
18 );
19}
20
21export function DiffPanelShell(props: {
22 mode: DiffPanelMode;
23 header: ReactNode;
24 children: ReactNode;
25}) {
26 const shouldUseDragRegion = isElectron && props.mode !== "sheet" && props.mode !== "embedded";
27
28 return (
29 <div
30 className={cn(
31 "flex h-full min-w-0 flex-col bg-background",
32 props.mode === "inline"
33 ? "w-[42vw] min-w-[360px] max-w-[560px] shrink-0 border-l border-border"
34 : "w-full",
35 )}
36 >
37 {shouldUseDragRegion ? (
38 <div className={getDiffPanelHeaderRowClassName(props.mode)}>{props.header}</div>
39 ) : (
40 <div className={getDiffPanelHeaderRowClassName(props.mode)} data-surface-subheader>
41 {props.header}
42 </div>
43 )}
44 {props.children}
45 </div>
46 );
47}
48
49export function DiffFileHeaderSkeleton({
50 titleWidth,
51}: {
52 titleWidth: "short" | "medium" | "long";
53}) {
54 return (
55 <div className="flex h-8 items-center gap-2 px-2 pr-3">
56 <div className="flex size-5 shrink-0 items-center justify-center">
57 <Skeleton className="size-2.5" />
58 </div>
59 <Skeleton className="size-5 shrink-0" />
60 <Skeleton
61 shape="pill"
62 className={
63 titleWidth === "short"
64 ? "h-3 w-2/5 max-w-52"
65 : titleWidth === "medium"
66 ? "h-3 w-1/2 max-w-64"
67 : "h-3 w-3/5 max-w-72"
68 }
69 />
70 <div className="ml-auto flex shrink-0 items-center gap-2">
71 <Skeleton shape="pill" className="h-3 w-5" />
72 <Skeleton shape="pill" className="h-3 w-5" />
73 </div>
74 </div>
75 );
76}
77
78function DiffCodeLineSkeleton({ width }: { width: "short" | "medium" | "long" }) {
79 return (
80 <div className="flex items-center gap-3">
81 <Skeleton shape="pill" className="h-2.5 w-5 shrink-0" />
82 <Skeleton
83 shape="pill"
84 className={
85 width === "short" ? "h-2.5 w-3/5" : width === "medium" ? "h-2.5 w-2/3" : "h-2.5 w-4/5"
86 }
87 />
88 </div>
89 );
90}
91
92export function DiffPanelLoadingState(props: { label: string }) {
93 return (
94 <div
95 className="min-h-0 flex-1 overflow-hidden bg-background"
96 role="status"
97 aria-live="polite"
98 aria-label={props.label}
99 >
100 <DiffFileHeaderSkeleton titleWidth="medium" />
101 <div className="flex h-6 items-center gap-2 px-2 pr-3">
102 <div className="h-px flex-1 bg-border/40" />
103 <Skeleton shape="pill" className="h-2.5 w-24" />
104 <div className="h-px flex-1 bg-border/40" />
105 </div>
106 <div className="space-y-2 px-3 py-2">
107 <DiffCodeLineSkeleton width="medium" />
108 <DiffCodeLineSkeleton width="long" />
109 <DiffCodeLineSkeleton width="short" />
110 </div>
111 <DiffFileHeaderSkeleton titleWidth="short" />
112 <DiffFileHeaderSkeleton titleWidth="long" />
113 <span className="sr-only">{props.label}</span>
114 </div>
115 );
116}