diff --git a/app/package.json b/app/package.json index a2f2ea5..b19a44e 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.7.0-rc.8", + "version": "0.7.0-rc.11", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { @@ -55,6 +55,7 @@ "oauth4webapi": "^2.11.1", "object-path-immutable": "^4.1.2", "radix-ui": "^1.1.2", + "json-schema-to-ts": "^3.1.1", "swr": "^2.2.5" }, "devDependencies": { @@ -75,7 +76,6 @@ "clsx": "^2.1.1", "esbuild-postcss": "^0.0.4", "jotai": "^2.10.1", - "json-schema-to-ts": "^3.1.1", "open": "^10.1.0", "openapi-types": "^12.1.3", "postcss": "^8.4.47", diff --git a/app/src/ui/components/code/JsonViewer.tsx b/app/src/ui/components/code/JsonViewer.tsx index 3eaa6c4..e2ac8d8 100644 --- a/app/src/ui/components/code/JsonViewer.tsx +++ b/app/src/ui/components/code/JsonViewer.tsx @@ -1,4 +1,3 @@ -import { IconCopy } from "@tabler/icons-react"; import { TbCopy } from "react-icons/tb"; import { JsonView } from "react-json-view-lite"; import { twMerge } from "tailwind-merge"; diff --git a/app/src/ui/components/form/json-schema-form/ArrayField.tsx b/app/src/ui/components/form/json-schema-form/ArrayField.tsx index 905fb96..1c95b0e 100644 --- a/app/src/ui/components/form/json-schema-form/ArrayField.tsx +++ b/app/src/ui/components/form/json-schema-form/ArrayField.tsx @@ -31,7 +31,7 @@ export const ArrayField = ({ onChange={(e: any) => { // @ts-ignore const selected = Array.from(e.target.selectedOptions).map((o) => o.value); - setValue(pointer, selected); + setValue(ctx.path, selected); }} /> diff --git a/app/src/ui/components/form/json-schema-form/FieldWrapper.tsx b/app/src/ui/components/form/json-schema-form/FieldWrapper.tsx index a788a65..aae5329 100644 --- a/app/src/ui/components/form/json-schema-form/FieldWrapper.tsx +++ b/app/src/ui/components/form/json-schema-form/FieldWrapper.tsx @@ -1,4 +1,3 @@ -import { Popover } from "@mantine/core"; import { IconBug } from "@tabler/icons-react"; import type { JsonSchema } from "json-schema-library"; import { Children, type ReactElement, type ReactNode, cloneElement, isValidElement } from "react"; @@ -10,6 +9,7 @@ import { useFormError, useFormValue } from "ui/components/form/json-schema-form/Form"; +import { Popover } from "ui/components/overlay/Popover"; import { getLabel } from "./utils"; export type FieldwrapperProps = { @@ -62,6 +62,7 @@ export function FieldWrapper({ {label} {required && *} )} +
{Children.count(children) === 1 && isValidElement(children) @@ -96,14 +97,15 @@ const FieldDebug = ({ const errors = useFormError(name, { strict: true }); return ( -
- {/* @todo: use radix */} - - - - - +
+ ( - + )} + > +
); diff --git a/app/src/ui/components/form/json-schema-form/Form.tsx b/app/src/ui/components/form/json-schema-form/Form.tsx index e7f1a6a..c26cb01 100644 --- a/app/src/ui/components/form/json-schema-form/Form.tsx +++ b/app/src/ui/components/form/json-schema-form/Form.tsx @@ -43,24 +43,9 @@ type FormState = { data: Data; }; -export type FormProps< - Schema extends JSONSchema = JSONSchema, - Data = Schema extends JSONSchema ? FromSchema : any, - InitialData = Schema extends JSONSchema ? FromSchema : any -> = Omit, "onChange" | "onSubmit"> & { - schema: Schema; - validateOn?: "change" | "submit"; - initialOpts?: LibTemplateOptions; - ignoreKeys?: string[]; - onChange?: (data: Partial, name: string, value: any) => void; - onSubmit?: (data: Data) => void | Promise; - onInvalidSubmit?: (errors: JsonError[], data: Partial) => void; - hiddenSubmit?: boolean; - options?: { - debug?: boolean; - keepEmpty?: boolean; - }; - initialValues?: InitialData; +type FormOptions = { + debug?: boolean; + keepEmpty?: boolean; }; export type FormContext = { @@ -72,7 +57,7 @@ export type FormContext = { submitting: boolean; schema: LibJsonSchema; lib: Draft2019; - options: FormProps["options"]; + options: FormOptions; root: string; _formStateAtom: PrimitiveAtom>; }; @@ -81,8 +66,8 @@ const FormContext = createContext>(undefined!); FormContext.displayName = "FormContext"; export function Form< - Schema extends JSONSchema = JSONSchema, - Data = Schema extends JSONSchema ? FromSchema : any + const Schema extends JSONSchema, + const Data = Schema extends JSONSchema ? FromSchema : any >({ schema: _schema, initialValues: _initialValues, @@ -96,7 +81,18 @@ export function Form< ignoreKeys = [], options = {}, ...props -}: FormProps) { +}: Omit, "onChange" | "onSubmit"> & { + schema: Schema; + validateOn?: "change" | "submit"; + initialOpts?: LibTemplateOptions; + ignoreKeys?: string[]; + onChange?: (data: Partial, name: string, value: any) => void; + onSubmit?: (data: Data) => void | Promise; + onInvalidSubmit?: (errors: JsonError[], data: Partial) => void; + hiddenSubmit?: boolean; + options?: FormOptions; + initialValues?: Schema extends JSONSchema ? FromSchema : never; +}) { const [schema, initial] = omitSchema(_schema, ignoreKeys, _initialValues); const lib = useMemo(() => new Draft2019(schema), [JSON.stringify(schema)]); const initialValues = initial ?? lib.getTemplate(undefined, schema, initialOpts); diff --git a/app/src/ui/components/overlay/Popover.tsx b/app/src/ui/components/overlay/Popover.tsx index 22c9e5f..09e71bd 100644 --- a/app/src/ui/components/overlay/Popover.tsx +++ b/app/src/ui/components/overlay/Popover.tsx @@ -1,7 +1,7 @@ import { useClickOutside } from "@mantine/hooks"; -import { type ReactElement, cloneElement, useState } from "react"; +import { type ComponentPropsWithoutRef, type ReactElement, cloneElement, useState } from "react"; import { twMerge } from "tailwind-merge"; -import { useEvent } from "../../hooks/use-event"; +import { useEvent } from "ui/hooks/use-event"; export type PopoverProps = { className?: string; @@ -10,6 +10,7 @@ export type PopoverProps = { backdrop?: boolean; target: (props: { toggle: () => void }) => ReactElement; children: ReactElement<{ onClick: () => void }>; + overlayProps?: ComponentPropsWithoutRef<"div">; }; export function Popover({ @@ -18,20 +19,21 @@ export function Popover({ defaultOpen = false, backdrop = false, position = "bottom-start", - className, + overlayProps, + className }: PopoverProps) { const [open, setOpen] = useState(defaultOpen); const clickoutsideRef = useClickOutside(() => setOpen(false)); const toggle = useEvent((delay: number = 50) => - setTimeout(() => setOpen((prev) => !prev), typeof delay === "number" ? delay : 0), + setTimeout(() => setOpen((prev) => !prev), typeof delay === "number" ? delay : 0) ); const pos = { "bottom-start": "mt-1 top-[100%]", "bottom-end": "right-0 top-[100%] mt-1", "top-start": "bottom-[100%] mb-1", - "top-end": "bottom-[100%] right-0 mb-1", + "top-end": "bottom-[100%] right-0 mb-1" }[position]; return ( @@ -43,9 +45,11 @@ export function Popover({ {cloneElement(children as any, { onClick: toggle })} {open && (
{target({ toggle })} diff --git a/app/src/ui/index.ts b/app/src/ui/index.ts index 6631083..33a1c59 100644 --- a/app/src/ui/index.ts +++ b/app/src/ui/index.ts @@ -1,2 +1,3 @@ export { default as Admin, type BkndAdminProps } from "./Admin"; export * from "./components/form/json-schema-form"; +export { JsonViewer } from "./components/code/JsonViewer"; diff --git a/app/src/ui/routes/test/tests/json-schema-form3.tsx b/app/src/ui/routes/test/tests/json-schema-form3.tsx index 9edd99f..0640872 100644 --- a/app/src/ui/routes/test/tests/json-schema-form3.tsx +++ b/app/src/ui/routes/test/tests/json-schema-form3.tsx @@ -30,7 +30,7 @@ const schema2 = { } }, required: ["age"] -}; +} as const satisfies JSONSchema; export default function JsonSchemaForm3() { const { schema: _schema, config } = useBknd(); @@ -46,7 +46,9 @@ export default function JsonSchemaForm3() { return (
- {/*
console.log("change", data)} + onSubmit={(data) => console.log("submit", data)} schema={{ type: "object", properties: { @@ -59,12 +61,14 @@ export default function JsonSchemaForm3() { } } }, - required: ["age"] + required: ["age"], + additionalProperties: false }} initialValues={{ name: "Peter", age: 20, deep: { nested: "hello" } }} className="flex flex-col gap-3" validateOn="change" - />*/} + options={{ debug: true }} + /> {/**/} {/**/} - + />*/} {/*