feat: add Form
This commit is contained in:
parent
bfbce754d9
commit
617b7be249
@ -0,0 +1,183 @@
|
|||||||
|
import { type AsChild, Slot, type VariantProps, vcn } from "@pswui-lib";
|
||||||
|
import {
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
createContext,
|
||||||
|
forwardRef,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Form Item Context
|
||||||
|
**/
|
||||||
|
interface IFormItemContext {
|
||||||
|
invalid?: string | null | undefined;
|
||||||
|
}
|
||||||
|
const FormItemContext = createContext<IFormItemContext>({});
|
||||||
|
|
||||||
|
/**
|
||||||
|
FormItem
|
||||||
|
**/
|
||||||
|
const [formItemVariant, resolveFormItemVariantProps] = vcn({
|
||||||
|
base: "flex flex-col gap-2 items-start w-full",
|
||||||
|
variants: {},
|
||||||
|
defaults: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FormItemProps
|
||||||
|
extends VariantProps<typeof formItemVariant>,
|
||||||
|
AsChild,
|
||||||
|
ComponentPropsWithoutRef<"label"> {
|
||||||
|
invalid?: string | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormItem = forwardRef<HTMLLabelElement, FormItemProps>((props, ref) => {
|
||||||
|
const [variantProps, restPropsCompressed] =
|
||||||
|
resolveFormItemVariantProps(props);
|
||||||
|
const { asChild, children, invalid, ...restPropsExtracted } =
|
||||||
|
restPropsCompressed;
|
||||||
|
const innerRef = useRef<HTMLLabelElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const invalidAsString = invalid ? invalid : "";
|
||||||
|
|
||||||
|
const input = innerRef.current?.querySelector?.("input");
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
input.setCustomValidity(invalidAsString);
|
||||||
|
}, [invalid]);
|
||||||
|
|
||||||
|
const Comp = asChild ? Slot : "label";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItemContext.Provider value={{ invalid }}>
|
||||||
|
<Comp
|
||||||
|
ref={(el: HTMLLabelElement | null) => {
|
||||||
|
innerRef.current = el;
|
||||||
|
if (typeof ref === "function") {
|
||||||
|
ref(el);
|
||||||
|
} else if (ref) {
|
||||||
|
ref.current = el;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={formItemVariant(variantProps)}
|
||||||
|
{...restPropsExtracted}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
</FormItemContext.Provider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FormItem.displayName = "FormItem";
|
||||||
|
|
||||||
|
/**
|
||||||
|
FormLabel
|
||||||
|
**/
|
||||||
|
const [formLabelVariant, resolveFormLabelVariantProps] = vcn({
|
||||||
|
base: "text-sm font-bold",
|
||||||
|
variants: {},
|
||||||
|
defaults: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FormLabelProps
|
||||||
|
extends VariantProps<typeof formLabelVariant>,
|
||||||
|
AsChild,
|
||||||
|
ComponentPropsWithoutRef<"span"> {}
|
||||||
|
|
||||||
|
const FormLabel = forwardRef<HTMLSpanElement, FormLabelProps>((props, ref) => {
|
||||||
|
const [variantProps, otherPropsCompressed] =
|
||||||
|
resolveFormLabelVariantProps(props);
|
||||||
|
const { children, asChild, ...otherPropsExtracted } = otherPropsCompressed;
|
||||||
|
|
||||||
|
const Comp = asChild ? Slot : "span";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
ref={ref}
|
||||||
|
className={formLabelVariant(variantProps)}
|
||||||
|
{...otherPropsExtracted}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FormLabel.displayName = "FormLabel";
|
||||||
|
|
||||||
|
/**
|
||||||
|
FormHelper
|
||||||
|
**/
|
||||||
|
const [formHelperVariant, resolveFormHelperVariantProps] = vcn({
|
||||||
|
base: "opacity-75 text-sm font-light",
|
||||||
|
variants: {},
|
||||||
|
defaults: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FormHelperProps
|
||||||
|
extends VariantProps<typeof formHelperVariant>,
|
||||||
|
AsChild,
|
||||||
|
ComponentPropsWithoutRef<"span"> {
|
||||||
|
hiddenOnInvalid?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormHelper = forwardRef<HTMLSpanElement, FormHelperProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const [variantProps, otherPropsCompressed] =
|
||||||
|
resolveFormHelperVariantProps(props);
|
||||||
|
const { asChild, children, hiddenOnInvalid, ...otherPropsExtracted } =
|
||||||
|
otherPropsCompressed;
|
||||||
|
const item = useContext(FormItemContext);
|
||||||
|
|
||||||
|
if (item.invalid && hiddenOnInvalid) return null;
|
||||||
|
|
||||||
|
const Comp = asChild ? Slot : "span";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
ref={ref}
|
||||||
|
className={formHelperVariant(variantProps)}
|
||||||
|
{...otherPropsExtracted}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
FormHelper.displayName = "FormHelper";
|
||||||
|
|
||||||
|
/**
|
||||||
|
FormError
|
||||||
|
**/
|
||||||
|
const [formErrorVariant, resolveFormErrorVariantProps] = vcn({
|
||||||
|
base: "text-sm text-red-500",
|
||||||
|
variants: {},
|
||||||
|
defaults: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FormErrorProps
|
||||||
|
extends VariantProps<typeof formErrorVariant>,
|
||||||
|
AsChild,
|
||||||
|
ComponentPropsWithoutRef<"span"> {}
|
||||||
|
|
||||||
|
const FormError = forwardRef<HTMLSpanElement, FormErrorProps>((props, ref) => {
|
||||||
|
const [variantProps, otherPropsCompressed] =
|
||||||
|
resolveFormErrorVariantProps(props);
|
||||||
|
const { asChild, children, ...otherPropsExtracted } = otherPropsCompressed;
|
||||||
|
|
||||||
|
const item = useContext(FormItemContext);
|
||||||
|
|
||||||
|
const Comp = asChild ? Slot : "span";
|
||||||
|
|
||||||
|
return item.invalid ? (
|
||||||
|
<Comp
|
||||||
|
ref={ref}
|
||||||
|
className={formErrorVariant(variantProps)}
|
||||||
|
{...otherPropsExtracted}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
) : null;
|
||||||
|
});
|
||||||
|
FormError.displayName = "FormError";
|
||||||
|
|
||||||
|
export { FormItem, FormLabel, FormHelper, FormError };
|
Loading…
x
Reference in New Issue
Block a user