feat: add collapsible/expandable zones for canvas decluttering

- Add collapsed state to NodeData.custom_colors (type=groupRect only)
- Implement toggleNodeCollapsed action in canvasStore (Zustand)
- Extend GroupRectNode UI with smooth chevron toggle button
  - Rotating chevron icon (↓ → when collapsed)
  - Shows '+N' badge when zone is hidden
  - Reduces zone opacity to 0.6 when collapsed
  - All transitions target 60 FPS (ease-out 200ms)
- Filter child nodes/edges in CanvasContainer based on parent collapse state
  - Breadth-first traversal handles multi-level nesting
  - Connecting edges to hidden nodes are automatically hidden
- Add comprehensive test coverage
  - Store: toggleNodeCollapsed state mutation, unsaved flag
  - Component: chevron rendering, click handlers, opacity transitions
- Persist collapsed state via YAML serialization (part of custom_colors)

Benefits:
- Declutter large Zigbee meshes, multi-building networks
- Preserve layout structure without deleting nodes
- Smooth 60 FPS transitions for UX polish

CONTRIBUTING.md compliance:
- Strict TypeScript types, no 'any'
- Zustand store pattern, no prop drilling
- Tests for store logic and component behavior
- Frontend linting requirements met

Co-authored-by: CyberClaw <noreply@openclaw.ai>
This commit is contained in:
pranjal-joshi
2026-05-17 19:37:04 +00:00
parent d066f37e88
commit 525dfe5ece
7 changed files with 300 additions and 65 deletions
+20
View File
@@ -52,6 +52,7 @@ interface CanvasState {
setEditingGroupRectId: (id: string | null) => void
editingTextId: string | null
setEditingTextId: (id: string | null) => void
toggleNodeCollapsed: (id: string) => void
createGroup: (nodeIds: string[], name: string) => void
ungroup: (groupId: string) => void
markSaved: () => void
@@ -374,6 +375,25 @@ export const useCanvasStore = create<CanvasState>((set) => ({
setEditingTextId: (id) => set({ editingTextId: id }),
toggleNodeCollapsed: (id) =>
set((state) => ({
nodes: state.nodes.map((n) =>
n.id === id
? {
...n,
data: {
...n.data,
custom_colors: {
...n.data.custom_colors,
collapsed: !n.data.custom_colors?.collapsed,
},
},
}
: n
),
hasUnsavedChanges: true,
})),
createGroup: (nodeIds, name) =>
set((state) => {
const PADDING_H = 24