mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
upgrade hook form resolvers
This commit is contained in:
@@ -66,7 +66,7 @@
|
|||||||
"@dagrejs/dagre": "^1.1.4",
|
"@dagrejs/dagre": "^1.1.4",
|
||||||
"@hono/typebox-validator": "^0.3.2",
|
"@hono/typebox-validator": "^0.3.2",
|
||||||
"@hono/vite-dev-server": "^0.19.0",
|
"@hono/vite-dev-server": "^0.19.0",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^4.1.3",
|
||||||
"@libsql/kysely-libsql": "^0.4.1",
|
"@libsql/kysely-libsql": "^0.4.1",
|
||||||
"@mantine/modals": "^7.17.1",
|
"@mantine/modals": "^7.17.1",
|
||||||
"@mantine/notifications": "^7.17.1",
|
"@mantine/notifications": "^7.17.1",
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function StepCreate() {
|
|||||||
name: "",
|
name: "",
|
||||||
trigger: "manual",
|
trigger: "manual",
|
||||||
mode: "async",
|
mode: "async",
|
||||||
},
|
} as Static<typeof schema>,
|
||||||
mode: "onSubmit",
|
mode: "onSubmit",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import ModalTest from "../../routes/test/tests/modal-test";
|
|||||||
import QueryJsonFormTest from "../../routes/test/tests/query-jsonform";
|
import QueryJsonFormTest from "../../routes/test/tests/query-jsonform";
|
||||||
import DropdownTest from "./tests/dropdown-test";
|
import DropdownTest from "./tests/dropdown-test";
|
||||||
import DropzoneElementTest from "./tests/dropzone-element-test";
|
import DropzoneElementTest from "./tests/dropzone-element-test";
|
||||||
import EntityFieldsForm from "./tests/entity-fields-form";
|
|
||||||
import FlowsTest from "./tests/flows-test";
|
import FlowsTest from "./tests/flows-test";
|
||||||
import JsonSchemaForm3 from "./tests/json-schema-form3";
|
import JsonSchemaForm3 from "./tests/json-schema-form3";
|
||||||
import JsonFormTest from "./tests/jsonform-test";
|
import JsonFormTest from "./tests/jsonform-test";
|
||||||
@@ -42,7 +41,6 @@ const tests = {
|
|||||||
SqlAiTest,
|
SqlAiTest,
|
||||||
SortableTest,
|
SortableTest,
|
||||||
ReactHookErrors,
|
ReactHookErrors,
|
||||||
EntityFieldsForm,
|
|
||||||
FlowsTest,
|
FlowsTest,
|
||||||
AppShellAccordionsTest,
|
AppShellAccordionsTest,
|
||||||
SwaggerTest,
|
SwaggerTest,
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
|
||||||
import { Select, Switch, Tabs, TextInput, Textarea, Tooltip } from "@mantine/core";
|
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
|
||||||
import { Type } from "@sinclair/typebox";
|
|
||||||
import { StringEnum, StringIdentifier, transformObject } from "core/utils";
|
|
||||||
import { FieldClassMap } from "data";
|
|
||||||
import { omit } from "lodash-es";
|
|
||||||
import {
|
|
||||||
type FieldArrayWithId,
|
|
||||||
type FieldValues,
|
|
||||||
type UseControllerProps,
|
|
||||||
type UseFormReturn,
|
|
||||||
useController,
|
|
||||||
useFieldArray,
|
|
||||||
useForm,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import { TbChevronDown, TbChevronUp, TbGripVertical, TbTrash } from "react-icons/tb";
|
|
||||||
import { Button } from "../../../components/buttons/Button";
|
|
||||||
import { IconButton } from "../../../components/buttons/IconButton";
|
|
||||||
import { MantineSelect } from "../../../components/form/hook-form-mantine/MantineSelect";
|
|
||||||
|
|
||||||
const fieldSchemas = transformObject(omit(FieldClassMap, ["primary"]), (value) => value.schema);
|
|
||||||
const fieldSchema = Type.Union(
|
|
||||||
Object.entries(fieldSchemas).map(([type, schema]) =>
|
|
||||||
Type.Object(
|
|
||||||
{
|
|
||||||
type: Type.Const(type),
|
|
||||||
name: StringIdentifier,
|
|
||||||
config: Type.Optional(schema),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const schema = Type.Object({
|
|
||||||
fields: Type.Array(fieldSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const fieldSchema2 = Type.Object({
|
|
||||||
type: StringEnum(Object.keys(fieldSchemas)),
|
|
||||||
name: StringIdentifier,
|
|
||||||
});
|
|
||||||
|
|
||||||
function specificFieldSchema(type: keyof typeof fieldSchemas) {
|
|
||||||
return Type.Omit(fieldSchemas[type], [
|
|
||||||
"label",
|
|
||||||
"description",
|
|
||||||
"required",
|
|
||||||
"fillable",
|
|
||||||
"hidden",
|
|
||||||
"virtual",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function EntityFieldsForm() {
|
|
||||||
const {
|
|
||||||
control,
|
|
||||||
formState: { isValid, errors },
|
|
||||||
getValues,
|
|
||||||
handleSubmit,
|
|
||||||
watch,
|
|
||||||
register,
|
|
||||||
setValue,
|
|
||||||
} = useForm({
|
|
||||||
mode: "onTouched",
|
|
||||||
resolver: typeboxResolver(schema),
|
|
||||||
defaultValues: {
|
|
||||||
fields: [{ type: "text", name: "", config: {} }],
|
|
||||||
sort: { by: "-1", dir: "asc" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const defaultType = Object.keys(fieldSchemas)[0];
|
|
||||||
const { fields, append, prepend, remove, swap, move, insert, update } = useFieldArray({
|
|
||||||
control, // control props comes from useForm (optional: if you are using FormProvider)
|
|
||||||
name: "fields", // unique name for your Field Array
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleAppend() {
|
|
||||||
append({ type: "text", name: "", config: {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-1 p-8">
|
|
||||||
{/*{fields.map((field, index) => (
|
|
||||||
<EntityField
|
|
||||||
key={field.id}
|
|
||||||
field={field}
|
|
||||||
index={index}
|
|
||||||
form={{ watch, register, setValue, getValues, control }}
|
|
||||||
defaultType={defaultType}
|
|
||||||
remove={remove}
|
|
||||||
/>
|
|
||||||
))}*/}
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<EntityFieldForm key={field.id} value={field} index={index} update={update} />
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Button className="justify-center" onClick={handleAppend}>
|
|
||||||
Add Field
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<pre>{JSON.stringify(watch(), null, 2)}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EntityFieldForm({ update, index, value }) {
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
control,
|
|
||||||
formState: { errors },
|
|
||||||
} = useForm({
|
|
||||||
mode: "onBlur",
|
|
||||||
resolver: typeboxResolver(
|
|
||||||
Type.Object({
|
|
||||||
type: StringEnum(Object.keys(fieldSchemas)),
|
|
||||||
name: Type.String({ minLength: 1, maxLength: 3 }),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
defaultValues: value,
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleUpdate({ id, ...data }) {
|
|
||||||
console.log("data", data);
|
|
||||||
update(index, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form>
|
|
||||||
<MantineSelect
|
|
||||||
control={control}
|
|
||||||
name="type"
|
|
||||||
data={[...Object.keys(fieldSchemas), "test"]}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Name"
|
|
||||||
placeholder="name"
|
|
||||||
classNames={{ root: "w-full" }}
|
|
||||||
{...register("name")}
|
|
||||||
error={errors.name?.message as any}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EntityFieldController({
|
|
||||||
name,
|
|
||||||
control,
|
|
||||||
defaultValue,
|
|
||||||
rules,
|
|
||||||
shouldUnregister,
|
|
||||||
}: UseControllerProps & {
|
|
||||||
index: number;
|
|
||||||
}) {
|
|
||||||
const {
|
|
||||||
field: { value, onChange: fieldOnChange, ...field },
|
|
||||||
fieldState,
|
|
||||||
} = useController({
|
|
||||||
name,
|
|
||||||
control,
|
|
||||||
defaultValue,
|
|
||||||
rules,
|
|
||||||
shouldUnregister,
|
|
||||||
});
|
|
||||||
|
|
||||||
return <div>field</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function EntityField({
|
|
||||||
field,
|
|
||||||
index,
|
|
||||||
form: { watch, register, setValue, getValues, control },
|
|
||||||
remove,
|
|
||||||
defaultType,
|
|
||||||
}: {
|
|
||||||
field: FieldArrayWithId;
|
|
||||||
index: number;
|
|
||||||
form: Pick<UseFormReturn<any>, "watch" | "register" | "setValue" | "getValues" | "control">;
|
|
||||||
remove: (index: number) => void;
|
|
||||||
defaultType: string;
|
|
||||||
}) {
|
|
||||||
const [opened, handlers] = useDisclosure(false);
|
|
||||||
const prefix = `fields.${index}` as const;
|
|
||||||
const name = watch(`${prefix}.name`);
|
|
||||||
const enabled = name?.length > 0;
|
|
||||||
const type = watch(`${prefix}.type`);
|
|
||||||
//const config = watch(`${prefix}.config`);
|
|
||||||
const selectFieldRegister = register(`${prefix}.type`);
|
|
||||||
//console.log("type", type, specificFieldSchema(type as any));
|
|
||||||
|
|
||||||
function handleDelete(index: number) {
|
|
||||||
return () => {
|
|
||||||
if (name.length === 0) {
|
|
||||||
remove(index);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.confirm(`Sure to delete "${name}"?`) && remove(index);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={field.id} className="flex flex-col border border-muted rounded">
|
|
||||||
<div className="flex flex-row gap-2 px-2 pt-1 pb-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<IconButton Icon={TbGripVertical} className="mt-1" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row flex-grow gap-1">
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
label="Type"
|
|
||||||
data={[...Object.keys(fieldSchemas), "test"]}
|
|
||||||
defaultValue={defaultType}
|
|
||||||
onBlur={selectFieldRegister.onBlur}
|
|
||||||
onChange={(value) => {
|
|
||||||
setValue(`${prefix}.type`, value as any);
|
|
||||||
setValue(`${prefix}.config`, {} as any);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<TextInput
|
|
||||||
label="Name"
|
|
||||||
placeholder="name"
|
|
||||||
classNames={{ root: "w-full" }}
|
|
||||||
{...register(`fields.${index}.name`)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-end ">
|
|
||||||
<div className="flex flex-row gap-1">
|
|
||||||
<Tooltip label="Specify a property name to see options." disabled={enabled}>
|
|
||||||
<Button
|
|
||||||
IconRight={opened ? TbChevronUp : TbChevronDown}
|
|
||||||
onClick={handlers.toggle}
|
|
||||||
variant={opened ? "default" : "ghost"}
|
|
||||||
disabled={!enabled}
|
|
||||||
>
|
|
||||||
Options
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
<IconButton Icon={TbTrash} onClick={handleDelete(index)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/*{enabled && opened && (
|
|
||||||
<div className="flex flex-col border-t border-t-muted px-3 py-2">
|
|
||||||
<Tabs defaultValue="general">
|
|
||||||
<Tabs.List>
|
|
||||||
<Tabs.Tab value="general">General</Tabs.Tab>
|
|
||||||
<Tabs.Tab value="specific">Specific</Tabs.Tab>
|
|
||||||
<Tabs.Tab value="visibility">Visiblity</Tabs.Tab>
|
|
||||||
</Tabs.List>
|
|
||||||
<Tabs.Panel value="general">
|
|
||||||
<div className="flex flex-col gap-2 pt-3 pb-1" key={`${prefix}_${type}`}>
|
|
||||||
<Switch
|
|
||||||
label="Required"
|
|
||||||
{...register(`${prefix}.config.required` as any)}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Label"
|
|
||||||
placeholder="Label"
|
|
||||||
{...register(`${prefix}.config.label` as any)}
|
|
||||||
/>
|
|
||||||
<Textarea
|
|
||||||
label="Description"
|
|
||||||
placeholder="Description"
|
|
||||||
{...register(`${prefix}.config.description` as any)}
|
|
||||||
/>
|
|
||||||
<Switch label="Virtual" {...register(`${prefix}.config.virtual` as any)} />
|
|
||||||
</div>
|
|
||||||
</Tabs.Panel>
|
|
||||||
<Tabs.Panel value="specific">
|
|
||||||
<div className="flex flex-col gap-2 pt-3 pb-1">
|
|
||||||
<JsonSchemaForm
|
|
||||||
key={type}
|
|
||||||
schema={specificFieldSchema(type as any)}
|
|
||||||
uiSchema={dataFieldsUiSchema.config}
|
|
||||||
className="legacy hide-required-mark fieldset-alternative mute-root"
|
|
||||||
onChange={(value) => {
|
|
||||||
setValue(`${prefix}.config`, {
|
|
||||||
...getValues([`fields.${index}.config`])[0],
|
|
||||||
...value
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tabs.Panel>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
)}*/}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
6
bun.lock
6
bun.lock
@@ -64,7 +64,7 @@
|
|||||||
"@dagrejs/dagre": "^1.1.4",
|
"@dagrejs/dagre": "^1.1.4",
|
||||||
"@hono/typebox-validator": "^0.3.2",
|
"@hono/typebox-validator": "^0.3.2",
|
||||||
"@hono/vite-dev-server": "^0.19.0",
|
"@hono/vite-dev-server": "^0.19.0",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^4.1.3",
|
||||||
"@libsql/kysely-libsql": "^0.4.1",
|
"@libsql/kysely-libsql": "^0.4.1",
|
||||||
"@mantine/modals": "^7.17.1",
|
"@mantine/modals": "^7.17.1",
|
||||||
"@mantine/notifications": "^7.17.1",
|
"@mantine/notifications": "^7.17.1",
|
||||||
@@ -578,7 +578,7 @@
|
|||||||
|
|
||||||
"@hono/vite-dev-server": ["@hono/vite-dev-server@0.19.0", "", { "dependencies": { "@hono/node-server": "^1.12.0", "minimatch": "^9.0.3" }, "peerDependencies": { "hono": "*", "miniflare": "*", "wrangler": "*" }, "optionalPeers": ["miniflare", "wrangler"] }, "sha512-myMc4Nm0nFQSPaeE6I/a1ODyDR5KpQ4EHodX4Tu/7qlB31GfUemhUH/WsO91HJjDEpRRpsT4Zbg+PleMlpTljw=="],
|
"@hono/vite-dev-server": ["@hono/vite-dev-server@0.19.0", "", { "dependencies": { "@hono/node-server": "^1.12.0", "minimatch": "^9.0.3" }, "peerDependencies": { "hono": "*", "miniflare": "*", "wrangler": "*" }, "optionalPeers": ["miniflare", "wrangler"] }, "sha512-myMc4Nm0nFQSPaeE6I/a1ODyDR5KpQ4EHodX4Tu/7qlB31GfUemhUH/WsO91HJjDEpRRpsT4Zbg+PleMlpTljw=="],
|
||||||
|
|
||||||
"@hookform/resolvers": ["@hookform/resolvers@3.10.0", "", { "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag=="],
|
"@hookform/resolvers": ["@hookform/resolvers@4.1.3", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-Jsv6UOWYTrEFJ/01ZrnwVXs7KDvP8XIo115i++5PWvNkNvkrsTfGiLS6w+eJ57CYtUtDQalUWovCZDHFJ8u1VQ=="],
|
||||||
|
|
||||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
|
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
|
||||||
|
|
||||||
@@ -1060,6 +1060,8 @@
|
|||||||
|
|
||||||
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||||
|
|
||||||
|
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||||
|
|
||||||
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
|
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||||
|
|||||||
Reference in New Issue
Block a user