1
1
import { useState , useEffect , useCallback , useRef } from "react" ;
2
2
import { Button } from "@/components/ui/button" ;
3
3
import { Input } from "@/components/ui/input" ;
4
- import { Label } from "@/components/ui/label" ;
5
4
import JsonEditor from "./JsonEditor" ;
6
5
import { updateValueAtPath } from "@/utils/jsonUtils" ;
7
- import { generateDefaultValue , formatFieldLabel } from "@/utils/schemaUtils" ;
8
- import type { JsonValue , JsonSchemaType , JsonObject } from "@/utils/jsonUtils" ;
6
+ import { generateDefaultValue } from "@/utils/schemaUtils" ;
7
+ import type { JsonValue , JsonSchemaType } from "@/utils/jsonUtils" ;
9
8
10
9
interface DynamicJsonFormProps {
11
10
schema : JsonSchemaType ;
@@ -14,13 +13,23 @@ interface DynamicJsonFormProps {
14
13
maxDepth ?: number ;
15
14
}
16
15
16
+ const isSimpleObject = ( schema : JsonSchemaType ) : boolean => {
17
+ const supportedTypes = [ "string" , "number" , "integer" , "boolean" , "null" ] ;
18
+ if ( supportedTypes . includes ( schema . type ) ) return true ;
19
+ if ( schema . type !== "object" ) return false ;
20
+ return Object . values ( schema . properties ?? { } ) . every ( ( prop ) =>
21
+ supportedTypes . includes ( prop . type ) ,
22
+ ) ;
23
+ } ;
24
+
17
25
const DynamicJsonForm = ( {
18
26
schema,
19
27
value,
20
28
onChange,
21
29
maxDepth = 3 ,
22
30
} : DynamicJsonFormProps ) => {
23
- const [ isJsonMode , setIsJsonMode ] = useState ( false ) ;
31
+ const isOnlyJSON = ! isSimpleObject ( schema ) ;
32
+ const [ isJsonMode , setIsJsonMode ] = useState ( isOnlyJSON ) ;
24
33
const [ jsonError , setJsonError ] = useState < string > ( ) ;
25
34
// Store the raw JSON string to allow immediate feedback during typing
26
35
// while deferring parsing until the user stops typing
@@ -207,111 +216,6 @@ const DynamicJsonForm = ({
207
216
required = { propSchema . required }
208
217
/>
209
218
) ;
210
- case "object" : {
211
- // Handle case where we have a value but no schema properties
212
- const objectValue = ( currentValue as JsonObject ) || { } ;
213
-
214
- // If we have schema properties, use them to render fields
215
- if ( propSchema . properties ) {
216
- return (
217
- < div className = "space-y-4 border rounded-md p-4" >
218
- { Object . entries ( propSchema . properties ) . map ( ( [ key , prop ] ) => (
219
- < div key = { key } className = "space-y-2" >
220
- < Label > { formatFieldLabel ( key ) } </ Label >
221
- { renderFormFields (
222
- prop ,
223
- objectValue [ key ] ,
224
- [ ...path , key ] ,
225
- depth + 1 ,
226
- ) }
227
- </ div >
228
- ) ) }
229
- </ div >
230
- ) ;
231
- }
232
- // If we have a value but no schema properties, render fields based on the value
233
- else if ( Object . keys ( objectValue ) . length > 0 ) {
234
- return (
235
- < div className = "space-y-4 border rounded-md p-4" >
236
- { Object . entries ( objectValue ) . map ( ( [ key , value ] ) => (
237
- < div key = { key } className = "space-y-2" >
238
- < Label > { formatFieldLabel ( key ) } </ Label >
239
- < Input
240
- type = "text"
241
- value = { String ( value ) }
242
- onChange = { ( e ) =>
243
- handleFieldChange ( [ ...path , key ] , e . target . value )
244
- }
245
- />
246
- </ div >
247
- ) ) }
248
- </ div >
249
- ) ;
250
- }
251
- // If we have neither schema properties nor value, return null
252
- return null ;
253
- }
254
- case "array" : {
255
- const arrayValue = Array . isArray ( currentValue ) ? currentValue : [ ] ;
256
- if ( ! propSchema . items ) return null ;
257
- return (
258
- < div className = "space-y-4" >
259
- { propSchema . description && (
260
- < p className = "text-sm text-gray-600" > { propSchema . description } </ p >
261
- ) }
262
-
263
- { propSchema . items ?. description && (
264
- < p className = "text-sm text-gray-500" >
265
- Items: { propSchema . items . description }
266
- </ p >
267
- ) }
268
-
269
- < div className = "space-y-2" >
270
- { arrayValue . map ( ( item , index ) => (
271
- < div key = { index } className = "flex items-center gap-2" >
272
- { renderFormFields (
273
- propSchema . items as JsonSchemaType ,
274
- item ,
275
- [ ...path , index . toString ( ) ] ,
276
- depth + 1 ,
277
- ) }
278
- < Button
279
- variant = "outline"
280
- size = "sm"
281
- onClick = { ( ) => {
282
- const newArray = [ ...arrayValue ] ;
283
- newArray . splice ( index , 1 ) ;
284
- handleFieldChange ( path , newArray ) ;
285
- } }
286
- >
287
- Remove
288
- </ Button >
289
- </ div >
290
- ) ) }
291
- < Button
292
- variant = "outline"
293
- size = "sm"
294
- onClick = { ( ) => {
295
- const defaultValue = generateDefaultValue (
296
- propSchema . items as JsonSchemaType ,
297
- ) ;
298
- handleFieldChange ( path , [
299
- ...arrayValue ,
300
- defaultValue ?? null ,
301
- ] ) ;
302
- } }
303
- title = {
304
- propSchema . items ?. description
305
- ? `Add new ${ propSchema . items . description } `
306
- : "Add new item"
307
- }
308
- >
309
- Add Item
310
- </ Button >
311
- </ div >
312
- </ div >
313
- ) ;
314
- }
315
219
default :
316
220
return null ;
317
221
}
@@ -350,9 +254,11 @@ const DynamicJsonForm = ({
350
254
Format JSON
351
255
</ Button >
352
256
) }
353
- < Button variant = "outline" size = "sm" onClick = { handleSwitchToFormMode } >
354
- { isJsonMode ? "Switch to Form" : "Switch to JSON" }
355
- </ Button >
257
+ { ! isOnlyJSON && (
258
+ < Button variant = "outline" size = "sm" onClick = { handleSwitchToFormMode } >
259
+ { isJsonMode ? "Switch to Form" : "Switch to JSON" }
260
+ </ Button >
261
+ ) }
356
262
</ div >
357
263
358
264
{ isJsonMode ? (
0 commit comments