58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
"use client"
|
||
|
||
/**
|
||
* 视觉类节点统一 hover 大预览:
|
||
* - 浮在缩略图正上方(bottom: calc(100% + 10px),居中),跟随 ReactFlow viewport 缩放
|
||
* - 固定 280px 宽,原比例高
|
||
* - 视频自动播放(muted loop),图片静态
|
||
* - pointer-events-none,不阻挡同行/邻近交互
|
||
* - 用法:<div className="group ..."> ... <HoverPreview ... /> </div>
|
||
*/
|
||
interface Props {
|
||
imgSrc?: string
|
||
videoSrc?: string
|
||
poster?: string
|
||
aspect: string
|
||
label?: string
|
||
caption?: string
|
||
borderClass?: string
|
||
width?: number
|
||
}
|
||
|
||
export function HoverPreview({
|
||
imgSrc, videoSrc, poster, aspect,
|
||
label, caption,
|
||
borderClass = "border-violet-300/55",
|
||
width = 280,
|
||
}: Props) {
|
||
return (
|
||
<div
|
||
className="pointer-events-none absolute opacity-0 group-hover:opacity-100 scale-95 group-hover:scale-100 transition-all duration-150 z-[60]"
|
||
style={{
|
||
bottom: "calc(100% + 10px)",
|
||
left: "50%",
|
||
transform: "translateX(-50%)",
|
||
transformOrigin: "bottom center",
|
||
}}
|
||
>
|
||
<div className={`rounded-lg overflow-hidden border-2 bg-black shadow-2xl ${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>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|