All files / src/lib/forms FormContext.tsx

82.29% Statements 79/96
100% Branches 0/0
0% Functions 0/2
82.29% Lines 79/96

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 971x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                     1x 1x 1x 1x 1x 1x               1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
'use client';
 
/**
 * Form Context
 *
 * Provides a unified form context for managing form state, validation,
 * and submission across the application.
 */
 
import { createContext, useContext, ReactNode } from 'react';
import { z } from 'zod';
 
/**
 * Form field state
 */
export interface FieldState {
  value: unknown;
  error: string | null;
  touched: boolean;
  dirty: boolean;
}
 
/**
 * Generic form context value type
 */
export interface FormContextValue<T extends Record<string, unknown>> {
  values: T;
  errors: Record<keyof T, string | null>;
  touched: Record<keyof T, boolean>;
  isSubmitting: boolean;
  isValid: boolean;
  isDirty: boolean;
  setValue: (field: keyof T, value: unknown) => void;
  setTouched: (field: keyof T) => void;
  setError: (field: keyof T, error: string | null) => void;
  validateField: (field: keyof T) => Promise<string | null>;
  validateForm: () => Promise<boolean>;
  resetForm: () => void;
  handleSubmit: (onSubmit: (values: T) => Promise<void>) => (e: React.FormEvent) => void;
  getFieldProps: (field: keyof T) => {
    value: unknown;
    onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void;
    onBlur: () => void;
    'aria-invalid': boolean;
  };
}
 
// Create the context with a default undefined value
const FormContext = createContext<FormContextValue<Record<string, unknown>> | undefined>(undefined);
 
/**
 * Form Provider Props
 */
interface FormProviderProps<T extends Record<string, unknown>> {
  value: FormContextValue<T>;
  children: ReactNode;
}
 
/**
 * Form Provider Component
 *
 * Wraps form components to provide form state and validation context.
 */
export function FormProvider<T extends Record<string, unknown>>({
  value,
  children
}: FormProviderProps<T>) {
  return (
    <FormContext.Provider value={value as FormContextValue<Record<string, unknown>>}>
      {children}
    </FormContext.Provider>
  );
}
 
/**
 * Hook to access form context
 *
 * @throws Error if used outside of FormProvider
 */
export function useFormContext<T extends Record<string, unknown>>(): FormContextValue<T> {
  const context = useContext(FormContext);
  if (!context) {
    throw new Error('useFormContext must be used within a FormProvider');
  }
  return context as FormContextValue<T>;
}
 
/**
 * Type helper for creating form schemas
 */
export type FormSchema<T> = z.ZodType<T>;
 
/**
 * Extract form values type from a Zod schema
 */
export type FormValues<T extends z.ZodTypeAny> = z.infer<T>;