"use client"; import * as React from "react"; import { type Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui"; import { Controller, type ControllerProps, type DefaultValues, type FieldPath, type FieldValues, FormProvider, useFormContext, type UseFormReturn, } from "react-hook-form"; import { cn } from "@/utils/ui"; import { Label } from "./label"; const DRAFT_DEBOUNCE_MS = 500; export interface DraftStorage { getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; } type FormProps< TFieldValues extends FieldValues = FieldValues, TContext = unknown, > = UseFormReturn & { children: React.ReactNode; persistKey?: string; storage?: DraftStorage; }; function Form< TFieldValues extends FieldValues = FieldValues, TContext = unknown, >({ persistKey, storage, children, ...methods }: FormProps) { const { watch, reset } = methods; // To change keys after mount, re-mount the component with key={persistKey} const persistKeyOnMount = React.useRef(persistKey); const storageOnMount = React.useRef(storage); const resetRef = React.useRef(reset); React.useEffect(() => { if (!persistKeyOnMount.current) return; const store = storageOnMount.current ?? window.localStorage; try { const stored = store.getItem(persistKeyOnMount.current); if (stored) { resetRef.current( JSON.parse(stored) as DefaultValues, ); } } catch { // Storage may be unavailable (private browsing, storage blocked) } }, []); React.useEffect(() => { if (!persistKey) return; const store = storageOnMount.current ?? window.localStorage; let timer: ReturnType; const subscription = watch((values) => { clearTimeout(timer); timer = setTimeout(() => { try { store.setItem(persistKey, JSON.stringify(values)); } catch { // Storage may be full or blocked } }, DRAFT_DEBOUNCE_MS); }); return () => { clearTimeout(timer); subscription.unsubscribe(); }; }, [persistKey, watch]); return {children}; } export function clearFormDraft({ key, storage, }: { key: string; storage?: DraftStorage; }): void { const store = storage ?? window.localStorage; try { store.removeItem(key); } catch { // Storage may be unavailable } } type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, > = { name: TName; }; const FormFieldContext = React.createContext( {} as FormFieldContextValue, ); const FormField = < TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, >({ ...props }: ControllerProps) => { return ( ); }; const useFormField = () => { const fieldContext = React.useContext(FormFieldContext); const itemContext = React.useContext(FormItemContext); const { getFieldState, formState } = useFormContext(); const fieldState = getFieldState(fieldContext.name, formState); if (!fieldContext) { throw new Error("useFormField should be used within "); } const { id } = itemContext; return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState, }; }; type FormItemContextValue = { id: string; }; const FormItemContext = React.createContext( {} as FormItemContextValue, ); const FormItem = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => { const id = React.useId(); return (
); }); FormItem.displayName = "FormItem"; const FormLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { const { error, formItemId } = useFormField(); return (