126 lines
3.3 KiB
TypeScript
126 lines
3.3 KiB
TypeScript
'use client'
|
|
|
|
import {
|
|
getSmoothStepPath,
|
|
EdgeLabelRenderer,
|
|
BaseEdge,
|
|
type EdgeProps,
|
|
} from '@xyflow/react'
|
|
import type { StreamEdgeData } from '@/lib/types'
|
|
|
|
const STATUS_COLORS: Record<string, { stroke: string; bg: string; text: string; label: string }> = {
|
|
planned: { stroke: '#22c55e', bg: 'rgba(34,197,94,0.12)', text: '#16a34a', label: 'Planned' },
|
|
active: { stroke: '#3b82f6', bg: 'rgba(59,130,246,0.12)', text: '#2563eb', label: 'Active' },
|
|
paused: { stroke: '#f97316', bg: 'rgba(249,115,22,0.12)', text: '#ea580c', label: 'Paused' },
|
|
}
|
|
|
|
export default function StreamEdge({
|
|
id,
|
|
sourceX,
|
|
sourceY,
|
|
targetX,
|
|
targetY,
|
|
sourcePosition,
|
|
targetPosition,
|
|
data,
|
|
markerEnd,
|
|
}: EdgeProps) {
|
|
const edgeData = data as StreamEdgeData | undefined
|
|
const status = edgeData?.status ?? 'planned'
|
|
const flowRate = edgeData?.flowRate ?? 0
|
|
const tokenSymbol = edgeData?.tokenSymbol ?? 'DAIx'
|
|
const colors = STATUS_COLORS[status] || STATUS_COLORS.planned
|
|
|
|
const [edgePath, labelX, labelY] = getSmoothStepPath({
|
|
sourceX,
|
|
sourceY,
|
|
targetX,
|
|
targetY,
|
|
sourcePosition,
|
|
targetPosition,
|
|
})
|
|
|
|
// Animated dashed line to simulate flowing tokens
|
|
const isPaused = status === 'paused'
|
|
|
|
return (
|
|
<>
|
|
{/* Background glow path */}
|
|
<path
|
|
d={edgePath}
|
|
fill="none"
|
|
stroke={colors.stroke}
|
|
strokeWidth={8}
|
|
strokeOpacity={0.15}
|
|
/>
|
|
{/* Main path with animated dash */}
|
|
<BaseEdge
|
|
id={id}
|
|
path={edgePath}
|
|
markerEnd={markerEnd}
|
|
style={{
|
|
stroke: colors.stroke,
|
|
strokeWidth: 3,
|
|
strokeDasharray: '8 4',
|
|
animation: isPaused ? 'none' : 'streamFlow 1s linear infinite',
|
|
opacity: isPaused ? 0.5 : 0.9,
|
|
}}
|
|
/>
|
|
{/* Inject animation keyframes */}
|
|
<foreignObject width={0} height={0}>
|
|
<style>{`
|
|
@keyframes streamFlow {
|
|
to { stroke-dashoffset: -12; }
|
|
}
|
|
`}</style>
|
|
</foreignObject>
|
|
<EdgeLabelRenderer>
|
|
<div
|
|
className="nodrag nopan"
|
|
style={{
|
|
position: 'absolute',
|
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
pointerEvents: 'all',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
gap: 2,
|
|
}}
|
|
>
|
|
{/* Flow rate label */}
|
|
<span
|
|
style={{
|
|
fontSize: 10,
|
|
fontWeight: 600,
|
|
fontFamily: 'monospace',
|
|
color: colors.text,
|
|
background: 'rgba(255,255,255,0.95)',
|
|
padding: '2px 6px',
|
|
borderRadius: 4,
|
|
border: `1px solid ${colors.stroke}`,
|
|
whiteSpace: 'nowrap',
|
|
}}
|
|
>
|
|
{flowRate.toLocaleString()} {tokenSymbol}/mo
|
|
</span>
|
|
{/* Status pill */}
|
|
<span
|
|
style={{
|
|
fontSize: 8,
|
|
fontWeight: 700,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.05em',
|
|
color: colors.text,
|
|
background: colors.bg,
|
|
padding: '1px 5px',
|
|
borderRadius: 6,
|
|
}}
|
|
>
|
|
{colors.label}
|
|
</span>
|
|
</div>
|
|
</EdgeLabelRenderer>
|
|
</>
|
|
)
|
|
}
|