Files
resolutionflow/frontend/src/components/network/nodes/GroupNode.tsx
2026-04-13 20:13:03 +00:00

87 lines
2.6 KiB
TypeScript

import { memo, useState, useRef, useEffect } from 'react'
import { NodeProps, NodeResizer, useReactFlow } from '@xyflow/react'
import type { GroupNodeData } from '@/types/network-diagram'
const GROUP_COLORS: Record<string, string> = {
subnet: '#60a5fa',
vlan: '#a78bfa',
site: '#34d399',
dmz: '#f87171',
custom: '#94a3b8',
}
const GroupNodeComponent = ({ data, selected, id }: NodeProps) => {
const groupData = data as GroupNodeData
const color = GROUP_COLORS[groupData.groupType] ?? GROUP_COLORS.custom
const [editing, setEditing] = useState(false)
const [labelValue, setLabelValue] = useState(groupData.label ?? '')
const inputRef = useRef<HTMLInputElement>(null)
const { updateNodeData } = useReactFlow()
useEffect(() => {
if (editing) inputRef.current?.focus()
}, [editing])
// Sync if external data.label changes
useEffect(() => {
if (!editing) setLabelValue(groupData.label ?? '')
}, [groupData.label, editing])
const handleLabelCommit = () => {
setEditing(false)
if (labelValue !== groupData.label) {
updateNodeData(id, { ...groupData, label: labelValue })
}
}
return (
<>
<NodeResizer
isVisible={selected}
minWidth={120}
minHeight={80}
lineStyle={{ border: `1px solid ${color}` }}
handleStyle={{ width: 8, height: 8, borderRadius: 2, background: color }}
/>
<div
className="w-full h-full rounded-lg relative"
style={{
border: `1.5px dashed ${color}`,
background: `${color}0d`,
boxSizing: 'border-box',
}}
>
<div className="absolute top-0 left-0 -translate-y-full pb-0.5 pl-1">
{editing ? (
<input
ref={inputRef}
value={labelValue}
onChange={e => setLabelValue(e.target.value)}
onBlur={handleLabelCommit}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === 'Escape') handleLabelCommit()
e.stopPropagation()
}}
className="text-[11px] font-medium bg-transparent border-none outline-none text-primary min-w-[40px] max-w-[200px]"
style={{ color }}
/>
) : (
<span
className="text-[11px] font-medium cursor-text select-none"
style={{ color }}
onDoubleClick={() => setEditing(true)}
>
{labelValue || groupData.groupType}
</span>
)}
</div>
</div>
</>
)
}
GroupNodeComponent.displayName = 'GroupNode'
export const GroupNode = memo(GroupNodeComponent)
export default GroupNode