import { useState } from 'react' import { Handle, Position, NodeResizer, type NodeProps, type Node } from '@xyflow/react' import { useCanvasStore } from '@/stores/canvasStore' import type { NodeData, TextPosition } from '@/types' const FONT_FAMILIES: Record = { inter: 'Inter, sans-serif', mono: '"JetBrains Mono", monospace', serif: 'Georgia, serif', } interface AlignStyle { alignItems: string justifyContent: string textAlign: React.CSSProperties['textAlign'] } const POSITION_STYLES: Record = { 'top-left': { alignItems: 'flex-start', justifyContent: 'flex-start', textAlign: 'left' }, 'top-center': { alignItems: 'flex-start', justifyContent: 'center', textAlign: 'center' }, 'top-right': { alignItems: 'flex-start', justifyContent: 'flex-end', textAlign: 'right' }, 'middle-left': { alignItems: 'center', justifyContent: 'flex-start', textAlign: 'left' }, 'center': { alignItems: 'center', justifyContent: 'center', textAlign: 'center' }, 'middle-right': { alignItems: 'center', justifyContent: 'flex-end', textAlign: 'right' }, 'bottom-left': { alignItems: 'flex-end', justifyContent: 'flex-start', textAlign: 'left' }, 'bottom-center': { alignItems: 'flex-end', justifyContent: 'center', textAlign: 'center' }, 'bottom-right': { alignItems: 'flex-end', justifyContent: 'flex-end', textAlign: 'right' }, } const HANDLE_SIDES = [ { id: 'zone-top', position: Position.Top }, { id: 'zone-right', position: Position.Right }, { id: 'zone-bottom', position: Position.Bottom }, { id: 'zone-left', position: Position.Left }, ] as const export function GroupRectNode({ id, data, selected }: NodeProps>) { const setEditingGroupRectId = useCanvasStore((s) => s.setEditingGroupRectId) const [hovered, setHovered] = useState(false) const rc = data.custom_colors ?? {} const borderColor = rc.border ?? '#00d4ff' const borderStyle = rc.border_style ?? 'solid' const borderWidth = rc.border_width ?? 2 const backgroundColor = rc.background ?? 'rgba(0,212,255,0.05)' const textColor = rc.text_color ?? '#e6edf3' const textSize: number = rc.text_size ?? 12 const labelPosition: string = rc.label_position ?? 'inside' const fontFamily = FONT_FAMILIES[rc.font ?? 'inter'] ?? FONT_FAMILIES.inter const textPos = (rc.text_position ?? 'top-left') as TextPosition const posStyle = POSITION_STYLES[textPos] const outsideJustify = textPos.includes('right') ? 'flex-end' : (textPos.includes('center') || textPos === 'center') ? 'center' : 'flex-start' const isOutsideBottom = textPos.startsWith('bottom') const outsideOffset = textSize + 16 const outsideVertical: React.CSSProperties = isOutsideBottom ? { bottom: -outsideOffset } : { top: -outsideOffset } const sharedTextStyle: React.CSSProperties = { color: textColor, fontFamily, fontSize: textSize, fontWeight: 500, userSelect: 'none', whiteSpace: 'pre-wrap', } const handleStyle: React.CSSProperties = { width: 10, height: 10, background: borderColor, border: '2px solid #0d1117', borderRadius: '50%', opacity: hovered ? 1 : 0, transition: 'opacity 0.15s', } return ( <> {HANDLE_SIDES.map(({ id: hid, position }) => ( ))}
setHovered(true)} onMouseLeave={() => setHovered(false)} onDoubleClick={(e) => { e.stopPropagation() setEditingGroupRectId(id) }} > {labelPosition === 'outside' && data.label && ( {data.label} )} {labelPosition === 'inside' && data.label && ( {data.label} )}
) }