mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 12:56:05 +00:00
added more input field types and improved typing
This commit is contained in:
@@ -100,7 +100,7 @@ const ArrayIterator = memo(
|
|||||||
({ name, children }: any) => {
|
({ name, children }: any) => {
|
||||||
return children(useFormValue(name));
|
return children(useFormValue(name));
|
||||||
},
|
},
|
||||||
(prev, next) => prev.value.length === next.value.length
|
(prev, next) => prev.value?.length === next.value?.length
|
||||||
);
|
);
|
||||||
|
|
||||||
const ArrayAdd = ({ schema, path }: { schema: JsonSchema; path: string }) => {
|
const ArrayAdd = ({ schema, path }: { schema: JsonSchema; path: string }) => {
|
||||||
@@ -114,7 +114,6 @@ const ArrayAdd = ({ schema, path }: { schema: JsonSchema; path: string }) => {
|
|||||||
const itemsMultiSchema = getMultiSchema(schema.items);
|
const itemsMultiSchema = getMultiSchema(schema.items);
|
||||||
|
|
||||||
function handleAdd(template?: any) {
|
function handleAdd(template?: any) {
|
||||||
//const currentIndex = value?.length ?? 0;
|
|
||||||
const newPointer = `${path}/${currentIndex}`.replace(/\/+/g, "/");
|
const newPointer = `${path}/${currentIndex}`.replace(/\/+/g, "/");
|
||||||
setValue(newPointer, template ?? ctx.lib.getTemplate(undefined, schema!.items));
|
setValue(newPointer, template ?? ctx.lib.getTemplate(undefined, schema!.items));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,12 +81,61 @@ export const FieldComponent = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isType(schema.type, ["number", "integer"])) {
|
if (isType(schema.type, ["number", "integer"])) {
|
||||||
return <Formy.Input type="number" id={props.name} {...props} value={props.value ?? ""} />;
|
const additional = {
|
||||||
|
min: schema.minimum,
|
||||||
|
max: schema.maximum,
|
||||||
|
step: schema.multipleOf
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formy.Input
|
||||||
|
type="number"
|
||||||
|
id={props.name}
|
||||||
|
{...props}
|
||||||
|
value={props.value ?? ""}
|
||||||
|
{...additional}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isType(schema.type, "boolean")) {
|
if (isType(schema.type, "boolean")) {
|
||||||
return <Formy.Switch id={props.name} {...(props as any)} checked={value as any} />;
|
return <Formy.Switch id={props.name} {...(props as any)} checked={value === true} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Formy.Input id={props.name} {...props} value={props.value ?? ""} />;
|
if (isType(schema.type, "string") && schema.format === "date-time") {
|
||||||
|
const value = props.value ? new Date(props.value as string).toISOString().slice(0, 16) : "";
|
||||||
|
return (
|
||||||
|
<Formy.DateInput
|
||||||
|
id={props.name}
|
||||||
|
{...props}
|
||||||
|
value={value}
|
||||||
|
type="datetime-local"
|
||||||
|
onChange={(e) => {
|
||||||
|
const date = new Date(e.target.value);
|
||||||
|
props.onChange?.({
|
||||||
|
// @ts-ignore
|
||||||
|
target: { value: date.toISOString() }
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isType(schema.type, "string") && schema.format === "date") {
|
||||||
|
return <Formy.DateInput id={props.name} {...props} value={props.value ?? ""} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additional = {
|
||||||
|
maxLength: schema.maxLength,
|
||||||
|
minLength: schema.minLength,
|
||||||
|
pattern: schema.pattern
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
if (schema.format) {
|
||||||
|
if (["password", "hidden", "url", "email", "tel"].includes(schema.format)) {
|
||||||
|
additional.type = schema.format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Formy.Input id={props.name} {...props} value={props.value ?? ""} {...additional} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import { Children, type ReactElement, type ReactNode, cloneElement, isValidEleme
|
|||||||
import { IconButton } from "ui/components/buttons/IconButton";
|
import { IconButton } from "ui/components/buttons/IconButton";
|
||||||
import { JsonViewer } from "ui/components/code/JsonViewer";
|
import { JsonViewer } from "ui/components/code/JsonViewer";
|
||||||
import * as Formy from "ui/components/form/Formy";
|
import * as Formy from "ui/components/form/Formy";
|
||||||
import { useFormError } from "ui/components/form/json-schema-form/Form";
|
import {
|
||||||
|
useFormContext,
|
||||||
|
useFormError,
|
||||||
|
useFormValue
|
||||||
|
} from "ui/components/form/json-schema-form/Form";
|
||||||
import { getLabel } from "./utils";
|
import { getLabel } from "./utils";
|
||||||
|
|
||||||
export type FieldwrapperProps = {
|
export type FieldwrapperProps = {
|
||||||
@@ -24,7 +28,6 @@ export function FieldWrapper({
|
|||||||
label: _label,
|
label: _label,
|
||||||
required,
|
required,
|
||||||
schema,
|
schema,
|
||||||
debug,
|
|
||||||
wrapper,
|
wrapper,
|
||||||
hidden,
|
hidden,
|
||||||
children
|
children
|
||||||
@@ -41,29 +44,7 @@ export function FieldWrapper({
|
|||||||
as={wrapper === "fieldset" ? "fieldset" : "div"}
|
as={wrapper === "fieldset" ? "fieldset" : "div"}
|
||||||
className={hidden ? "hidden" : "relative"}
|
className={hidden ? "hidden" : "relative"}
|
||||||
>
|
>
|
||||||
{debug && (
|
<FieldDebug name={name} schema={schema} required={required} />
|
||||||
<div className="absolute right-0 top-0">
|
|
||||||
{/* @todo: use radix */}
|
|
||||||
<Popover>
|
|
||||||
<Popover.Target>
|
|
||||||
<IconButton Icon={IconBug} size="xs" className="opacity-30" />
|
|
||||||
</Popover.Target>
|
|
||||||
<Popover.Dropdown>
|
|
||||||
<JsonViewer
|
|
||||||
json={{
|
|
||||||
...(typeof debug === "object" ? debug : {}),
|
|
||||||
name,
|
|
||||||
required,
|
|
||||||
schema,
|
|
||||||
errors
|
|
||||||
}}
|
|
||||||
expand={6}
|
|
||||||
className="p-0"
|
|
||||||
/>
|
|
||||||
</Popover.Dropdown>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{label && (
|
{label && (
|
||||||
<Formy.Label
|
<Formy.Label
|
||||||
@@ -98,3 +79,38 @@ export function FieldWrapper({
|
|||||||
</Formy.Group>
|
</Formy.Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FieldDebug = ({
|
||||||
|
name,
|
||||||
|
schema,
|
||||||
|
required
|
||||||
|
}: Pick<FieldwrapperProps, "name" | "schema" | "required">) => {
|
||||||
|
const { options } = useFormContext();
|
||||||
|
if (!options?.debug) return null;
|
||||||
|
const { value } = useFormValue(name);
|
||||||
|
const errors = useFormError(name, { strict: true });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="absolute right-0 top-0">
|
||||||
|
{/* @todo: use radix */}
|
||||||
|
<Popover>
|
||||||
|
<Popover.Target>
|
||||||
|
<IconButton Icon={IconBug} size="xs" className="opacity-30" />
|
||||||
|
</Popover.Target>
|
||||||
|
<Popover.Dropdown>
|
||||||
|
<JsonViewer
|
||||||
|
json={{
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
required,
|
||||||
|
schema,
|
||||||
|
errors
|
||||||
|
}}
|
||||||
|
expand={6}
|
||||||
|
className="p-0"
|
||||||
|
/>
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -45,32 +45,32 @@ type FormState<Data = any> = {
|
|||||||
|
|
||||||
export type FormProps<
|
export type FormProps<
|
||||||
Schema extends JSONSchema = JSONSchema,
|
Schema extends JSONSchema = JSONSchema,
|
||||||
Data = Schema extends JSONSchema ? FromSchema<JSONSchema> : any
|
Data = Schema extends JSONSchema ? FromSchema<Schema> : any,
|
||||||
> = Omit<ComponentPropsWithoutRef<"form">, "onChange"> & {
|
InitialData = Schema extends JSONSchema ? FromSchema<Schema> : any
|
||||||
|
> = Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit"> & {
|
||||||
schema: Schema;
|
schema: Schema;
|
||||||
validateOn?: "change" | "submit";
|
validateOn?: "change" | "submit";
|
||||||
initialValues?: Partial<Data>;
|
|
||||||
initialOpts?: LibTemplateOptions;
|
initialOpts?: LibTemplateOptions;
|
||||||
ignoreKeys?: string[];
|
ignoreKeys?: string[];
|
||||||
onChange?: (data: Partial<Data>, name: string, value: any) => void;
|
onChange?: (data: Partial<Data>, name: string, value: any) => void;
|
||||||
onSubmit?: (data: Partial<Data>) => void | Promise<void>;
|
onSubmit?: (data: Data) => void | Promise<void>;
|
||||||
onInvalidSubmit?: (errors: JsonError[], data: Partial<Data>) => void;
|
onInvalidSubmit?: (errors: JsonError[], data: Partial<Data>) => void;
|
||||||
hiddenSubmit?: boolean;
|
hiddenSubmit?: boolean;
|
||||||
options?: {
|
options?: {
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
keepEmpty?: boolean;
|
keepEmpty?: boolean;
|
||||||
};
|
};
|
||||||
|
initialValues?: InitialData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FormContext<Data> = {
|
export type FormContext<Data> = {
|
||||||
data: Data;
|
|
||||||
setData: (data: Data) => void;
|
setData: (data: Data) => void;
|
||||||
setValue: (pointer: string, value: any) => void;
|
setValue: (pointer: string, value: any) => void;
|
||||||
deleteValue: (pointer: string) => void;
|
deleteValue: (pointer: string) => void;
|
||||||
errors: JsonError[];
|
errors: JsonError[];
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
submitting: boolean;
|
submitting: boolean;
|
||||||
schema: JSONSchema;
|
schema: LibJsonSchema;
|
||||||
lib: Draft2019;
|
lib: Draft2019;
|
||||||
options: FormProps["options"];
|
options: FormProps["options"];
|
||||||
root: string;
|
root: string;
|
||||||
@@ -82,7 +82,7 @@ FormContext.displayName = "FormContext";
|
|||||||
|
|
||||||
export function Form<
|
export function Form<
|
||||||
Schema extends JSONSchema = JSONSchema,
|
Schema extends JSONSchema = JSONSchema,
|
||||||
Data = Schema extends JSONSchema ? FromSchema<JSONSchema> : any
|
Data = Schema extends JSONSchema ? FromSchema<Schema> : any
|
||||||
>({
|
>({
|
||||||
schema: _schema,
|
schema: _schema,
|
||||||
initialValues: _initialValues,
|
initialValues: _initialValues,
|
||||||
@@ -126,7 +126,7 @@ export function Form<
|
|||||||
try {
|
try {
|
||||||
const { data, errors } = validate();
|
const { data, errors } = validate();
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
await onSubmit(data);
|
await onSubmit(data as Data);
|
||||||
} else {
|
} else {
|
||||||
console.log("invalid", errors);
|
console.log("invalid", errors);
|
||||||
onInvalidSubmit?.(errors, data);
|
onInvalidSubmit?.(errors, data);
|
||||||
@@ -200,6 +200,7 @@ export function Form<
|
|||||||
<form {...props} ref={formRef} onSubmit={handleSubmit}>
|
<form {...props} ref={formRef} onSubmit={handleSubmit}>
|
||||||
<FormContext.Provider value={context}>
|
<FormContext.Provider value={context}>
|
||||||
{children ? children : <Field name="" />}
|
{children ? children : <Field name="" />}
|
||||||
|
{options?.debug && <FormDebug />}
|
||||||
</FormContext.Provider>
|
</FormContext.Provider>
|
||||||
{hiddenSubmit && (
|
{hiddenSubmit && (
|
||||||
<button style={{ visibility: "hidden" }} type="submit">
|
<button style={{ visibility: "hidden" }} type="submit">
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export function isRequired(pointer: string, schema: JsonSchema, data?: any) {
|
|||||||
const lib = new Draft2019(schema as any);
|
const lib = new Draft2019(schema as any);
|
||||||
|
|
||||||
const childSchema = lib.getSchema({ pointer, data });
|
const childSchema = lib.getSchema({ pointer, data });
|
||||||
if (typeof childSchema === "object" && ("const" in childSchema || "enum" in childSchema)) {
|
if (typeof childSchema === "object" && "const" in childSchema) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { JSONSchema } from "json-schema-to-ts";
|
||||||
import { useBknd } from "ui/client/bknd";
|
import { useBknd } from "ui/client/bknd";
|
||||||
import { Button } from "ui/components/buttons/Button";
|
import { Button } from "ui/components/buttons/Button";
|
||||||
import {
|
import {
|
||||||
@@ -237,7 +238,7 @@ export default function JsonSchemaForm3() {
|
|||||||
<FormDebug force />
|
<FormDebug force />
|
||||||
</Form>*/}
|
</Form>*/}
|
||||||
|
|
||||||
<CustomMediaForm />
|
{/*<CustomMediaForm />*/}
|
||||||
{/*<Form schema={schema.media} initialValues={config.media} validateOn="change">
|
{/*<Form schema={schema.media} initialValues={config.media} validateOn="change">
|
||||||
<Field name="" />
|
<Field name="" />
|
||||||
</Form>*/}
|
</Form>*/}
|
||||||
@@ -255,11 +256,57 @@ export default function JsonSchemaForm3() {
|
|||||||
>
|
>
|
||||||
<AutoForm />
|
<AutoForm />
|
||||||
</Form>*/}
|
</Form>*/}
|
||||||
|
|
||||||
|
<Form
|
||||||
|
schema={ss}
|
||||||
|
initialValues={{
|
||||||
|
name: "Peter",
|
||||||
|
age: 20,
|
||||||
|
interested: true,
|
||||||
|
dinnerTime: "2023-12-31T23:59:59+02:00"
|
||||||
|
}}
|
||||||
|
ignoreKeys={["what"]}
|
||||||
|
onChange={(state) => console.log(state)}
|
||||||
|
onSubmit={(state) => console.log(state)}
|
||||||
|
validateOn="change"
|
||||||
|
options={{ debug: true }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Scrollable>
|
</Scrollable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ss = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: { type: "string" },
|
||||||
|
email: { type: "string", format: "email" },
|
||||||
|
interested: { type: "boolean" },
|
||||||
|
bla: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["small", "medium", "large"]
|
||||||
|
},
|
||||||
|
password: { type: "string", format: "password" },
|
||||||
|
birthdate: { type: "string", format: "date" },
|
||||||
|
dinnerTime: { type: "string", format: "date-time" },
|
||||||
|
age: { type: "number", minimum: 0, multipleOf: 5 },
|
||||||
|
tags: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
min: { type: "number" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["name"],
|
||||||
|
additionalProperties: false
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
|
||||||
function CustomMediaForm() {
|
function CustomMediaForm() {
|
||||||
const { schema, config } = useBknd();
|
const { schema, config } = useBknd();
|
||||||
|
|
||||||
@@ -269,7 +316,7 @@ function CustomMediaForm() {
|
|||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
schema={schema.media}
|
schema={schema.media}
|
||||||
initialValues={config.media}
|
initialValues={config.media as any}
|
||||||
className="flex flex-col gap-3"
|
className="flex flex-col gap-3"
|
||||||
validateOn="change"
|
validateOn="change"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user