@principal-ai/principal-view-react 0.6.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.
Files changed (96) hide show
  1. package/README.md +111 -0
  2. package/dist/components/ConfigurationSelector.d.ts +37 -0
  3. package/dist/components/ConfigurationSelector.d.ts.map +1 -0
  4. package/dist/components/ConfigurationSelector.js +67 -0
  5. package/dist/components/ConfigurationSelector.js.map +1 -0
  6. package/dist/components/EdgeInfoPanel.d.ts +16 -0
  7. package/dist/components/EdgeInfoPanel.d.ts.map +1 -0
  8. package/dist/components/EdgeInfoPanel.js +85 -0
  9. package/dist/components/EdgeInfoPanel.js.map +1 -0
  10. package/dist/components/EventLog.d.ts +20 -0
  11. package/dist/components/EventLog.d.ts.map +1 -0
  12. package/dist/components/EventLog.js +13 -0
  13. package/dist/components/EventLog.js.map +1 -0
  14. package/dist/components/EventLog.test.d.ts +2 -0
  15. package/dist/components/EventLog.test.d.ts.map +1 -0
  16. package/dist/components/EventLog.test.js +73 -0
  17. package/dist/components/EventLog.test.js.map +1 -0
  18. package/dist/components/GraphRenderer.d.ts +121 -0
  19. package/dist/components/GraphRenderer.d.ts.map +1 -0
  20. package/dist/components/GraphRenderer.js +809 -0
  21. package/dist/components/GraphRenderer.js.map +1 -0
  22. package/dist/components/GraphRenderer.test.d.ts +2 -0
  23. package/dist/components/GraphRenderer.test.d.ts.map +1 -0
  24. package/dist/components/GraphRenderer.test.js +88 -0
  25. package/dist/components/GraphRenderer.test.js.map +1 -0
  26. package/dist/components/MetricsDashboard.d.ts +14 -0
  27. package/dist/components/MetricsDashboard.d.ts.map +1 -0
  28. package/dist/components/MetricsDashboard.js +13 -0
  29. package/dist/components/MetricsDashboard.js.map +1 -0
  30. package/dist/components/NodeInfoPanel.d.ts +21 -0
  31. package/dist/components/NodeInfoPanel.d.ts.map +1 -0
  32. package/dist/components/NodeInfoPanel.js +217 -0
  33. package/dist/components/NodeInfoPanel.js.map +1 -0
  34. package/dist/edges/CustomEdge.d.ts +16 -0
  35. package/dist/edges/CustomEdge.d.ts.map +1 -0
  36. package/dist/edges/CustomEdge.js +200 -0
  37. package/dist/edges/CustomEdge.js.map +1 -0
  38. package/dist/edges/GenericEdge.d.ts +18 -0
  39. package/dist/edges/GenericEdge.d.ts.map +1 -0
  40. package/dist/edges/GenericEdge.js +14 -0
  41. package/dist/edges/GenericEdge.js.map +1 -0
  42. package/dist/hooks/usePathBasedEvents.d.ts +42 -0
  43. package/dist/hooks/usePathBasedEvents.d.ts.map +1 -0
  44. package/dist/hooks/usePathBasedEvents.js +122 -0
  45. package/dist/hooks/usePathBasedEvents.js.map +1 -0
  46. package/dist/index.d.ts +33 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +41 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/nodes/CustomNode.d.ts +18 -0
  51. package/dist/nodes/CustomNode.d.ts.map +1 -0
  52. package/dist/nodes/CustomNode.js +298 -0
  53. package/dist/nodes/CustomNode.js.map +1 -0
  54. package/dist/nodes/GenericNode.d.ts +20 -0
  55. package/dist/nodes/GenericNode.d.ts.map +1 -0
  56. package/dist/nodes/GenericNode.js +24 -0
  57. package/dist/nodes/GenericNode.js.map +1 -0
  58. package/dist/utils/animationMapping.d.ts +53 -0
  59. package/dist/utils/animationMapping.d.ts.map +1 -0
  60. package/dist/utils/animationMapping.js +133 -0
  61. package/dist/utils/animationMapping.js.map +1 -0
  62. package/dist/utils/graphConverter.d.ts +22 -0
  63. package/dist/utils/graphConverter.d.ts.map +1 -0
  64. package/dist/utils/graphConverter.js +176 -0
  65. package/dist/utils/graphConverter.js.map +1 -0
  66. package/dist/utils/iconResolver.d.ts +29 -0
  67. package/dist/utils/iconResolver.d.ts.map +1 -0
  68. package/dist/utils/iconResolver.js +68 -0
  69. package/dist/utils/iconResolver.js.map +1 -0
  70. package/package.json +61 -0
  71. package/src/components/ConfigurationSelector.tsx +147 -0
  72. package/src/components/EdgeInfoPanel.tsx +198 -0
  73. package/src/components/EventLog.test.tsx +85 -0
  74. package/src/components/EventLog.tsx +51 -0
  75. package/src/components/GraphRenderer.test.tsx +118 -0
  76. package/src/components/GraphRenderer.tsx +1222 -0
  77. package/src/components/MetricsDashboard.tsx +40 -0
  78. package/src/components/NodeInfoPanel.tsx +425 -0
  79. package/src/edges/CustomEdge.tsx +344 -0
  80. package/src/edges/GenericEdge.tsx +40 -0
  81. package/src/hooks/usePathBasedEvents.ts +182 -0
  82. package/src/index.ts +67 -0
  83. package/src/nodes/CustomNode.tsx +432 -0
  84. package/src/nodes/GenericNode.tsx +54 -0
  85. package/src/stories/AnimationWorkshop.stories.tsx +608 -0
  86. package/src/stories/EventDrivenAnimations.stories.tsx +499 -0
  87. package/src/stories/EventLog.stories.tsx +161 -0
  88. package/src/stories/GraphRenderer.stories.tsx +628 -0
  89. package/src/stories/Introduction.mdx +51 -0
  90. package/src/stories/MetricsDashboard.stories.tsx +227 -0
  91. package/src/stories/MultiConfig.stories.tsx +531 -0
  92. package/src/stories/MultiDirectionalConnections.stories.tsx +345 -0
  93. package/src/stories/NodeShapes.stories.tsx +769 -0
  94. package/src/utils/animationMapping.ts +170 -0
  95. package/src/utils/graphConverter.ts +218 -0
  96. package/src/utils/iconResolver.tsx +49 -0
@@ -0,0 +1,198 @@
1
+ import React from 'react';
2
+ import type { EdgeState, EdgeTypeDefinition } from '@principal-ai/principal-view-core';
3
+
4
+ export interface EdgeInfoPanelProps {
5
+ edge: EdgeState;
6
+ typeDefinition: EdgeTypeDefinition;
7
+ sourceNodeId: string;
8
+ targetNodeId: string;
9
+ onClose: () => void;
10
+ /** Optional callback to delete the edge. If not provided, delete button is hidden. */
11
+ onDelete?: (edgeId: string) => void;
12
+ }
13
+
14
+ /**
15
+ * Panel that displays information about a selected edge
16
+ */
17
+ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
18
+ edge,
19
+ typeDefinition,
20
+ sourceNodeId,
21
+ targetNodeId,
22
+ onClose,
23
+ onDelete,
24
+ }) => {
25
+ const color = typeDefinition.color || '#888';
26
+
27
+ // Get fields to display based on dataSchema
28
+ const displayFields = typeDefinition.dataSchema
29
+ ? Object.entries(typeDefinition.dataSchema)
30
+ .filter(([, schema]) => schema.displayInInfo)
31
+ .map(([field, schema]) => ({
32
+ field,
33
+ label: schema.label || field,
34
+ value: edge.data?.[field],
35
+ }))
36
+ : [];
37
+
38
+ // Always show basic edge data if no schema is defined
39
+ const hasSchemaFields = displayFields.length > 0;
40
+ const edgeDataEntries = edge.data ? Object.entries(edge.data) : [];
41
+
42
+ return (
43
+ <div
44
+ style={{
45
+ position: 'absolute',
46
+ top: '60px',
47
+ right: '20px',
48
+ backgroundColor: 'white',
49
+ borderRadius: '8px',
50
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
51
+ padding: '16px',
52
+ minWidth: '250px',
53
+ maxWidth: '350px',
54
+ zIndex: 1000,
55
+ }}
56
+ >
57
+ {/* Header */}
58
+ <div style={{
59
+ display: 'flex',
60
+ justifyContent: 'space-between',
61
+ alignItems: 'center',
62
+ marginBottom: '12px',
63
+ paddingBottom: '8px',
64
+ borderBottom: `2px solid ${color}`,
65
+ }}>
66
+ <div style={{ fontWeight: 'bold', fontSize: '14px' }}>
67
+ Edge Information
68
+ </div>
69
+ <button
70
+ onClick={onClose}
71
+ style={{
72
+ border: 'none',
73
+ background: 'none',
74
+ cursor: 'pointer',
75
+ fontSize: '18px',
76
+ color: '#666',
77
+ padding: '0',
78
+ width: '24px',
79
+ height: '24px',
80
+ display: 'flex',
81
+ alignItems: 'center',
82
+ justifyContent: 'center',
83
+ }}
84
+ >
85
+ ×
86
+ </button>
87
+ </div>
88
+
89
+ {/* Edge Type */}
90
+ <div style={{ marginBottom: '12px' }}>
91
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
92
+ Type
93
+ </div>
94
+ <div style={{
95
+ fontSize: '12px',
96
+ padding: '4px 8px',
97
+ backgroundColor: color,
98
+ color: 'white',
99
+ borderRadius: '4px',
100
+ display: 'inline-block',
101
+ }}>
102
+ {edge.type}
103
+ </div>
104
+ </div>
105
+
106
+ {/* Connection Info */}
107
+ <div style={{ marginBottom: '12px' }}>
108
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
109
+ Connection
110
+ </div>
111
+ <div style={{ fontSize: '12px', color: '#333' }}>
112
+ <span style={{ fontFamily: 'monospace', backgroundColor: '#f0f0f0', padding: '2px 6px', borderRadius: '3px' }}>
113
+ {sourceNodeId}
114
+ </span>
115
+ <span style={{ margin: '0 8px', color: '#888' }}>→</span>
116
+ <span style={{ fontFamily: 'monospace', backgroundColor: '#f0f0f0', padding: '2px 6px', borderRadius: '3px' }}>
117
+ {targetNodeId}
118
+ </span>
119
+ </div>
120
+ </div>
121
+
122
+ {/* Display schema-defined fields */}
123
+ {hasSchemaFields && (
124
+ <div style={{ marginBottom: '12px' }}>
125
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '8px', fontWeight: 'bold' }}>
126
+ Properties
127
+ </div>
128
+ {displayFields.map(({ field, label, value }) => (
129
+ <div key={field} style={{ marginBottom: '8px' }}>
130
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '2px' }}>
131
+ {label}
132
+ </div>
133
+ <div style={{ fontSize: '12px', color: '#333' }}>
134
+ {value !== undefined && value !== null
135
+ ? typeof value === 'object'
136
+ ? JSON.stringify(value, null, 2)
137
+ : String(value)
138
+ : '-'}
139
+ </div>
140
+ </div>
141
+ ))}
142
+ </div>
143
+ )}
144
+
145
+ {/* Show all edge data if no schema is defined */}
146
+ {!hasSchemaFields && edgeDataEntries.length > 0 && (
147
+ <div style={{ marginBottom: '12px' }}>
148
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '8px', fontWeight: 'bold' }}>
149
+ Data
150
+ </div>
151
+ {edgeDataEntries.map(([key, value]) => (
152
+ <div key={key} style={{ marginBottom: '8px' }}>
153
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '2px' }}>
154
+ {key}
155
+ </div>
156
+ <div style={{ fontSize: '12px', color: '#333', wordBreak: 'break-word' }}>
157
+ {value !== undefined && value !== null
158
+ ? typeof value === 'object'
159
+ ? JSON.stringify(value, null, 2)
160
+ : String(value)
161
+ : '-'}
162
+ </div>
163
+ </div>
164
+ ))}
165
+ </div>
166
+ )}
167
+
168
+ {/* Metadata */}
169
+ <div style={{ fontSize: '10px', color: '#999', marginTop: '12px', paddingTop: '8px', borderTop: '1px solid #eee' }}>
170
+ ID: {edge.id}
171
+ </div>
172
+
173
+ {/* Delete Button */}
174
+ {onDelete && (
175
+ <button
176
+ onClick={() => {
177
+ onDelete(edge.id);
178
+ onClose();
179
+ }}
180
+ style={{
181
+ marginTop: '12px',
182
+ width: '100%',
183
+ padding: '8px 12px',
184
+ backgroundColor: '#dc3545',
185
+ color: 'white',
186
+ border: 'none',
187
+ borderRadius: '4px',
188
+ cursor: 'pointer',
189
+ fontSize: '12px',
190
+ fontWeight: 'bold',
191
+ }}
192
+ >
193
+ Delete Edge
194
+ </button>
195
+ )}
196
+ </div>
197
+ );
198
+ };
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { EventLog } from './EventLog';
4
+ import type { GraphEvent, Violation } from '@principal-ai/principal-view-core';
5
+
6
+ describe('EventLog', () => {
7
+ const testEvents: GraphEvent[] = [
8
+ {
9
+ id: 'evt-1',
10
+ type: 'node_created',
11
+ timestamp: Date.now(),
12
+ category: 'node',
13
+ operation: 'create',
14
+ payload: {
15
+ operation: 'create',
16
+ nodeId: 'user-1',
17
+ nodeType: 'user',
18
+ data: { userId: 'alice' },
19
+ },
20
+ expected: true,
21
+ },
22
+ {
23
+ id: 'evt-2',
24
+ type: 'edge_created',
25
+ timestamp: Date.now(),
26
+ category: 'edge',
27
+ operation: 'create',
28
+ payload: {
29
+ operation: 'create',
30
+ edgeId: 'conn-1',
31
+ edgeType: 'connection',
32
+ from: 'user-1',
33
+ to: 'user-2',
34
+ },
35
+ expected: true,
36
+ },
37
+ ];
38
+
39
+ const testViolations: Violation[] = [
40
+ {
41
+ id: 'violation-1',
42
+ severity: 'error',
43
+ type: 'connection',
44
+ description: 'Invalid connection',
45
+ },
46
+ ];
47
+
48
+ it('should render without crashing', () => {
49
+ render(<EventLog events={testEvents} />);
50
+
51
+ expect(screen.getByText(/Event Log/i)).toBeDefined();
52
+ });
53
+
54
+ it('should display event count', () => {
55
+ render(<EventLog events={testEvents} />);
56
+
57
+ expect(screen.getByText(/Events: 2/i)).toBeDefined();
58
+ });
59
+
60
+ it('should display violation count', () => {
61
+ render(<EventLog events={testEvents} violations={testViolations} />);
62
+
63
+ expect(screen.getByText(/Violations: 1/i)).toBeDefined();
64
+ });
65
+
66
+ it('should render with empty events', () => {
67
+ render(<EventLog events={[]} />);
68
+
69
+ expect(screen.getByText(/Events: 0/i)).toBeDefined();
70
+ });
71
+
72
+ it('should apply custom className', () => {
73
+ const { container } = render(<EventLog events={testEvents} className="custom-log" />);
74
+
75
+ const element = container.querySelector('.custom-log');
76
+ expect(element).toBeDefined();
77
+ });
78
+
79
+ it('should apply custom maxHeight', () => {
80
+ const { container } = render(<EventLog events={testEvents} maxHeight="300px" />);
81
+
82
+ const element = container.querySelector('div');
83
+ expect(element?.style.maxHeight).toBe('300px');
84
+ });
85
+ });
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import type { GraphEvent, Violation } from '@principal-ai/principal-view-core';
3
+
4
+ export interface EventLogProps {
5
+ /** List of events to display */
6
+ events: GraphEvent[];
7
+
8
+ /** Optional violations to highlight */
9
+ violations?: Violation[];
10
+
11
+ /** Callback when an event is clicked */
12
+ onEventClick?: (event: GraphEvent) => void;
13
+
14
+ /** Optional class name */
15
+ className?: string;
16
+
17
+ /** Optional max height */
18
+ maxHeight?: number | string;
19
+ }
20
+
21
+ /**
22
+ * Event log component for displaying graph events
23
+ * TODO: Implement filtering, search, and severity indicators
24
+ */
25
+ export const EventLog: React.FC<EventLogProps> = ({
26
+ events,
27
+ violations = [],
28
+ className,
29
+ maxHeight = '400px',
30
+ }) => {
31
+ return (
32
+ <div className={className} style={{ maxHeight, overflowY: 'auto', border: '1px solid #ccc' }}>
33
+ <div style={{ padding: '10px' }}>
34
+ <h3>Event Log (TODO)</h3>
35
+ <p>Events: {events.length}</p>
36
+ <p>Violations: {violations.length}</p>
37
+ <div>
38
+ <strong>TODO:</strong>
39
+ <ul>
40
+ <li>Display events in chronological order</li>
41
+ <li>Show event type, category, operation</li>
42
+ <li>Highlight violations with color coding</li>
43
+ <li>Add filtering by category, type, severity</li>
44
+ <li>Add text search</li>
45
+ <li>Add timestamp formatting</li>
46
+ </ul>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ );
51
+ };
@@ -0,0 +1,118 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { GraphRenderer } from './GraphRenderer';
4
+ import type { GraphConfiguration, NodeState, EdgeState } from '@principal-ai/principal-view-core';
5
+
6
+ describe('GraphRenderer', () => {
7
+ const testConfig: GraphConfiguration = {
8
+ metadata: {
9
+ name: 'Test Graph',
10
+ version: '1.0.0',
11
+ },
12
+ nodeTypes: {
13
+ user: {
14
+ shape: 'circle',
15
+ color: '#4CAF50',
16
+ dataSchema: {
17
+ userId: { type: 'string', required: true },
18
+ },
19
+ },
20
+ },
21
+ edgeTypes: {
22
+ connection: {
23
+ style: 'solid',
24
+ directed: true,
25
+ },
26
+ },
27
+ allowedConnections: [{ from: 'user', to: 'user', via: 'connection' }],
28
+ };
29
+
30
+ const testNodes: NodeState[] = [
31
+ {
32
+ id: 'user-1',
33
+ type: 'user',
34
+ data: { userId: 'alice' },
35
+ createdAt: Date.now(),
36
+ updatedAt: Date.now(),
37
+ },
38
+ {
39
+ id: 'user-2',
40
+ type: 'user',
41
+ data: { userId: 'bob' },
42
+ createdAt: Date.now(),
43
+ updatedAt: Date.now(),
44
+ },
45
+ ];
46
+
47
+ const testEdges: EdgeState[] = [
48
+ {
49
+ id: 'conn-1',
50
+ type: 'connection',
51
+ from: 'user-1',
52
+ to: 'user-2',
53
+ createdAt: Date.now(),
54
+ updatedAt: Date.now(),
55
+ },
56
+ ];
57
+
58
+ it('should render without crashing', () => {
59
+ render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
60
+
61
+ expect(screen.getByText(/Graph Renderer/i)).toBeDefined();
62
+ });
63
+
64
+ it('should display configuration name', () => {
65
+ render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
66
+
67
+ expect(screen.getByText(/Test Graph/i)).toBeDefined();
68
+ });
69
+
70
+ it('should display node count', () => {
71
+ render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
72
+
73
+ expect(screen.getByText(/Nodes: 2/i)).toBeDefined();
74
+ });
75
+
76
+ it('should display edge count', () => {
77
+ render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
78
+
79
+ expect(screen.getByText(/Edges: 1/i)).toBeDefined();
80
+ });
81
+
82
+ it('should render with empty nodes and edges', () => {
83
+ render(<GraphRenderer configuration={testConfig} nodes={[]} edges={[]} />);
84
+
85
+ expect(screen.getByText(/Nodes: 0/i)).toBeDefined();
86
+ expect(screen.getByText(/Edges: 0/i)).toBeDefined();
87
+ });
88
+
89
+ it('should apply custom className', () => {
90
+ const { container } = render(
91
+ <GraphRenderer
92
+ configuration={testConfig}
93
+ nodes={testNodes}
94
+ edges={testEdges}
95
+ className="custom-class"
96
+ />
97
+ );
98
+
99
+ const element = container.querySelector('.custom-class');
100
+ expect(element).toBeDefined();
101
+ });
102
+
103
+ it('should apply custom width and height', () => {
104
+ const { container } = render(
105
+ <GraphRenderer
106
+ configuration={testConfig}
107
+ nodes={testNodes}
108
+ edges={testEdges}
109
+ width="500px"
110
+ height="400px"
111
+ />
112
+ );
113
+
114
+ const element = container.querySelector('div');
115
+ expect(element?.style.width).toBe('500px');
116
+ expect(element?.style.height).toBe('400px');
117
+ });
118
+ });