@principal-ai/subsystems-react 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +11 -85
  2. package/dist/index.d.ts +7 -44
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -32
  5. package/dist/index.js.map +1 -1
  6. package/dist/subsystem/SubsystemCarousel.d.ts +36 -0
  7. package/dist/subsystem/SubsystemCarousel.d.ts.map +1 -0
  8. package/dist/subsystem/SubsystemCarousel.js +163 -0
  9. package/dist/subsystem/SubsystemCarousel.js.map +1 -0
  10. package/dist/subsystem/SubsystemComponentGraph.d.ts +25 -0
  11. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  12. package/dist/subsystem/SubsystemComponentGraph.js +329 -68
  13. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  14. package/package.json +4 -4
  15. package/src/index.ts +9 -199
  16. package/src/subsystem/SubsystemComponentGraph.tsx +396 -50
  17. package/src/components/ConfigurationSelector.tsx +0 -133
  18. package/src/components/GraphRenderer.tsx +0 -3195
  19. package/src/components/MultiCanvasRenderer.tsx +0 -295
  20. package/src/components/NodeTooltip.tsx +0 -279
  21. package/src/components/PendingChanges.test.tsx +0 -375
  22. package/src/components/SelectionSidebar.tsx +0 -352
  23. package/src/components/SequenceDiagramRenderer.tsx +0 -1156
  24. package/src/components/WorkflowSequenceDiagram.tsx +0 -270
  25. package/src/components/dashboard/DashboardRenderer.tsx +0 -317
  26. package/src/components/dashboard/MetricPanel.tsx +0 -254
  27. package/src/components/dashboard/MockDataProvider.ts +0 -330
  28. package/src/components/dashboard/components/BarChart.tsx +0 -299
  29. package/src/components/dashboard/components/LineChart.tsx +0 -279
  30. package/src/components/dashboard/components/MetricCard.tsx +0 -270
  31. package/src/components/dashboard/components/SourceLink.tsx +0 -63
  32. package/src/components/dashboard/components/TimeRangeSelector.tsx +0 -280
  33. package/src/components/dashboard/components/index.ts +0 -5
  34. package/src/components/dashboard/index.ts +0 -47
  35. package/src/components/dashboard/types.ts +0 -126
  36. package/src/components/state-view/PipelineView.tsx +0 -347
  37. package/src/components/state-view/index.ts +0 -14
  38. package/src/components/state-view/types.ts +0 -261
  39. package/src/components/state-view/useStateView.ts +0 -205
  40. package/src/contexts/GraphEditContext.tsx +0 -27
  41. package/src/contexts/TooltipPortalContext.ts +0 -8
  42. package/src/edges/CustomEdge.tsx +0 -402
  43. package/src/edges/GenericEdge.tsx +0 -36
  44. package/src/hooks/usePathBasedEvents.ts +0 -192
  45. package/src/hooks/useSequenceLayout.ts +0 -543
  46. package/src/hooks/useUndoRedo.ts +0 -144
  47. package/src/nodes/CustomNode.tsx +0 -1231
  48. package/src/nodes/GenericNode.tsx +0 -55
  49. package/src/nodes/otel/OtelBoundaryNode.tsx +0 -224
  50. package/src/nodes/otel/OtelEventNode.tsx +0 -225
  51. package/src/nodes/otel/OtelResourceNode.tsx +0 -251
  52. package/src/nodes/otel/OtelScopeNode.tsx +0 -221
  53. package/src/nodes/otel/OtelSpanConventionNode.tsx +0 -295
  54. package/src/nodes/otel/index.ts +0 -23
  55. package/src/nodes/otel/shared/NodeBadges.tsx +0 -295
  56. package/src/nodes/otel/shared/NodeContent.tsx +0 -204
  57. package/src/nodes/otel/shared/index.ts +0 -8
  58. package/src/nodes/otel/shared/types.ts +0 -138
  59. package/src/nodes/otel/shared/useNodeBehavior.ts +0 -114
  60. package/src/stories/Audit/BoundaryNodes.stories.tsx +0 -385
  61. package/src/stories/Audit/CanvasEdgeTypes.stories.tsx +0 -1448
  62. package/src/stories/Audit/CanvasNodeTypes.stories.tsx +0 -1049
  63. package/src/stories/Audit/ColorPriority.stories.tsx +0 -895
  64. package/src/stories/Audit/DiamondBadges.stories.tsx +0 -629
  65. package/src/stories/Audit/NodeDimensionsTesting.stories.tsx +0 -724
  66. package/src/stories/Audit/NodeFieldsAudit.stories.tsx +0 -996
  67. package/src/stories/Audit/NodeFontSizeTesting.stories.tsx +0 -715
  68. package/src/stories/Audit/NodeShapes.stories.tsx +0 -1208
  69. package/src/stories/Audit/WideNodesTesting.stories.tsx +0 -1045
  70. package/src/stories/Components/DashboardRenderer.stories.tsx +0 -263
  71. package/src/stories/Components/GraphRenderer.stories.tsx +0 -3236
  72. package/src/stories/Components/MultiCanvasRenderer.stories.tsx +0 -370
  73. package/src/stories/Components/SequenceDiagram.stories.tsx +0 -255
  74. package/src/stories/Components/StateView.stories.tsx +0 -417
  75. package/src/stories/Components/TraceViewer.stories.tsx +0 -442
  76. package/src/stories/Components/WorkflowSequenceDiagram.stories.tsx +0 -802
  77. package/src/stories/Features/AnimationWorkshop.stories.tsx +0 -727
  78. package/src/stories/Features/ChangeTypeVisual.stories.tsx +0 -67
  79. package/src/stories/Features/EventDrivenAnimations.stories.tsx +0 -513
  80. package/src/stories/Features/MultiConfig.stories.tsx +0 -539
  81. package/src/stories/Layout/ElkEdgeRouting.stories.tsx +0 -539
  82. package/src/stories/Layout/GraphOrientation.stories.tsx +0 -500
  83. package/src/stories/Layout/MultiDirectionalConnections.stories.tsx +0 -352
  84. package/src/stories/Nodes/NodeDefinitionComparison.stories.tsx +0 -560
  85. package/src/stories/Nodes/NodeNameFallbacks.stories.tsx +0 -491
  86. package/src/stories/Nodes/NumericColors.stories.tsx +0 -440
  87. package/src/stories/OTEL/EventNameDisplay.stories.tsx +0 -694
  88. package/src/stories/OTEL/EventNameFallback.stories.tsx +0 -578
  89. package/src/stories/OTEL/OtelComponents.stories.tsx +0 -422
  90. package/src/stories/OTEL/OtelNodeTypesPrototype.stories.tsx +0 -806
  91. package/src/stories/OTEL/RawEventCards.stories.tsx +0 -57
  92. package/src/stories/OTEL/SpanBadges.stories.tsx +0 -840
  93. package/src/stories/OTEL/ValidationEventsCanvas.stories.tsx +0 -209
  94. package/src/stories/RealWorld/BacklogMdCanvases.stories.tsx +0 -721
  95. package/src/stories/RealWorld/FileCitySequence.stories.tsx +0 -516
  96. package/src/stories/RealWorld/RealCanvasFiles.stories.tsx +0 -336
  97. package/src/stories/__traces__/test-run.canvas.json +0 -241
  98. package/src/stories/data/activity-feed-analytics.dashboard.json +0 -300
  99. package/src/stories/data/file-city-images-transformed.otel.canvas.json +0 -295
  100. package/src/stories/data/file-city-workflows.json +0 -269
  101. package/src/stories/data/graph-converter-test-execution.json +0 -443
  102. package/src/stories/data/graph-converter-test.workflow.json +0 -105
  103. package/src/stories/data/graph-converter-validated-execution.json +0 -58
  104. package/src/utils/animationMapping.ts +0 -166
  105. package/src/utils/canvasBounds.ts +0 -146
  106. package/src/utils/graphConverter.ts +0 -148
  107. package/src/utils/iconResolver.tsx +0 -53
  108. package/src/utils/orientationUtils.ts +0 -73
@@ -1,166 +0,0 @@
1
- /**
2
- * Animation mapping utilities
3
- *
4
- * Maps log levels and component events to appropriate animations
5
- * for Milestone 1 default visualization behavior
6
- */
7
-
8
- import type { LogLevel } from '@principal-ai/subsystems-core';
9
-
10
- /**
11
- * Node animation configuration
12
- */
13
- export interface NodeAnimation {
14
- type: 'pulse' | 'flash' | 'shake' | 'entry';
15
- duration: number;
16
- intensity?: number; // 0-1 scale
17
- color?: string;
18
- }
19
-
20
- /**
21
- * Edge animation configuration
22
- */
23
- export interface EdgeAnimation {
24
- type: 'flow' | 'particle' | 'pulse' | 'glow';
25
- duration: number;
26
- direction?: 'forward' | 'backward' | 'bidirectional';
27
- color?: string;
28
- }
29
-
30
- /**
31
- * Map log level to node animation
32
- *
33
- * Default behavior for Milestone 1:
34
- * - debug: subtle pulse (low intensity)
35
- * - info: standard pulse (medium intensity)
36
- * - warn: amber pulse (high intensity)
37
- * - error: red flash + shake
38
- */
39
- export function logLevelToNodeAnimation(level: LogLevel): NodeAnimation {
40
- switch (level) {
41
- case 'debug':
42
- return {
43
- type: 'pulse',
44
- duration: 800,
45
- intensity: 0.3,
46
- color: '#94a3b8', // slate-400
47
- };
48
-
49
- case 'info':
50
- return {
51
- type: 'pulse',
52
- duration: 1000,
53
- intensity: 0.5,
54
- color: '#3b82f6', // blue-500
55
- };
56
-
57
- case 'warn':
58
- return {
59
- type: 'pulse',
60
- duration: 1200,
61
- intensity: 1.0,
62
- color: '#f59e0b', // amber-500
63
- };
64
-
65
- case 'error':
66
- return {
67
- type: 'flash', // More dramatic animation for errors
68
- duration: 1500,
69
- intensity: 1.0,
70
- color: '#ef4444', // red-500
71
- };
72
- }
73
- }
74
-
75
- /**
76
- * Map component action to node animation (Milestone 2)
77
- */
78
- export function actionToNodeAnimation(_action: string, state?: string): NodeAnimation {
79
- // Default mapping - can be overridden by configuration
80
- switch (state) {
81
- case 'acquired':
82
- case 'active':
83
- case 'processing':
84
- return {
85
- type: 'pulse',
86
- duration: 1000,
87
- intensity: 0.8,
88
- color: '#22c55e', // green-500
89
- };
90
-
91
- case 'waiting':
92
- case 'pending':
93
- return {
94
- type: 'pulse',
95
- duration: 1500,
96
- intensity: 0.5,
97
- color: '#eab308', // yellow-500
98
- };
99
-
100
- case 'error':
101
- case 'failed':
102
- return {
103
- type: 'shake',
104
- duration: 600,
105
- intensity: 1.0,
106
- color: '#ef4444', // red-500
107
- };
108
-
109
- case 'completed':
110
- case 'success':
111
- return {
112
- type: 'flash',
113
- duration: 800,
114
- intensity: 0.7,
115
- color: '#22c55e', // green-500
116
- };
117
-
118
- default:
119
- // Generic action animation
120
- return {
121
- type: 'pulse',
122
- duration: 1000,
123
- intensity: 0.6,
124
- color: '#3b82f6', // blue-500
125
- };
126
- }
127
- }
128
-
129
- /**
130
- * Get animation config for edge based on action (Milestone 2)
131
- */
132
- export function actionToEdgeAnimation(_action: string, edgeConfig?: EdgeAnimation): EdgeAnimation {
133
- // Use edge config if provided, otherwise default
134
- return (
135
- edgeConfig || {
136
- type: 'flow',
137
- duration: 2000,
138
- direction: 'forward',
139
- }
140
- );
141
- }
142
-
143
- /**
144
- * Check if log level should trigger animation
145
- */
146
- export function shouldAnimate(level: LogLevel, minLevel: LogLevel = 'info'): boolean {
147
- const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];
148
- const minIndex = levels.indexOf(minLevel);
149
- const currentIndex = levels.indexOf(level);
150
-
151
- return currentIndex >= minIndex;
152
- }
153
-
154
- /**
155
- * Calculate animation intensity based on frequency
156
- * Used to adjust animation intensity when multiple logs occur rapidly
157
- */
158
- export function calculateIntensity(
159
- baseIntensity: number,
160
- eventCount: number,
161
- _timeWindow: number = 1000
162
- ): number {
163
- // Reduce intensity if too many events in time window
164
- const scaleFactor = Math.min(1.0, 5.0 / eventCount);
165
- return Math.max(0.2, baseIntensity * scaleFactor);
166
- }
@@ -1,146 +0,0 @@
1
- import type { ExtendedCanvas } from '@principal-ai/subsystems-core';
2
-
3
- export interface CanvasBounds {
4
- minX: number;
5
- minY: number;
6
- maxX: number;
7
- maxY: number;
8
- width: number;
9
- height: number;
10
- }
11
-
12
- const DEFAULT_NODE_WIDTH = 200;
13
- const DEFAULT_NODE_HEIGHT = 100;
14
-
15
- /**
16
- * Calculate the bounding box of all nodes in a canvas.
17
- * Returns the min/max coordinates and total dimensions.
18
- */
19
- export function getCanvasBounds(canvas: ExtendedCanvas): CanvasBounds {
20
- if (!canvas.nodes || canvas.nodes.length === 0) {
21
- return {
22
- minX: 0,
23
- minY: 0,
24
- maxX: DEFAULT_NODE_WIDTH,
25
- maxY: DEFAULT_NODE_HEIGHT,
26
- width: DEFAULT_NODE_WIDTH,
27
- height: DEFAULT_NODE_HEIGHT,
28
- };
29
- }
30
-
31
- let minX = Infinity;
32
- let minY = Infinity;
33
- let maxX = -Infinity;
34
- let maxY = -Infinity;
35
-
36
- for (const node of canvas.nodes) {
37
- const x = node.x ?? 0;
38
- const y = node.y ?? 0;
39
- const width = node.width ?? DEFAULT_NODE_WIDTH;
40
- const height = node.height ?? DEFAULT_NODE_HEIGHT;
41
-
42
- minX = Math.min(minX, x);
43
- minY = Math.min(minY, y);
44
- maxX = Math.max(maxX, x + width);
45
- maxY = Math.max(maxY, y + height);
46
- }
47
-
48
- return {
49
- minX,
50
- minY,
51
- maxX,
52
- maxY,
53
- width: maxX - minX,
54
- height: maxY - minY,
55
- };
56
- }
57
-
58
- export interface Viewport {
59
- x: number;
60
- y: number;
61
- zoom: number;
62
- }
63
-
64
- /**
65
- * Calculate the initial viewport to fit all nodes within a container.
66
- * This allows setting defaultViewport on ReactFlow to avoid the
67
- * "zoom in then animate out" effect.
68
- *
69
- * @param bounds - The bounding box of all nodes
70
- * @param containerWidth - Width of the container in pixels
71
- * @param containerHeight - Height of the container in pixels
72
- * @param options - Additional options for padding and zoom limits
73
- * @returns The viewport { x, y, zoom } to pass to ReactFlow's defaultViewport
74
- */
75
- export function calculateInitialViewport(
76
- bounds: CanvasBounds,
77
- containerWidth: number,
78
- containerHeight: number,
79
- options: {
80
- padding?: number;
81
- minZoom?: number;
82
- maxZoom?: number;
83
- } = {}
84
- ): Viewport {
85
- const { padding = 0.2, minZoom = 0.1, maxZoom = 1.5 } = options;
86
-
87
- // Calculate the zoom level needed to fit the bounds within the container
88
- // Account for padding by reducing the effective container size
89
- const effectiveWidth = containerWidth * (1 - padding);
90
- const effectiveHeight = containerHeight * (1 - padding);
91
-
92
- const zoomX = effectiveWidth / bounds.width;
93
- const zoomY = effectiveHeight / bounds.height;
94
-
95
- // Use the smaller zoom to ensure both dimensions fit
96
- let zoom = Math.min(zoomX, zoomY);
97
-
98
- // Clamp to min/max zoom
99
- zoom = Math.max(minZoom, Math.min(maxZoom, zoom));
100
-
101
- // Calculate the center of the bounds
102
- const boundsCenterX = bounds.minX + bounds.width / 2;
103
- const boundsCenterY = bounds.minY + bounds.height / 2;
104
-
105
- // Calculate viewport position to center the bounds
106
- // ReactFlow viewport x,y represent the offset of the canvas origin from the container's top-left
107
- const x = containerWidth / 2 - boundsCenterX * zoom;
108
- const y = containerHeight / 2 - boundsCenterY * zoom;
109
-
110
- return { x, y, zoom };
111
- }
112
-
113
- /**
114
- * Calculate a recommended display size for a canvas cell.
115
- * Adds padding and enforces minimum dimensions.
116
- */
117
- export function getCanvasDisplaySize(
118
- canvas: ExtendedCanvas,
119
- options: {
120
- padding?: number;
121
- minWidth?: number;
122
- minHeight?: number;
123
- maxWidth?: number;
124
- maxHeight?: number;
125
- } = {}
126
- ): { width: number; height: number } {
127
- const {
128
- padding = 100,
129
- minWidth = 300,
130
- minHeight = 200,
131
- maxWidth = 1200,
132
- maxHeight = 800,
133
- } = options;
134
-
135
- const bounds = getCanvasBounds(canvas);
136
-
137
- // Add padding to content dimensions
138
- const contentWidth = bounds.width + padding * 2;
139
- const contentHeight = bounds.height + padding * 2;
140
-
141
- // Clamp to min/max
142
- const width = Math.min(maxWidth, Math.max(minWidth, contentWidth));
143
- const height = Math.min(maxHeight, Math.max(minHeight, contentHeight));
144
-
145
- return { width, height };
146
- }
@@ -1,148 +0,0 @@
1
- import { MarkerType, type Node, type Edge } from '@xyflow/react';
2
- import type {
3
- NodeState,
4
- EdgeState,
5
- GraphConfiguration,
6
- Violation,
7
- } from '@principal-ai/subsystems-core';
8
- import type { CustomNodeData } from '../nodes/CustomNode';
9
- import type { CustomEdgeData } from '../edges/CustomEdge';
10
-
11
- /**
12
- * Convert our NodeState to xyflow Node format
13
- */
14
- export function convertToXYFlowNodes(
15
- nodes: NodeState[],
16
- configuration: GraphConfiguration,
17
- violations: Violation[] = []
18
- ): Node<CustomNodeData>[] {
19
- return nodes.map((node) => {
20
- let typeDefinition = configuration.nodeTypes[node.type];
21
-
22
- // Warn if node type is not defined in configuration
23
- if (!typeDefinition) {
24
- console.warn(`Node type "${node.type}" not found in configuration for node "${node.id}"`);
25
- // Provide minimal typeDefinition with global states fallback to prevent undefined errors
26
- typeDefinition = {
27
- description: '',
28
- shape: 'rectangle',
29
- dataSchema: {},
30
- states: configuration.states,
31
- };
32
- } else if (!typeDefinition.states && configuration.states) {
33
- // If typeDefinition exists but has no states, use global states as fallback
34
- typeDefinition = {
35
- ...typeDefinition,
36
- states: configuration.states,
37
- };
38
- }
39
-
40
- const hasViolations = violations.some((v) => v.context?.nodeId === node.id);
41
-
42
- // Groups should render behind other nodes and edges
43
- const isGroup = node.data?.canvasType === 'group';
44
-
45
- return {
46
- id: node.id,
47
- type: 'custom',
48
- position: node.position || { x: 0, y: 0 },
49
- // Groups have lower zIndex to render behind other nodes
50
- // Use 1 for groups, 100 for regular nodes (positive values to avoid event handling issues)
51
- zIndex: isGroup ? 1 : 100,
52
- // Apply persisted dimensions if available
53
- ...(node.width !== undefined ? { width: node.width } : {}),
54
- ...(node.height !== undefined ? { height: node.height } : {}),
55
- data: {
56
- name: node.name || node.id,
57
- typeDefinition,
58
- state: node.state,
59
- hasViolations,
60
- data: node.data,
61
- },
62
- };
63
- });
64
- }
65
-
66
- /** Extended edge state with optional handle information for ReactFlow */
67
- export interface EdgeStateWithHandles extends EdgeState {
68
- sourceHandle?: string;
69
- targetHandle?: string;
70
- }
71
-
72
- /**
73
- * Map canvas side to source handle ID
74
- * Source handles use '-out' suffix
75
- */
76
- function sideToSourceHandle(side?: string): string | undefined {
77
- if (!side) return undefined;
78
- return `${side}-out`;
79
- }
80
-
81
- /**
82
- * Map canvas side to target handle ID
83
- * Target handles use the side name directly
84
- */
85
- function sideToTargetHandle(side?: string): string | undefined {
86
- return side;
87
- }
88
-
89
- /**
90
- * Convert our EdgeState to xyflow Edge format
91
- */
92
- export function convertToXYFlowEdges(
93
- edges: (EdgeState | EdgeStateWithHandles)[],
94
- configuration: GraphConfiguration,
95
- violations: Violation[] = []
96
- ): Edge<CustomEdgeData>[] {
97
- return edges.map((edge) => {
98
- const typeDefinition = configuration.edgeTypes[edge.type];
99
-
100
- // Warn if edge type is not defined in configuration
101
- if (!typeDefinition) {
102
- console.warn(`Edge type "${edge.type}" not found in configuration for edge "${edge.id}"`);
103
- }
104
-
105
- const hasViolations = violations.some((v) => v.context?.edgeId === edge.id);
106
- const edgeWithHandles = edge as EdgeStateWithHandles;
107
-
108
- // Get handle IDs from edge data (fromSide/toSide) or explicit handles
109
- // If no side specified, default to right->left for left-to-right flow
110
- const fromSide = edge.data?.fromSide as string | undefined;
111
- const toSide = edge.data?.toSide as string | undefined;
112
- const sourceHandle = edgeWithHandles.sourceHandle || sideToSourceHandle(fromSide || 'right');
113
- const targetHandle = edgeWithHandles.targetHandle || sideToTargetHandle(toSide || 'left');
114
-
115
- // Add arrow marker if edge type is directed
116
- // Color priority: edge data color > type definition color > default
117
- const edgeColor = edge.data?.color as string | undefined;
118
- const markerEnd =
119
- typeDefinition?.directed !== false
120
- ? {
121
- type: MarkerType.ArrowClosed,
122
- color: edgeColor || typeDefinition?.color || '#888',
123
- width: 20,
124
- height: 20,
125
- }
126
- : undefined;
127
-
128
- return {
129
- id: edge.id,
130
- source: edge.from,
131
- target: edge.to,
132
- sourceHandle,
133
- targetHandle,
134
- type: 'custom',
135
- // Edges should render above groups (zIndex: 1) but below regular nodes (zIndex: 100)
136
- zIndex: 50,
137
- animated: typeDefinition?.style === 'animated',
138
- markerEnd,
139
- data: {
140
- typeDefinition,
141
- hasViolations,
142
- data: edge.data,
143
- edgeType: edge.type,
144
- },
145
- };
146
- });
147
- }
148
-
@@ -1,53 +0,0 @@
1
- import React from 'react';
2
- import * as LucideIcons from 'lucide-react';
3
-
4
- /**
5
- * Icon Resolver
6
- *
7
- * Resolves icon strings to React components. Supports:
8
- * 1. Lucide icon names (e.g., "Database", "Settings", "Package")
9
- * 2. Emoji/Unicode strings (e.g., "⚙️", "💾", "📦")
10
- *
11
- * This keeps configurations JSON-serializable while supporting icon libraries.
12
- */
13
-
14
- export interface IconProps {
15
- icon?: string;
16
- size?: number;
17
- className?: string;
18
- }
19
-
20
- /**
21
- * Resolves an icon string to a React element
22
- *
23
- * @param icon - Icon name (Lucide) or emoji/text string
24
- * @param size - Icon size in pixels (default: 20)
25
- * @param className - Optional CSS class
26
- * @returns React element or null
27
- */
28
- export function resolveIcon(icon?: string, size: number = 20, className?: string): React.ReactNode {
29
- if (!icon) return null;
30
-
31
- // Try to resolve as Lucide icon first
32
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
- const LucideIcon = (LucideIcons as any)[icon];
34
-
35
- // Check if it's a valid React component (function or object with $$typeof)
36
- if (LucideIcon && (typeof LucideIcon === 'function' || typeof LucideIcon === 'object')) {
37
- return <LucideIcon size={size} className={className} />;
38
- }
39
-
40
- // Fall back to rendering as text (emoji or unicode)
41
- return (
42
- <span className={className} style={{ fontSize: `${size}px` }}>
43
- {icon}
44
- </span>
45
- );
46
- }
47
-
48
- /**
49
- * Icon component that resolves icon strings
50
- */
51
- export const Icon: React.FC<IconProps> = ({ icon, size = 20, className }) => {
52
- return <>{resolveIcon(icon, size, className)}</>;
53
- };
@@ -1,73 +0,0 @@
1
- import type { NodeState, EdgeState, CanvasSide } from '@principal-ai/subsystems-core';
2
-
3
- /**
4
- * Swaps the orientation of a graph from horizontal to vertical or vice versa.
5
- * This swaps x/y coordinates and adjusts edge connection sides accordingly.
6
- */
7
- export function swapGraphOrientation(
8
- nodes: NodeState[],
9
- edges: EdgeState[]
10
- ): { nodes: NodeState[]; edges: EdgeState[] } {
11
- return {
12
- nodes: swapNodePositions(nodes),
13
- edges: swapEdgeSides(edges),
14
- };
15
- }
16
-
17
- /**
18
- * Swaps x and y positions for all nodes
19
- */
20
- export function swapNodePositions(nodes: NodeState[]): NodeState[] {
21
- return nodes.map(node => ({
22
- ...node,
23
- position: node.position ? {
24
- x: node.position.y,
25
- y: node.position.x,
26
- } : undefined,
27
- }));
28
- }
29
-
30
- /**
31
- * Swaps edge connection sides to match the new orientation
32
- * Both sides rotate clockwise: top → right → bottom → left → top
33
- */
34
- export function swapEdgeSides(edges: EdgeState[]): EdgeState[] {
35
- return edges.map(edge => {
36
- const data = edge.data as Record<string, unknown> | undefined;
37
- const fromSide = data?.fromSide as CanvasSide | undefined;
38
- const toSide = data?.toSide as CanvasSide | undefined;
39
-
40
- const rotatedFromSide = rotateClockwise(fromSide);
41
- const rotatedToSide = rotateClockwise(toSide);
42
-
43
- // Only include fromSide/toSide if they have defined values (not undefined)
44
- const newData = { ...edge.data } as Record<string, unknown>;
45
- if (rotatedFromSide !== undefined) {
46
- newData.fromSide = rotatedFromSide;
47
- }
48
- if (rotatedToSide !== undefined) {
49
- newData.toSide = rotatedToSide;
50
- }
51
-
52
- return {
53
- ...edge,
54
- data: newData as EdgeState['data'],
55
- };
56
- });
57
- }
58
-
59
- /**
60
- * Rotates a side clockwise: top → right → bottom → left → top
61
- */
62
- function rotateClockwise(side?: CanvasSide): CanvasSide | undefined {
63
- if (!side) return undefined;
64
-
65
- const clockwiseMap: Record<CanvasSide, CanvasSide> = {
66
- top: 'right',
67
- right: 'bottom',
68
- bottom: 'left',
69
- left: 'top',
70
- };
71
-
72
- return clockwiseMap[side];
73
- }