diff --git a/.cursorrules/lambda-curry-forms.mdc b/.cursorrules/lambda-curry-forms.mdc new file mode 100644 index 0000000..327326c --- /dev/null +++ b/.cursorrules/lambda-curry-forms.mdc @@ -0,0 +1,273 @@ +--- +description: Guidelines for using @lambdacurry/forms library with Remix Hook Form and Zod validation +globs: apps/**/app/**/*.tsx, apps/**/app/**/*.ts, packages/**/*.tsx, packages/**/*.ts +alwaysApply: true +--- + +**Summary:** +This document provides essential guidelines for using `@lambdacurry/forms` with `remix-hook-form` and Zod v4. Key benefits include progressive enhancement, type safety, consistent UI, and WCAG 2.1 AA accessibility compliance. Always prefer `remix-hook-form` over other form libraries in Remix applications. + +**Reference Documentation:** https://raw.githubusercontent.com/lambda-curry/forms/refs/heads/main/llms.txt + +## Core Architecture & Setup + +**@lambdacurry/forms** provides form-aware wrapper components that automatically integrate with React Router and Remix Hook Form context, eliminating boilerplate while maintaining full customization. + +### Essential Imports & Setup +```typescript +import { zodResolver } from '@hookform/resolvers/zod'; +import { RemixFormProvider, useRemixForm, getValidatedFormData } from 'remix-hook-form'; +import { z } from 'zod'; +import { useFetcher, type ActionFunctionArgs } from 'react-router'; +import { TextField, Checkbox, RadioGroup, DatePicker, FormError } from '@lambdacurry/forms'; +import { Button } from '@lambdacurry/forms/ui'; +``` + +### Zod Schema Patterns +```typescript +const formSchema = z.object({ + email: z.string().email('Invalid email address'), + password: z.string().min(8, 'Password must be at least 8 characters'), + terms: z.boolean().refine(val => val === true, 'You must accept terms'), +}); + +type FormData = z.infer; +``` + +## Standard Form Implementation Pattern + +### Complete Login Form Example with FormError +```typescript +const LoginForm = () => { + const fetcher = useFetcher<{ + message?: string; + errors?: Record + }>(); + + const methods = useRemixForm({ + resolver: zodResolver(formSchema), + defaultValues: { email: '', password: '' }, + fetcher, + submitConfig: { action: '/login', method: 'post' }, + }); + + const isSubmitting = fetcher.state === 'submitting'; + + return ( + + + + + + + {/* Place FormError before the submit button for critical errors */} + + + + + + ); +}; +``` + +### Server Action Handler with FormError Support +```typescript +export const action = async ({ request }: ActionFunctionArgs) => { + const { data, errors } = await getValidatedFormData( + request, + zodResolver(formSchema) + ); + + if (errors) return { errors }; + + try { + const user = await authenticateUser(data.email, data.password); + return { message: 'Login successful!' }; + } catch (error) { + // Multiple error types - field-level and form-level + return { + errors: { + email: { message: 'Account may be suspended' }, + _form: { message: 'Invalid credentials. Please try again.' } + } + }; + } +}; +``` + +## Advanced Patterns + +### Conditional Fields +```typescript +const watchAccountType = methods.watch('accountType'); + +{watchAccountType === 'business' && ( + +)} +``` + +## Available Form Components + +### TextField Component +```typescript + +``` + +### Checkbox Component +```typescript + +``` + +### RadioGroup Component +```typescript +// Pattern 1: Using options prop + + +// Pattern 2: Using RadioGroupItem children + + + + +``` + +### Other Components +```typescript +