feat(network): add align/distribute/group sections to context menu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { Copy, CopyPlus, Trash2, ClipboardPaste, BoxSelect, Maximize2, BringToFront, SendToBack } from 'lucide-react'
|
import {
|
||||||
|
Copy, CopyPlus, Trash2, ClipboardPaste, BoxSelect, Maximize2, BringToFront, SendToBack,
|
||||||
|
AlignStartVertical, AlignCenterHorizontal, AlignEndVertical,
|
||||||
|
AlignStartHorizontal, AlignCenterVertical, AlignEndHorizontal,
|
||||||
|
AlignHorizontalSpaceAround, AlignVerticalSpaceAround,
|
||||||
|
} from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
interface MenuAction {
|
interface MenuAction {
|
||||||
@@ -15,9 +20,41 @@ interface ContextMenuProps {
|
|||||||
position: { x: number; y: number }
|
position: { x: number; y: number }
|
||||||
actions: MenuAction[]
|
actions: MenuAction[]
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
|
onAlignLeft?: () => void
|
||||||
|
onAlignRight?: () => void
|
||||||
|
onAlignCenterH?: () => void
|
||||||
|
onAlignTop?: () => void
|
||||||
|
onAlignBottom?: () => void
|
||||||
|
onAlignCenterV?: () => void
|
||||||
|
onDistributeH?: () => void
|
||||||
|
onDistributeV?: () => void
|
||||||
|
canAlign?: boolean
|
||||||
|
canDistribute?: boolean
|
||||||
|
onGroupSelection?: () => void
|
||||||
|
onUngroupSelection?: () => void
|
||||||
|
canGroup?: boolean
|
||||||
|
canUngroup?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ContextMenu({ position, actions, onClose }: ContextMenuProps) {
|
export function ContextMenu({
|
||||||
|
position,
|
||||||
|
actions,
|
||||||
|
onClose,
|
||||||
|
onAlignLeft,
|
||||||
|
onAlignRight,
|
||||||
|
onAlignCenterH,
|
||||||
|
onAlignTop,
|
||||||
|
onAlignBottom,
|
||||||
|
onAlignCenterV,
|
||||||
|
onDistributeH,
|
||||||
|
onDistributeV,
|
||||||
|
canAlign,
|
||||||
|
canDistribute,
|
||||||
|
onGroupSelection,
|
||||||
|
onUngroupSelection,
|
||||||
|
canGroup,
|
||||||
|
canUngroup,
|
||||||
|
}: ContextMenuProps) {
|
||||||
const menuRef = useRef<HTMLDivElement>(null)
|
const menuRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const clampedPosition = { ...position }
|
const clampedPosition = { ...position }
|
||||||
@@ -83,6 +120,59 @@ export function ContextMenu({ position, actions, onClose }: ContextMenuProps) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{canAlign && (
|
||||||
|
<>
|
||||||
|
<div className="border-t border-default my-1" />
|
||||||
|
<div className="px-3 py-0.5 text-[10px] font-medium text-muted uppercase tracking-wider">Align</div>
|
||||||
|
<button onClick={() => { onAlignLeft?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<AlignStartVertical size={13} /> <span>Align Left</span>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { onAlignCenterH?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<AlignCenterHorizontal size={13} /> <span>Align Center</span>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { onAlignRight?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<AlignEndVertical size={13} /> <span>Align Right</span>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { onAlignTop?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<AlignStartHorizontal size={13} /> <span>Align Top</span>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { onAlignCenterV?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<AlignCenterVertical size={13} /> <span>Align Middle</span>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { onAlignBottom?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<AlignEndHorizontal size={13} /> <span>Align Bottom</span>
|
||||||
|
</button>
|
||||||
|
{canDistribute && (
|
||||||
|
<>
|
||||||
|
<div className="border-t border-default my-1" />
|
||||||
|
<div className="px-3 py-0.5 text-[10px] font-medium text-muted uppercase tracking-wider">Distribute</div>
|
||||||
|
<button onClick={() => { onDistributeH?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<AlignHorizontalSpaceAround size={13} /> <span>Space Horizontally</span>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { onDistributeV?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<AlignVerticalSpaceAround size={13} /> <span>Space Vertically</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(canGroup || canUngroup) && (
|
||||||
|
<>
|
||||||
|
<div className="border-t border-default my-1" />
|
||||||
|
{canGroup && (
|
||||||
|
<button onClick={() => { onGroupSelection?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<BoxSelect size={13} /> <span>Group Selection</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{canUngroup && (
|
||||||
|
<button onClick={() => { onUngroupSelection?.(); onClose() }} className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-elevated">
|
||||||
|
<BoxSelect size={13} /> <span>Ungroup</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -736,6 +736,20 @@ function DiagramEditorInner() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
onClose={closeContextMenu}
|
onClose={closeContextMenu}
|
||||||
|
onAlignLeft={diagramCommands.alignLeft}
|
||||||
|
onAlignRight={diagramCommands.alignRight}
|
||||||
|
onAlignCenterH={diagramCommands.alignCenterH}
|
||||||
|
onAlignTop={diagramCommands.alignTop}
|
||||||
|
onAlignBottom={diagramCommands.alignBottom}
|
||||||
|
onAlignCenterV={diagramCommands.alignCenterV}
|
||||||
|
onDistributeH={diagramCommands.distributeHorizontally}
|
||||||
|
onDistributeV={diagramCommands.distributeVertically}
|
||||||
|
canAlign={contextMenu.type === 'node' ? diagramCommands.canAlign : false}
|
||||||
|
canDistribute={contextMenu.type === 'node' ? diagramCommands.canDistribute : false}
|
||||||
|
onGroupSelection={() => {}}
|
||||||
|
onUngroupSelection={() => {}}
|
||||||
|
canGroup={false}
|
||||||
|
canUngroup={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{pendingDeleteNodeId && (
|
{pendingDeleteNodeId && (
|
||||||
|
|||||||
Reference in New Issue
Block a user