polish(network): visual refinements across node, edge, and panel components
- DeviceNode: flat bg-card (no surface gradient), darker icon plate inset, correct text-muted token for category label - GroupNode: label pill gets bg-card/90 background so it reads against canvas - ConnectionEdge: label now has border + bg-card so it doesn't float invisible - BaseHandle: tightened to 12px with accent-toned border - NodeStatusIndicator: glow reduced to 0.15 opacity (design system compliant) - ContextMenu: Ungroup now uses Ungroup icon instead of BoxSelect - DeviceToolbar: group type icons coloured with semantic palette - PropertiesPanel: empty state gets icon tile + cleaner copy hierarchy - DiagramEditor: shortcut ? button repositioned above MiniMap, accent hover - NetworkDiagrams list: card thumbnail placeholder uses dot-grid pattern, card menu gets icons and divider before destructive action Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #139.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import {
|
||||
Copy, CopyPlus, Trash2, ClipboardPaste, BoxSelect, Maximize2, BringToFront, SendToBack,
|
||||
Copy, CopyPlus, Trash2, ClipboardPaste, BoxSelect, Ungroup, Maximize2, BringToFront, SendToBack,
|
||||
AlignStartVertical, AlignCenterHorizontal, AlignEndVertical,
|
||||
AlignStartHorizontal, AlignCenterVertical, AlignEndHorizontal,
|
||||
AlignHorizontalSpaceAround, AlignVerticalSpaceAround,
|
||||
@@ -168,7 +168,7 @@ export function ContextMenu({
|
||||
)}
|
||||
{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>
|
||||
<Ungroup size={13} /> <span>Ungroup</span>
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -54,7 +54,7 @@ function ConnectionEdgeComponent(props: EdgeProps) {
|
||||
{props.label && (
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
className="nodrag nopan rounded bg-page px-1.5 py-0.5 text-[10px] text-muted-foreground"
|
||||
className="nodrag nopan rounded border border-default bg-card px-1.5 py-0.5 text-[10px] text-muted-foreground shadow-sm"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
||||
|
||||
@@ -84,7 +84,7 @@ function DeviceNodeComponent({ id, data, selected, width, height }: NodeProps) {
|
||||
<NodeStatusIndicator status={status}>
|
||||
<NodeTooltip>
|
||||
<NodeTooltipTrigger>
|
||||
<BaseNode className="group h-full w-full bg-gradient-to-b from-card via-card to-page/70">
|
||||
<BaseNode className="group h-full w-full bg-card">
|
||||
<div className={cn('pointer-events-none absolute inset-x-0 top-0 h-10 bg-gradient-to-b opacity-80', surfaceClass)} />
|
||||
<BaseNodeHeader className="flex h-full flex-col items-center justify-center gap-2 px-2 py-3">
|
||||
<div
|
||||
@@ -94,7 +94,7 @@ function DeviceNodeComponent({ id, data, selected, width, height }: NodeProps) {
|
||||
)}
|
||||
style={{ width: iconPlateSize, height: iconPlateSize }}
|
||||
>
|
||||
<div className="absolute inset-[4px] rounded-[10px] border border-white/6 bg-page/55" />
|
||||
<div className="absolute inset-[4px] rounded-[10px] border border-white/[0.06] bg-sidebar/50" />
|
||||
<div className="relative z-10">
|
||||
<Icon size={iconPx} style={{ color }} />
|
||||
</div>
|
||||
@@ -135,7 +135,7 @@ function DeviceNodeComponent({ id, data, selected, width, height }: NodeProps) {
|
||||
)}
|
||||
<span
|
||||
style={{ fontSize: metaPx }}
|
||||
className="text-[9px] uppercase tracking-[0.16em] text-muted-foreground/80"
|
||||
className="text-[9px] uppercase tracking-[0.16em] text-muted"
|
||||
>
|
||||
{CATEGORY_LABELS[category] ?? nodeData.deviceType.replace(/-/g, ' ')}
|
||||
</span>
|
||||
|
||||
@@ -51,7 +51,7 @@ const GroupNodeComponent = ({ data, selected, id }: NodeProps) => {
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
<div className="absolute top-0 left-0 -translate-y-full pb-0.5 pl-1">
|
||||
<div className="absolute top-0 left-2 -translate-y-full pb-0.5">
|
||||
{editing ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
@@ -62,12 +62,12 @@ const GroupNodeComponent = ({ data, selected, id }: NodeProps) => {
|
||||
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]"
|
||||
className="rounded-sm px-1.5 py-0.5 text-[11px] font-semibold bg-card/90 border-none outline-none min-w-[40px] max-w-[200px]"
|
||||
style={{ color }}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="text-[11px] font-medium cursor-text select-none"
|
||||
className="inline-block rounded-sm bg-card/90 px-1.5 py-0.5 text-[11px] font-semibold cursor-text select-none tracking-wide"
|
||||
style={{ color }}
|
||||
onDoubleClick={() => setEditing(true)}
|
||||
>
|
||||
|
||||
@@ -151,22 +151,22 @@ export function DeviceToolbar({ deviceTypes, onDeviceTypesChange }: DeviceToolba
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{[
|
||||
{ slug: 'subnet', label: 'Subnet' },
|
||||
{ slug: 'vlan', label: 'VLAN' },
|
||||
{ slug: 'site', label: 'Site' },
|
||||
{ slug: 'dmz', label: 'DMZ' },
|
||||
{ slug: 'subnet', label: 'Subnet', color: '#60a5fa' },
|
||||
{ slug: 'vlan', label: 'VLAN', color: '#a78bfa' },
|
||||
{ slug: 'site', label: 'Site', color: '#34d399' },
|
||||
{ slug: 'dmz', label: 'DMZ', color: '#f87171' },
|
||||
].map(item => (
|
||||
<div
|
||||
key={item.slug}
|
||||
draggable
|
||||
onDragStart={e => {
|
||||
e.dataTransfer.setData('application/reactflow-group', JSON.stringify(item))
|
||||
e.dataTransfer.setData('application/reactflow-group', JSON.stringify({ slug: item.slug, label: item.label }))
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
}}
|
||||
className="flex cursor-grab items-center gap-2 rounded px-2 py-1.5 text-xs text-primary hover:bg-elevated active:cursor-grabbing active:scale-[0.98] transition-transform"
|
||||
>
|
||||
<GripVertical size={12} className="shrink-0 text-muted-foreground/50" />
|
||||
<LayoutGrid size={14} className="text-muted-foreground" />
|
||||
<LayoutGrid size={14} style={{ color: item.color }} />
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
AlignStartVertical, AlignCenterHorizontal, AlignEndVertical,
|
||||
AlignStartHorizontal, AlignCenterVertical, AlignEndHorizontal,
|
||||
AlignHorizontalSpaceAround, AlignVerticalSpaceAround,
|
||||
BoxSelect, Ungroup,
|
||||
BoxSelect, Ungroup, MousePointer,
|
||||
} from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { DeviceProperties, DiagramEdge } from '@/types'
|
||||
@@ -235,12 +235,15 @@ export function PropertiesPanel({
|
||||
|
||||
if (!selectedNode && !selectedEdge) {
|
||||
return (
|
||||
<div className="flex h-full w-[260px] flex-col items-center justify-center border-l border-default bg-sidebar px-4">
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
Select a device or connection to edit its properties
|
||||
<div className="flex h-full w-[260px] flex-col items-center justify-center border-l border-default bg-sidebar px-6">
|
||||
<div className="mb-3 flex h-9 w-9 items-center justify-center rounded-lg border border-default bg-elevated text-muted-foreground">
|
||||
<MousePointer size={15} />
|
||||
</div>
|
||||
<p className="text-center text-xs font-medium text-muted-foreground">
|
||||
Select a device or connection
|
||||
</p>
|
||||
<p className="mt-1 text-center text-[10px] text-muted-foreground/60">
|
||||
Hover a device to preview its info
|
||||
<p className="mt-1 text-center text-[10px] text-muted-foreground/50 leading-relaxed">
|
||||
Properties appear here. Hover a device to see a quick summary.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ export function BaseHandle({ className, children, ...props }: ComponentProps<typ
|
||||
<Handle
|
||||
{...props}
|
||||
className={cn(
|
||||
'h-[14px] w-[14px] rounded-full border border-default bg-elevated transition-opacity',
|
||||
'h-3 w-3 rounded-full border border-accent/60 bg-card transition-opacity',
|
||||
'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',
|
||||
'[.rf-connect-mode_&]:opacity-100',
|
||||
className,
|
||||
|
||||
@@ -11,9 +11,9 @@ const STATUS_BORDER_COLORS: Record<NodeStatus, string> = {
|
||||
}
|
||||
|
||||
const STATUS_GLOW: Record<NodeStatus, string> = {
|
||||
online: 'shadow-[0_0_8px_rgba(52,211,153,0.3)]',
|
||||
offline: 'shadow-[0_0_8px_rgba(248,113,113,0.3)]',
|
||||
degraded: 'shadow-[0_0_8px_rgba(250,204,21,0.3)]',
|
||||
online: 'shadow-[0_0_6px_rgba(52,211,153,0.15)]',
|
||||
offline: 'shadow-[0_0_6px_rgba(248,113,113,0.15)]',
|
||||
degraded: 'shadow-[0_0_6px_rgba(250,204,21,0.15)]',
|
||||
unknown: '',
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user