@flowuent-org/diagramming-core 1.3.4 → 1.3.6

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.
@@ -55,7 +55,7 @@ export interface AutomationSlackNodeData {
55
55
  label: string;
56
56
  description: string;
57
57
  operationType?: SlackOperationType;
58
- status: 'idle' | 'running' | 'success' | 'error' | 'authenticated';
58
+ status: 'Ready' | 'Running' | 'Completed' | 'Failed' | 'Need to Config' | 'authenticated';
59
59
  parameters?: {
60
60
  // Message operations
61
61
  channel?: string;
@@ -228,11 +228,11 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
228
228
  const tokenExpired = isTokenExpired(data.parameters?.slackTokenExpiresAt);
229
229
 
230
230
  // Execution state
231
- const status = data.status || 'idle';
232
- const executionProgress = status === 'running' ? 50 : 0;
231
+ const status = data.status || 'Ready';
232
+ const executionProgress = status === 'Running' ? 50 : 0;
233
233
 
234
234
  // Status configuration - using centralized status colors
235
- const statusConfig = getStatusColor(status, status === 'authenticated' ? 'authenticated' : 'idle');
235
+ const statusConfig = getStatusColor(status, status === 'authenticated' ? 'authenticated' : 'ready');
236
236
 
237
237
  // Handle JSON view
238
238
  const handleJsonClick = () => {
@@ -342,8 +342,8 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
342
342
  size="small"
343
343
  label={
344
344
  <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
345
- {status === 'success' && <CheckCircleIcon sx={{ fontSize: 12 }} />}
346
- {status === 'error' && <ErrorIcon sx={{ fontSize: 12 }} />}
345
+ {(status === 'Completed' || status === 'success') && <CheckCircleIcon sx={{ fontSize: 12 }} />}
346
+ {(status === 'Failed' || status === 'error') && <ErrorIcon sx={{ fontSize: 12 }} />}
347
347
  <span>{statusConfig.label}</span>
348
348
  </Box>
349
349
  }
@@ -522,7 +522,7 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
522
522
  </Box>
523
523
 
524
524
  {/* Progress Bar (when running) */}
525
- {status === 'running' && (
525
+ {status === 'Running' && (
526
526
  <Box sx={{ mb: 1.5 }}>
527
527
  <LinearProgress
528
528
  variant="determinate"
@@ -58,7 +58,7 @@ export interface AutomationTelegramNodeData {
58
58
  label: string;
59
59
  description: string;
60
60
  operationType?: TelegramOperationType;
61
- status: 'idle' | 'running' | 'success' | 'error' | 'authenticated';
61
+ status: 'Ready' | 'Running' | 'Completed' | 'Failed' | 'Need to Config' | 'authenticated';
62
62
  parameters?: {
63
63
  // Message operations
64
64
  chatId?: string;
@@ -221,11 +221,11 @@ export const AutomationTelegramNode: React.FC<AutomationTelegramNodeProps> = ({
221
221
  const botId = data.telegramAuth?.botId || data.parameters?.botId;
222
222
 
223
223
  // Execution state
224
- const status = data.status || 'idle';
225
- const executionProgress = status === 'running' ? 50 : 0;
224
+ const status = data.status || 'Ready';
225
+ const executionProgress = status === 'Running' ? 50 : 0;
226
226
 
227
227
  // Status configuration - using centralized status colors
228
- const statusConfig = getStatusColor(status, status === 'authenticated' ? 'authenticated' : 'idle');
228
+ const statusConfig = getStatusColor(status, status === 'authenticated' ? 'authenticated' : 'ready');
229
229
 
230
230
  // Handle JSON view
231
231
  const handleJsonClick = () => {
@@ -335,8 +335,8 @@ export const AutomationTelegramNode: React.FC<AutomationTelegramNodeProps> = ({
335
335
  size="small"
336
336
  label={
337
337
  <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
338
- {status === 'success' && <CheckCircleIcon sx={{ fontSize: 12 }} />}
339
- {status === 'error' && <ErrorIcon sx={{ fontSize: 12 }} />}
338
+ {(status === 'Completed' || status === 'success') && <CheckCircleIcon sx={{ fontSize: 12 }} />}
339
+ {(status === 'Failed' || status === 'error') && <ErrorIcon sx={{ fontSize: 12 }} />}
340
340
  <span>{statusConfig.label}</span>
341
341
  </Box>
342
342
  }
@@ -515,7 +515,7 @@ export const AutomationTelegramNode: React.FC<AutomationTelegramNodeProps> = ({
515
515
  </Box>
516
516
 
517
517
  {/* Progress Bar (when running) */}
518
- {status === 'running' && (
518
+ {status === 'Running' && (
519
519
  <Box sx={{ mb: 1.5 }}>
520
520
  <LinearProgress
521
521
  variant="determinate"
@@ -17,8 +17,9 @@ export interface AutomationStatusColors {
17
17
  completed: StatusColorConfig;
18
18
  success: StatusColorConfig; // Alias for completed (used in some nodes)
19
19
  error: StatusColorConfig;
20
+ failed: StatusColorConfig; // Alias for error
20
21
  ready: StatusColorConfig;
21
- idle: StatusColorConfig;
22
+ 'need to config': StatusColorConfig; // Status when configuration is needed
22
23
  authenticated?: StatusColorConfig; // Optional, used in auth-enabled nodes
23
24
  }
24
25
 
@@ -28,8 +29,9 @@ export interface AutomationStatusColors {
28
29
  * Colors are designed to be visually distinct and accessible:
29
30
  * - Running: Blue (#2563EB) - indicates active execution
30
31
  * - Completed/Success: Yellow (#FCD34D) - indicates successful completion
31
- * - Error: Red (#EF4444) - indicates failure
32
- * - Ready/Idle: Gray/Green - indicates waiting state
32
+ * - Error/Failed: Red (#EF4444) - indicates failure
33
+ * - Ready: Green (#86EFAC) - indicates ready to execute
34
+ * - Need to Config: Yellow (#FBBF24) - indicates configuration required
33
35
  */
34
36
  export const automationStatusColors: AutomationStatusColors = {
35
37
  running: {
@@ -52,15 +54,20 @@ export const automationStatusColors: AutomationStatusColors = {
52
54
  bgColor: 'rgba(239, 68, 68, 0.1)',
53
55
  label: 'Error',
54
56
  },
57
+ failed: {
58
+ color: '#EF4444',
59
+ bgColor: 'rgba(239, 68, 68, 0.1)',
60
+ label: 'Failed',
61
+ },
55
62
  ready: {
56
63
  color: '#86EFAC',
57
64
  bgColor: 'rgba(16, 185, 129, 0.1)',
58
65
  label: 'Ready',
59
66
  },
60
- idle: {
61
- color: '#6B7280',
62
- bgColor: 'rgba(107, 114, 128, 0.1)',
63
- label: 'Idle',
67
+ 'need to config': {
68
+ color: '#FBBF24',
69
+ bgColor: 'rgba(251, 191, 36, 0.1)',
70
+ label: 'Need to Config',
64
71
  },
65
72
  authenticated: {
66
73
  color: '#0088cc',
@@ -88,15 +95,23 @@ export const getStatusColor = (
88
95
  'completed': 'completed',
89
96
  'success': 'success',
90
97
  'error': 'error',
91
- 'failed': 'error',
98
+ 'failed': 'failed',
92
99
  'ready': 'ready',
93
- 'idle': 'idle',
100
+ 'idle': 'ready', // Map idle to ready
101
+ 'need to config': 'need to config',
102
+ 'need_to_config': 'need to config',
103
+ 'not_ready': 'need to config',
104
+ 'not ready': 'need to config',
94
105
  'authenticated': 'authenticated',
95
106
  'connected': 'authenticated',
96
107
  };
97
108
 
98
109
  const configKey = statusMap[normalizedStatus] || defaultStatus;
99
- return automationStatusColors[configKey] || automationStatusColors[defaultStatus];
110
+ const config = automationStatusColors[configKey];
111
+ if (config) {
112
+ return config;
113
+ }
114
+ return automationStatusColors[defaultStatus];
100
115
  };
101
116
 
102
117
  /**
@@ -66,6 +66,7 @@ export interface DiagramState {
66
66
  nodes: ICardNode[];
67
67
  defaultNodes: ICardNode[];
68
68
  dragStartPosition: DragStartPosition | null;
69
+ dragStartPositions: Record<string, { x: number; y: number }> | null;
69
70
  edges: Edge[];
70
71
  defaultEdges: Edge[];
71
72
  history: HistoryEvent[];
@@ -20,9 +20,26 @@ export const onNodeDragStart =
20
20
  positionAbsolute: cardNode.position,
21
21
  };
22
22
 
23
+ // Store initial positions for ALL selected nodes (for multi-node dragging)
24
+ // Get selected nodes from state.nodes (they should have selected property set by ReactFlow)
25
+ const selectedNodes = state.nodes.filter(n => (n as any).selected);
26
+ let dragStartPositions: Record<string, { x: number; y: number }> | null = null;
27
+
28
+ if (selectedNodes.length > 1) {
29
+ // Multiple nodes are selected - store all their initial positions
30
+ dragStartPositions = {};
31
+ selectedNodes.forEach(selectedNode => {
32
+ dragStartPositions![selectedNode.id] = {
33
+ x: selectedNode.position.x,
34
+ y: selectedNode.position.y,
35
+ };
36
+ });
37
+ }
38
+
23
39
  return {
24
40
  ...state,
25
41
  dragStartPosition: changeForUndo,
42
+ dragStartPositions: dragStartPositions || null,
26
43
  };
27
44
  });
28
45
  };
@@ -18,10 +18,101 @@ export const onNodeDragEnd =
18
18
  const cardNode = node as ICardNode;
19
19
  set((state: DiagramStore) => {
20
20
  const dragStartPosition = state.dragStartPosition;
21
+ const dragStartPositions = state.dragStartPositions;
21
22
 
22
- // If there's no drag start position, just return the state unchanged
23
+ // Get all currently selected nodes from state (they should have selected property)
24
+ // Also get the current node's position from ReactFlow (cardNode parameter)
25
+ const selectedNodesInState = state.nodes.filter(n => (n as any).selected);
26
+ const isMultiDrag = dragStartPositions && Object.keys(dragStartPositions).length > 1;
27
+
28
+ // If multiple nodes were dragged, handle all of them
29
+ if (isMultiDrag && dragStartPositions) {
30
+ const historyEvents: HistoryEvent[] = [];
31
+ const updatedNodes = state.nodes.map((n) => {
32
+ // Check if this node was part of the multi-drag
33
+ if (dragStartPositions![n.id]) {
34
+ const startPos = dragStartPositions[n.id];
35
+ // For the dragged node, use position from ReactFlow's node parameter (most up-to-date)
36
+ // For other nodes, use position from state.nodes (updated by onNodesChange)
37
+ let endPos = n.position;
38
+ if (n.id === cardNode.id) {
39
+ // Use the position from ReactFlow for the node being dragged
40
+ endPos = cardNode.position;
41
+ }
42
+
43
+ // Check if this node moved
44
+ if (startPos.x !== endPos.x || startPos.y !== endPos.y) {
45
+ // Create history event for this node
46
+ historyEvents.push({
47
+ title: `Node '${n.id}' moved from (${startPos.x.toFixed(1)}, ${startPos.y.toFixed(1)}) to (${endPos.x.toFixed(1)}, ${endPos.y.toFixed(1)})`,
48
+ backward: {
49
+ t: 'node-change',
50
+ type: 'position',
51
+ id: n.id,
52
+ message: `Revert: Node '${n.id}' position back to (${startPos.x.toFixed(1)}, ${startPos.y.toFixed(1)})`,
53
+ position: startPos,
54
+ positionAbsolute: startPos,
55
+ },
56
+ forward: {
57
+ t: 'node-change',
58
+ type: 'position',
59
+ id: n.id,
60
+ message: `Redo: Node '${n.id}' moved to (${endPos.x}, ${endPos.y})`,
61
+ position: endPos,
62
+ positionAbsolute: endPos,
63
+ },
64
+ });
65
+
66
+ // Update node position - ensure it matches ReactFlow's current state
67
+ return {
68
+ ...n,
69
+ position: endPos,
70
+ data: { ...n.data, isPinned: true },
71
+ };
72
+ }
73
+ }
74
+ return n;
75
+ });
76
+
77
+ // Trigger onChange callbacks for all moved nodes
78
+ if (onChange && historyEvents.length > 0) {
79
+ historyEvents.forEach((historyEvent) => {
80
+ onChange({
81
+ t: 'node-change',
82
+ type: 'position',
83
+ id: historyEvent.forward.id,
84
+ position: historyEvent.forward.position,
85
+ positionAbsolute: historyEvent.forward.positionAbsolute,
86
+ message: historyEvent.title,
87
+ });
88
+ });
89
+ }
90
+
91
+ return {
92
+ ...addToHistory(state, historyEvents),
93
+ dragStartPosition: null,
94
+ dragStartPositions: null,
95
+ nodes: updatedNodes,
96
+ };
97
+ }
98
+
99
+ // Single node drag handling (original logic)
23
100
  if (!dragStartPosition) {
24
- return state;
101
+ // Ensure the node's current position is persisted in state
102
+ const nodeInState = state.nodes.find((n) => n.id === cardNode.id);
103
+ if (nodeInState && (
104
+ nodeInState.position.x !== cardNode.position.x ||
105
+ nodeInState.position.y !== cardNode.position.y
106
+ )) {
107
+ // Update the node position to match the dragged position
108
+ const updatedNodes = state.nodes.map((n) =>
109
+ n.id === cardNode.id
110
+ ? { ...n, position: cardNode.position, data: { ...n.data, isPinned: true } }
111
+ : n,
112
+ );
113
+ return { ...state, nodes: updatedNodes, dragStartPositions: null };
114
+ }
115
+ return { ...state, dragStartPositions: null };
25
116
  }
26
117
 
27
118
  // Check if the node has moved
@@ -29,17 +120,21 @@ export const onNodeDragEnd =
29
120
  dragStartPosition.position.x !== cardNode.position.x ||
30
121
  dragStartPosition.position.y !== cardNode.position.y
31
122
  ) {
32
- const updatedNodes = state.nodes.map((n) =>
33
- n.id === cardNode.id
34
- ? { ...n, data: { ...n.data, isPinned: true } }
35
- : n,
36
- );
123
+ // Update nodes array to ensure position is persisted
124
+ const updatedNodes = state.nodes.map((n) => {
125
+ if (n.id === cardNode.id) {
126
+ return {
127
+ ...n,
128
+ position: cardNode.position,
129
+ data: { ...n.data, isPinned: true }
130
+ };
131
+ }
132
+ return n;
133
+ });
37
134
 
38
- // Create the HistoryEvent entry with forward and backward changes
135
+ // Create the HistoryEvent entry
39
136
  const historyEvent: HistoryEvent = {
40
137
  title: `Node '${cardNode.id}' moved from (${dragStartPosition.position.x.toFixed(1)}, ${dragStartPosition.position.y.toFixed(1)}) to (${cardNode.position.x.toFixed(1)}, ${cardNode.position.y.toFixed(1)})`,
41
-
42
- // Backward change for undo
43
138
  backward: {
44
139
  t: 'node-change',
45
140
  type: 'position',
@@ -48,8 +143,6 @@ export const onNodeDragEnd =
48
143
  position: dragStartPosition.position,
49
144
  positionAbsolute: dragStartPosition.position,
50
145
  },
51
-
52
- // Forward change for redo
53
146
  forward: {
54
147
  t: 'node-change',
55
148
  type: 'position',
@@ -74,12 +167,13 @@ export const onNodeDragEnd =
74
167
 
75
168
  return {
76
169
  ...addToHistory(state, [historyEvent]),
77
- dragStartPosition: null, // Clear the drag start position
170
+ dragStartPosition: null,
171
+ dragStartPositions: null,
78
172
  nodes: updatedNodes,
79
173
  };
80
174
  }
81
175
 
82
- // If no movement occurred, just reset dragStartPosition without updating history
83
- return { ...state, dragStartPosition: null };
176
+ // If no movement occurred, just reset dragStartPosition
177
+ return { ...state, dragStartPosition: null, dragStartPositions: null };
84
178
  });
85
179
  };
@@ -277,24 +277,44 @@ function handleElementChanges(
277
277
 
278
278
  const currentElements = elementType === 'node' ? get().nodes : get().edges;
279
279
 
280
+ // Separate position changes from other changes
281
+ const positionChanges: NodePositionChange[] = [];
282
+ const dimensionChanges: NodeDimensionChange[] = [];
283
+ const otherChanges: (NodeChange | EdgeChange)[] = [];
284
+
280
285
  changes.forEach((change) => {
281
- if (change.type === 'position' || change.type === 'dimensions') {
282
- return;
286
+ if (change.type === 'position') {
287
+ positionChanges.push(change as NodePositionChange);
288
+ } else if (change.type === 'dimensions') {
289
+ dimensionChanges.push(change as NodeDimensionChange);
283
290
  } else if (change.type === 'replace') {
284
291
  accumulatedResetChanges.push(change as ResetChange & { t: string });
285
292
  } else {
286
- const historyEvent = createHistoryEvent(
287
- change,
288
- currentElements,
289
- elementType,
290
- );
291
- if (historyEvent) {
292
- newChanges.push(historyEvent.forward);
293
- newHistory.push(historyEvent);
294
- }
293
+ otherChanges.push(change);
294
+ }
295
+ });
296
+
297
+ // Handle other changes (add, remove, etc.)
298
+ otherChanges.forEach((change) => {
299
+ const historyEvent = createHistoryEvent(
300
+ change,
301
+ currentElements,
302
+ elementType,
303
+ );
304
+ if (historyEvent) {
305
+ newChanges.push(historyEvent.forward);
306
+ newHistory.push(historyEvent);
295
307
  }
296
308
  });
297
309
 
310
+ // Handle position changes - apply them but don't add to history
311
+ // History will be handled by onNodeDragEnd for individual nodes
312
+ // For multiple nodes, we need to ensure positions are applied
313
+ if (positionChanges.length > 0 && elementType === 'node') {
314
+ // Apply position changes immediately to ensure they persist
315
+ // Don't add to history here as onNodeDragEnd will handle it
316
+ }
317
+
298
318
  if (accumulatedResetChanges.length > 0) {
299
319
  const [resetChangesToEmit, historyChanges] = handleAccumulatedResets(
300
320
  accumulatedResetChanges,
@@ -310,6 +330,7 @@ function handleElementChanges(
310
330
  newChanges.forEach((change) => onChange(change));
311
331
  }
312
332
 
333
+ // Apply ALL changes including position changes to ensure they persist
313
334
  const updatedElements =
314
335
  elementType === 'node'
315
336
  ? applyNodeChanges(
@@ -1,25 +1,113 @@
1
1
  import React from 'react';
2
- import { Typography } from '@mui/material';
3
- import { useNodeTitle, useNodeType } from '../../contexts/CardDataProvider';
2
+ import { Typography, Box } from '@mui/material';
3
+ import { useNodeTitle, useNodeType, useNodeVariant } from '../../contexts/CardDataProvider';
4
+ import { NodeVariant } from '../../types/card-node';
5
+ import {
6
+ ParallelIcon,
7
+ BranchIcon,
8
+ GroupIcon,
9
+ ReturnIcon,
10
+ LoopIcon,
11
+ SwitchIcon,
12
+ TryIcon,
13
+ CatchIcon,
14
+ TryCatchIcon,
15
+ FunctionIcon,
16
+ CallIcon,
17
+ LetIcon,
18
+ SetIcon,
19
+ EmptyNodeIcon,
20
+ EntityIcon,
21
+ } from '@flowuent-labs/molecules';
22
+
23
+ // Icon mapping for different node types
24
+ const getNodeIcon = (type: NodeVariant | string | undefined) => {
25
+ if (!type) return null;
26
+
27
+ const iconStyle = {
28
+ marginRight: '6px',
29
+ display: 'inline-block',
30
+ verticalAlign: 'middle',
31
+ flexShrink: 0,
32
+ };
33
+
34
+ switch (type) {
35
+ case 'parallel':
36
+ return <ParallelIcon size={15} color="#86EFAC" style={iconStyle} />;
37
+ case 'branch':
38
+ return <BranchIcon size={15} color="#86EFAC" style={iconStyle} />;
39
+ case 'group':
40
+ return <GroupIcon size={15} color="#86EFAC" style={iconStyle} />;
41
+ case 'return':
42
+ return <ReturnIcon size={15} color="#86EFAC" style={iconStyle} />;
43
+ case 'loop':
44
+ case 'forLoop':
45
+ return <LoopIcon size={15} color="#86EFAC" style={iconStyle} />;
46
+ case 'switch':
47
+ return <SwitchIcon size={15} color="#86EFAC" style={iconStyle} />;
48
+ case 'try':
49
+ return <TryIcon size={15} color="#86EFAC" style={iconStyle} />;
50
+ case 'catch':
51
+ return <CatchIcon size={15} color="#86EFAC" style={iconStyle} />;
52
+ case 'tryCatch':
53
+ return <TryCatchIcon size={15} color="#86EFAC" style={iconStyle} />;
54
+ case 'function':
55
+ return <FunctionIcon size={15} color="#86EFAC" style={iconStyle} />;
56
+ case 'call':
57
+ return <CallIcon size={15} color="#86EFAC" style={iconStyle} />;
58
+ case 'let':
59
+ return <LetIcon size={15} color="#86EFAC" style={iconStyle} />;
60
+ case 'set':
61
+ return <SetIcon size={15} color="#86EFAC" style={iconStyle} />;
62
+ case 'emptyNode':
63
+ return <EmptyNodeIcon size={15} color="#86EFAC" style={iconStyle} />;
64
+ case 'entity':
65
+ return <EntityIcon size={15} color="#86EFAC" style={iconStyle} />;
66
+ default:
67
+ return null;
68
+ }
69
+ };
4
70
 
5
71
  const NodeTypeDisplay = React.memo(() => {
6
72
  const type = useNodeType();
73
+ const variant = useNodeVariant();
7
74
  const title = useNodeTitle();
75
+ // Get type from data.type or data.variant as fallback
76
+ const nodeType = (type || variant) as NodeVariant | string | undefined;
77
+ const icon = getNodeIcon(nodeType as NodeVariant);
78
+
8
79
  return (
9
- <Typography
10
- sx={{
11
- textTransform: 'capitalize',
12
- // fontWeight: 'bold',
13
- fontStyle: 'italic',
14
- fontSize: 12,
15
- color: '#FFFFFF',
16
- }}
17
- >
18
- {`[${type}]`}
19
- {(title === 'Then' || title === 'Else') && (
20
- <span >{': ' + title}</span>
80
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'nowrap' }}>
81
+ {icon && (
82
+ <Box
83
+ component="span"
84
+ sx={{
85
+ display: 'inline-flex',
86
+ alignItems: 'center',
87
+ justifyContent: 'center',
88
+ flexShrink: 0,
89
+ lineHeight: 1,
90
+ }}
91
+ >
92
+ {icon}
93
+ </Box>
21
94
  )}
22
- </Typography>
95
+ <Typography
96
+ sx={{
97
+ textTransform: 'capitalize',
98
+ fontStyle: 'italic',
99
+ fontSize: 12,
100
+ color: '#FFFFFF',
101
+ display: 'inline-flex',
102
+ alignItems: 'center',
103
+ }}
104
+ >
105
+ {`[${nodeType || 'unknown'}]`}
106
+ {(title === 'Then' || title === 'Else') && (
107
+ <span>{': ' + title}</span>
108
+ )}
109
+ </Typography>
110
+ </Box>
23
111
  );
24
112
  });
25
113
 
@@ -0,0 +1,23 @@
1
+ // Re-export all icons from molecules package
2
+ export {
3
+ ParallelIcon,
4
+ BranchIcon,
5
+ GroupIcon,
6
+ ReturnIcon,
7
+ LoopIcon,
8
+ SwitchIcon,
9
+ TryIcon,
10
+ CatchIcon,
11
+ TryCatchIcon,
12
+ FunctionIcon,
13
+ CallIcon,
14
+ LetIcon,
15
+ SetIcon,
16
+ EmptyNodeIcon,
17
+ EntityIcon,
18
+ StartNodeIcon,
19
+ NavigationIcon,
20
+ } from '@flowuent-labs/molecules';
21
+
22
+ // Re-export types for backward compatibility
23
+ export type { SvgIconProps as IconProps } from '@flowuent-labs/molecules';