Skip to content

Commit f7e6928

Browse files
committed
form: fix popover, improve form types
1 parent 3fa682b commit f7e6928

File tree

9 files changed

+56
-49
lines changed

9 files changed

+56
-49
lines changed

app/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"type": "module",
44
"sideEffects": false,
55
"bin": "./dist/cli/index.js",
6-
"version": "0.7.0-rc.8",
6+
"version": "0.7.0-rc.11",
77
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
88
"homepage": "https://bknd.io",
99
"repository": {
@@ -55,6 +55,7 @@
5555
"oauth4webapi": "^2.11.1",
5656
"object-path-immutable": "^4.1.2",
5757
"radix-ui": "^1.1.2",
58+
"json-schema-to-ts": "^3.1.1",
5859
"swr": "^2.2.5"
5960
},
6061
"devDependencies": {
@@ -75,7 +76,6 @@
7576
"clsx": "^2.1.1",
7677
"esbuild-postcss": "^0.0.4",
7778
"jotai": "^2.10.1",
78-
"json-schema-to-ts": "^3.1.1",
7979
"open": "^10.1.0",
8080
"openapi-types": "^12.1.3",
8181
"postcss": "^8.4.47",

app/src/ui/components/code/JsonViewer.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { IconCopy } from "@tabler/icons-react";
21
import { TbCopy } from "react-icons/tb";
32
import { JsonView } from "react-json-view-lite";
43
import { twMerge } from "tailwind-merge";

app/src/ui/components/form/json-schema-form/ArrayField.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const ArrayField = ({
3131
onChange={(e: any) => {
3232
// @ts-ignore
3333
const selected = Array.from(e.target.selectedOptions).map((o) => o.value);
34-
setValue(pointer, selected);
34+
setValue(ctx.path, selected);
3535
}}
3636
/>
3737
</FieldWrapper>

app/src/ui/components/form/json-schema-form/FieldWrapper.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Popover } from "@mantine/core";
21
import { IconBug } from "@tabler/icons-react";
32
import type { JsonSchema } from "json-schema-library";
43
import { Children, type ReactElement, type ReactNode, cloneElement, isValidElement } from "react";
@@ -10,6 +9,7 @@ import {
109
useFormError,
1110
useFormValue
1211
} from "ui/components/form/json-schema-form/Form";
12+
import { Popover } from "ui/components/overlay/Popover";
1313
import { getLabel } from "./utils";
1414

1515
export type FieldwrapperProps = {
@@ -62,6 +62,7 @@ export function FieldWrapper({
6262
{label} {required && <span className="font-medium opacity-30">*</span>}
6363
</Formy.Label>
6464
)}
65+
6566
<div className="flex flex-row gap-2">
6667
<div className="flex flex-1 flex-col gap-3">
6768
{Children.count(children) === 1 && isValidElement(children)
@@ -96,14 +97,15 @@ const FieldDebug = ({
9697
const errors = useFormError(name, { strict: true });
9798

9899
return (
99-
<div className="absolute right-0 top-0">
100-
{/* @todo: use radix */}
101-
<Popover>
102-
<Popover.Target>
103-
<IconButton Icon={IconBug} size="xs" className="opacity-30" />
104-
</Popover.Target>
105-
<Popover.Dropdown>
100+
<div className="absolute top-0 right-0">
101+
<Popover
102+
overlayProps={{
103+
className: "max-w-none"
104+
}}
105+
position="bottom-end"
106+
target={({ toggle }) => (
106107
<JsonViewer
108+
className="bg-background pr-3 text-sm"
107109
json={{
108110
name,
109111
value,
@@ -112,9 +114,10 @@ const FieldDebug = ({
112114
errors
113115
}}
114116
expand={6}
115-
className="p-0"
116117
/>
117-
</Popover.Dropdown>
118+
)}
119+
>
120+
<IconButton Icon={IconBug} size="xs" className="opacity-30" />
118121
</Popover>
119122
</div>
120123
);

app/src/ui/components/form/json-schema-form/Form.tsx

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,9 @@ type FormState<Data = any> = {
4343
data: Data;
4444
};
4545

46-
export type FormProps<
47-
Schema extends JSONSchema = JSONSchema,
48-
Data = Schema extends JSONSchema ? FromSchema<Schema> : any,
49-
InitialData = Schema extends JSONSchema ? FromSchema<Schema> : any
50-
> = Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit"> & {
51-
schema: Schema;
52-
validateOn?: "change" | "submit";
53-
initialOpts?: LibTemplateOptions;
54-
ignoreKeys?: string[];
55-
onChange?: (data: Partial<Data>, name: string, value: any) => void;
56-
onSubmit?: (data: Data) => void | Promise<void>;
57-
onInvalidSubmit?: (errors: JsonError[], data: Partial<Data>) => void;
58-
hiddenSubmit?: boolean;
59-
options?: {
60-
debug?: boolean;
61-
keepEmpty?: boolean;
62-
};
63-
initialValues?: InitialData;
46+
type FormOptions = {
47+
debug?: boolean;
48+
keepEmpty?: boolean;
6449
};
6550

6651
export type FormContext<Data> = {
@@ -72,7 +57,7 @@ export type FormContext<Data> = {
7257
submitting: boolean;
7358
schema: LibJsonSchema;
7459
lib: Draft2019;
75-
options: FormProps["options"];
60+
options: FormOptions;
7661
root: string;
7762
_formStateAtom: PrimitiveAtom<FormState<Data>>;
7863
};
@@ -81,8 +66,8 @@ const FormContext = createContext<FormContext<any>>(undefined!);
8166
FormContext.displayName = "FormContext";
8267

8368
export function Form<
84-
Schema extends JSONSchema = JSONSchema,
85-
Data = Schema extends JSONSchema ? FromSchema<Schema> : any
69+
const Schema extends JSONSchema,
70+
const Data = Schema extends JSONSchema ? FromSchema<Schema> : any
8671
>({
8772
schema: _schema,
8873
initialValues: _initialValues,
@@ -96,7 +81,18 @@ export function Form<
9681
ignoreKeys = [],
9782
options = {},
9883
...props
99-
}: FormProps<Schema, Data>) {
84+
}: Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit"> & {
85+
schema: Schema;
86+
validateOn?: "change" | "submit";
87+
initialOpts?: LibTemplateOptions;
88+
ignoreKeys?: string[];
89+
onChange?: (data: Partial<Data>, name: string, value: any) => void;
90+
onSubmit?: (data: Data) => void | Promise<void>;
91+
onInvalidSubmit?: (errors: JsonError[], data: Partial<Data>) => void;
92+
hiddenSubmit?: boolean;
93+
options?: FormOptions;
94+
initialValues?: Schema extends JSONSchema ? FromSchema<Schema> : never;
95+
}) {
10096
const [schema, initial] = omitSchema(_schema, ignoreKeys, _initialValues);
10197
const lib = useMemo(() => new Draft2019(schema), [JSON.stringify(schema)]);
10298
const initialValues = initial ?? lib.getTemplate(undefined, schema, initialOpts);

app/src/ui/components/overlay/Popover.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useClickOutside } from "@mantine/hooks";
2-
import { type ReactElement, cloneElement, useState } from "react";
2+
import { type ComponentPropsWithoutRef, type ReactElement, cloneElement, useState } from "react";
33
import { twMerge } from "tailwind-merge";
4-
import { useEvent } from "../../hooks/use-event";
4+
import { useEvent } from "ui/hooks/use-event";
55

66
export type PopoverProps = {
77
className?: string;
@@ -10,6 +10,7 @@ export type PopoverProps = {
1010
backdrop?: boolean;
1111
target: (props: { toggle: () => void }) => ReactElement;
1212
children: ReactElement<{ onClick: () => void }>;
13+
overlayProps?: ComponentPropsWithoutRef<"div">;
1314
};
1415

1516
export function Popover({
@@ -18,20 +19,21 @@ export function Popover({
1819
defaultOpen = false,
1920
backdrop = false,
2021
position = "bottom-start",
21-
className,
22+
overlayProps,
23+
className
2224
}: PopoverProps) {
2325
const [open, setOpen] = useState(defaultOpen);
2426
const clickoutsideRef = useClickOutside(() => setOpen(false));
2527

2628
const toggle = useEvent((delay: number = 50) =>
27-
setTimeout(() => setOpen((prev) => !prev), typeof delay === "number" ? delay : 0),
29+
setTimeout(() => setOpen((prev) => !prev), typeof delay === "number" ? delay : 0)
2830
);
2931

3032
const pos = {
3133
"bottom-start": "mt-1 top-[100%]",
3234
"bottom-end": "right-0 top-[100%] mt-1",
3335
"top-start": "bottom-[100%] mb-1",
34-
"top-end": "bottom-[100%] right-0 mb-1",
36+
"top-end": "bottom-[100%] right-0 mb-1"
3537
}[position];
3638

3739
return (
@@ -43,9 +45,11 @@ export function Popover({
4345
{cloneElement(children as any, { onClick: toggle })}
4446
{open && (
4547
<div
48+
{...overlayProps}
4649
className={twMerge(
47-
"animate-fade-in absolute z-20 flex flex-col bg-background border border-muted px-1 py-1 rounded-lg shadow-lg min-w-full max-w-20 backdrop-blur-sm",
50+
"animate-fade-in absolute z-20 flex flex-col bg-background border border-muted px-1 py-1 rounded-lg shadow-lg backdrop-blur-sm min-w-0 max-w-20",
4851
pos,
52+
overlayProps?.className
4953
)}
5054
>
5155
{target({ toggle })}

app/src/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as Admin, type BkndAdminProps } from "./Admin";
22
export * from "./components/form/json-schema-form";
3+
export { JsonViewer } from "./components/code/JsonViewer";

app/src/ui/routes/test/tests/json-schema-form3.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const schema2 = {
3030
}
3131
},
3232
required: ["age"]
33-
};
33+
} as const satisfies JSONSchema;
3434

3535
export default function JsonSchemaForm3() {
3636
const { schema: _schema, config } = useBknd();
@@ -46,7 +46,9 @@ export default function JsonSchemaForm3() {
4646
return (
4747
<Scrollable>
4848
<div className="flex flex-col p-3">
49-
{/*<Form
49+
<Form
50+
onChange={(data) => console.log("change", data)}
51+
onSubmit={(data) => console.log("submit", data)}
5052
schema={{
5153
type: "object",
5254
properties: {
@@ -59,12 +61,14 @@ export default function JsonSchemaForm3() {
5961
}
6062
}
6163
},
62-
required: ["age"]
64+
required: ["age"],
65+
additionalProperties: false
6366
}}
6467
initialValues={{ name: "Peter", age: 20, deep: { nested: "hello" } }}
6568
className="flex flex-col gap-3"
6669
validateOn="change"
67-
/>*/}
70+
options={{ debug: true }}
71+
/>
6872

6973
{/*<Form
7074
schema={{
@@ -245,12 +249,12 @@ export default function JsonSchemaForm3() {
245249
</Form>*/}
246250

247251
{/*<CustomMediaForm />*/}
248-
<Form
252+
{/*<Form
249253
schema={schema.media}
250254
initialValues={config.media as any}
251255
onSubmit={console.log}
252256
validateOn="change"
253-
/>
257+
/>*/}
254258

255259
{/*<Form
256260
schema={removeKeyRecursively(schema.media, "pattern") as any}

app/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
"types": ["bun-types", "@cloudflare/workers-types"],
4-
"composite": true,
4+
"composite": false,
55
"module": "ESNext",
66
"moduleResolution": "bundler",
77
"jsx": "react-jsx",

0 commit comments

Comments
 (0)