@marlinjai/clearify 1.5.2 → 1.6.0

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,134 @@
1
+ import React from 'react';
2
+
3
+ interface Parameter {
4
+ name: string;
5
+ in: string;
6
+ required?: boolean;
7
+ schema?: Record<string, any>;
8
+ description?: string;
9
+ deprecated?: boolean;
10
+ }
11
+
12
+ interface ParameterTableProps {
13
+ parameters: Parameter[];
14
+ }
15
+
16
+ const LOCATION_ORDER = ['path', 'query', 'header', 'cookie'];
17
+
18
+ export function ParameterTable({ parameters }: ParameterTableProps) {
19
+ if (!parameters || parameters.length === 0) return null;
20
+
21
+ // Group by location
22
+ const grouped = new Map<string, Parameter[]>();
23
+ for (const param of parameters) {
24
+ const loc = param.in ?? 'query';
25
+ const existing = grouped.get(loc);
26
+ if (existing) existing.push(param);
27
+ else grouped.set(loc, [param]);
28
+ }
29
+
30
+ const sortedGroups = [...grouped.entries()].sort(
31
+ ([a], [b]) => LOCATION_ORDER.indexOf(a) - LOCATION_ORDER.indexOf(b)
32
+ );
33
+
34
+ return (
35
+ <div style={{ marginTop: '0.75rem' }}>
36
+ {sortedGroups.map(([location, params]) => (
37
+ <div key={location} style={{ marginBottom: '0.75rem' }}>
38
+ <div style={{
39
+ fontSize: '0.6875rem',
40
+ fontWeight: 600,
41
+ textTransform: 'uppercase' as const,
42
+ letterSpacing: '0.05em',
43
+ color: 'var(--clearify-text-tertiary)',
44
+ marginBottom: '0.375rem',
45
+ }}>
46
+ {location} parameters
47
+ </div>
48
+ <div style={{
49
+ border: '1px solid var(--clearify-border)',
50
+ borderRadius: 'var(--clearify-radius)',
51
+ overflow: 'hidden',
52
+ }}>
53
+ {params.map((param, i) => (
54
+ <div
55
+ key={param.name}
56
+ className="clearify-param-row"
57
+ style={{
58
+ padding: '0.625rem 0.875rem',
59
+ borderBottom: i < params.length - 1 ? '1px solid var(--clearify-border)' : undefined,
60
+ transition: 'background-color 0.15s ease',
61
+ }}
62
+ >
63
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
64
+ <span style={{
65
+ fontFamily: 'var(--font-mono)',
66
+ fontSize: '0.8125rem',
67
+ fontWeight: 600,
68
+ color: param.deprecated ? 'var(--clearify-text-tertiary)' : 'var(--clearify-text)',
69
+ textDecoration: param.deprecated ? 'line-through' : undefined,
70
+ }}>
71
+ {param.name}
72
+ </span>
73
+ {param.schema?.type && (
74
+ <span style={{
75
+ fontFamily: 'var(--font-mono)',
76
+ fontSize: '0.75rem',
77
+ color: 'var(--clearify-text-tertiary)',
78
+ }}>
79
+ {param.schema.type}
80
+ </span>
81
+ )}
82
+ {param.required && (
83
+ <span className="clearify-required-badge" style={{
84
+ fontSize: '0.6875rem',
85
+ fontWeight: 600,
86
+ textTransform: 'uppercase' as const,
87
+ letterSpacing: '0.04em',
88
+ color: '#d97706',
89
+ }}>
90
+ required
91
+ </span>
92
+ )}
93
+ {param.deprecated && (
94
+ <span style={{ fontSize: '0.6875rem', color: 'var(--clearify-text-tertiary)' }}>deprecated</span>
95
+ )}
96
+ </div>
97
+ {param.description && (
98
+ <div style={{
99
+ fontSize: '0.8125rem',
100
+ color: 'var(--clearify-text-secondary)',
101
+ marginTop: '0.25rem',
102
+ lineHeight: 1.5,
103
+ }}>
104
+ {param.description}
105
+ </div>
106
+ )}
107
+ {param.schema?.enum && (
108
+ <div style={{ display: 'flex', gap: '0.375rem', flexWrap: 'wrap', marginTop: '0.375rem' }}>
109
+ {param.schema.enum.map((val: any, j: number) => (
110
+ <span key={j} style={{
111
+ fontFamily: 'var(--font-mono)',
112
+ fontSize: '0.75rem',
113
+ background: 'var(--clearify-bg-secondary)',
114
+ border: '1px solid var(--clearify-border)',
115
+ borderRadius: '4px',
116
+ padding: '0.1rem 0.4rem',
117
+ }}>
118
+ {String(val)}
119
+ </span>
120
+ ))}
121
+ </div>
122
+ )}
123
+ </div>
124
+ ))}
125
+ </div>
126
+ </div>
127
+ ))}
128
+ <style>{`
129
+ .clearify-param-row:hover { background-color: var(--clearify-bg-secondary) !important; }
130
+ .dark .clearify-required-badge { color: #fbbf24 !important; }
131
+ `}</style>
132
+ </div>
133
+ );
134
+ }
@@ -0,0 +1,148 @@
1
+ import React, { useState } from 'react';
2
+ import { SchemaViewer } from './SchemaViewer.js';
3
+
4
+ interface ResponseListProps {
5
+ responses: Record<string, any>;
6
+ }
7
+
8
+ function statusColor(code: string): string {
9
+ if (code.startsWith('2')) return '#059669';
10
+ if (code.startsWith('3')) return '#2563eb';
11
+ if (code.startsWith('4')) return '#d97706';
12
+ if (code.startsWith('5')) return '#dc2626';
13
+ return 'var(--clearify-text-secondary)';
14
+ }
15
+
16
+ function statusDarkColor(code: string): string {
17
+ if (code.startsWith('2')) return '#34d399';
18
+ if (code.startsWith('3')) return '#60a5fa';
19
+ if (code.startsWith('4')) return '#fbbf24';
20
+ if (code.startsWith('5')) return '#f87171';
21
+ return 'var(--clearify-text-secondary)';
22
+ }
23
+
24
+ export function ResponseList({ responses }: ResponseListProps) {
25
+ if (!responses || Object.keys(responses).length === 0) return null;
26
+
27
+ const entries = Object.entries(responses).sort(([a], [b]) => a.localeCompare(b));
28
+
29
+ return (
30
+ <div style={{ marginTop: '0.75rem' }}>
31
+ <div style={{
32
+ fontSize: '0.6875rem',
33
+ fontWeight: 600,
34
+ textTransform: 'uppercase' as const,
35
+ letterSpacing: '0.05em',
36
+ color: 'var(--clearify-text-tertiary)',
37
+ marginBottom: '0.375rem',
38
+ }}>
39
+ Responses
40
+ </div>
41
+ <div style={{
42
+ border: '1px solid var(--clearify-border)',
43
+ borderRadius: 'var(--clearify-radius)',
44
+ overflow: 'hidden',
45
+ }}>
46
+ {entries.map(([statusCode, response], i) => (
47
+ <ResponseItem
48
+ key={statusCode}
49
+ statusCode={statusCode}
50
+ response={response}
51
+ isLast={i === entries.length - 1}
52
+ defaultExpanded={i === 0 && statusCode.startsWith('2')}
53
+ />
54
+ ))}
55
+ </div>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ function ResponseItem({
61
+ statusCode,
62
+ response,
63
+ isLast,
64
+ defaultExpanded,
65
+ }: {
66
+ statusCode: string;
67
+ response: Record<string, any>;
68
+ isLast: boolean;
69
+ defaultExpanded: boolean;
70
+ }) {
71
+ const [expanded, setExpanded] = useState(defaultExpanded);
72
+ const schema = response.content?.['application/json']?.schema;
73
+ const hasDetails = !!(schema || response.description);
74
+
75
+ return (
76
+ <div style={{ borderBottom: isLast ? undefined : '1px solid var(--clearify-border)' }}>
77
+ <button
78
+ onClick={hasDetails ? () => setExpanded(!expanded) : undefined}
79
+ style={{
80
+ display: 'flex',
81
+ alignItems: 'center',
82
+ gap: '0.625rem',
83
+ width: '100%',
84
+ padding: '0.625rem 0.875rem',
85
+ background: 'none',
86
+ border: 'none',
87
+ cursor: hasDetails ? 'pointer' : 'default',
88
+ textAlign: 'left',
89
+ fontFamily: 'var(--font-sans)',
90
+ transition: 'background-color 0.15s ease',
91
+ }}
92
+ className="clearify-response-row"
93
+ >
94
+ {hasDetails && (
95
+ <svg
96
+ width="14"
97
+ height="14"
98
+ viewBox="0 0 24 24"
99
+ fill="none"
100
+ stroke="currentColor"
101
+ strokeWidth="2"
102
+ strokeLinecap="round"
103
+ strokeLinejoin="round"
104
+ style={{
105
+ flexShrink: 0,
106
+ transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
107
+ transition: 'transform 0.15s cubic-bezier(0.4, 0, 0.2, 1)',
108
+ color: 'var(--clearify-text-tertiary)',
109
+ }}
110
+ >
111
+ <polyline points="9 18 15 12 9 6" />
112
+ </svg>
113
+ )}
114
+ <span
115
+ className={`clearify-status-${statusCode.charAt(0)}xx`}
116
+ style={{
117
+ fontFamily: 'var(--font-mono)',
118
+ fontSize: '0.8125rem',
119
+ fontWeight: 700,
120
+ color: statusColor(statusCode),
121
+ }}
122
+ >
123
+ {statusCode}
124
+ </span>
125
+ <span style={{
126
+ fontSize: '0.8125rem',
127
+ color: 'var(--clearify-text-secondary)',
128
+ }}>
129
+ {response.description || ''}
130
+ </span>
131
+ </button>
132
+
133
+ {expanded && hasDetails && (
134
+ <div style={{ padding: '0 0.875rem 0.75rem 2.5rem' }}>
135
+ {schema && <SchemaViewer schema={schema} />}
136
+ </div>
137
+ )}
138
+
139
+ <style>{`
140
+ .clearify-response-row:hover { background-color: var(--clearify-bg-secondary) !important; }
141
+ .dark .clearify-status-2xx { color: ${statusDarkColor('200')} !important; }
142
+ .dark .clearify-status-3xx { color: ${statusDarkColor('300')} !important; }
143
+ .dark .clearify-status-4xx { color: ${statusDarkColor('400')} !important; }
144
+ .dark .clearify-status-5xx { color: ${statusDarkColor('500')} !important; }
145
+ `}</style>
146
+ </div>
147
+ );
148
+ }
@@ -0,0 +1,377 @@
1
+ import React, { useState } from 'react';
2
+
3
+ interface SchemaViewerProps {
4
+ schema: Record<string, any>;
5
+ name?: string;
6
+ required?: boolean;
7
+ defaultExpanded?: boolean;
8
+ depth?: number;
9
+ }
10
+
11
+ export function SchemaViewer({
12
+ schema,
13
+ name,
14
+ required = false,
15
+ defaultExpanded = true,
16
+ depth = 0,
17
+ }: SchemaViewerProps) {
18
+ if (!schema || typeof schema !== 'object') return null;
19
+
20
+ // Handle oneOf / anyOf
21
+ const variants = schema.oneOf ?? schema.anyOf;
22
+ if (variants && Array.isArray(variants)) {
23
+ return (
24
+ <SchemaVariants
25
+ variants={variants}
26
+ label={schema.oneOf ? 'oneOf' : 'anyOf'}
27
+ name={name}
28
+ required={required}
29
+ depth={depth}
30
+ />
31
+ );
32
+ }
33
+
34
+ // Handle array type
35
+ if (schema.type === 'array' && schema.items) {
36
+ return (
37
+ <SchemaProperty name={name} schema={schema} required={required} depth={depth}>
38
+ <SchemaViewer schema={schema.items} depth={depth + 1} defaultExpanded={depth < 2} />
39
+ </SchemaProperty>
40
+ );
41
+ }
42
+
43
+ // Handle object type (or object with properties)
44
+ if (schema.type === 'object' || schema.properties) {
45
+ const properties = schema.properties ?? {};
46
+ const requiredProps: string[] = schema.required ?? [];
47
+
48
+ if (Object.keys(properties).length === 0 && !name) {
49
+ return (
50
+ <div style={{ fontSize: '0.8125rem', color: 'var(--clearify-text-tertiary)', padding: '0.5rem 0' }}>
51
+ No properties defined
52
+ </div>
53
+ );
54
+ }
55
+
56
+ const propertyEntries = Object.entries(properties);
57
+
58
+ // If this is a named property (nested object), wrap in collapsible
59
+ if (name) {
60
+ return (
61
+ <SchemaProperty name={name} schema={schema} required={required} depth={depth}>
62
+ <div style={{ borderLeft: '2px solid var(--clearify-border)', marginLeft: '0.5rem', paddingLeft: '0.75rem' }}>
63
+ {propertyEntries.map(([propName, propSchema]) => (
64
+ <SchemaViewer
65
+ key={propName}
66
+ schema={propSchema as Record<string, any>}
67
+ name={propName}
68
+ required={requiredProps.includes(propName)}
69
+ depth={depth + 1}
70
+ defaultExpanded={depth < 1}
71
+ />
72
+ ))}
73
+ </div>
74
+ </SchemaProperty>
75
+ );
76
+ }
77
+
78
+ // Top-level object — render properties directly
79
+ return (
80
+ <div>
81
+ {propertyEntries.map(([propName, propSchema]) => (
82
+ <SchemaViewer
83
+ key={propName}
84
+ schema={propSchema as Record<string, any>}
85
+ name={propName}
86
+ required={requiredProps.includes(propName)}
87
+ depth={depth}
88
+ defaultExpanded={depth < 2}
89
+ />
90
+ ))}
91
+ </div>
92
+ );
93
+ }
94
+
95
+ // Primitive type — render as leaf property
96
+ if (name) {
97
+ return <SchemaProperty name={name} schema={schema} required={required} depth={depth} />;
98
+ }
99
+
100
+ // Unnamed primitive (e.g., array items that are strings)
101
+ return (
102
+ <div style={{ fontSize: '0.8125rem', color: 'var(--clearify-text-secondary)', padding: '0.25rem 0' }}>
103
+ <TypeBadge type={schema.type} format={schema.format} />
104
+ {schema.description && (
105
+ <span style={{ marginLeft: '0.5rem' }}>{schema.description}</span>
106
+ )}
107
+ </div>
108
+ );
109
+ }
110
+
111
+ /** A single property row with name, type, required badge, constraints, description */
112
+ function SchemaProperty({
113
+ name,
114
+ schema,
115
+ required,
116
+ depth,
117
+ children,
118
+ }: {
119
+ name?: string;
120
+ schema: Record<string, any>;
121
+ required: boolean;
122
+ depth: number;
123
+ children?: React.ReactNode;
124
+ }) {
125
+ const hasChildren = !!children;
126
+ const [expanded, setExpanded] = useState(depth < 2);
127
+
128
+ return (
129
+ <div
130
+ style={{
131
+ borderBottom: depth === 0 ? '1px solid var(--clearify-border)' : undefined,
132
+ padding: '0.5rem 0',
133
+ }}
134
+ >
135
+ <div
136
+ style={{
137
+ display: 'flex',
138
+ alignItems: 'flex-start',
139
+ gap: '0.5rem',
140
+ cursor: hasChildren ? 'pointer' : undefined,
141
+ }}
142
+ onClick={hasChildren ? () => setExpanded(!expanded) : undefined}
143
+ >
144
+ {hasChildren && (
145
+ <svg
146
+ width="14"
147
+ height="14"
148
+ viewBox="0 0 24 24"
149
+ fill="none"
150
+ stroke="currentColor"
151
+ strokeWidth="2"
152
+ strokeLinecap="round"
153
+ strokeLinejoin="round"
154
+ style={{
155
+ flexShrink: 0,
156
+ marginTop: '0.15rem',
157
+ transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
158
+ transition: 'transform 0.15s cubic-bezier(0.4, 0, 0.2, 1)',
159
+ color: 'var(--clearify-text-tertiary)',
160
+ }}
161
+ >
162
+ <polyline points="9 18 15 12 9 6" />
163
+ </svg>
164
+ )}
165
+ <div style={{ flex: 1, minWidth: 0 }}>
166
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
167
+ {name && (
168
+ <span style={{
169
+ fontFamily: 'var(--font-mono)',
170
+ fontSize: '0.8125rem',
171
+ fontWeight: 600,
172
+ color: 'var(--clearify-text)',
173
+ }}>
174
+ {name}
175
+ </span>
176
+ )}
177
+ <TypeBadge
178
+ type={schema.type === 'array' ? `${schema.items?.type ?? 'any'}[]` : schema.type}
179
+ format={schema.format}
180
+ />
181
+ {required && (
182
+ <span style={{
183
+ fontSize: '0.6875rem',
184
+ fontWeight: 600,
185
+ textTransform: 'uppercase' as const,
186
+ letterSpacing: '0.04em',
187
+ color: '#d97706',
188
+ }}
189
+ className="clearify-required-badge"
190
+ >
191
+ required
192
+ </span>
193
+ )}
194
+ {schema.nullable && (
195
+ <span style={{
196
+ fontSize: '0.6875rem',
197
+ color: 'var(--clearify-text-tertiary)',
198
+ }}>
199
+ nullable
200
+ </span>
201
+ )}
202
+ </div>
203
+
204
+ {schema.description && (
205
+ <div style={{
206
+ fontSize: '0.8125rem',
207
+ color: 'var(--clearify-text-secondary)',
208
+ marginTop: '0.25rem',
209
+ lineHeight: 1.5,
210
+ }}>
211
+ {schema.description}
212
+ </div>
213
+ )}
214
+
215
+ {/* Constraints */}
216
+ <SchemaConstraints schema={schema} />
217
+
218
+ {/* Enum values */}
219
+ {schema.enum && (
220
+ <div style={{ display: 'flex', gap: '0.375rem', flexWrap: 'wrap', marginTop: '0.375rem' }}>
221
+ {schema.enum.map((val: any, i: number) => (
222
+ <span
223
+ key={i}
224
+ style={{
225
+ fontFamily: 'var(--font-mono)',
226
+ fontSize: '0.75rem',
227
+ background: 'var(--clearify-bg-secondary)',
228
+ border: '1px solid var(--clearify-border)',
229
+ borderRadius: '4px',
230
+ padding: '0.1rem 0.4rem',
231
+ }}
232
+ >
233
+ {String(val)}
234
+ </span>
235
+ ))}
236
+ </div>
237
+ )}
238
+
239
+ {/* Example */}
240
+ {schema.example !== undefined && (
241
+ <div style={{ marginTop: '0.25rem', fontSize: '0.75rem', color: 'var(--clearify-text-tertiary)' }}>
242
+ Example: <code style={{ fontFamily: 'var(--font-mono)' }}>{JSON.stringify(schema.example)}</code>
243
+ </div>
244
+ )}
245
+ </div>
246
+ </div>
247
+
248
+ {/* Nested children */}
249
+ {hasChildren && expanded && (
250
+ <div style={{ marginTop: '0.25rem' }}>
251
+ {children}
252
+ </div>
253
+ )}
254
+
255
+ <style>{`
256
+ .dark .clearify-required-badge { color: #fbbf24 !important; }
257
+ `}</style>
258
+ </div>
259
+ );
260
+ }
261
+
262
+ function TypeBadge({ type, format }: { type?: string; format?: string }) {
263
+ if (!type) return null;
264
+ const label = format ? `${type}<${format}>` : type;
265
+ return (
266
+ <span style={{
267
+ fontFamily: 'var(--font-mono)',
268
+ fontSize: '0.75rem',
269
+ color: 'var(--clearify-text-tertiary)',
270
+ background: 'var(--clearify-bg-secondary)',
271
+ padding: '0.05rem 0.375rem',
272
+ borderRadius: '4px',
273
+ border: '1px solid var(--clearify-border)',
274
+ }}>
275
+ {label}
276
+ </span>
277
+ );
278
+ }
279
+
280
+ function SchemaConstraints({ schema }: { schema: Record<string, any> }) {
281
+ const constraints: string[] = [];
282
+ if (schema.minLength !== undefined) constraints.push(`minLength: ${schema.minLength}`);
283
+ if (schema.maxLength !== undefined) constraints.push(`maxLength: ${schema.maxLength}`);
284
+ if (schema.minimum !== undefined) constraints.push(`min: ${schema.minimum}`);
285
+ if (schema.maximum !== undefined) constraints.push(`max: ${schema.maximum}`);
286
+ if (schema.pattern !== undefined) constraints.push(`pattern: ${schema.pattern}`);
287
+ if (schema.minItems !== undefined) constraints.push(`minItems: ${schema.minItems}`);
288
+ if (schema.maxItems !== undefined) constraints.push(`maxItems: ${schema.maxItems}`);
289
+ if (schema.uniqueItems) constraints.push('uniqueItems');
290
+ if (schema.default !== undefined) constraints.push(`default: ${JSON.stringify(schema.default)}`);
291
+
292
+ if (constraints.length === 0) return null;
293
+
294
+ return (
295
+ <div style={{
296
+ display: 'flex',
297
+ gap: '0.375rem',
298
+ flexWrap: 'wrap',
299
+ marginTop: '0.25rem',
300
+ }}>
301
+ {constraints.map((c, i) => (
302
+ <span
303
+ key={i}
304
+ style={{
305
+ fontFamily: 'var(--font-mono)',
306
+ fontSize: '0.6875rem',
307
+ color: 'var(--clearify-text-tertiary)',
308
+ background: 'var(--clearify-bg-secondary)',
309
+ padding: '0.05rem 0.375rem',
310
+ borderRadius: '4px',
311
+ border: '1px solid var(--clearify-border)',
312
+ }}
313
+ >
314
+ {c}
315
+ </span>
316
+ ))}
317
+ </div>
318
+ );
319
+ }
320
+
321
+ function SchemaVariants({
322
+ variants,
323
+ label,
324
+ name,
325
+ required,
326
+ depth,
327
+ }: {
328
+ variants: Record<string, any>[];
329
+ label: string;
330
+ name?: string;
331
+ required: boolean;
332
+ depth: number;
333
+ }) {
334
+ const [activeIndex, setActiveIndex] = useState(0);
335
+
336
+ return (
337
+ <div style={{ padding: '0.5rem 0', borderBottom: depth === 0 ? '1px solid var(--clearify-border)' : undefined }}>
338
+ {name && (
339
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.375rem' }}>
340
+ <span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8125rem', fontWeight: 600, color: 'var(--clearify-text)' }}>
341
+ {name}
342
+ </span>
343
+ <span style={{ fontSize: '0.6875rem', color: 'var(--clearify-text-tertiary)' }}>{label}</span>
344
+ {required && (
345
+ <span style={{ fontSize: '0.6875rem', fontWeight: 600, textTransform: 'uppercase' as const, letterSpacing: '0.04em', color: '#d97706' }}>
346
+ required
347
+ </span>
348
+ )}
349
+ </div>
350
+ )}
351
+ <div style={{ display: 'flex', gap: '0.25rem', marginBottom: '0.5rem' }}>
352
+ {variants.map((v, i) => (
353
+ <button
354
+ key={i}
355
+ onClick={() => setActiveIndex(i)}
356
+ style={{
357
+ fontFamily: 'var(--font-mono)',
358
+ fontSize: '0.75rem',
359
+ padding: '0.2rem 0.5rem',
360
+ borderRadius: '4px',
361
+ border: '1px solid var(--clearify-border)',
362
+ background: i === activeIndex ? 'var(--clearify-primary-soft)' : 'transparent',
363
+ color: i === activeIndex ? 'var(--clearify-primary)' : 'var(--clearify-text-secondary)',
364
+ cursor: 'pointer',
365
+ fontWeight: i === activeIndex ? 600 : 400,
366
+ }}
367
+ >
368
+ {v.title ?? v.type ?? `Option ${i + 1}`}
369
+ </button>
370
+ ))}
371
+ </div>
372
+ <div style={{ borderLeft: '2px solid var(--clearify-border)', marginLeft: '0.5rem', paddingLeft: '0.75rem' }}>
373
+ <SchemaViewer schema={variants[activeIndex]} depth={depth + 1} />
374
+ </div>
375
+ </div>
376
+ );
377
+ }