@vertesia/fusion-ux 1.2.0 → 1.4.0-dev.20260614.160504Z
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.
- package/LICENSE +198 -10
- package/lib/fusion-fragment/ChartRenderer.d.ts.map +1 -0
- package/lib/{esm/fusion-fragment → fusion-fragment}/ChartRenderer.js +8 -6
- package/lib/fusion-fragment/ChartRenderer.js.map +1 -0
- package/lib/{types/fusion-fragment → fusion-fragment}/FieldRenderer.d.ts +1 -1
- package/lib/fusion-fragment/FieldRenderer.d.ts.map +1 -0
- package/lib/{esm/fusion-fragment → fusion-fragment}/FieldRenderer.js +14 -8
- package/lib/fusion-fragment/FieldRenderer.js.map +1 -0
- package/lib/{types/fusion-fragment → fusion-fragment}/FusionFragmentContext.d.ts +3 -3
- package/lib/fusion-fragment/FusionFragmentContext.d.ts.map +1 -0
- package/lib/{esm/fusion-fragment → fusion-fragment}/FusionFragmentContext.js +2 -2
- package/lib/fusion-fragment/FusionFragmentContext.js.map +1 -0
- package/lib/fusion-fragment/FusionFragmentHandler.d.ts.map +1 -0
- package/lib/{esm/fusion-fragment → fusion-fragment}/FusionFragmentHandler.js +7 -14
- package/lib/fusion-fragment/FusionFragmentHandler.js.map +1 -0
- package/lib/fusion-fragment/FusionFragmentRenderer.d.ts.map +1 -0
- package/lib/{esm/fusion-fragment → fusion-fragment}/FusionFragmentRenderer.js +2 -2
- package/lib/fusion-fragment/FusionFragmentRenderer.js.map +1 -0
- package/lib/{types/fusion-fragment → fusion-fragment}/SectionRenderer.d.ts +1 -1
- package/lib/fusion-fragment/SectionRenderer.d.ts.map +1 -0
- package/lib/{esm/fusion-fragment → fusion-fragment}/SectionRenderer.js +12 -5
- package/lib/fusion-fragment/SectionRenderer.js.map +1 -0
- package/lib/fusion-fragment/TableRenderer.d.ts.map +1 -0
- package/lib/{esm/fusion-fragment → fusion-fragment}/TableRenderer.js +9 -7
- package/lib/fusion-fragment/TableRenderer.js.map +1 -0
- package/lib/{types/fusion-fragment → fusion-fragment}/index.d.ts +4 -4
- package/lib/fusion-fragment/index.d.ts.map +1 -0
- package/lib/{esm/fusion-fragment → fusion-fragment}/index.js +4 -4
- package/lib/fusion-fragment/index.js.map +1 -0
- package/lib/{types/index.d.ts → index.d.ts} +4 -4
- package/lib/index.d.ts.map +1 -0
- package/lib/{esm/index.js → index.js} +4 -4
- package/lib/index.js.map +1 -0
- package/lib/{types/render → render}/index.d.ts +1 -1
- package/lib/render/index.d.ts.map +1 -0
- package/lib/{esm/render → render}/index.js +1 -1
- package/lib/render/index.js.map +1 -0
- package/lib/render/textPreview.d.ts.map +1 -0
- package/lib/{esm/render → render}/textPreview.js +12 -9
- package/lib/render/textPreview.js.map +1 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js.map +1 -0
- package/lib/validation/formatErrors.d.ts.map +1 -0
- package/lib/{esm/validation → validation}/formatErrors.js +1 -1
- package/lib/validation/formatErrors.js.map +1 -0
- package/lib/validation/fuzzyMatch.d.ts.map +1 -0
- package/lib/{esm/validation → validation}/fuzzyMatch.js +3 -4
- package/lib/validation/fuzzyMatch.js.map +1 -0
- package/lib/validation/index.d.ts +8 -0
- package/lib/validation/index.d.ts.map +1 -0
- package/lib/validation/index.js +8 -0
- package/lib/validation/index.js.map +1 -0
- package/lib/{types/validation → validation}/schemas.d.ts +1 -1
- package/lib/validation/schemas.d.ts.map +1 -0
- package/lib/{esm/validation → validation}/schemas.js +25 -25
- package/lib/validation/schemas.js.map +1 -0
- package/lib/validation/validateTemplate.d.ts.map +1 -0
- package/lib/{esm/validation → validation}/validateTemplate.js +23 -21
- package/lib/validation/validateTemplate.js.map +1 -0
- package/lib/vertesia-fusion-ux.js +1 -1
- package/lib/vertesia-fusion-ux.js.map +1 -1
- package/package.json +23 -35
- package/src/fusion-fragment/ChartRenderer.tsx +95 -96
- package/src/fusion-fragment/FieldRenderer.tsx +173 -174
- package/src/fusion-fragment/FusionFragmentContext.tsx +31 -37
- package/src/fusion-fragment/FusionFragmentHandler.tsx +214 -223
- package/src/fusion-fragment/FusionFragmentRenderer.tsx +102 -107
- package/src/fusion-fragment/SectionRenderer.tsx +174 -169
- package/src/fusion-fragment/TableRenderer.tsx +175 -171
- package/src/fusion-fragment/index.ts +11 -11
- package/src/index.ts +42 -45
- package/src/render/index.ts +3 -3
- package/src/render/textPreview.ts +183 -186
- package/src/types.ts +174 -174
- package/src/validation/formatErrors.ts +86 -86
- package/src/validation/fuzzyMatch.ts +69 -77
- package/src/validation/index.ts +3 -3
- package/src/validation/schemas.ts +120 -120
- package/src/validation/validateTemplate.ts +225 -226
- package/lib/esm/fusion-fragment/ChartRenderer.js.map +0 -1
- package/lib/esm/fusion-fragment/FieldRenderer.js.map +0 -1
- package/lib/esm/fusion-fragment/FusionFragmentContext.js.map +0 -1
- package/lib/esm/fusion-fragment/FusionFragmentHandler.js.map +0 -1
- package/lib/esm/fusion-fragment/FusionFragmentRenderer.js.map +0 -1
- package/lib/esm/fusion-fragment/SectionRenderer.js.map +0 -1
- package/lib/esm/fusion-fragment/TableRenderer.js.map +0 -1
- package/lib/esm/fusion-fragment/index.js.map +0 -1
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/render/index.js.map +0 -1
- package/lib/esm/render/textPreview.js.map +0 -1
- package/lib/esm/types.js.map +0 -1
- package/lib/esm/validation/formatErrors.js.map +0 -1
- package/lib/esm/validation/fuzzyMatch.js.map +0 -1
- package/lib/esm/validation/index.js +0 -8
- package/lib/esm/validation/index.js.map +0 -1
- package/lib/esm/validation/schemas.js.map +0 -1
- package/lib/esm/validation/validateTemplate.js.map +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
- package/lib/types/fusion-fragment/ChartRenderer.d.ts.map +0 -1
- package/lib/types/fusion-fragment/FieldRenderer.d.ts.map +0 -1
- package/lib/types/fusion-fragment/FusionFragmentContext.d.ts.map +0 -1
- package/lib/types/fusion-fragment/FusionFragmentHandler.d.ts.map +0 -1
- package/lib/types/fusion-fragment/FusionFragmentRenderer.d.ts.map +0 -1
- package/lib/types/fusion-fragment/SectionRenderer.d.ts.map +0 -1
- package/lib/types/fusion-fragment/TableRenderer.d.ts.map +0 -1
- package/lib/types/fusion-fragment/index.d.ts.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/render/index.d.ts.map +0 -1
- package/lib/types/render/textPreview.d.ts.map +0 -1
- package/lib/types/types.d.ts.map +0 -1
- package/lib/types/validation/formatErrors.d.ts.map +0 -1
- package/lib/types/validation/fuzzyMatch.d.ts.map +0 -1
- package/lib/types/validation/index.d.ts +0 -8
- package/lib/types/validation/index.d.ts.map +0 -1
- package/lib/types/validation/schemas.d.ts.map +0 -1
- package/lib/types/validation/validateTemplate.d.ts.map +0 -1
- /package/lib/{types/fusion-fragment → fusion-fragment}/ChartRenderer.d.ts +0 -0
- /package/lib/{types/fusion-fragment → fusion-fragment}/FusionFragmentHandler.d.ts +0 -0
- /package/lib/{types/fusion-fragment → fusion-fragment}/FusionFragmentRenderer.d.ts +0 -0
- /package/lib/{types/fusion-fragment → fusion-fragment}/TableRenderer.d.ts +0 -0
- /package/lib/{types/render → render}/textPreview.d.ts +0 -0
- /package/lib/{types/types.d.ts → types.d.ts} +0 -0
- /package/lib/{esm/types.js → types.js} +0 -0
- /package/lib/{types/validation → validation}/formatErrors.d.ts +0 -0
- /package/lib/{types/validation → validation}/fuzzyMatch.d.ts +0 -0
- /package/lib/{types/validation → validation}/validateTemplate.d.ts +0 -0
|
@@ -3,91 +3,90 @@
|
|
|
3
3
|
* Renders a model-generated template with actual data values
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { type ReactElement, useMemo } from 'react';
|
|
7
7
|
import type { FusionFragmentRendererProps, ValidationError } from '../types.js';
|
|
8
8
|
import { validateTemplate } from '../validation/validateTemplate.js';
|
|
9
9
|
import { SectionRenderer } from './SectionRenderer.js';
|
|
10
10
|
|
|
11
11
|
const styles = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
12
|
+
container: {
|
|
13
|
+
backgroundColor: 'var(--gray-2, #f9fafb)',
|
|
14
|
+
border: '1px solid var(--gray-5, #e5e7eb)',
|
|
15
|
+
borderRadius: '8px',
|
|
16
|
+
padding: '16px 20px',
|
|
17
|
+
},
|
|
18
|
+
title: {
|
|
19
|
+
fontSize: '16px',
|
|
20
|
+
fontWeight: 600,
|
|
21
|
+
color: 'var(--gray-12, #1f2937)',
|
|
22
|
+
marginBottom: '16px',
|
|
23
|
+
paddingBottom: '12px',
|
|
24
|
+
borderBottom: '1px solid var(--gray-5, #e5e7eb)',
|
|
25
|
+
},
|
|
26
|
+
footer: {
|
|
27
|
+
fontSize: '12px',
|
|
28
|
+
color: 'var(--gray-10, #9ca3af)',
|
|
29
|
+
marginTop: '16px',
|
|
30
|
+
paddingTop: '12px',
|
|
31
|
+
borderTop: '1px solid var(--gray-5, #e5e7eb)',
|
|
32
|
+
},
|
|
33
|
+
error: {
|
|
34
|
+
backgroundColor: 'var(--red-2, #fef2f2)',
|
|
35
|
+
border: '1px solid var(--red-6, #fca5a5)',
|
|
36
|
+
borderRadius: '8px',
|
|
37
|
+
padding: '16px',
|
|
38
|
+
},
|
|
39
|
+
errorTitle: {
|
|
40
|
+
fontSize: '14px',
|
|
41
|
+
fontWeight: 600,
|
|
42
|
+
color: 'var(--red-11, #dc2626)',
|
|
43
|
+
marginBottom: '12px',
|
|
44
|
+
},
|
|
45
|
+
errorList: {
|
|
46
|
+
listStyle: 'none',
|
|
47
|
+
padding: 0,
|
|
48
|
+
margin: 0,
|
|
49
|
+
},
|
|
50
|
+
errorItem: {
|
|
51
|
+
fontSize: '13px',
|
|
52
|
+
color: 'var(--red-11, #dc2626)',
|
|
53
|
+
marginBottom: '8px',
|
|
54
|
+
fontFamily: 'var(--font-mono, ui-monospace, monospace)',
|
|
55
|
+
},
|
|
56
|
+
errorPath: {
|
|
57
|
+
fontSize: '11px',
|
|
58
|
+
color: 'var(--red-9, #ef4444)',
|
|
59
|
+
display: 'block',
|
|
60
|
+
},
|
|
61
|
+
errorSuggestion: {
|
|
62
|
+
fontSize: '12px',
|
|
63
|
+
color: 'var(--gray-11, #6b7280)',
|
|
64
|
+
marginTop: '4px',
|
|
65
|
+
fontStyle: 'italic',
|
|
66
|
+
},
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Component to display validation errors
|
|
71
71
|
*/
|
|
72
72
|
function ValidationErrors({ errors }: { errors: ValidationError[] }): ReactElement {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
);
|
|
73
|
+
return (
|
|
74
|
+
<div style={styles.error}>
|
|
75
|
+
<div style={styles.errorTitle}>
|
|
76
|
+
Template Validation Failed ({errors.length} error{errors.length > 1 ? 's' : ''})
|
|
77
|
+
</div>
|
|
78
|
+
<ul style={styles.errorList}>
|
|
79
|
+
{errors.map((error, index) => (
|
|
80
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: list order is stable for this render
|
|
81
|
+
<li key={index} style={styles.errorItem}>
|
|
82
|
+
<span>{error.message}</span>
|
|
83
|
+
<span style={styles.errorPath}>at {error.path}</span>
|
|
84
|
+
{error.suggestion && <span style={styles.errorSuggestion}>\u2192 {error.suggestion}</span>}
|
|
85
|
+
</li>
|
|
86
|
+
))}
|
|
87
|
+
</ul>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
/**
|
|
@@ -115,42 +114,38 @@ function ValidationErrors({ errors }: { errors: ValidationError[] }): ReactEleme
|
|
|
115
114
|
* ```
|
|
116
115
|
*/
|
|
117
116
|
export function FusionFragmentRenderer({
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
template,
|
|
118
|
+
data,
|
|
119
|
+
onUpdate,
|
|
120
|
+
agentMode,
|
|
121
|
+
className,
|
|
123
122
|
}: FusionFragmentRendererProps): ReactElement {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
// Validate template against data keys
|
|
124
|
+
const validation = useMemo(() => {
|
|
125
|
+
const dataKeys = Object.keys(data);
|
|
126
|
+
return validateTemplate(template, dataKeys);
|
|
127
|
+
}, [template, data]);
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
// Show validation errors if any
|
|
130
|
+
if (!validation.valid) {
|
|
131
|
+
return <ValidationErrors errors={validation.errors} />;
|
|
132
|
+
}
|
|
134
133
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<div style={styles.title}>{template.title}</div>
|
|
139
|
-
)}
|
|
134
|
+
return (
|
|
135
|
+
<div style={styles.container} className={className}>
|
|
136
|
+
{template.title && <div style={styles.title}>{template.title}</div>}
|
|
140
137
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
138
|
+
{template.sections.map((section, index) => (
|
|
139
|
+
<SectionRenderer
|
|
140
|
+
key={section.title || index}
|
|
141
|
+
section={section}
|
|
142
|
+
data={data}
|
|
143
|
+
onUpdate={onUpdate}
|
|
144
|
+
agentMode={agentMode}
|
|
145
|
+
/>
|
|
146
|
+
))}
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
</div>
|
|
155
|
-
);
|
|
148
|
+
{template.footer && <div style={styles.footer}>{template.footer}</div>}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
156
151
|
}
|
|
@@ -3,193 +3,198 @@
|
|
|
3
3
|
* Renders a section with grid layout and collapsible behavior
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { type ReactElement, useMemo, useState } from 'react';
|
|
7
7
|
import type { SectionRendererProps } from '../types.js';
|
|
8
|
+
import { ChartRenderer } from './ChartRenderer.js';
|
|
8
9
|
import { FieldRenderer } from './FieldRenderer.js';
|
|
9
10
|
import { TableRenderer } from './TableRenderer.js';
|
|
10
|
-
import { ChartRenderer } from './ChartRenderer.js';
|
|
11
11
|
|
|
12
12
|
// Layout grid configurations
|
|
13
13
|
const gridLayouts = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
14
|
+
'grid-2': {
|
|
15
|
+
display: 'grid',
|
|
16
|
+
gridTemplateColumns: 'repeat(2, 1fr)',
|
|
17
|
+
gap: '16px',
|
|
18
|
+
},
|
|
19
|
+
'grid-3': {
|
|
20
|
+
display: 'grid',
|
|
21
|
+
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
22
|
+
gap: '16px',
|
|
23
|
+
},
|
|
24
|
+
'grid-4': {
|
|
25
|
+
display: 'grid',
|
|
26
|
+
gridTemplateColumns: 'repeat(4, 1fr)',
|
|
27
|
+
gap: '16px',
|
|
28
|
+
},
|
|
29
|
+
list: {
|
|
30
|
+
display: 'flex',
|
|
31
|
+
flexDirection: 'column' as const,
|
|
32
|
+
gap: '12px',
|
|
33
|
+
},
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const styles = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
37
|
+
section: {
|
|
38
|
+
marginBottom: '20px',
|
|
39
|
+
},
|
|
40
|
+
header: {
|
|
41
|
+
display: 'flex',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
gap: '8px',
|
|
44
|
+
marginBottom: '12px',
|
|
45
|
+
cursor: 'default',
|
|
46
|
+
},
|
|
47
|
+
headerCollapsible: {
|
|
48
|
+
cursor: 'pointer',
|
|
49
|
+
userSelect: 'none' as const,
|
|
50
|
+
},
|
|
51
|
+
title: {
|
|
52
|
+
fontSize: '11px',
|
|
53
|
+
fontWeight: 600,
|
|
54
|
+
color: 'var(--gray-11, #6b7280)',
|
|
55
|
+
textTransform: 'uppercase' as const,
|
|
56
|
+
letterSpacing: '0.5px',
|
|
57
|
+
},
|
|
58
|
+
chevron: {
|
|
59
|
+
width: '14px',
|
|
60
|
+
height: '14px',
|
|
61
|
+
color: 'var(--gray-10, #9ca3af)',
|
|
62
|
+
transition: 'transform 0.2s',
|
|
63
|
+
},
|
|
64
|
+
chevronCollapsed: {
|
|
65
|
+
transform: 'rotate(-90deg)',
|
|
66
|
+
},
|
|
67
|
+
content: {
|
|
68
|
+
overflow: 'hidden',
|
|
69
|
+
transition: 'max-height 0.2s ease-out',
|
|
70
|
+
},
|
|
71
|
+
contentCollapsed: {
|
|
72
|
+
maxHeight: '0',
|
|
73
|
+
opacity: 0,
|
|
74
|
+
},
|
|
75
|
+
contentExpanded: {
|
|
76
|
+
maxHeight: '2000px', // Large enough for most content
|
|
77
|
+
opacity: 1,
|
|
78
|
+
},
|
|
79
|
+
divider: {
|
|
80
|
+
height: '1px',
|
|
81
|
+
backgroundColor: 'var(--gray-5, #e5e7eb)',
|
|
82
|
+
marginTop: '16px',
|
|
83
|
+
},
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
// Simple chevron SVG component
|
|
87
87
|
function ChevronIcon({ collapsed }: { collapsed: boolean }): ReactElement {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
88
|
+
return (
|
|
89
|
+
<svg
|
|
90
|
+
style={{
|
|
91
|
+
...styles.chevron,
|
|
92
|
+
...(collapsed ? styles.chevronCollapsed : {}),
|
|
93
|
+
}}
|
|
94
|
+
viewBox="0 0 24 24"
|
|
95
|
+
fill="none"
|
|
96
|
+
stroke="currentColor"
|
|
97
|
+
strokeWidth="2"
|
|
98
|
+
strokeLinecap="round"
|
|
99
|
+
strokeLinejoin="round"
|
|
100
|
+
aria-hidden="true"
|
|
101
|
+
>
|
|
102
|
+
<polyline points="6 9 12 15 18 9" />
|
|
103
|
+
</svg>
|
|
104
|
+
);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/**
|
|
107
108
|
* SectionRenderer component
|
|
108
109
|
* Displays a section with title, optional collapse, and grid of fields
|
|
109
110
|
*/
|
|
110
|
-
export function SectionRenderer({
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// Render content based on layout type
|
|
155
|
-
const renderContent = () => {
|
|
156
|
-
if (isTable && section.columns) {
|
|
157
|
-
return <TableRenderer columns={section.columns} rows={tableRows} />;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (isChart && section.chart) {
|
|
161
|
-
return <ChartRenderer chart={section.chart} data={data} />;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Default: render fields
|
|
165
|
-
return section.fields?.map((field, index) => (
|
|
166
|
-
<FieldRenderer
|
|
167
|
-
key={field.key || index}
|
|
168
|
-
field={field}
|
|
169
|
-
value={data[field.key]}
|
|
170
|
-
onUpdate={
|
|
171
|
-
onUpdate ? (value) => onUpdate(field.key, value) : undefined
|
|
111
|
+
export function SectionRenderer({ section, data, onUpdate, agentMode }: SectionRendererProps): ReactElement {
|
|
112
|
+
const [isCollapsed, setIsCollapsed] = useState(section.collapsed ?? false);
|
|
113
|
+
|
|
114
|
+
const layout = section.layout || 'grid-3';
|
|
115
|
+
const isTable = layout === 'table';
|
|
116
|
+
const isChart = layout === 'chart';
|
|
117
|
+
|
|
118
|
+
const isCollapsible = section.collapsed !== undefined;
|
|
119
|
+
|
|
120
|
+
// Get table rows from data
|
|
121
|
+
const tableRows = useMemo(() => {
|
|
122
|
+
if (!isTable || !section.dataKey) return [];
|
|
123
|
+
const rows = data[section.dataKey];
|
|
124
|
+
return Array.isArray(rows) ? (rows as Record<string, unknown>[]) : [];
|
|
125
|
+
}, [isTable, section.dataKey, data]);
|
|
126
|
+
|
|
127
|
+
const headerStyle = useMemo(
|
|
128
|
+
() => ({
|
|
129
|
+
...styles.header,
|
|
130
|
+
...(isCollapsible ? styles.headerCollapsible : {}),
|
|
131
|
+
}),
|
|
132
|
+
[isCollapsible],
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const contentStyle = useMemo(
|
|
136
|
+
() => ({
|
|
137
|
+
...styles.content,
|
|
138
|
+
...(isTable || isChart ? {} : gridLayouts[layout as keyof typeof gridLayouts]),
|
|
139
|
+
...(isCollapsed ? styles.contentCollapsed : styles.contentExpanded),
|
|
140
|
+
}),
|
|
141
|
+
[isCollapsed, isTable, isChart, layout],
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const handleHeaderClick = () => {
|
|
145
|
+
if (isCollapsible) {
|
|
146
|
+
setIsCollapsed(!isCollapsed);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Render content based on layout type
|
|
151
|
+
const renderContent = () => {
|
|
152
|
+
if (isTable && section.columns) {
|
|
153
|
+
return <TableRenderer columns={section.columns} rows={tableRows} />;
|
|
172
154
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
155
|
+
|
|
156
|
+
if (isChart && section.chart) {
|
|
157
|
+
return <ChartRenderer chart={section.chart} data={data} />;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Default: render fields
|
|
161
|
+
return section.fields?.map((field, index) => (
|
|
162
|
+
<FieldRenderer
|
|
163
|
+
key={field.key || index}
|
|
164
|
+
field={field}
|
|
165
|
+
value={data[field.key]}
|
|
166
|
+
onUpdate={onUpdate ? (value) => onUpdate(field.key, value) : undefined}
|
|
167
|
+
agentMode={agentMode}
|
|
168
|
+
/>
|
|
169
|
+
));
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div style={styles.section}>
|
|
174
|
+
{/* biome-ignore lint/a11y/noStaticElementInteractions: onClick is no-op when not collapsible; interactive role/keyboard only added when isCollapsible is true. */}
|
|
175
|
+
{/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: aria-expanded only emitted when role='button' is also set (both gated on isCollapsible). */}
|
|
176
|
+
<div
|
|
177
|
+
style={headerStyle}
|
|
178
|
+
onClick={handleHeaderClick}
|
|
179
|
+
onKeyDown={
|
|
180
|
+
isCollapsible
|
|
181
|
+
? (e) => {
|
|
182
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
handleHeaderClick();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
: undefined
|
|
188
|
+
}
|
|
189
|
+
role={isCollapsible ? 'button' : undefined}
|
|
190
|
+
tabIndex={isCollapsible ? 0 : undefined}
|
|
191
|
+
aria-expanded={isCollapsible ? !isCollapsed : undefined}
|
|
192
|
+
>
|
|
193
|
+
{isCollapsible && <ChevronIcon collapsed={isCollapsed} />}
|
|
194
|
+
<span style={styles.title}>{section.title}</span>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div style={contentStyle}>{renderContent()}</div>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
195
200
|
}
|