{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "smithery-schema-form", "title": "Smithery Schema Form", "description": "A dynamic form component that renders form fields based on JSON Schema definitions.", "dependencies": ["react-hook-form"], "registryDependencies": [ "button", "field", "input", "select", "switch", "textarea" ], "files": [ { "path": "registry/new-york/smithery/schema-form.tsx", "content": "\"use client\";\n\nimport { useForm } from \"react-hook-form\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldLabel,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface SchemaFormProps {\n\tschema: unknown;\n\tonSubmit: (data: Record) => void;\n\tisLoading?: boolean;\n}\n\nexport function SchemaForm({ schema, onSubmit, isLoading }: SchemaFormProps) {\n\t// Extract JSON schema from various formats\n\tconst jsonSchema: JSONSchema =\n\t\tschema && typeof schema === \"object\" && \"jsonSchema\" in schema\n\t\t\t? (schema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (schema as JSONSchema) || {};\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(jsonSchema),\n\t});\n\n\tconst properties = jsonSchema.properties || {};\n\tconst requiredFields = jsonSchema.required || [];\n\n\treturn (\n\t\t
\n\t\t\t{Object.entries(properties).map(([name, property]) => {\n\t\t\t\tconst isRequired = requiredFields.includes(name);\n\t\t\t\treturn (\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{formatFieldName(name)}\n\t\t\t\t\t\t\t{isRequired && *}\n\t\t\t\t\t\t\n\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{renderField(name, property, register, setValue, watch, isRequired)}\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t);\n\t\t\t})}\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = property.minimum || 0;\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"[]\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"{}\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n\nfunction formatFieldName(name: string): string {\n\t// Keep the original field name as-is\n\treturn name;\n}\n", "type": "registry:component", "target": "components/smithery/schema-form.tsx" } ], "type": "registry:block" }