SPB Git
2.9 KB · 32 lines tsx
1"use client";2/** Compact depth-1 neighbourhood of a page (for the page sidebar / dashboard). Click a node to open it; link to the full graph. */3import Link from "next/link";4import { useRouter } from "next/navigation";5import { useRef, useState } from "react";6import { useQuery } from "@tanstack/react-query";7import { Share2, Maximize2 } from "lucide-react";8import { api } from "@/lib/client/api";9import { cn } from "@/lib/utils";10import { Spinner } from "@/components/ui/primitives";11import type { GraphData, GraphNode } from "@/lib/graph/types";12import { GraphCanvas, type GraphCanvasHandle } from "./GraphCanvas";1314export function PageGraphCard({ pageId, depth = 1, height = 220, className, title = "Connections" }: { pageId: string; depth?: 1 | 2 | 3; height?: number; className?: string; title?: string | null }) {15  const r = useRouter(); const canvas = useRef<GraphCanvasHandle>(null); const [hover, setHover] = useState<GraphNode | null>(null);16  const q = useQuery({ queryKey: ["graph-neighbors", pageId, depth], queryFn: () => api<GraphData>(`/api/graph/neighbors?pageId=${pageId}&depth=${depth}`), staleTime: 30_000 });17  const data = q.data; const others = data ? data.nodes.length - 1 : 0;18  return (19    <div className={cn("overflow-hidden rounded-lg border border-line bg-bg-elev", className)}>20      {title !== null && <div className="flex items-center gap-2 border-b border-line px-3 py-1.5 text-[12px] font-medium text-fg-muted"><Share2 className="size-3.5" /><span className="flex-1">{title}{data ? ` · ${others} linked` : ""}{data?.stats.suggested ? ` · ${data.stats.suggested} suggested` : ""}</span>21        <button className="grid size-7 place-items-center rounded text-fg-faint hover:bg-bg-sunk hover:text-fg" onClick={() => canvas.current?.zoomToFit()} title="Fit"><Maximize2 className="size-3.5" /></button>22        <Link href={`/graph?focus=${pageId}`} className="rounded px-1.5 py-0.5 text-[11px] text-accent hover:bg-bg-sunk">Open graph</Link></div>}23      <div className="relative" style={{ height }}>24        {q.isLoading ? <div className="grid h-full place-items-center"><Spinner /></div>25          : !data || others === 0 ? <div className="grid h-full place-items-center px-4 text-center text-[12.5px] text-fg-faint">No connections yet — mention pages with @ or run AI suggestions from the graph.</div>26          : <GraphCanvas ref={canvas} data={data} selectedId={pageId} compact labelsAt={1.1} onHover={setHover} onSelect={(n) => { if (n && n.id !== pageId && n.kind !== "tag" && n.kind !== "attachment") r.push(`/p/${n.id}`); else if (n?.kind === "attachment") window.open(`/api/files/${n.id.slice(4)}`, "_blank"); }} />}27        {hover && hover.id !== pageId && <div className="pointer-events-none absolute left-2 top-2 max-w-[80%] truncate rounded bg-bg-elev/95 px-1.5 py-0.5 text-[11px] text-fg-muted shadow-card">{hover.icon ? `${hover.icon} ` : ""}{hover.title}</div>}28      </div>29    </div>30  );31}32
Files · 2c37cb5a3e2af18e5ba361996d453cab97da82c2
Menu

Theme