Files
20260512-skg-tk/web/components/nodes/hover-preview.tsx

77 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { X } from "lucide-react"
/**
* 视觉类节点统一大预览:
* - 浮在缩略图正上方居中,跟随 ReactFlow viewport 缩放
* - 默认 480px 宽原比例高视频自动播放muted loop图片静态
* - 不 pinned 时pointer-events-none依赖 group-hover 显示/隐藏
* - pinned=true强制 visible常驻不消失pointer-events 开启可点 × 关闭
* - 用法:<div className="group ..."> ... <HoverPreview pinned={...} onClose={...} /> </div>
*/
interface Props {
imgSrc?: string
videoSrc?: string
poster?: string
aspect: string
label?: string
caption?: string
borderClass?: string
width?: number
pinned?: boolean
onClose?: () => void
}
export function HoverPreview({
imgSrc, videoSrc, poster, aspect,
label, caption,
borderClass = "border-violet-300/55",
width = 280,
pinned = false,
onClose,
}: Props) {
const visibilityCls = pinned
? "opacity-100 scale-100 pointer-events-auto"
: "pointer-events-none opacity-0 group-hover:opacity-100 scale-95 group-hover:scale-100"
return (
<div
className={`absolute transition-all duration-150 z-[60] ${visibilityCls}`}
style={{
bottom: "calc(100% + 10px)",
left: "50%",
transform: "translateX(-50%)",
transformOrigin: "bottom center",
}}
>
<div className={`relative rounded-lg overflow-hidden border-2 bg-black shadow-2xl ${pinned ? "ring-2 ring-violet-400/70" : ""} ${borderClass}`} style={{ width }}>
<div style={{ aspectRatio: aspect }}>
{videoSrc ? (
<video src={videoSrc} poster={poster} muted loop playsInline autoPlay className="w-full h-full object-cover" />
) : imgSrc ? (
<img src={imgSrc} alt="" className="w-full h-full object-cover" />
) : (
<div className="w-full h-full bg-black/40" />
)}
</div>
{(label || caption) && (
<div className="px-2 py-1 bg-black/80 text-white text-[10.5px] flex items-center justify-between">
{label && <span>{label}</span>}
{caption && <span className="text-white/60 font-mono">{caption}</span>}
</div>
)}
{pinned && onClose && (
<button
type="button"
onClick={(e) => { e.stopPropagation(); onClose() }}
onMouseDown={(e) => e.stopPropagation()}
title="取消固定预览"
className="absolute right-1 top-1 h-6 w-6 rounded-full bg-black/75 text-white shadow-lg hover:bg-rose-500 inline-flex items-center justify-center z-10"
>
<X className="h-3.5 w-3.5" />
</button>
)}
</div>
</div>
)
}