initial json schema form implementation

This commit is contained in:
dswbx
2025-02-05 10:31:08 +01:00
parent babeaae8d1
commit f432473ed9
30 changed files with 2453 additions and 66 deletions

View File

@@ -1,25 +1,37 @@
import { getBrowser } from "core/utils";
import type { Field } from "data";
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
import {
type ElementType,
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState
} from "react";
import { TbCalendar, TbChevronDown, TbInfoCircle } from "react-icons/tb";
import { twMerge } from "tailwind-merge";
import { IconButton } from "ui/components/buttons/IconButton";
import { useEvent } from "ui/hooks/use-event";
export const Group: React.FC<React.ComponentProps<"div"> & { error?: boolean }> = ({
export const Group = <E extends ElementType = "div">({
error,
as,
...props
}) => (
<div
{...props}
className={twMerge(
"flex flex-col gap-1.5",
}: React.ComponentProps<E> & { error?: boolean; as?: E }) => {
const Tag = as || "div";
error && "text-red-500",
props.className
)}
/>
);
return (
<Tag
{...props}
className={twMerge(
"flex flex-col gap-1.5",
as === "fieldset" && "border border-primary/10 p-3 rounded-md",
error && "text-red-500",
props.className
)}
/>
);
};
export const formElementFactory = (element: string, props: any) => {
switch (element) {
@@ -34,7 +46,21 @@ export const formElementFactory = (element: string, props: any) => {
}
};
export const Label: React.FC<React.ComponentProps<"label">> = (props) => <label {...props} />;
export const Label = <E extends ElementType = "label">({
as,
...props
}: React.ComponentProps<E> & { as?: E }) => {
const Tag = as || "label";
return <Tag {...props} />;
};
export const Help: React.FC<React.ComponentProps<"div">> = ({ className, ...props }) => (
<div {...props} className={twMerge("text-sm text-primary/50", className)} />
);
export const ErrorMessage: React.FC<React.ComponentProps<"div">> = ({ className, ...props }) => (
<div {...props} className={twMerge("text-sm text-red-500", className)} />
);
export const FieldLabel: React.FC<React.ComponentProps<"label"> & { field: Field }> = ({
field,
@@ -145,20 +171,45 @@ export const BooleanInput = forwardRef<HTMLInputElement, React.ComponentProps<"i
}
);
export const Select = forwardRef<HTMLSelectElement, React.ComponentProps<"select">>(
(props, ref) => (
<div className="flex w-full relative">
<select
{...props}
ref={ref}
className={twMerge(
"bg-muted/40 focus:bg-muted rounded-md py-2.5 px-4 outline-none focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all disabled:bg-muted/50 disabled:text-primary/50",
"appearance-none h-11 w-full",
"border-r-8 border-r-transparent",
props.className
)}
/>
export const Select = forwardRef<
HTMLSelectElement,
React.ComponentProps<"select"> & {
options?: { value: string; label: string }[] | (string | number)[];
}
>(({ children, options, ...props }, ref) => (
<div className="flex w-full relative">
<select
{...props}
ref={ref}
className={twMerge(
"bg-muted/40 focus:bg-muted rounded-md py-2.5 px-4 outline-none focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all disabled:bg-muted/50 disabled:text-primary/50",
"appearance-none h-11 w-full",
!props.multiple && "border-r-8 border-r-transparent",
props.className
)}
>
{options ? (
<>
{!props.required && <option value="" />}
{options
.map((o, i) => {
if (typeof o !== "object") {
return { value: o, label: String(o) };
}
return o;
})
.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</>
) : (
children
)}
</select>
{!props.multiple && (
<TbChevronDown className="absolute right-3 top-0 bottom-0 h-full opacity-70" size={18} />
</div>
)
);
)}
</div>
));