mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 20:37:21 +00:00
form: fix popover, improve form types
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
/>
|
||||
</FieldWrapper>
|
||||
|
||||
@@ -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 && <span className="font-medium opacity-30">*</span>}
|
||||
</Formy.Label>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex flex-1 flex-col gap-3">
|
||||
{Children.count(children) === 1 && isValidElement(children)
|
||||
@@ -96,14 +97,15 @@ const FieldDebug = ({
|
||||
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>
|
||||
<div className="absolute top-0 right-0">
|
||||
<Popover
|
||||
overlayProps={{
|
||||
className: "max-w-none"
|
||||
}}
|
||||
position="bottom-end"
|
||||
target={({ toggle }) => (
|
||||
<JsonViewer
|
||||
className="bg-background pr-3 text-sm"
|
||||
json={{
|
||||
name,
|
||||
value,
|
||||
@@ -112,9 +114,10 @@ const FieldDebug = ({
|
||||
errors
|
||||
}}
|
||||
expand={6}
|
||||
className="p-0"
|
||||
/>
|
||||
</Popover.Dropdown>
|
||||
)}
|
||||
>
|
||||
<IconButton Icon={IconBug} size="xs" className="opacity-30" />
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -43,24 +43,9 @@ type FormState<Data = any> = {
|
||||
data: Data;
|
||||
};
|
||||
|
||||
export type FormProps<
|
||||
Schema extends JSONSchema = JSONSchema,
|
||||
Data = Schema extends JSONSchema ? FromSchema<Schema> : any,
|
||||
InitialData = Schema extends JSONSchema ? FromSchema<Schema> : any
|
||||
> = Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit"> & {
|
||||
schema: Schema;
|
||||
validateOn?: "change" | "submit";
|
||||
initialOpts?: LibTemplateOptions;
|
||||
ignoreKeys?: string[];
|
||||
onChange?: (data: Partial<Data>, name: string, value: any) => void;
|
||||
onSubmit?: (data: Data) => void | Promise<void>;
|
||||
onInvalidSubmit?: (errors: JsonError[], data: Partial<Data>) => void;
|
||||
hiddenSubmit?: boolean;
|
||||
options?: {
|
||||
debug?: boolean;
|
||||
keepEmpty?: boolean;
|
||||
};
|
||||
initialValues?: InitialData;
|
||||
type FormOptions = {
|
||||
debug?: boolean;
|
||||
keepEmpty?: boolean;
|
||||
};
|
||||
|
||||
export type FormContext<Data> = {
|
||||
@@ -72,7 +57,7 @@ export type FormContext<Data> = {
|
||||
submitting: boolean;
|
||||
schema: LibJsonSchema;
|
||||
lib: Draft2019;
|
||||
options: FormProps["options"];
|
||||
options: FormOptions;
|
||||
root: string;
|
||||
_formStateAtom: PrimitiveAtom<FormState<Data>>;
|
||||
};
|
||||
@@ -81,8 +66,8 @@ const FormContext = createContext<FormContext<any>>(undefined!);
|
||||
FormContext.displayName = "FormContext";
|
||||
|
||||
export function Form<
|
||||
Schema extends JSONSchema = JSONSchema,
|
||||
Data = Schema extends JSONSchema ? FromSchema<Schema> : any
|
||||
const Schema extends JSONSchema,
|
||||
const Data = Schema extends JSONSchema ? FromSchema<Schema> : any
|
||||
>({
|
||||
schema: _schema,
|
||||
initialValues: _initialValues,
|
||||
@@ -96,7 +81,18 @@ export function Form<
|
||||
ignoreKeys = [],
|
||||
options = {},
|
||||
...props
|
||||
}: FormProps<Schema, Data>) {
|
||||
}: Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit"> & {
|
||||
schema: Schema;
|
||||
validateOn?: "change" | "submit";
|
||||
initialOpts?: LibTemplateOptions;
|
||||
ignoreKeys?: string[];
|
||||
onChange?: (data: Partial<Data>, name: string, value: any) => void;
|
||||
onSubmit?: (data: Data) => void | Promise<void>;
|
||||
onInvalidSubmit?: (errors: JsonError[], data: Partial<Data>) => void;
|
||||
hiddenSubmit?: boolean;
|
||||
options?: FormOptions;
|
||||
initialValues?: Schema extends JSONSchema ? FromSchema<Schema> : 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);
|
||||
|
||||
@@ -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 && (
|
||||
<div
|
||||
{...overlayProps}
|
||||
className={twMerge(
|
||||
"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",
|
||||
"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",
|
||||
pos,
|
||||
overlayProps?.className
|
||||
)}
|
||||
>
|
||||
{target({ toggle })}
|
||||
|
||||
Reference in New Issue
Block a user