@principal-ai/principal-view-react 0.6.7 → 0.6.9

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.
@@ -0,0 +1,1095 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { GraphRenderer } from '../components/GraphRenderer';
4
+ import type { ExtendedCanvas } from '@principal-ai/principal-view-core';
5
+
6
+ const meta = {
7
+ title: 'Audit/NodeFieldsAudit',
8
+ component: GraphRenderer,
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ tags: ['autodocs'],
13
+ } satisfies Meta<typeof GraphRenderer>;
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ /**
19
+ * Canvas showing a single node with ALL fields populated and labeled
20
+ */
21
+ const allFieldsCanvas: ExtendedCanvas = {
22
+ nodes: [
23
+ {
24
+ id: 'all-fields-node',
25
+ type: 'text',
26
+ x: 200,
27
+ y: 150,
28
+ width: 160,
29
+ height: 120,
30
+ text: 'Display Label (from dataSchema)',
31
+ color: '#6366f1', // color field
32
+ pv: {
33
+ nodeType: 'fully-populated',
34
+ shape: 'rectangle', // shape field
35
+ icon: 'Server', // icon field
36
+ fill: '#6366f1', // pv.fill (takes priority over node.color)
37
+ stroke: '#4f46e5', // pv.stroke (border color)
38
+ dataSchema: {
39
+ name: { type: 'string', displayInLabel: true },
40
+ description: { type: 'string', displayInLabel: true },
41
+ status: { type: 'string', displayInLabel: false },
42
+ },
43
+ states: {
44
+ idle: { color: '#94a3b8', icon: 'Circle', label: 'Idle' },
45
+ active: { color: '#22c55e', icon: 'CheckCircle', label: 'Active' },
46
+ error: { color: '#ef4444', icon: 'XCircle', label: 'Error' },
47
+ },
48
+ },
49
+ },
50
+ ],
51
+ edges: [],
52
+ pv: {
53
+ version: '1.0.0',
54
+ name: 'All Fields Audit',
55
+ edgeTypes: {},
56
+ },
57
+ };
58
+
59
+ /**
60
+ * Canvas showing nodes with each field labeled for reference
61
+ */
62
+ const labeledFieldsCanvas: ExtendedCanvas = {
63
+ nodes: [
64
+ // Reference node with annotations
65
+ {
66
+ id: 'labeled-node',
67
+ type: 'text',
68
+ x: 300,
69
+ y: 200,
70
+ width: 180,
71
+ height: 140,
72
+ text: 'API Gateway',
73
+ color: '#3b82f6',
74
+ pv: {
75
+ nodeType: 'server',
76
+ shape: 'rectangle',
77
+ icon: 'Server',
78
+ fill: '#3b82f6',
79
+ stroke: '#2563eb',
80
+ dataSchema: {
81
+ name: { type: 'string', displayInLabel: true },
82
+ },
83
+ states: {
84
+ processing: { color: '#3b82f6', icon: 'Loader', label: 'Processing' },
85
+ },
86
+ },
87
+ },
88
+ // Icon label
89
+ {
90
+ id: 'icon-label',
91
+ type: 'text',
92
+ x: 100,
93
+ y: 140,
94
+ width: 120,
95
+ height: 30,
96
+ text: 'ICON',
97
+ color: '#f97316',
98
+ pv: {
99
+ nodeType: 'label',
100
+ shape: 'rectangle',
101
+ fill: '#f97316',
102
+ dataSchema: {},
103
+ },
104
+ },
105
+ // Label field annotation
106
+ {
107
+ id: 'label-annotation',
108
+ type: 'text',
109
+ x: 100,
110
+ y: 200,
111
+ width: 120,
112
+ height: 30,
113
+ text: 'LABEL',
114
+ color: '#f97316',
115
+ pv: {
116
+ nodeType: 'label',
117
+ shape: 'rectangle',
118
+ fill: '#f97316',
119
+ dataSchema: {},
120
+ },
121
+ },
122
+ // State badge annotation
123
+ {
124
+ id: 'state-annotation',
125
+ type: 'text',
126
+ x: 100,
127
+ y: 260,
128
+ width: 120,
129
+ height: 30,
130
+ text: 'STATE BADGE',
131
+ color: '#f97316',
132
+ pv: {
133
+ nodeType: 'label',
134
+ shape: 'rectangle',
135
+ fill: '#f97316',
136
+ dataSchema: {},
137
+ },
138
+ },
139
+ // Border/stroke annotation
140
+ {
141
+ id: 'stroke-annotation',
142
+ type: 'text',
143
+ x: 540,
144
+ y: 200,
145
+ width: 140,
146
+ height: 30,
147
+ text: 'STROKE/BORDER',
148
+ color: '#f97316',
149
+ pv: {
150
+ nodeType: 'label',
151
+ shape: 'rectangle',
152
+ fill: '#f97316',
153
+ dataSchema: {},
154
+ },
155
+ },
156
+ ],
157
+ edges: [
158
+ {
159
+ id: 'icon-pointer',
160
+ fromNode: 'icon-label',
161
+ toNode: 'labeled-node',
162
+ fromSide: 'right',
163
+ toSide: 'left',
164
+ pv: { edgeType: 'pointer' },
165
+ },
166
+ {
167
+ id: 'label-pointer',
168
+ fromNode: 'label-annotation',
169
+ toNode: 'labeled-node',
170
+ fromSide: 'right',
171
+ toSide: 'left',
172
+ pv: { edgeType: 'pointer' },
173
+ },
174
+ {
175
+ id: 'state-pointer',
176
+ fromNode: 'state-annotation',
177
+ toNode: 'labeled-node',
178
+ fromSide: 'right',
179
+ toSide: 'left',
180
+ pv: { edgeType: 'pointer' },
181
+ },
182
+ {
183
+ id: 'stroke-pointer',
184
+ fromNode: 'stroke-annotation',
185
+ toNode: 'labeled-node',
186
+ fromSide: 'left',
187
+ toSide: 'right',
188
+ pv: { edgeType: 'pointer' },
189
+ },
190
+ ],
191
+ pv: {
192
+ version: '1.0.0',
193
+ name: 'Labeled Fields',
194
+ edgeTypes: {
195
+ pointer: {
196
+ style: 'dashed',
197
+ color: '#f97316',
198
+ directed: true,
199
+ },
200
+ },
201
+ },
202
+ };
203
+
204
+ /**
205
+ * Canvas showing all visual variations of node fields
206
+ */
207
+ const fieldVariationsCanvas: ExtendedCanvas = {
208
+ nodes: [
209
+ // Row 1: Shape variations
210
+ {
211
+ id: 'shape-rect',
212
+ type: 'text',
213
+ x: 80,
214
+ y: 80,
215
+ width: 120,
216
+ height: 80,
217
+ text: 'Rectangle',
218
+ color: '#6366f1',
219
+ pv: {
220
+ nodeType: 'shape-rect',
221
+ shape: 'rectangle',
222
+ icon: 'Square',
223
+ fill: '#6366f1',
224
+ description: 'Default shape',
225
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
226
+ },
227
+ },
228
+ {
229
+ id: 'shape-circle',
230
+ type: 'text',
231
+ x: 260,
232
+ y: 80,
233
+ width: 100,
234
+ height: 100,
235
+ text: 'Circle',
236
+ color: '#8b5cf6',
237
+ pv: {
238
+ nodeType: 'shape-circle',
239
+ shape: 'circle',
240
+ icon: 'Circle',
241
+ fill: '#8b5cf6',
242
+ description: 'Round shape',
243
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
244
+ },
245
+ },
246
+ {
247
+ id: 'shape-hexagon',
248
+ type: 'text',
249
+ x: 420,
250
+ y: 80,
251
+ width: 120,
252
+ height: 120,
253
+ text: 'Hexagon',
254
+ color: '#06b6d4',
255
+ pv: {
256
+ nodeType: 'shape-hexagon',
257
+ shape: 'hexagon',
258
+ icon: 'Hexagon',
259
+ fill: '#06b6d4',
260
+ description: '6-sided polygon',
261
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
262
+ },
263
+ },
264
+ {
265
+ id: 'shape-diamond',
266
+ type: 'text',
267
+ x: 600,
268
+ y: 80,
269
+ width: 90,
270
+ height: 90,
271
+ text: 'Diamond',
272
+ color: '#f59e0b',
273
+ pv: {
274
+ nodeType: 'shape-diamond',
275
+ shape: 'diamond',
276
+ icon: 'Diamond',
277
+ fill: '#f59e0b',
278
+ description: 'Rotated square',
279
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
280
+ },
281
+ },
282
+
283
+ // Row 2: State variations (same node type, different states)
284
+ {
285
+ id: 'state-idle',
286
+ type: 'text',
287
+ x: 80,
288
+ y: 260,
289
+ width: 120,
290
+ height: 80,
291
+ text: 'Idle State',
292
+ color: '#94a3b8',
293
+ pv: {
294
+ nodeType: 'stateful-idle',
295
+ shape: 'rectangle',
296
+ icon: 'Pause',
297
+ fill: '#94a3b8',
298
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
299
+ states: {
300
+ idle: { color: '#94a3b8', icon: 'Pause', label: 'Idle' },
301
+ },
302
+ },
303
+ },
304
+ {
305
+ id: 'state-active',
306
+ type: 'text',
307
+ x: 260,
308
+ y: 260,
309
+ width: 120,
310
+ height: 80,
311
+ text: 'Active State',
312
+ color: '#22c55e',
313
+ pv: {
314
+ nodeType: 'stateful-active',
315
+ shape: 'rectangle',
316
+ icon: 'Play',
317
+ fill: '#22c55e',
318
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
319
+ states: {
320
+ active: { color: '#22c55e', icon: 'Play', label: 'Active' },
321
+ },
322
+ },
323
+ },
324
+ {
325
+ id: 'state-warning',
326
+ type: 'text',
327
+ x: 440,
328
+ y: 260,
329
+ width: 120,
330
+ height: 80,
331
+ text: 'Warning State',
332
+ color: '#f59e0b',
333
+ pv: {
334
+ nodeType: 'stateful-warning',
335
+ shape: 'rectangle',
336
+ icon: 'AlertTriangle',
337
+ fill: '#f59e0b',
338
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
339
+ states: {
340
+ warning: { color: '#f59e0b', icon: 'AlertTriangle', label: 'Warning' },
341
+ },
342
+ },
343
+ },
344
+ {
345
+ id: 'state-error',
346
+ type: 'text',
347
+ x: 620,
348
+ y: 260,
349
+ width: 120,
350
+ height: 80,
351
+ text: 'Error State',
352
+ color: '#ef4444',
353
+ pv: {
354
+ nodeType: 'stateful-error',
355
+ shape: 'rectangle',
356
+ icon: 'XCircle',
357
+ fill: '#ef4444',
358
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
359
+ states: {
360
+ error: { color: '#ef4444', icon: 'XCircle', label: 'Error' },
361
+ },
362
+ },
363
+ },
364
+
365
+ // Row 3: With and without icon/state
366
+ {
367
+ id: 'with-icon-state',
368
+ type: 'text',
369
+ x: 80,
370
+ y: 420,
371
+ width: 140,
372
+ height: 100,
373
+ text: 'With Icon & State',
374
+ color: '#3b82f6',
375
+ pv: {
376
+ nodeType: 'full-featured',
377
+ shape: 'rectangle',
378
+ icon: 'Server',
379
+ fill: '#3b82f6',
380
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
381
+ states: {
382
+ online: { color: '#22c55e', icon: 'CheckCircle', label: 'Online' },
383
+ },
384
+ },
385
+ },
386
+ {
387
+ id: 'with-icon-no-state',
388
+ type: 'text',
389
+ x: 280,
390
+ y: 420,
391
+ width: 140,
392
+ height: 100,
393
+ text: 'Icon, No State',
394
+ color: '#8b5cf6',
395
+ pv: {
396
+ nodeType: 'icon-only',
397
+ shape: 'rectangle',
398
+ icon: 'Database',
399
+ fill: '#8b5cf6',
400
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
401
+ },
402
+ },
403
+ {
404
+ id: 'no-icon-with-state',
405
+ type: 'text',
406
+ x: 480,
407
+ y: 420,
408
+ width: 140,
409
+ height: 100,
410
+ text: 'State, No Icon',
411
+ color: '#06b6d4',
412
+ pv: {
413
+ nodeType: 'state-only',
414
+ shape: 'rectangle',
415
+ fill: '#06b6d4',
416
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
417
+ states: {
418
+ pending: { color: '#f59e0b', label: 'Pending' },
419
+ },
420
+ },
421
+ },
422
+ {
423
+ id: 'minimal',
424
+ type: 'text',
425
+ x: 680,
426
+ y: 420,
427
+ width: 140,
428
+ height: 100,
429
+ text: 'Minimal (Label Only)',
430
+ color: '#64748b',
431
+ pv: {
432
+ nodeType: 'minimal',
433
+ shape: 'rectangle',
434
+ fill: '#64748b',
435
+ dataSchema: { name: { type: 'string', displayInLabel: true } },
436
+ },
437
+ },
438
+ ],
439
+ edges: [],
440
+ pv: {
441
+ version: '1.0.0',
442
+ name: 'Field Variations',
443
+ description: 'All combinations of node fields',
444
+ edgeTypes: {},
445
+ },
446
+ };
447
+
448
+ /**
449
+ * Interactive field reference with documentation
450
+ */
451
+ const FieldReferenceTemplate = () => {
452
+ return (
453
+ <div style={{ padding: 20, fontFamily: 'system-ui' }}>
454
+ <h2 style={{ marginBottom: 20 }}>Node Card Field Reference</h2>
455
+
456
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 30 }}>
457
+ {/* Left: Visual Reference */}
458
+ <div>
459
+ <h3 style={{ marginBottom: 16, color: '#374151' }}>Visual Structure</h3>
460
+ <div
461
+ style={{
462
+ border: '3px solid #6366f1',
463
+ borderRadius: 8,
464
+ padding: 16,
465
+ backgroundColor: 'white',
466
+ width: 180,
467
+ textAlign: 'center',
468
+ position: 'relative',
469
+ }}
470
+ >
471
+ {/* Stroke/Border annotation */}
472
+ <div
473
+ style={{
474
+ position: 'absolute',
475
+ top: -30,
476
+ left: '50%',
477
+ transform: 'translateX(-50%)',
478
+ fontSize: 10,
479
+ color: '#6366f1',
480
+ fontWeight: 'bold',
481
+ }}
482
+ >
483
+ stroke/border color
484
+ </div>
485
+
486
+ {/* Icon */}
487
+ <div
488
+ style={{
489
+ marginBottom: 8,
490
+ padding: 4,
491
+ border: '2px dashed #f97316',
492
+ borderRadius: 4,
493
+ }}
494
+ >
495
+ <div style={{ fontSize: 10, color: '#f97316', marginBottom: 4 }}>
496
+ ICON
497
+ </div>
498
+ <span style={{ fontSize: 24 }}>S</span>
499
+ </div>
500
+
501
+ {/* Label */}
502
+ <div
503
+ style={{
504
+ marginBottom: 8,
505
+ padding: 4,
506
+ border: '2px dashed #22c55e',
507
+ borderRadius: 4,
508
+ }}
509
+ >
510
+ <div style={{ fontSize: 10, color: '#22c55e', marginBottom: 4 }}>
511
+ LABEL (displayInLabel)
512
+ </div>
513
+ <div style={{ fontWeight: 500 }}>Node Name</div>
514
+ </div>
515
+
516
+ {/* State Badge */}
517
+ <div
518
+ style={{
519
+ padding: 4,
520
+ border: '2px dashed #3b82f6',
521
+ borderRadius: 4,
522
+ }}
523
+ >
524
+ <div style={{ fontSize: 10, color: '#3b82f6', marginBottom: 4 }}>
525
+ STATE BADGE
526
+ </div>
527
+ <span
528
+ style={{
529
+ backgroundColor: '#22c55e',
530
+ color: 'white',
531
+ padding: '2px 8px',
532
+ borderRadius: 4,
533
+ fontSize: 11,
534
+ }}
535
+ >
536
+ Active
537
+ </span>
538
+ </div>
539
+
540
+ {/* Shape annotation */}
541
+ <div
542
+ style={{
543
+ position: 'absolute',
544
+ bottom: -30,
545
+ left: '50%',
546
+ transform: 'translateX(-50%)',
547
+ fontSize: 10,
548
+ color: '#6366f1',
549
+ fontWeight: 'bold',
550
+ }}
551
+ >
552
+ shape: rectangle
553
+ </div>
554
+ </div>
555
+ </div>
556
+
557
+ {/* Right: Field Documentation */}
558
+ <div>
559
+ <h3 style={{ marginBottom: 16, color: '#374151' }}>Field Descriptions</h3>
560
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
561
+ <thead>
562
+ <tr style={{ backgroundColor: '#f3f4f6' }}>
563
+ <th style={{ padding: 8, textAlign: 'left', borderBottom: '2px solid #e5e7eb' }}>
564
+ Field
565
+ </th>
566
+ <th style={{ padding: 8, textAlign: 'left', borderBottom: '2px solid #e5e7eb' }}>
567
+ Source
568
+ </th>
569
+ <th style={{ padding: 8, textAlign: 'left', borderBottom: '2px solid #e5e7eb' }}>
570
+ Description
571
+ </th>
572
+ </tr>
573
+ </thead>
574
+ <tbody>
575
+ <tr>
576
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#f97316' }}>
577
+ icon
578
+ </td>
579
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
580
+ <code>vv.icon</code> or <code>states[state].icon</code>
581
+ </td>
582
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
583
+ Lucide icon name displayed at top
584
+ </td>
585
+ </tr>
586
+ <tr>
587
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#22c55e' }}>
588
+ label
589
+ </td>
590
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
591
+ <code>dataSchema[field].displayInLabel</code>
592
+ </td>
593
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
594
+ Main text from data field with displayInLabel=true
595
+ </td>
596
+ </tr>
597
+ <tr>
598
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#3b82f6' }}>
599
+ state
600
+ </td>
601
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
602
+ <code>states[currentState]</code>
603
+ </td>
604
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
605
+ Badge showing current state with color and label
606
+ </td>
607
+ </tr>
608
+ <tr>
609
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#6366f1' }}>
610
+ color
611
+ </td>
612
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
613
+ <code>vv.color</code> or <code>states[state].color</code>
614
+ </td>
615
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
616
+ Primary node color (state overrides base)
617
+ </td>
618
+ </tr>
619
+ <tr>
620
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#6366f1' }}>
621
+ stroke
622
+ </td>
623
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
624
+ <code>vv.stroke</code>
625
+ </td>
626
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
627
+ Border color (defaults to color if not set)
628
+ </td>
629
+ </tr>
630
+ <tr>
631
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#6366f1' }}>
632
+ shape
633
+ </td>
634
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
635
+ <code>vv.shape</code>
636
+ </td>
637
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
638
+ rectangle | circle | hexagon | diamond | custom
639
+ </td>
640
+ </tr>
641
+ <tr>
642
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#6366f1' }}>
643
+ size
644
+ </td>
645
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
646
+ <code>vv.size</code>
647
+ </td>
648
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
649
+ {'{width, height}'} for node dimensions
650
+ </td>
651
+ </tr>
652
+ <tr>
653
+ <td style={{ padding: 8, fontWeight: 'bold', color: '#ef4444' }}>
654
+ hasViolations
655
+ </td>
656
+ <td style={{ padding: 8 }}>
657
+ <code>computed</code>
658
+ </td>
659
+ <td style={{ padding: 8 }}>
660
+ Shows warning indicator when true
661
+ </td>
662
+ </tr>
663
+ </tbody>
664
+ </table>
665
+ </div>
666
+ </div>
667
+
668
+ <h3 style={{ marginTop: 30, marginBottom: 16, color: '#374151' }}>Live Examples</h3>
669
+ <GraphRenderer canvas={fieldVariationsCanvas} width={850} height={600} />
670
+ </div>
671
+ );
672
+ };
673
+
674
+ export const FieldReference: Story = {
675
+ render: () => <FieldReferenceTemplate />,
676
+ parameters: {
677
+ docs: {
678
+ description: {
679
+ story: `
680
+ **Node Card Field Reference**
681
+
682
+ This story provides a comprehensive visual guide to all fields displayed on node cards:
683
+
684
+ **Visual Fields:**
685
+ - **icon** - Lucide icon displayed above the label
686
+ - **label** - Main text from dataSchema field with \`displayInLabel: true\`
687
+ - **state** - Badge showing current state with color and label
688
+ - **color** - Primary node color (can be overridden by state)
689
+ - **stroke** - Border color (defaults to color)
690
+ - **shape** - rectangle, circle, hexagon, diamond, or custom
691
+ - **size** - Node dimensions {width, height}
692
+ - **hasViolations** - Warning indicator for validation errors
693
+ `,
694
+ },
695
+ },
696
+ },
697
+ };
698
+
699
+ export const AllShapes: Story = {
700
+ args: {
701
+ canvas: fieldVariationsCanvas,
702
+ width: 850,
703
+ height: 600,
704
+ },
705
+ parameters: {
706
+ docs: {
707
+ description: {
708
+ story: `
709
+ Shows all node shape variations with different field combinations:
710
+ - **Row 1**: Shape types (rectangle, circle, hexagon, diamond)
711
+ - **Row 2**: State variations (idle, active, warning, error)
712
+ - **Row 3**: Feature combinations (with/without icon and state)
713
+ `,
714
+ },
715
+ },
716
+ },
717
+ };
718
+
719
+ /**
720
+ * Interactive popup panel reference
721
+ */
722
+ const PopupReferenceTemplate = () => {
723
+ return (
724
+ <div style={{ padding: 20, fontFamily: 'system-ui' }}>
725
+ <h2 style={{ marginBottom: 20 }}>Node Info Panel (Popup) Field Reference</h2>
726
+
727
+ <div style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: 30 }}>
728
+ {/* Left: Mock Panel */}
729
+ <div
730
+ style={{
731
+ backgroundColor: 'white',
732
+ borderRadius: 8,
733
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
734
+ padding: 16,
735
+ }}
736
+ >
737
+ {/* Header */}
738
+ <div
739
+ style={{
740
+ display: 'flex',
741
+ justifyContent: 'space-between',
742
+ alignItems: 'center',
743
+ marginBottom: 12,
744
+ paddingBottom: 8,
745
+ borderBottom: '2px solid #6366f1',
746
+ }}
747
+ >
748
+ <div style={{ fontWeight: 'bold', fontSize: 14 }}>Node Information</div>
749
+ <span style={{ color: '#666', fontSize: 18, cursor: 'pointer' }}>x</span>
750
+ </div>
751
+
752
+ {/* Icon Field */}
753
+ <div style={{ marginBottom: 12, position: 'relative' }}>
754
+ <div
755
+ style={{
756
+ position: 'absolute',
757
+ right: -80,
758
+ top: 0,
759
+ fontSize: 10,
760
+ color: '#f97316',
761
+ fontWeight: 'bold',
762
+ }}
763
+ >
764
+ 1. ICON
765
+ </div>
766
+ <div style={{ fontSize: 10, color: '#666', marginBottom: 4 }}>Icon</div>
767
+ <div
768
+ style={{
769
+ display: 'flex',
770
+ alignItems: 'center',
771
+ gap: 8,
772
+ padding: '6px 10px',
773
+ backgroundColor: '#f5f5f5',
774
+ border: '1px dashed #ccc',
775
+ borderRadius: 4,
776
+ }}
777
+ >
778
+ <span>S</span>
779
+ <span style={{ fontSize: 12 }}>Server</span>
780
+ <span style={{ marginLeft: 'auto', color: '#999', fontSize: 10 }}>edit</span>
781
+ </div>
782
+ </div>
783
+
784
+ {/* Type Field */}
785
+ <div style={{ marginBottom: 12, position: 'relative' }}>
786
+ <div
787
+ style={{
788
+ position: 'absolute',
789
+ right: -80,
790
+ top: 0,
791
+ fontSize: 10,
792
+ color: '#22c55e',
793
+ fontWeight: 'bold',
794
+ }}
795
+ >
796
+ 2. TYPE
797
+ </div>
798
+ <div style={{ fontSize: 10, color: '#666', marginBottom: 4 }}>Type</div>
799
+ <div
800
+ style={{
801
+ fontSize: 12,
802
+ padding: '4px 8px',
803
+ backgroundColor: '#6366f1',
804
+ color: 'white',
805
+ borderRadius: 4,
806
+ display: 'inline-block',
807
+ }}
808
+ >
809
+ server (rectangle)
810
+ </div>
811
+ </div>
812
+
813
+ {/* State Field */}
814
+ <div style={{ marginBottom: 12, position: 'relative' }}>
815
+ <div
816
+ style={{
817
+ position: 'absolute',
818
+ right: -80,
819
+ top: 0,
820
+ fontSize: 10,
821
+ color: '#3b82f6',
822
+ fontWeight: 'bold',
823
+ }}
824
+ >
825
+ 3. STATE
826
+ </div>
827
+ <div style={{ fontSize: 10, color: '#666', marginBottom: 4 }}>State</div>
828
+ <div
829
+ style={{
830
+ fontSize: 12,
831
+ padding: '4px 8px',
832
+ backgroundColor: '#22c55e',
833
+ color: 'white',
834
+ borderRadius: 4,
835
+ display: 'inline-block',
836
+ }}
837
+ >
838
+ Online
839
+ </div>
840
+ </div>
841
+
842
+ {/* Name Field */}
843
+ <div style={{ marginBottom: 12, position: 'relative' }}>
844
+ <div
845
+ style={{
846
+ position: 'absolute',
847
+ right: -80,
848
+ top: 0,
849
+ fontSize: 10,
850
+ color: '#8b5cf6',
851
+ fontWeight: 'bold',
852
+ }}
853
+ >
854
+ 4. NAME
855
+ </div>
856
+ <div style={{ fontSize: 10, color: '#666', marginBottom: 4 }}>Name</div>
857
+ <div
858
+ style={{
859
+ fontSize: 12,
860
+ padding: '4px 8px',
861
+ backgroundColor: '#f5f5f5',
862
+ borderRadius: 4,
863
+ border: '1px dashed #ccc',
864
+ }}
865
+ >
866
+ API Gateway
867
+ <span style={{ marginLeft: 8, color: '#999', fontSize: 10 }}>edit</span>
868
+ </div>
869
+ </div>
870
+
871
+ {/* Properties */}
872
+ <div style={{ marginBottom: 12, position: 'relative' }}>
873
+ <div
874
+ style={{
875
+ position: 'absolute',
876
+ right: -100,
877
+ top: 0,
878
+ fontSize: 10,
879
+ color: '#06b6d4',
880
+ fontWeight: 'bold',
881
+ }}
882
+ >
883
+ 5. PROPERTIES
884
+ </div>
885
+ <div style={{ fontSize: 10, color: '#666', marginBottom: 8, fontWeight: 'bold' }}>
886
+ Properties
887
+ </div>
888
+ <div style={{ marginBottom: 8 }}>
889
+ <div style={{ fontSize: 10, color: '#666', marginBottom: 2 }}>description</div>
890
+ <div style={{ fontSize: 12, color: '#333' }}>Main entry point for API</div>
891
+ </div>
892
+ <div>
893
+ <div style={{ fontSize: 10, color: '#666', marginBottom: 2 }}>port</div>
894
+ <div style={{ fontSize: 12, color: '#333' }}>8080</div>
895
+ </div>
896
+ </div>
897
+
898
+ {/* ID */}
899
+ <div
900
+ style={{
901
+ fontSize: 10,
902
+ color: '#999',
903
+ marginTop: 12,
904
+ paddingTop: 8,
905
+ borderTop: '1px solid #eee',
906
+ position: 'relative',
907
+ }}
908
+ >
909
+ <div
910
+ style={{
911
+ position: 'absolute',
912
+ right: -60,
913
+ top: 8,
914
+ fontSize: 10,
915
+ color: '#64748b',
916
+ fontWeight: 'bold',
917
+ }}
918
+ >
919
+ 6. ID
920
+ </div>
921
+ ID: api-gateway-1
922
+ </div>
923
+
924
+ {/* Delete Button */}
925
+ <div style={{ position: 'relative' }}>
926
+ <div
927
+ style={{
928
+ position: 'absolute',
929
+ right: -80,
930
+ top: 12,
931
+ fontSize: 10,
932
+ color: '#ef4444',
933
+ fontWeight: 'bold',
934
+ }}
935
+ >
936
+ 7. DELETE
937
+ </div>
938
+ <button
939
+ style={{
940
+ marginTop: 12,
941
+ width: '100%',
942
+ padding: '8px 12px',
943
+ backgroundColor: '#dc3545',
944
+ color: 'white',
945
+ border: 'none',
946
+ borderRadius: 4,
947
+ cursor: 'pointer',
948
+ fontSize: 12,
949
+ fontWeight: 'bold',
950
+ }}
951
+ >
952
+ Delete Node
953
+ </button>
954
+ </div>
955
+ </div>
956
+
957
+ {/* Right: Field Documentation */}
958
+ <div>
959
+ <h3 style={{ marginBottom: 16, color: '#374151' }}>Panel Fields</h3>
960
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
961
+ <thead>
962
+ <tr style={{ backgroundColor: '#f3f4f6' }}>
963
+ <th style={{ padding: 8, textAlign: 'left', borderBottom: '2px solid #e5e7eb' }}>
964
+ #
965
+ </th>
966
+ <th style={{ padding: 8, textAlign: 'left', borderBottom: '2px solid #e5e7eb' }}>
967
+ Field
968
+ </th>
969
+ <th style={{ padding: 8, textAlign: 'left', borderBottom: '2px solid #e5e7eb' }}>
970
+ Editable?
971
+ </th>
972
+ <th style={{ padding: 8, textAlign: 'left', borderBottom: '2px solid #e5e7eb' }}>
973
+ Description
974
+ </th>
975
+ </tr>
976
+ </thead>
977
+ <tbody>
978
+ <tr>
979
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>1</td>
980
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#f97316' }}>
981
+ Icon
982
+ </td>
983
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>Yes (picker)</td>
984
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
985
+ Icon selector with 48 common Lucide icons
986
+ </td>
987
+ </tr>
988
+ <tr>
989
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>2</td>
990
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#22c55e' }}>
991
+ Type
992
+ </td>
993
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>Yes (dropdown)</td>
994
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
995
+ Node type selector from availableNodeTypes
996
+ </td>
997
+ </tr>
998
+ <tr>
999
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>3</td>
1000
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#3b82f6' }}>
1001
+ State
1002
+ </td>
1003
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>No</td>
1004
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
1005
+ Current state badge (from node.state)
1006
+ </td>
1007
+ </tr>
1008
+ <tr>
1009
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>4</td>
1010
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#8b5cf6' }}>
1011
+ Name
1012
+ </td>
1013
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>Yes (inline)</td>
1014
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
1015
+ Field with displayInLabel from dataSchema
1016
+ </td>
1017
+ </tr>
1018
+ <tr>
1019
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>5</td>
1020
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#06b6d4' }}>
1021
+ Properties
1022
+ </td>
1023
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>No</td>
1024
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
1025
+ Other dataSchema fields or all node.data
1026
+ </td>
1027
+ </tr>
1028
+ <tr>
1029
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>6</td>
1030
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb', fontWeight: 'bold', color: '#64748b' }}>
1031
+ ID
1032
+ </td>
1033
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>No</td>
1034
+ <td style={{ padding: 8, borderBottom: '1px solid #e5e7eb' }}>
1035
+ Read-only node identifier
1036
+ </td>
1037
+ </tr>
1038
+ <tr>
1039
+ <td style={{ padding: 8 }}>7</td>
1040
+ <td style={{ padding: 8, fontWeight: 'bold', color: '#ef4444' }}>Delete</td>
1041
+ <td style={{ padding: 8 }}>Action</td>
1042
+ <td style={{ padding: 8 }}>Shown only when onDelete prop provided</td>
1043
+ </tr>
1044
+ </tbody>
1045
+ </table>
1046
+
1047
+ <h4 style={{ marginTop: 24, marginBottom: 12, color: '#374151' }}>Edit Mode Notes</h4>
1048
+ <ul style={{ fontSize: 13, lineHeight: 1.8, margin: 0, paddingLeft: 20 }}>
1049
+ <li>
1050
+ <strong>Icon:</strong> Clicking opens a 6x8 grid picker of common icons
1051
+ </li>
1052
+ <li>
1053
+ <strong>Type:</strong> Dropdown only shown if availableNodeTypes has multiple types
1054
+ </li>
1055
+ <li>
1056
+ <strong>Name:</strong> Click to edit inline, press Enter to save, Escape to cancel
1057
+ </li>
1058
+ <li>
1059
+ <strong>Delete:</strong> Only shown if onDelete callback is provided
1060
+ </li>
1061
+ </ul>
1062
+ </div>
1063
+ </div>
1064
+
1065
+ <h3 style={{ marginTop: 30, marginBottom: 16, color: '#374151' }}>
1066
+ Try It: Click a node to see the info panel
1067
+ </h3>
1068
+ <GraphRenderer canvas={fieldVariationsCanvas} width={850} height={600} editable={true} />
1069
+ </div>
1070
+ );
1071
+ };
1072
+
1073
+ export const PopupReference: Story = {
1074
+ render: () => <PopupReferenceTemplate />,
1075
+ parameters: {
1076
+ docs: {
1077
+ description: {
1078
+ story: `
1079
+ **Node Info Panel (Popup) Reference**
1080
+
1081
+ When a node is clicked, the NodeInfoPanel displays:
1082
+ 1. **Icon** - Editable with icon picker (48 common Lucide icons)
1083
+ 2. **Type** - Node type with selector dropdown (if multiple types available)
1084
+ 3. **State** - Current state badge with color
1085
+ 4. **Name** - Editable name field (from dataSchema displayInLabel field)
1086
+ 5. **Properties** - Other schema fields or all node data
1087
+ 6. **ID** - Read-only node identifier
1088
+ 7. **Delete Button** - Only shown in edit mode
1089
+
1090
+ Click a node in the interactive example below to see the panel.
1091
+ `,
1092
+ },
1093
+ },
1094
+ },
1095
+ };