@principal-ai/principal-view-react 0.14.30 → 0.14.31

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,777 @@
1
+ import React, { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { WorkflowSequenceDiagram } from '../components/WorkflowSequenceDiagram';
4
+ import type { WorkflowScenario, Canvas } from '@principal-ai/principal-view-core';
5
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
6
+
7
+ const meta = {
8
+ title: 'Components/WorkflowSequenceDiagram',
9
+ component: WorkflowSequenceDiagram,
10
+ parameters: {
11
+ layout: 'fullscreen',
12
+ docs: {
13
+ description: {
14
+ component: `
15
+ # WorkflowSequenceDiagram Component
16
+
17
+ A wrapper around \`SequenceDiagramRenderer\` that simplifies working with workflow scenarios.
18
+ Automatically converts workflow scenarios to sequence diagram format and optionally extracts
19
+ participant metadata from a canvas.
20
+
21
+ ## Key Features
22
+
23
+ - **Simplified API**: Just pass a workflow scenario instead of manually creating events/edges
24
+ - **Canvas Integration**: Optionally provide a canvas to extract scope and participant metadata
25
+ - **Move vs Transform Events**: Automatically distinguishes between cross-participant (move) and internal (transform) events
26
+ - **Clickable Labels**: Edge labels are fully clickable and highlight when selected (no dots needed!)
27
+ - **Event Selection**: Click any label to select it - the label changes color to show selection
28
+ - **Empty State Handling**: Gracefully handles scenarios with no events
29
+
30
+ ## Usage
31
+
32
+ \`\`\`tsx
33
+ <WorkflowSequenceDiagram
34
+ scenario={workflowScenario}
35
+ canvas={otelCanvas}
36
+ height={600}
37
+ onEventIndexChange={(index) => console.log('Selected event:', index)}
38
+ />
39
+ \`\`\`
40
+ `,
41
+ },
42
+ },
43
+ },
44
+ tags: ['autodocs'],
45
+ decorators: [
46
+ (Story) => (
47
+ <ThemeProvider theme={defaultEditorTheme}>
48
+ <div style={{ padding: 20, height: '100vh', boxSizing: 'border-box' }}>
49
+ <Story />
50
+ </div>
51
+ </ThemeProvider>
52
+ ),
53
+ ],
54
+ } satisfies Meta<typeof WorkflowSequenceDiagram>;
55
+
56
+ export default meta;
57
+ type Story = StoryObj<typeof meta>;
58
+
59
+ // ============================================================================
60
+ // Sample Scenarios
61
+ // ============================================================================
62
+
63
+ /**
64
+ * Simple authentication workflow
65
+ */
66
+ const simpleAuthScenario: WorkflowScenario = {
67
+ id: 'simple-auth',
68
+ name: 'Simple Authentication',
69
+ description: 'Basic authentication flow with validation',
70
+ template: {
71
+ events: {
72
+ 'auth.request.received': {},
73
+ 'auth.validation.started': {},
74
+ 'database.user.lookup': {},
75
+ 'auth.validation.completed': {},
76
+ 'auth.token.generated': {},
77
+ },
78
+ },
79
+ };
80
+
81
+ /**
82
+ * E-commerce order processing workflow
83
+ */
84
+ const orderProcessingScenario: WorkflowScenario = {
85
+ id: 'order-processing',
86
+ name: 'Order Processing',
87
+ description: 'Complete order processing with payment and inventory',
88
+ template: {
89
+ events: {
90
+ 'api.order.received': {},
91
+ 'auth.session.validate': {},
92
+ 'order.validation.started': {},
93
+ 'inventory.check.availability': {},
94
+ 'inventory.reserve.items': {},
95
+ 'order.validation.completed': {},
96
+ 'payment.charge.initiated': {},
97
+ 'payment.processor.authorize': {},
98
+ 'payment.charge.completed': {},
99
+ 'order.fulfillment.started': {},
100
+ 'notification.email.sent': {},
101
+ 'api.response.sent': {},
102
+ },
103
+ },
104
+ };
105
+
106
+ /**
107
+ * Microservices workflow with multiple participants
108
+ */
109
+ const microservicesScenario: WorkflowScenario = {
110
+ id: 'microservices-flow',
111
+ name: 'Microservices Communication',
112
+ description: 'Inter-service communication pattern',
113
+ template: {
114
+ events: {
115
+ 'gateway.request.received': {},
116
+ 'gateway.route.determined': {},
117
+ 'userservice.user.fetch': {},
118
+ 'userservice.database.query': {},
119
+ 'userservice.response.prepared': {},
120
+ 'gateway.response.aggregated': {},
121
+ 'cache.data.stored': {},
122
+ 'gateway.response.sent': {},
123
+ },
124
+ },
125
+ };
126
+
127
+ /**
128
+ * Error handling workflow
129
+ */
130
+ const errorHandlingScenario: WorkflowScenario = {
131
+ id: 'error-handling',
132
+ name: 'Error Handling Flow',
133
+ description: 'Workflow demonstrating error handling and recovery',
134
+ template: {
135
+ events: {
136
+ 'api.request.received': {},
137
+ 'service.processing.started': {},
138
+ 'service.validation.failed': {},
139
+ 'service.error.logged': {},
140
+ 'notification.alert.sent': {},
141
+ 'api.error.response.sent': {},
142
+ },
143
+ },
144
+ };
145
+
146
+ /**
147
+ * Empty scenario for testing empty state
148
+ */
149
+ const emptyScenario: WorkflowScenario = {
150
+ id: 'empty',
151
+ name: 'Empty Workflow',
152
+ description: 'Scenario with no events',
153
+ template: {
154
+ events: {},
155
+ },
156
+ };
157
+
158
+ /**
159
+ * Sample canvas with OTEL event nodes for metadata extraction
160
+ */
161
+ const sampleCanvas: Canvas = {
162
+ nodes: [
163
+ {
164
+ id: 'auth-validation-started',
165
+ type: 'otel-event',
166
+ x: 0,
167
+ y: 0,
168
+ width: 200,
169
+ height: 60,
170
+ event: { name: 'auth.validation.started' },
171
+ otel: { scope: 'auth' },
172
+ label: 'Start Validation',
173
+ },
174
+ {
175
+ id: 'database-lookup',
176
+ type: 'otel-event',
177
+ x: 0,
178
+ y: 100,
179
+ width: 200,
180
+ height: 60,
181
+ event: { name: 'database.user.lookup' },
182
+ otel: { scope: 'database' },
183
+ label: 'Lookup User',
184
+ },
185
+ {
186
+ id: 'auth-validation-completed',
187
+ type: 'otel-event',
188
+ x: 0,
189
+ y: 200,
190
+ width: 200,
191
+ height: 60,
192
+ event: { name: 'auth.validation.completed' },
193
+ otel: { scope: 'auth' },
194
+ label: 'Validation Complete',
195
+ },
196
+ ],
197
+ edges: [],
198
+ };
199
+
200
+ // ============================================================================
201
+ // Stories
202
+ // ============================================================================
203
+
204
+ /**
205
+ * Diagnostic story to test click functionality
206
+ * Shows click feedback with colored labels (no dots!)
207
+ * Verifies the label text matches the selected event (no off-by-one errors)
208
+ */
209
+ export const ClickDiagnostic: Story = {
210
+ render: () => {
211
+ const [selectedIndex, setSelectedIndex] = useState<number | undefined>(undefined);
212
+ const [clickCount, setClickCount] = useState(0);
213
+ const [lastClickTime, setLastClickTime] = useState<string>('Never');
214
+
215
+ const handleClick = (index: number) => {
216
+ console.log('Click detected! Event index:', index);
217
+ setSelectedIndex(index);
218
+ setClickCount(prev => prev + 1);
219
+ setLastClickTime(new Date().toLocaleTimeString());
220
+ };
221
+
222
+ const eventNames = Object.keys(simpleAuthScenario.template.events);
223
+
224
+ return (
225
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
226
+ <div
227
+ style={{
228
+ padding: '16px',
229
+ background: '#1a1a1a',
230
+ color: '#00ff00',
231
+ fontFamily: 'monospace',
232
+ borderBottom: '2px solid #00ff00',
233
+ marginBottom: 16,
234
+ }}
235
+ >
236
+ <h3 style={{ margin: '0 0 12px 0', color: '#00ff00' }}>🔍 CLICK DIAGNOSTIC MODE</h3>
237
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, fontSize: 13 }}>
238
+ <div>Total Clicks: <strong>{clickCount}</strong></div>
239
+ <div>Last Click: <strong>{lastClickTime}</strong></div>
240
+ <div>Selected Index: <strong>{selectedIndex !== undefined ? selectedIndex : 'None'}</strong></div>
241
+ <div>Selected Event: <strong>{selectedIndex !== undefined ? eventNames[selectedIndex] : 'None'}</strong></div>
242
+ </div>
243
+ <div style={{ marginTop: 12, padding: '8px', background: '#2a2a2a', borderRadius: 4 }}>
244
+ 💡 <strong>Click any label</strong> - it will highlight with colored background!
245
+ Watch the label change color and verify it matches the event shown above.
246
+ </div>
247
+ {selectedIndex !== undefined && (
248
+ <div style={{ marginTop: 8, padding: '8px', background: '#1e5a1e', borderRadius: 4, fontSize: 14 }}>
249
+ ✅ <strong>Selected:</strong> "{eventNames[selectedIndex]?.split('.').pop()}" (the highlighted label should match!)
250
+ </div>
251
+ )}
252
+ </div>
253
+ <div style={{ flex: 1 }}>
254
+ <WorkflowSequenceDiagram
255
+ scenario={simpleAuthScenario}
256
+ height="100%"
257
+ selectedEventIndex={selectedIndex}
258
+ onEventIndexChange={handleClick}
259
+ layoutOptions={{
260
+ namespaceStrategy: 'first',
261
+ eventSpacing: 100,
262
+ }}
263
+ />
264
+ </div>
265
+ </div>
266
+ );
267
+ },
268
+ };
269
+
270
+ /**
271
+ * Basic workflow scenario without canvas
272
+ */
273
+ export const BasicScenario: Story = {
274
+ args: {
275
+ scenario: simpleAuthScenario,
276
+ height: 500,
277
+ layoutOptions: {
278
+ namespaceStrategy: 'first',
279
+ eventSpacing: 80,
280
+ },
281
+ },
282
+ };
283
+
284
+ /**
285
+ * Workflow with canvas metadata for enhanced labels
286
+ */
287
+ export const WithCanvasMetadata: Story = {
288
+ args: {
289
+ scenario: simpleAuthScenario,
290
+ canvas: sampleCanvas,
291
+ height: 500,
292
+ layoutOptions: {
293
+ namespaceStrategy: 'first',
294
+ eventSpacing: 80,
295
+ },
296
+ },
297
+ };
298
+
299
+ /**
300
+ * Complex workflow with many events and participants
301
+ */
302
+ export const ComplexWorkflow: Story = {
303
+ args: {
304
+ scenario: orderProcessingScenario,
305
+ height: 700,
306
+ layoutOptions: {
307
+ namespaceStrategy: 'first',
308
+ laneWidth: 200,
309
+ eventSpacing: 70,
310
+ },
311
+ },
312
+ };
313
+
314
+ /**
315
+ * Microservices communication pattern
316
+ */
317
+ export const MicroservicesPattern: Story = {
318
+ args: {
319
+ scenario: microservicesScenario,
320
+ height: 600,
321
+ layoutOptions: {
322
+ namespaceStrategy: 'first',
323
+ laneWidth: 220,
324
+ eventSpacing: 75,
325
+ },
326
+ },
327
+ };
328
+
329
+ /**
330
+ * Error handling workflow
331
+ */
332
+ export const ErrorHandling: Story = {
333
+ args: {
334
+ scenario: errorHandlingScenario,
335
+ height: 500,
336
+ layoutOptions: {
337
+ namespaceStrategy: 'first',
338
+ eventSpacing: 80,
339
+ },
340
+ },
341
+ };
342
+
343
+ /**
344
+ * Empty scenario showing graceful handling of no events
345
+ */
346
+ export const EmptyState: Story = {
347
+ args: {
348
+ scenario: emptyScenario,
349
+ height: 300,
350
+ },
351
+ };
352
+
353
+ /**
354
+ * Interactive example with event selection
355
+ * Click on any event in the sequence diagram to select it
356
+ * NOTE: Event click areas are small (14x14px by default) - use larger nodes for better UX
357
+ */
358
+ export const WithEventSelection: Story = {
359
+ render: () => {
360
+ const [selectedIndex, setSelectedIndex] = useState<number | undefined>(undefined);
361
+
362
+ // Get event names from the scenario
363
+ const eventNames = Object.keys(simpleAuthScenario.template.events);
364
+ const selectedEventName = selectedIndex !== undefined ? eventNames[selectedIndex] : null;
365
+
366
+ return (
367
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
368
+ <div
369
+ style={{
370
+ padding: '16px',
371
+ background: '#2c3e50',
372
+ color: '#ecf0f1',
373
+ borderBottom: '3px solid #3498db',
374
+ marginBottom: 16,
375
+ }}
376
+ >
377
+ <div style={{ fontSize: 16, marginBottom: 8 }}>
378
+ <strong>💡 Click on any event dot in the diagram below</strong>
379
+ </div>
380
+ <div style={{ fontSize: 14, opacity: 0.9 }}>
381
+ <strong>Selected Event:</strong>{' '}
382
+ {selectedIndex !== undefined ? (
383
+ <span style={{ color: '#3498db', fontFamily: 'monospace' }}>
384
+ [{selectedIndex}] {selectedEventName}
385
+ </span>
386
+ ) : (
387
+ <span style={{ opacity: 0.6 }}>None - Click an event to select</span>
388
+ )}
389
+ </div>
390
+ {selectedIndex !== undefined && (
391
+ <button
392
+ onClick={() => setSelectedIndex(undefined)}
393
+ style={{
394
+ marginTop: 8,
395
+ padding: '6px 16px',
396
+ cursor: 'pointer',
397
+ background: '#e74c3c',
398
+ color: 'white',
399
+ border: 'none',
400
+ borderRadius: 4,
401
+ fontSize: 13,
402
+ }}
403
+ >
404
+ Clear Selection
405
+ </button>
406
+ )}
407
+ </div>
408
+ <div style={{ flex: 1 }}>
409
+ <WorkflowSequenceDiagram
410
+ scenario={simpleAuthScenario}
411
+ height="100%"
412
+ selectedEventIndex={selectedIndex}
413
+ onEventIndexChange={setSelectedIndex}
414
+ layoutOptions={{
415
+ namespaceStrategy: 'first',
416
+ eventSpacing: 80,
417
+ nodeWidth: 14,
418
+ nodeHeight: 14,
419
+ }}
420
+ />
421
+ </div>
422
+ </div>
423
+ );
424
+ },
425
+ };
426
+
427
+ /**
428
+ * Interactive selection example
429
+ * Click labels to see them highlight with colored background
430
+ */
431
+ export const InteractiveSelection: Story = {
432
+ render: () => {
433
+ const [selectedIndex, setSelectedIndex] = useState<number | undefined>(0);
434
+
435
+ // Get event names from the scenario
436
+ const eventNames = Object.keys(orderProcessingScenario.template.events);
437
+ const selectedEventName = selectedIndex !== undefined ? eventNames[selectedIndex] : null;
438
+
439
+ return (
440
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
441
+ <div
442
+ style={{
443
+ padding: '16px',
444
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
445
+ color: 'white',
446
+ borderBottom: '4px solid #5a67d8',
447
+ marginBottom: 16,
448
+ }}
449
+ >
450
+ <div style={{ fontSize: 16, marginBottom: 8 }}>
451
+ <strong>✨ Clickable Labels Demo</strong>
452
+ </div>
453
+ <div style={{ fontSize: 14, marginBottom: 8 }}>
454
+ Labels highlight with colored background when selected - clean and intuitive!
455
+ </div>
456
+ <div style={{ fontSize: 14, opacity: 0.9 }}>
457
+ <strong>Selected:</strong>{' '}
458
+ {selectedIndex !== undefined ? (
459
+ <span style={{ color: '#ffd700', fontFamily: 'monospace', fontWeight: 'bold' }}>
460
+ Event {selectedIndex + 1} - {selectedEventName}
461
+ </span>
462
+ ) : (
463
+ <span style={{ opacity: 0.7 }}>Click any label</span>
464
+ )}
465
+ </div>
466
+ </div>
467
+ <div style={{ flex: 1 }}>
468
+ <WorkflowSequenceDiagram
469
+ scenario={orderProcessingScenario}
470
+ height="100%"
471
+ selectedEventIndex={selectedIndex}
472
+ onEventIndexChange={setSelectedIndex}
473
+ layoutOptions={{
474
+ namespaceStrategy: 'first',
475
+ eventSpacing: 80,
476
+ }}
477
+ />
478
+ </div>
479
+ </div>
480
+ );
481
+ },
482
+ };
483
+
484
+ /**
485
+ * Comparison: Edge Labels (Default) vs Node Labels (Optional)
486
+ * Both are clickable! This shows different visualization styles.
487
+ */
488
+ export const EdgeLabelsVsNodeLabels: Story = {
489
+ render: () => {
490
+ const [selectedLeft, setSelectedLeft] = useState<number | undefined>(undefined);
491
+ const [selectedRight, setSelectedRight] = useState<number | undefined>(undefined);
492
+
493
+ return (
494
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', height: '100%', gap: 16 }}>
495
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
496
+ <div style={{
497
+ padding: '12px',
498
+ background: '#3498db',
499
+ color: 'white',
500
+ borderBottom: '3px solid #2980b9',
501
+ marginBottom: 12,
502
+ }}>
503
+ <h3 style={{ margin: '0 0 8px 0', fontSize: 15 }}>📊 Edge Labels (Default)</h3>
504
+ <div style={{ fontSize: 13 }}>
505
+ Selected: {selectedLeft !== undefined ? `Event ${selectedLeft + 1}` : 'None'}
506
+ </div>
507
+ <div style={{ fontSize: 12, marginTop: 4, opacity: 0.9 }}>
508
+ Traditional UML style - labels on arrows
509
+ </div>
510
+ </div>
511
+ <div style={{ flex: 1, border: '1px solid #ddd', borderRadius: 4 }}>
512
+ <WorkflowSequenceDiagram
513
+ scenario={simpleAuthScenario}
514
+ height="100%"
515
+ selectedEventIndex={selectedLeft}
516
+ onEventIndexChange={setSelectedLeft}
517
+ showEventLabels={false}
518
+ layoutOptions={{
519
+ namespaceStrategy: 'first',
520
+ eventSpacing: 100,
521
+ }}
522
+ />
523
+ </div>
524
+ </div>
525
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
526
+ <div style={{
527
+ padding: '12px',
528
+ background: '#9b59b6',
529
+ color: 'white',
530
+ borderBottom: '3px solid #8e44ad',
531
+ marginBottom: 12,
532
+ }}>
533
+ <h3 style={{ margin: '0 0 8px 0', fontSize: 15 }}>🏷️ Node Labels (Alternative)</h3>
534
+ <div style={{ fontSize: 13 }}>
535
+ Selected: {selectedRight !== undefined ? `Event ${selectedRight + 1}` : 'None'}
536
+ </div>
537
+ <div style={{ fontSize: 12, marginTop: 4, opacity: 0.9 }}>
538
+ Labels directly on events - alternative style
539
+ </div>
540
+ </div>
541
+ <div style={{ flex: 1, border: '1px solid #ddd', borderRadius: 4 }}>
542
+ <WorkflowSequenceDiagram
543
+ scenario={simpleAuthScenario}
544
+ height="100%"
545
+ selectedEventIndex={selectedRight}
546
+ onEventIndexChange={setSelectedRight}
547
+ showEventLabels={true}
548
+ layoutOptions={{
549
+ namespaceStrategy: 'first',
550
+ eventSpacing: 100,
551
+ nodeWidth: 80,
552
+ nodeHeight: 50,
553
+ }}
554
+ />
555
+ </div>
556
+ </div>
557
+ </div>
558
+ );
559
+ },
560
+ };
561
+
562
+ /**
563
+ * Comparison of different namespace strategies
564
+ */
565
+ export const NamespaceStrategies: Story = {
566
+ render: () => {
567
+ return (
568
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', height: '100%', gap: 16 }}>
569
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
570
+ <h3 style={{ margin: '0 0 12px 0' }}>First Segment (Participants)</h3>
571
+ <div style={{ flex: 1, border: '1px solid #ddd', borderRadius: 4 }}>
572
+ <WorkflowSequenceDiagram
573
+ scenario={orderProcessingScenario}
574
+ height="100%"
575
+ layoutOptions={{
576
+ namespaceStrategy: 'first',
577
+ laneWidth: 180,
578
+ eventSpacing: 60,
579
+ }}
580
+ />
581
+ </div>
582
+ </div>
583
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
584
+ <h3 style={{ margin: '0 0 12px 0' }}>All But Last (Detailed)</h3>
585
+ <div style={{ flex: 1, border: '1px solid #ddd', borderRadius: 4 }}>
586
+ <WorkflowSequenceDiagram
587
+ scenario={orderProcessingScenario}
588
+ height="100%"
589
+ layoutOptions={{
590
+ namespaceStrategy: 'all-but-last',
591
+ laneWidth: 180,
592
+ eventSpacing: 60,
593
+ }}
594
+ />
595
+ </div>
596
+ </div>
597
+ </div>
598
+ );
599
+ },
600
+ };
601
+
602
+ /**
603
+ * Custom layout options - compact view
604
+ */
605
+ export const CompactLayout: Story = {
606
+ args: {
607
+ scenario: simpleAuthScenario,
608
+ height: 400,
609
+ layoutOptions: {
610
+ namespaceStrategy: 'first',
611
+ laneWidth: 150,
612
+ laneGap: 30,
613
+ eventSpacing: 50,
614
+ },
615
+ },
616
+ };
617
+
618
+ /**
619
+ * Custom layout options - spacious view
620
+ */
621
+ export const SpaciousLayout: Story = {
622
+ args: {
623
+ scenario: simpleAuthScenario,
624
+ height: 600,
625
+ layoutOptions: {
626
+ namespaceStrategy: 'first',
627
+ laneWidth: 250,
628
+ laneGap: 100,
629
+ eventSpacing: 120,
630
+ },
631
+ },
632
+ };
633
+
634
+ /**
635
+ * Without controls for embedded use
636
+ */
637
+ export const WithoutControls: Story = {
638
+ args: {
639
+ scenario: simpleAuthScenario,
640
+ height: 500,
641
+ showControls: false,
642
+ layoutOptions: {
643
+ namespaceStrategy: 'first',
644
+ eventSpacing: 80,
645
+ },
646
+ },
647
+ };
648
+
649
+ /**
650
+ * With background grid
651
+ */
652
+ export const WithBackground: Story = {
653
+ args: {
654
+ scenario: simpleAuthScenario,
655
+ height: 500,
656
+ showBackground: true,
657
+ layoutOptions: {
658
+ namespaceStrategy: 'first',
659
+ eventSpacing: 80,
660
+ },
661
+ },
662
+ };
663
+
664
+ /**
665
+ * Full-featured example with all options
666
+ * Demonstrates event navigation, selection, and visual customization
667
+ */
668
+ export const FullFeatured: Story = {
669
+ render: () => {
670
+ const [selectedIndex, setSelectedIndex] = useState<number>(0);
671
+ const [showControls, setShowControls] = useState(true);
672
+ const [showBackground, setShowBackground] = useState(false);
673
+
674
+ const eventNames = Object.keys(orderProcessingScenario.template.events);
675
+ const totalEvents = eventNames.length;
676
+
677
+ return (
678
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
679
+ <div
680
+ style={{
681
+ padding: '16px',
682
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
683
+ color: 'white',
684
+ borderBottom: '4px solid #5a67d8',
685
+ marginBottom: 16,
686
+ }}
687
+ >
688
+ <div style={{ marginBottom: 12, fontSize: 15 }}>
689
+ <strong>📍 Current Event:</strong>{' '}
690
+ <span style={{ fontFamily: 'monospace', background: 'rgba(255,255,255,0.2)', padding: '4px 8px', borderRadius: 3 }}>
691
+ {selectedIndex + 1} / {totalEvents}
692
+ </span>
693
+ {' - '}
694
+ <span style={{ fontFamily: 'monospace' }}>{eventNames[selectedIndex]}</span>
695
+ </div>
696
+
697
+ <div style={{ display: 'flex', gap: 16, alignItems: 'center', flexWrap: 'wrap' }}>
698
+ <div style={{ display: 'flex', gap: 8 }}>
699
+ <button
700
+ onClick={() => setSelectedIndex(Math.max(0, selectedIndex - 1))}
701
+ disabled={selectedIndex === 0}
702
+ style={{
703
+ padding: '8px 16px',
704
+ cursor: selectedIndex === 0 ? 'not-allowed' : 'pointer',
705
+ background: selectedIndex === 0 ? '#ccc' : '#3498db',
706
+ color: 'white',
707
+ border: 'none',
708
+ borderRadius: 4,
709
+ fontSize: 13,
710
+ fontWeight: 'bold',
711
+ }}
712
+ >
713
+ ← Previous
714
+ </button>
715
+ <button
716
+ onClick={() => setSelectedIndex(Math.min(totalEvents - 1, selectedIndex + 1))}
717
+ disabled={selectedIndex === totalEvents - 1}
718
+ style={{
719
+ padding: '8px 16px',
720
+ cursor: selectedIndex === totalEvents - 1 ? 'not-allowed' : 'pointer',
721
+ background: selectedIndex === totalEvents - 1 ? '#ccc' : '#3498db',
722
+ color: 'white',
723
+ border: 'none',
724
+ borderRadius: 4,
725
+ fontSize: 13,
726
+ fontWeight: 'bold',
727
+ }}
728
+ >
729
+ Next →
730
+ </button>
731
+ </div>
732
+
733
+ <div style={{ borderLeft: '2px solid rgba(255,255,255,0.3)', paddingLeft: 16, display: 'flex', gap: 12 }}>
734
+ <label style={{ cursor: 'pointer', fontSize: 13 }}>
735
+ <input
736
+ type="checkbox"
737
+ checked={showControls}
738
+ onChange={(e) => setShowControls(e.target.checked)}
739
+ style={{ marginRight: 6 }}
740
+ />
741
+ Show Controls
742
+ </label>
743
+ <label style={{ cursor: 'pointer', fontSize: 13 }}>
744
+ <input
745
+ type="checkbox"
746
+ checked={showBackground}
747
+ onChange={(e) => setShowBackground(e.target.checked)}
748
+ style={{ marginRight: 6 }}
749
+ />
750
+ Show Background
751
+ </label>
752
+ </div>
753
+ </div>
754
+
755
+ <div style={{ marginTop: 12, fontSize: 12, opacity: 0.9, background: 'rgba(0,0,0,0.2)', padding: '8px', borderRadius: 4 }}>
756
+ 💡 <strong>Tip:</strong> Click directly on any event dot in the diagram to jump to it!
757
+ </div>
758
+ </div>
759
+ <div style={{ flex: 1 }}>
760
+ <WorkflowSequenceDiagram
761
+ scenario={orderProcessingScenario}
762
+ height="100%"
763
+ selectedEventIndex={selectedIndex}
764
+ onEventIndexChange={setSelectedIndex}
765
+ showControls={showControls}
766
+ showBackground={showBackground}
767
+ layoutOptions={{
768
+ namespaceStrategy: 'first',
769
+ laneWidth: 200,
770
+ eventSpacing: 70,
771
+ }}
772
+ />
773
+ </div>
774
+ </div>
775
+ );
776
+ },
777
+ };