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) Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com> Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
110 lines
3.3 KiB
TypeScript
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"
|
|
/>
|
|
);
|
|
}
|
|
}
|