feat: implement PinnedWikiPopup, RelatedEntityPopup, and PreviewLayout to support entity relationship navigation and wiki content display in editor preview mode.

This commit is contained in:
taDuc
2026-05-27 12:15:21 +07:00
parent 3d21d078cf
commit ef3766bc2a
7 changed files with 1446 additions and 798 deletions
@@ -0,0 +1,99 @@
"use client";
import { useEffect, useRef } from "react";
import type { Entity } from "@/uhm/api/entities";
import type { Wiki } from "@/uhm/api/wikis";
type PopupRow = {
entity: Entity;
wiki: Wiki | null;
quote: string;
};
type Props = {
rows: PopupRow[];
featureId: string | number;
top: number;
left: number;
onClose: () => void;
onSelectRow: (entityId: string, wikiId?: string) => void;
};
export default function PinnedWikiPopup({
rows,
featureId,
top,
left,
onClose,
onSelectRow,
}: Props) {
const containerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onClose();
}
};
const handlePointerDown = (event: PointerEvent) => {
const target = event.target as Node | null;
if (target && containerRef.current?.contains(target)) {
return;
}
onClose();
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("pointerdown", handlePointerDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("pointerdown", handlePointerDown);
};
}, [onClose]);
return (
<div
ref={containerRef}
className="absolute z-30 w-[320px] max-w-[calc(100vw-2rem)]"
style={{ left, top }}
>
<div className="overflow-hidden rounded-xl border border-white/10 bg-slate-950/95 shadow-xl backdrop-blur">
<div className="max-h-[300px] overflow-y-auto p-3">
<div className="grid gap-2">
{rows.map(({ entity, wiki, quote }) => (
<button
key={`${entity.id}:${wiki?.id || "entity-only"}`}
type="button"
onClick={() => {
onSelectRow(entity.id, wiki?.id);
}}
className="w-full rounded-lg border border-white/10 bg-white/[0.03] px-3 py-3 text-left transition hover:border-sky-400/40 hover:bg-sky-500/10"
>
<div className="truncate text-sm font-semibold text-white">
{entity.name || String(entity.id)}
</div>
{quote ? (
<div
className="mt-2 pl-3 pr-1 text-sm italic leading-relaxed text-slate-300"
style={{
borderLeft: "3px solid rgba(56, 189, 248, 0.4)",
display: "-webkit-box",
WebkitLineClamp: 4,
WebkitBoxOrient: "vertical",
overflow: "hidden",
whiteSpace: "normal",
}}
>
{quote}
</div>
) : null}
</button>
))}
</div>
</div>
</div>
</div>
);
}