| import { Input } from "@/components/ui/input"; |
| import { Label } from "@/components/ui/label"; |
| import { Select, SelectOption } from "@/components/ui/select"; |
| import { Switch } from "@/components/ui/switch"; |
|
|
| function FieldHint({ schema, schemaKey }: { schema: Record<string, unknown>; schemaKey: string }) { |
| const keyPath = schemaKey.includes(".") ? schemaKey : ""; |
| const description = schema.description ? String(schema.description) : ""; |
|
|
| if (!keyPath && !description) return null; |
|
|
| return ( |
| <div className="flex flex-col gap-0.5"> |
| {keyPath && <span className="text-[10px] font-mono text-muted-foreground/50">{keyPath}</span>} |
| {description && <span className="text-xs text-muted-foreground/70">{description}</span>} |
| </div> |
| ); |
| } |
|
|
| export function AutoField({ |
| schemaKey, |
| schema, |
| value, |
| onChange, |
| }: AutoFieldProps) { |
| const rawLabel = schemaKey.split(".").pop() ?? schemaKey; |
| const label = rawLabel.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); |
|
|
| if (schema.type === "boolean") { |
| return ( |
| <div className="flex items-center justify-between gap-4"> |
| <div className="flex flex-col gap-0.5"> |
| <Label className="text-sm">{label}</Label> |
| <FieldHint schema={schema} schemaKey={schemaKey} /> |
| </div> |
| <Switch checked={!!value} onCheckedChange={onChange} /> |
| </div> |
| ); |
| } |
|
|
| if (schema.type === "select") { |
| const options = (schema.options as string[]) ?? []; |
| return ( |
| <div className="grid gap-1.5"> |
| <Label className="text-sm">{label}</Label> |
| <FieldHint schema={schema} schemaKey={schemaKey} /> |
| <Select value={String(value ?? "")} onValueChange={(v) => onChange(v)}> |
| {options.map((opt) => ( |
| <SelectOption key={opt} value={opt}> |
| {opt || "(none)"} |
| </SelectOption> |
| ))} |
| </Select> |
| </div> |
| ); |
| } |
|
|
| if (schema.type === "number") { |
| return ( |
| <div className="grid gap-1.5"> |
| <Label className="text-sm">{label}</Label> |
| <FieldHint schema={schema} schemaKey={schemaKey} /> |
| <Input |
| type="number" |
| value={value === undefined || value === null ? "" : String(value)} |
| onChange={(e) => { |
| const raw = e.target.value; |
| if (raw === "") { |
| onChange(0); |
| return; |
| } |
| const n = Number(raw); |
| if (!Number.isNaN(n)) { |
| onChange(n); |
| } |
| }} |
| /> |
| </div> |
| ); |
| } |
|
|
| if (schema.type === "text") { |
| return ( |
| <div className="grid gap-1.5"> |
| <Label className="text-sm">{label}</Label> |
| <FieldHint schema={schema} schemaKey={schemaKey} /> |
| <textarea |
| className="flex min-h-[80px] w-full border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" |
| value={String(value ?? "")} |
| onChange={(e) => onChange(e.target.value)} |
| /> |
| </div> |
| ); |
| } |
|
|
| if (schema.type === "list") { |
| return ( |
| <div className="grid gap-1.5"> |
| <Label className="text-sm">{label}</Label> |
| <FieldHint schema={schema} schemaKey={schemaKey} /> |
| <Input |
| value={Array.isArray(value) ? value.join(", ") : String(value ?? "")} |
| onChange={(e) => |
| onChange( |
| e.target.value |
| .split(",") |
| .map((s) => s.trim()) |
| .filter(Boolean), |
| ) |
| } |
| placeholder="comma-separated values" |
| /> |
| </div> |
| ); |
| } |
|
|
| if (typeof value === "object" && value !== null && !Array.isArray(value)) { |
| const obj = value as Record<string, unknown>; |
| return ( |
| <div className="grid gap-3 border border-border p-3"> |
| <Label className="text-xs font-medium">{label}</Label> |
| <FieldHint schema={schema} schemaKey={schemaKey} /> |
| {Object.entries(obj).map(([subKey, subVal]) => ( |
| <div key={subKey} className="grid gap-1"> |
| <Label className="text-xs text-muted-foreground">{subKey}</Label> |
| <Input |
| value={String(subVal ?? "")} |
| onChange={(e) => onChange({ ...obj, [subKey]: e.target.value })} |
| className="text-xs" |
| /> |
| </div> |
| ))} |
| </div> |
| ); |
| } |
|
|
| return ( |
| <div className="grid gap-1.5"> |
| <Label className="text-sm">{label}</Label> |
| <FieldHint schema={schema} schemaKey={schemaKey} /> |
| <Input value={String(value ?? "")} onChange={(e) => onChange(e.target.value)} /> |
| </div> |
| ); |
| } |
|
|
| interface AutoFieldProps { |
| schemaKey: string; |
| schema: Record<string, unknown>; |
| value: unknown; |
| onChange: (v: unknown) => void; |
| } |
|
|