11import { useState , useEffect , useCallback , useRef } from "react" ;
22import { Button } from "@/components/ui/button" ;
33import { Input } from "@/components/ui/input" ;
4- import { Label } from "@/components/ui/label" ;
54import JsonEditor from "./JsonEditor" ;
65import { 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" ;
98
109interface DynamicJsonFormProps {
1110 schema : JsonSchemaType ;
@@ -14,13 +13,23 @@ interface DynamicJsonFormProps {
1413 maxDepth ?: number ;
1514}
1615
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+
1725const DynamicJsonForm = ( {
1826 schema,
1927 value,
2028 onChange,
2129 maxDepth = 3 ,
2230} : DynamicJsonFormProps ) => {
23- const [ isJsonMode , setIsJsonMode ] = useState ( false ) ;
31+ const isOnlyJSON = ! isSimpleObject ( schema ) ;
32+ const [ isJsonMode , setIsJsonMode ] = useState ( isOnlyJSON ) ;
2433 const [ jsonError , setJsonError ] = useState < string > ( ) ;
2534 // Store the raw JSON string to allow immediate feedback during typing
2635 // while deferring parsing until the user stops typing
@@ -207,111 +216,6 @@ const DynamicJsonForm = ({
207216 required = { propSchema . required }
208217 />
209218 ) ;
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- }
315219 default :
316220 return null ;
317221 }
@@ -350,9 +254,11 @@ const DynamicJsonForm = ({
350254 Format JSON
351255 </ Button >
352256 ) }
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+ ) }
356262 </ div >
357263
358264 { isJsonMode ? (
0 commit comments