1"use client";
2
3
16import { X, Link2, Inbox } from "lucide-react";
17import Link from "next/link";
18import type { BacklinkRef } from "@/lib/types";
19
20interface RelatedPanelProps {
21 pageId: string;
22 workspaceSlug: string;
23 open: boolean;
24 onClose: () => void;
25 refs: BacklinkRef[];
26 isLoading: boolean;
27 isError: boolean;
28}
29
30export function RelatedPanel({
31 pageId,
32 workspaceSlug,
33 open,
34 onClose,
35 refs,
36 isLoading,
37 isError
38}: RelatedPanelProps) {
39 if (!open) return null;
40
41 return (
42 <>
43 {}
44 <div className="fixed inset-0 z-40 bg-black/20" aria-hidden onClick={onClose} />
45 <aside
46 className="fixed inset-y-0 right-0 z-50 flex w-full max-w-md flex-col border-l border-border bg-background shadow-xl"
47 role="dialog"
48 aria-label={`Related pages referencing this page`}
49 data-testid="related-panel"
50 >
51 <div className="flex shrink-0 items-center justify-between border-b border-border px-4 py-3">
52 <h2 className="inline-flex items-center gap-2 text-sm font-semibold">
53 <Link2 className="size-4 text-muted-foreground" aria-hidden />
54 Related
55 {refs.length > 0 ? (
56 <span className="text-xs font-normal text-muted-foreground" aria-hidden>
57 ({refs.length})
58 </span>
59 ) : null}
60 </h2>
61 <button
62 type="button"
63 className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
64 onClick={onClose}
65 aria-label="Close related pages"
66 >
67 <X className="size-4" aria-hidden />
68 </button>
69 </div>
70
71 <div className="min-h-0 flex-1 overflow-y-auto p-3" data-testid="related-panel-body">
72 {isLoading ? (
73 <p className="px-1 py-2 text-sm text-muted-foreground" data-testid="related-loading">
74 Loading related pages…
75 </p>
76 ) : isError ? (
77 <p className="px-1 py-2 text-sm text-muted-foreground" data-testid="related-error">
78 Couldn't load related pages.
79 </p>
80 ) : refs.length === 0 ? (
81 <div className="flex flex-col items-center gap-2 px-4 py-10 text-center">
82 <Inbox className="size-6 text-muted-foreground/70" aria-hidden />
83 <p className="text-sm font-medium">No related pages yet</p>
84 <p className="text-xs text-muted-foreground">
85 Pages that link to this one (child page, linked database, or a
86 comment mention) will appear here.
87 </p>
88 </div>
89 ) : (
90 <ul className="space-y-1" data-testid="related-list">
91 {refs.map((r) => (
92 <li key={r.pageId}>
93 <Link
94 href={`/w/${workspaceSlug}/page/${r.pageId}`}
95 data-testid={`related-item-${r.pageId}`}
96 onClick={onClose}
97 className="flex items-center gap-2.5 rounded-md border border-border/60 px-3 py-2 text-sm hover:bg-accent"
98 >
99 {r.icon ? (
100 <span className="shrink-0 text-base" aria-hidden>
101 {r.icon}
102 </span>
103 ) : null}
104 <span className="min-w-0 flex-1 truncate font-medium">
105 {r.title || "Untitled"}
106 </span>
107 </Link>
108 </li>
109 ))}
110 </ul>
111 )}
112 </div>
113 </aside>
114 </>
115 );
116}
117