Files
Nexus/apps/web/src/components/projects/project-wizard/DynamicFieldInput.tsx
T
Hartmut b41c1d2501
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)

Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
2026-05-21 16:28:40 +02:00

110 lines
3.3 KiB
TypeScript

import type { BlueprintFieldDefinition } from "@nexus/shared";
import { FieldType } from "@nexus/shared";
import { DateInput } from "~/components/ui/DateInput.js";
export function DynamicFieldInput({
field,
value,
onChange,
}: {
field: BlueprintFieldDefinition;
value: unknown;
onChange: (key: string, val: unknown) => void;
}) {
const strVal = value !== undefined && value !== null ? String(value) : "";
const arrVal = Array.isArray(value) ? (value as string[]) : [];
switch (field.type) {
case FieldType.TEXTAREA:
return (
<textarea
value={strVal}
onChange={(e) => onChange(field.key, e.target.value)}
placeholder={field.placeholder}
rows={3}
className="app-input"
/>
);
case FieldType.NUMBER:
return (
<input
type="number"
value={strVal}
min={field.validation?.min}
max={field.validation?.max}
onChange={(e) =>
onChange(field.key, e.target.value === "" ? "" : parseFloat(e.target.value))
}
placeholder={field.placeholder}
className="app-input"
/>
);
case FieldType.BOOLEAN:
return (
<label className="flex items-center gap-2 text-sm cursor-pointer">
<input
type="checkbox"
checked={value === true || value === "true"}
onChange={(e) => onChange(field.key, e.target.checked)}
className="accent-brand-600"
/>
{field.description && <span className="text-gray-500">{field.description}</span>}
</label>
);
case FieldType.DATE:
return <DateInput value={strVal} onChange={(v) => onChange(field.key, v)} />;
case FieldType.SELECT:
return (
<select
value={strVal}
onChange={(e) => onChange(field.key, e.target.value)}
className="app-select w-full"
>
<option value=""> select </option>
{(field.options ?? []).map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
);
case FieldType.MULTI_SELECT:
return (
<div className="flex flex-wrap gap-2">
{(field.options ?? []).map((opt) => {
const checked = arrVal.includes(opt.value);
return (
<label key={opt.value} className="flex items-center gap-1.5 text-sm cursor-pointer">
<input
type="checkbox"
checked={checked}
onChange={(e) => {
const next = e.target.checked
? [...arrVal, opt.value]
: arrVal.filter((v) => v !== opt.value);
onChange(field.key, next);
}}
className="accent-brand-600"
/>
{opt.label}
</label>
);
})}
</div>
);
default:
// TEXT, URL, EMAIL
return (
<input
type={
field.type === FieldType.EMAIL ? "email" : field.type === FieldType.URL ? "url" : "text"
}
value={strVal}
onChange={(e) => onChange(field.key, e.target.value)}
placeholder={field.placeholder}
className="app-input"
/>
);
}
}