@principal-ai/principal-view-react 0.13.7 → 0.13.8

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 (37) hide show
  1. package/dist/components/GraphRenderer.d.ts +1 -15
  2. package/dist/components/GraphRenderer.d.ts.map +1 -1
  3. package/dist/components/GraphRenderer.js +94 -269
  4. package/dist/components/GraphRenderer.js.map +1 -1
  5. package/dist/components/NodeInfoPanel.d.ts.map +1 -1
  6. package/dist/components/NodeInfoPanel.js.map +1 -1
  7. package/dist/edges/CustomEdge.d.ts +2 -2
  8. package/dist/edges/CustomEdge.d.ts.map +1 -1
  9. package/dist/edges/CustomEdge.js +5 -4
  10. package/dist/edges/CustomEdge.js.map +1 -1
  11. package/dist/hooks/usePathBasedEvents.d.ts.map +1 -1
  12. package/dist/hooks/usePathBasedEvents.js +2 -1
  13. package/dist/hooks/usePathBasedEvents.js.map +1 -1
  14. package/dist/index.d.ts +0 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +0 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/nodes/CustomNode.d.ts +2 -2
  19. package/dist/nodes/CustomNode.d.ts.map +1 -1
  20. package/dist/nodes/CustomNode.js +41 -10
  21. package/dist/nodes/CustomNode.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/components/GraphRenderer.tsx +106 -354
  24. package/src/edges/CustomEdge.tsx +8 -7
  25. package/src/hooks/usePathBasedEvents.ts +2 -1
  26. package/src/index.ts +0 -6
  27. package/src/nodes/CustomNode.tsx +50 -13
  28. package/src/stories/ColorPriority.stories.tsx +2 -0
  29. package/src/stories/EventDrivenAnimations.stories.tsx +332 -326
  30. package/src/stories/GraphRenderer.stories.tsx +2 -2
  31. package/src/stories/NodeDefinitionComparison.stories.tsx +1 -1
  32. package/src/stories/NodeDimensionsTesting.stories.tsx +2 -2
  33. package/src/stories/OtelComponents.stories.tsx +0 -47
  34. package/src/stories/data/graph-converter-test-execution.json +244 -26
  35. package/src/stories/data/graph-converter-validated-execution.json +6 -6
  36. package/src/components/EdgeInfoPanel.tsx +0 -247
  37. package/src/components/NodeInfoPanel.tsx +0 -724
@@ -1,724 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { NodeState, NodeTypeDefinition, JsonValue, PVEventSchema } from '@principal-ai/principal-view-core';
3
- import { useTheme } from '@principal-ade/industry-theme';
4
- import { resolveIcon } from '../utils/iconResolver';
5
-
6
- // Common icons for the icon selector
7
- const COMMON_ICONS = [
8
- 'Settings',
9
- 'Database',
10
- 'Package',
11
- 'Server',
12
- 'Cloud',
13
- 'Globe',
14
- 'File',
15
- 'Folder',
16
- 'Code',
17
- 'Terminal',
18
- 'Cpu',
19
- 'HardDrive',
20
- 'Network',
21
- 'Wifi',
22
- 'Lock',
23
- 'Unlock',
24
- 'Key',
25
- 'Shield',
26
- 'User',
27
- 'Users',
28
- 'Mail',
29
- 'MessageSquare',
30
- 'Bell',
31
- 'Calendar',
32
- 'Clock',
33
- 'Timer',
34
- 'Zap',
35
- 'Activity',
36
- 'BarChart',
37
- 'PieChart',
38
- 'CheckCircle',
39
- 'XCircle',
40
- 'AlertCircle',
41
- 'Info',
42
- 'HelpCircle',
43
- 'Play',
44
- 'Pause',
45
- 'Square',
46
- 'Circle',
47
- 'Triangle',
48
- 'Hexagon',
49
- 'Box',
50
- 'Layers',
51
- 'GitBranch',
52
- 'GitCommit',
53
- 'GitMerge',
54
- 'GitPullRequest',
55
- ];
56
-
57
- export interface NodeInfoPanelProps {
58
- node: NodeState;
59
- typeDefinition: NodeTypeDefinition;
60
- /** Available node types for the type selector */
61
- availableNodeTypes?: Record<string, NodeTypeDefinition>;
62
- onClose: () => void;
63
- /** Optional callback to delete the node. If not provided, delete button is hidden. */
64
- onDelete?: (nodeId: string) => void;
65
- /** Optional callback to update the node. If not provided, edit fields are disabled. */
66
- onUpdate?: (nodeId: string, updates: { type?: string; data?: Record<string, JsonValue> }) => void;
67
- /** Optional callback to resolve event references to full event schemas */
68
- resolveEventRef?: (eventRef: string) => PVEventSchema | undefined;
69
- }
70
-
71
- /**
72
- * Panel that displays information about a selected node with optional editing
73
- */
74
- export const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
75
- node,
76
- typeDefinition,
77
- availableNodeTypes,
78
- onClose,
79
- onDelete,
80
- onUpdate,
81
- resolveEventRef,
82
- }) => {
83
- const { theme } = useTheme();
84
-
85
- // Color priority: node data color > type definition color > theme primary
86
- const nodeColor = (node.data?.color as string) || typeDefinition?.color || theme.colors.primary;
87
- const canEdit = Boolean(onUpdate);
88
-
89
- // Local state for UI
90
- const [showIconPicker, setShowIconPicker] = useState(false);
91
- const [showDetails, setShowDetails] = useState(false);
92
- const [isExpanded, setIsExpanded] = useState(false);
93
-
94
- // Current icon - either from node data override or type definition
95
- const currentIcon = (node.data?.icon as string) || typeDefinition?.icon;
96
-
97
- // Find the name field from data schema
98
- const nameField = typeDefinition?.dataSchema
99
- ? Object.entries(typeDefinition.dataSchema).find(([, schema]) => schema.displayInLabel)?.[0]
100
- : null;
101
-
102
- // Get fields to display based on dataSchema
103
- const displayFields = typeDefinition?.dataSchema
104
- ? Object.entries(typeDefinition.dataSchema)
105
- .filter(([, schema]) => schema.displayInLabel)
106
- .map(([field]) => ({
107
- field,
108
- label: field,
109
- value: node.data?.[field],
110
- }))
111
- : [];
112
-
113
- // Always show basic node data if no schema is defined
114
- const hasSchemaFields = displayFields.length > 0;
115
- // Internal fields that should not be displayed in the popup
116
- const internalFields = [
117
- 'icon',
118
- 'name',
119
- 'description',
120
- 'sources',
121
- 'color',
122
- 'stroke',
123
- 'width',
124
- 'height',
125
- 'canvasType',
126
- 'text',
127
- 'file',
128
- 'url',
129
- 'shape',
130
- 'states',
131
- 'actions',
132
- 'nodeType',
133
- 'otel',
134
- 'resourceMatch',
135
- 'event', // Handle separately in Event section
136
- 'eventRef', // Handle separately in Event section
137
- ];
138
-
139
- // Extract Event metadata
140
- const eventInfo = node.data?.event as unknown as PVEventSchema | undefined;
141
- const eventRef = node.data?.eventRef as unknown as string | undefined;
142
-
143
- // Try to resolve eventRef to full event schema if callback is provided
144
- const resolvedEvent = eventRef && resolveEventRef ? resolveEventRef(eventRef) : undefined;
145
-
146
- const nodeDataEntries = node.data
147
- ? Object.entries(node.data).filter(([key]) => !internalFields.includes(key))
148
- : [];
149
-
150
- const handleTypeChange = (newType: string) => {
151
- if (onUpdate && newType !== node.type) {
152
- onUpdate(node.id, { type: newType });
153
- }
154
- };
155
-
156
- const handleIconSelect = (iconName: string) => {
157
- if (onUpdate) {
158
- onUpdate(node.id, {
159
- data: { ...node.data, icon: iconName },
160
- });
161
- }
162
- setShowIconPicker(false);
163
- };
164
-
165
- return (
166
- <div
167
- style={{
168
- position: 'absolute',
169
- bottom: 0,
170
- left: 0,
171
- right: 0,
172
- backgroundColor: theme.colors.background,
173
- color: theme.colors.text,
174
- borderTop: `2px solid ${nodeColor}`,
175
- borderBottom: `2px solid ${nodeColor}`,
176
- boxShadow: '0 -4px 12px rgba(0,0,0,0.15)',
177
- padding: '16px 24px',
178
- zIndex: 1000,
179
- maxHeight: isExpanded ? '90%' : '40%',
180
- overflowY: 'auto',
181
- transition: 'max-height 0.3s ease',
182
- }}
183
- >
184
- {/* Header - shows node name */}
185
- <div
186
- style={{
187
- display: 'flex',
188
- justifyContent: 'space-between',
189
- alignItems: 'center',
190
- marginBottom: '16px',
191
- }}
192
- >
193
- <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flex: 1 }}>
194
- <div style={{ fontWeight: theme.fontWeights.bold, fontSize: theme.fontSizes[2], fontFamily: theme.fonts.body, color: nodeColor }}>
195
- {node.name || node.id}
196
- </div>
197
- </div>
198
- <div style={{ display: 'flex', gap: '4px' }}>
199
- <button
200
- onClick={() => setIsExpanded(!isExpanded)}
201
- style={{
202
- border: 'none',
203
- background: 'none',
204
- cursor: 'pointer',
205
- fontSize: theme.fontSizes[2],
206
- fontFamily: theme.fonts.body,
207
- color: theme.colors.textSecondary,
208
- padding: '4px',
209
- width: '28px',
210
- height: '28px',
211
- display: 'flex',
212
- alignItems: 'center',
213
- justifyContent: 'center',
214
- borderRadius: '4px',
215
- transition: 'background-color 0.15s',
216
- marginTop: '-8px',
217
- }}
218
- onMouseEnter={(e) => {
219
- e.currentTarget.style.backgroundColor = theme.colors.muted;
220
- }}
221
- onMouseLeave={(e) => {
222
- e.currentTarget.style.backgroundColor = 'transparent';
223
- }}
224
- title={isExpanded ? 'Collapse panel' : 'Expand panel'}
225
- >
226
- {isExpanded ? '▼' : '▲'}
227
- </button>
228
- <button
229
- onClick={onClose}
230
- style={{
231
- border: 'none',
232
- background: 'none',
233
- cursor: 'pointer',
234
- fontSize: theme.fontSizes[3],
235
- fontFamily: theme.fonts.body,
236
- color: theme.colors.textSecondary,
237
- padding: '4px',
238
- width: '28px',
239
- height: '28px',
240
- display: 'flex',
241
- alignItems: 'center',
242
- justifyContent: 'center',
243
- borderRadius: '4px',
244
- transition: 'background-color 0.15s',
245
- marginRight: '-8px',
246
- marginTop: '-8px',
247
- }}
248
- onMouseEnter={(e) => {
249
- e.currentTarget.style.backgroundColor = theme.colors.muted;
250
- }}
251
- onMouseLeave={(e) => {
252
- e.currentTarget.style.backgroundColor = 'transparent';
253
- }}
254
- >
255
- ×
256
- </button>
257
- </div>
258
- </div>
259
-
260
- {/* Content */}
261
- <div>
262
- {/* Description - first field under header */}
263
- {node.data?.description && (
264
- <div style={{ marginBottom: '12px' }}>
265
- <div style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body, color: theme.colors.textSecondary, marginBottom: '4px' }}>
266
- Description
267
- </div>
268
- <div style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body }}>{String(node.data.description)}</div>
269
- </div>
270
- )}
271
-
272
-
273
- {/* Event Info */}
274
- {(eventInfo || eventRef || resolvedEvent) && (
275
- <div style={{ marginBottom: '12px', marginTop: '-4px' }}>
276
- {(() => {
277
- // Use resolved event if available, otherwise use inline eventInfo
278
- const displayEvent = resolvedEvent || eventInfo;
279
-
280
- if (displayEvent) {
281
- // Show full event with schema
282
- return (
283
- <div>
284
- <div
285
- style={{
286
- fontSize: theme.fontSizes[1],
287
- fontFamily: 'monospace',
288
- color: theme.colors.primary,
289
- marginBottom: '4px',
290
- }}
291
- >
292
- {displayEvent.name}
293
- </div>
294
- {displayEvent.description && (
295
- <div style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body, color: theme.colors.textSecondary, marginTop: '4px', marginBottom: '8px' }}>
296
- {displayEvent.description}
297
- </div>
298
- )}
299
- {displayEvent.attributes && Object.keys(displayEvent.attributes).length > 0 && (
300
- <div style={{ marginTop: '16px' }}>
301
- <div style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body, color: theme.colors.textSecondary, marginBottom: '8px' }}>
302
- Event Attributes
303
- </div>
304
- <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
305
- {Object.entries(displayEvent.attributes).map(([attrName, attrSchema]: [string, any]) => (
306
- <div
307
- key={attrName}
308
- style={{
309
- padding: '8px 12px',
310
- backgroundColor: theme.colors.muted,
311
- borderRadius: '4px',
312
- border: `1px solid ${theme.colors.border}`,
313
- }}
314
- >
315
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
316
- <span
317
- style={{
318
- fontSize: theme.fontSizes[0],
319
- fontFamily: 'monospace',
320
- fontWeight: theme.fontWeights.semibold,
321
- color: theme.colors.text,
322
- }}
323
- >
324
- {attrName}
325
- </span>
326
- <span
327
- style={{
328
- fontSize: '10px',
329
- fontFamily: theme.fonts.body,
330
- padding: '2px 6px',
331
- borderRadius: '3px',
332
- backgroundColor: theme.colors.surface,
333
- color: theme.colors.textSecondary,
334
- border: `1px solid ${theme.colors.border}`,
335
- }}
336
- >
337
- {attrSchema.type || 'any'}
338
- </span>
339
- {attrSchema.required && (
340
- <span
341
- style={{
342
- fontSize: '10px',
343
- fontFamily: theme.fonts.body,
344
- fontWeight: theme.fontWeights.semibold,
345
- padding: '2px 6px',
346
- borderRadius: '3px',
347
- backgroundColor: '#ef4444',
348
- color: 'white',
349
- }}
350
- >
351
- required
352
- </span>
353
- )}
354
- </div>
355
- {attrSchema.description && (
356
- <div
357
- style={{
358
- fontSize: theme.fontSizes[0],
359
- fontFamily: theme.fonts.body,
360
- color: theme.colors.textSecondary,
361
- marginTop: '4px',
362
- }}
363
- >
364
- {attrSchema.description}
365
- </div>
366
- )}
367
- </div>
368
- ))}
369
- </div>
370
- </div>
371
- )}
372
- </div>
373
- );
374
- } else if (eventRef) {
375
- // Just show the reference string (not resolved)
376
- return (
377
- <div
378
- style={{
379
- fontSize: theme.fontSizes[0],
380
- fontFamily: 'monospace',
381
- padding: '6px 10px',
382
- backgroundColor: theme.colors.muted,
383
- borderRadius: '4px',
384
- color: theme.colors.primary,
385
- border: `1px solid ${theme.colors.border}`,
386
- }}
387
- >
388
- {eventRef}
389
- </div>
390
- );
391
- }
392
- return null;
393
- })()}
394
- </div>
395
- )}
396
- </div>
397
-
398
- {/* Expand/Collapse button for additional details - only show in editable mode */}
399
- {canEdit && (
400
- <button
401
- onClick={() => setShowDetails(!showDetails)}
402
- style={{
403
- width: '100%',
404
- padding: '8px',
405
- backgroundColor: theme.colors.surface,
406
- border: `1px solid ${theme.colors.border}`,
407
- borderRadius: '4px',
408
- cursor: 'pointer',
409
- fontSize: theme.fontSizes[0],
410
- fontFamily: theme.fonts.body,
411
- color: theme.colors.textSecondary,
412
- display: 'flex',
413
- alignItems: 'center',
414
- justifyContent: 'center',
415
- gap: '6px',
416
- marginBottom: showDetails ? '12px' : '0',
417
- }}
418
- >
419
- <span
420
- style={{
421
- transform: showDetails ? 'rotate(180deg)' : 'rotate(0deg)',
422
- transition: 'transform 0.2s',
423
- }}
424
- >
425
-
426
- </span>
427
- {showDetails ? 'Hide Details' : 'Show Details'}
428
- </button>
429
- )}
430
-
431
- {/* Expandable details section - only show in editable mode */}
432
- {canEdit && showDetails && (
433
- <>
434
- {/* Icon Selector */}
435
- <div style={{ marginBottom: '12px' }}>
436
- <div
437
- style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body, color: theme.colors.textSecondary, marginBottom: '4px' }}
438
- >
439
- Icon
440
- </div>
441
- <div style={{ position: 'relative' }}>
442
- <button
443
- onClick={() => canEdit && setShowIconPicker(!showIconPicker)}
444
- disabled={!canEdit}
445
- style={{
446
- display: 'flex',
447
- alignItems: 'center',
448
- gap: '8px',
449
- padding: '6px 10px',
450
- backgroundColor: theme.colors.surface,
451
- border: canEdit
452
- ? `1px dashed ${theme.colors.border}`
453
- : `1px solid ${theme.colors.border}`,
454
- borderRadius: '4px',
455
- cursor: canEdit ? 'pointer' : 'default',
456
- fontSize: theme.fontSizes[0],
457
- fontFamily: theme.fonts.body,
458
- width: '100%',
459
- justifyContent: 'flex-start',
460
- color: theme.colors.text,
461
- }}
462
- >
463
- <span style={{ display: 'flex', alignItems: 'center' }}>
464
- {resolveIcon(currentIcon, 18)}
465
- </span>
466
- <span>{currentIcon || 'No icon'}</span>
467
- {canEdit && (
468
- <span
469
- style={{ marginLeft: 'auto', color: theme.colors.textMuted, fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body }}
470
- >
471
-
472
- </span>
473
- )}
474
- </button>
475
-
476
- {/* Icon Picker Dropdown */}
477
- {showIconPicker && (
478
- <div
479
- style={{
480
- position: 'absolute',
481
- top: '100%',
482
- left: 0,
483
- right: 0,
484
- marginTop: '4px',
485
- backgroundColor: theme.colors.background,
486
- border: `1px solid ${theme.colors.border}`,
487
- borderRadius: '4px',
488
- boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
489
- padding: '8px',
490
- maxHeight: '200px',
491
- overflowY: 'auto',
492
- zIndex: 1001,
493
- }}
494
- >
495
- <div
496
- style={{
497
- display: 'grid',
498
- gridTemplateColumns: 'repeat(6, 1fr)',
499
- gap: '4px',
500
- }}
501
- >
502
- {COMMON_ICONS.map((iconName) => (
503
- <button
504
- key={iconName}
505
- onClick={() => handleIconSelect(iconName)}
506
- title={iconName}
507
- style={{
508
- padding: '6px',
509
- border:
510
- currentIcon === iconName
511
- ? `2px solid ${nodeColor}`
512
- : `1px solid ${theme.colors.border}`,
513
- borderRadius: '4px',
514
- backgroundColor:
515
- currentIcon === iconName
516
- ? theme.colors.highlight
517
- : theme.colors.background,
518
- cursor: 'pointer',
519
- display: 'flex',
520
- alignItems: 'center',
521
- justifyContent: 'center',
522
- color: theme.colors.text,
523
- }}
524
- >
525
- {resolveIcon(iconName, 16)}
526
- </button>
527
- ))}
528
- </div>
529
- </div>
530
- )}
531
- </div>
532
- </div>
533
-
534
- {/* Node Type - Editable if availableNodeTypes provided */}
535
- <div style={{ marginBottom: '12px' }}>
536
- <div
537
- style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body, color: theme.colors.textSecondary, marginBottom: '4px' }}
538
- >
539
- Type
540
- </div>
541
- {canEdit && availableNodeTypes && Object.keys(availableNodeTypes).length > 1 ? (
542
- <select
543
- value={node.type}
544
- onChange={(e) => handleTypeChange(e.target.value)}
545
- style={{
546
- fontSize: theme.fontSizes[0],
547
- fontFamily: theme.fonts.body,
548
- padding: '4px 8px',
549
- borderRadius: '4px',
550
- border: `1px solid ${theme.colors.border}`,
551
- backgroundColor: theme.colors.background,
552
- color: theme.colors.text,
553
- cursor: 'pointer',
554
- width: '100%',
555
- }}
556
- >
557
- {Object.entries(availableNodeTypes).map(([typeName, typeDef]) => (
558
- <option key={typeName} value={typeName}>
559
- {typeName} ({typeDef.shape})
560
- </option>
561
- ))}
562
- </select>
563
- ) : (
564
- <div
565
- style={{
566
- fontSize: theme.fontSizes[0],
567
- fontFamily: theme.fonts.body,
568
- padding: '4px 8px',
569
- backgroundColor: nodeColor,
570
- color: theme.colors.background,
571
- borderRadius: '4px',
572
- display: 'inline-block',
573
- }}
574
- >
575
- {node.type}
576
- </div>
577
- )}
578
- </div>
579
-
580
- {/* Node State */}
581
- {node.state && (
582
- <div style={{ marginBottom: '12px' }}>
583
- <div
584
- style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body, color: theme.colors.textSecondary, marginBottom: '4px' }}
585
- >
586
- State
587
- </div>
588
- <div
589
- style={{
590
- fontSize: theme.fontSizes[0],
591
- fontFamily: theme.fonts.body,
592
- padding: '4px 8px',
593
- backgroundColor:
594
- typeDefinition?.states?.[node.state]?.color || theme.colors.secondary,
595
- color: theme.colors.background,
596
- borderRadius: '4px',
597
- display: 'inline-block',
598
- }}
599
- >
600
- {typeDefinition?.states?.[node.state]?.label || node.state}
601
- </div>
602
- </div>
603
- )}
604
-
605
- {/* Display other schema-defined fields (non-editable for now) */}
606
- {hasSchemaFields && displayFields.filter((f) => f.field !== nameField).length > 0 && (
607
- <div style={{ marginBottom: '12px' }}>
608
- <div
609
- style={{
610
- fontSize: theme.fontSizes[0],
611
- fontFamily: theme.fonts.body,
612
- color: theme.colors.textSecondary,
613
- marginBottom: '8px',
614
- fontWeight: theme.fontWeights.bold,
615
- }}
616
- >
617
- Properties
618
- </div>
619
- {displayFields
620
- .filter((f) => f.field !== nameField)
621
- .map(({ field, label, value }) => (
622
- <div key={field} style={{ marginBottom: '8px' }}>
623
- <div
624
- style={{
625
- fontSize: '10px',
626
- color: theme.colors.textSecondary,
627
- marginBottom: '2px',
628
- }}
629
- >
630
- {label}
631
- </div>
632
- <div style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body }}>
633
- {value !== undefined && value !== null
634
- ? typeof value === 'object'
635
- ? JSON.stringify(value, null, 2)
636
- : String(value)
637
- : '-'}
638
- </div>
639
- </div>
640
- ))}
641
- </div>
642
- )}
643
-
644
- {/* Show all node data if no schema is defined */}
645
- {!hasSchemaFields && nodeDataEntries.length > 0 && (
646
- <div style={{ marginBottom: '12px' }}>
647
- <div
648
- style={{
649
- fontSize: theme.fontSizes[0],
650
- fontFamily: theme.fonts.body,
651
- color: theme.colors.textSecondary,
652
- marginBottom: '8px',
653
- fontWeight: theme.fontWeights.bold,
654
- }}
655
- >
656
- Data
657
- </div>
658
- {nodeDataEntries.map(([key, value]) => (
659
- <div key={key} style={{ marginBottom: '8px' }}>
660
- <div
661
- style={{
662
- fontSize: theme.fontSizes[0],
663
- fontFamily: theme.fonts.body,
664
- color: theme.colors.textSecondary,
665
- marginBottom: '2px',
666
- }}
667
- >
668
- {key}
669
- </div>
670
- <div style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.body, wordBreak: 'break-word' }}>
671
- {value !== undefined && value !== null
672
- ? typeof value === 'object'
673
- ? JSON.stringify(value, null, 2)
674
- : String(value)
675
- : '-'}
676
- </div>
677
- </div>
678
- ))}
679
- </div>
680
- )}
681
-
682
- {/* Metadata */}
683
- <div
684
- style={{
685
- fontSize: theme.fontSizes[0],
686
- fontFamily: theme.fonts.body,
687
- color: theme.colors.textMuted,
688
- marginTop: '12px',
689
- paddingTop: '8px',
690
- borderTop: `1px solid ${theme.colors.border}`,
691
- }}
692
- >
693
- ID: {node.id}
694
- </div>
695
- </>
696
- )}
697
-
698
- {/* Delete Button */}
699
- {onDelete && (
700
- <button
701
- onClick={() => {
702
- onDelete(node.id);
703
- onClose();
704
- }}
705
- style={{
706
- marginTop: '12px',
707
- width: '100%',
708
- padding: '8px 12px',
709
- backgroundColor: theme.colors.error,
710
- color: theme.colors.background,
711
- border: 'none',
712
- borderRadius: '4px',
713
- cursor: 'pointer',
714
- fontSize: theme.fontSizes[0],
715
- fontFamily: theme.fonts.body,
716
- fontWeight: theme.fontWeights.bold,
717
- }}
718
- >
719
- Delete Node
720
- </button>
721
- )}
722
- </div>
723
- );
724
- };