Skip to content

Commit 8a9be4a

Browse files
authored
Merge pull request #72 from lambda-curry/codegen-bot/cursor-rules-implementation-1748117239
2 parents 19e5643 + 91c9ed5 commit 8a9be4a

4 files changed

Lines changed: 1263 additions & 0 deletions

File tree

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
---
2+
type: Always
3+
description: Rules for form component integration patterns in the lambda-curry/forms repository
4+
---
5+
6+
You are an expert in React Hook Form, Remix Hook Form, Zod validation, and form component architecture for the lambda-curry/forms monorepo.
7+
8+
# Form Component Integration Patterns
9+
10+
## Core Principles
11+
- All form components must integrate seamlessly with Remix Hook Form
12+
- Use Zod schemas for validation with proper TypeScript inference
13+
- Follow the wrapper pattern for consistent component composition
14+
- Maintain separation between UI components and form-aware components
15+
- Ensure proper error handling and validation feedback
16+
17+
## Required Imports for Form Components
18+
```typescript
19+
// Remix Hook Form integration
20+
import { useRemixFormContext } from 'remix-hook-form';
21+
import { zodResolver } from '@hookform/resolvers/zod';
22+
import { z } from 'zod';
23+
24+
// Form components
25+
import { FormControl, FormDescription, FormLabel, FormMessage } from './form';
26+
27+
// Base UI components
28+
import { ComponentName as BaseComponentName } from '../ui/component-name';
29+
```
30+
31+
## Form Schema Pattern
32+
Always define Zod schemas with proper error messages:
33+
```typescript
34+
const formSchema = z.object({
35+
fieldName: z.string().min(1, 'Field is required'),
36+
email: z.string().email('Invalid email address'),
37+
price: z.string().min(1, 'Price is required'),
38+
});
39+
40+
type FormData = z.infer<typeof formSchema>;
41+
```
42+
43+
## Wrapper Component Pattern
44+
Follow this pattern for all form-aware components:
45+
```typescript
46+
export type ComponentNameProps = Omit<BaseComponentNameProps, 'control'>;
47+
48+
export function ComponentName(props: ComponentNameProps) {
49+
const { control } = useRemixFormContext();
50+
51+
// Merge provided components with default form components
52+
const defaultComponents = {
53+
FormControl,
54+
FormLabel,
55+
FormDescription,
56+
FormMessage,
57+
};
58+
59+
const components = {
60+
...defaultComponents,
61+
...props.components,
62+
};
63+
64+
return <BaseComponentName control={control} components={components} {...props} />;
65+
}
66+
```
67+
68+
## Component Composition Pattern
69+
For UI components that accept form integration:
70+
```typescript
71+
export interface ComponentNameProps extends Omit<InputProps, 'prefix' | 'suffix'> {
72+
control?: Control<FieldValues>;
73+
name: FieldPath<FieldValues>;
74+
label?: string;
75+
description?: string;
76+
components?: Partial<FieldComponents> & {
77+
Input?: React.ComponentType<InputProps>;
78+
};
79+
className?: string;
80+
}
81+
82+
export const ComponentName = ({
83+
control,
84+
name,
85+
label,
86+
description,
87+
className,
88+
components,
89+
...props
90+
}: ComponentNameProps) => {
91+
const InputComponent = components?.Input || DefaultInput;
92+
93+
return (
94+
<FormField
95+
control={control}
96+
name={name}
97+
render={({ field, fieldState }) => (
98+
<FormItem className={className}>
99+
{label && <FormLabel Component={components?.FormLabel}>{label}</FormLabel>}
100+
<FormControl Component={components?.FormControl}>
101+
<InputComponent {...field} {...props} />
102+
</FormControl>
103+
{description && <FormDescription Component={components?.FormDescription}>{description}</FormDescription>}
104+
{fieldState.error && (
105+
<FormMessage Component={components?.FormMessage}>{fieldState.error.message}</FormMessage>
106+
)}
107+
</FormItem>
108+
)}
109+
/>
110+
);
111+
};
112+
```
113+
114+
## Form Setup Pattern
115+
Use this pattern for form initialization:
116+
```typescript
117+
const ControlledComponentExample = () => {
118+
const fetcher = useFetcher<{ message: string }>();
119+
const methods = useRemixForm<FormData>({
120+
resolver: zodResolver(formSchema),
121+
defaultValues: {
122+
// Provide sensible defaults
123+
},
124+
fetcher,
125+
submitConfig: { action: '/', method: 'post' },
126+
});
127+
128+
return (
129+
<RemixFormProvider {...methods}>
130+
<fetcher.Form onSubmit={methods.handleSubmit}>
131+
{/* Form components */}
132+
</fetcher.Form>
133+
</RemixFormProvider>
134+
);
135+
};
136+
```
137+
138+
## Validation Patterns
139+
140+
### Client-Side Validation
141+
- Use Zod schemas for all form validation
142+
- Provide clear, user-friendly error messages
143+
- Validate on blur and submit, not on every keystroke
144+
145+
### Server-Side Validation
146+
```typescript
147+
export const action = async ({ request }: ActionFunctionArgs) => {
148+
const { data, errors } = await getValidatedFormData<FormData>(
149+
request,
150+
zodResolver(formSchema)
151+
);
152+
153+
if (errors) return { errors };
154+
155+
// Additional server-side validation
156+
if (data.username === 'taken') {
157+
return {
158+
errors: {
159+
username: { message: 'Username is already taken' }
160+
}
161+
};
162+
}
163+
164+
return { message: 'Success!' };
165+
};
166+
```
167+
168+
## Error Handling Best Practices
169+
- Always display field-level errors using FormMessage
170+
- Handle both client-side and server-side validation errors
171+
- Provide loading states during form submission
172+
- Clear errors appropriately when fields are corrected
173+
174+
## Component Naming Conventions
175+
- Form-aware components: `ComponentName` (e.g., `TextField`, `Checkbox`)
176+
- Base UI components: `ComponentName` in ui/ directory
177+
- Props interfaces: `ComponentNameProps`
178+
- Form schemas: `formSchema` or `componentNameSchema`
179+
180+
## File Organization
181+
```
182+
packages/components/src/
183+
├── remix-hook-form/ # Form-aware wrapper components
184+
│ ├── text-field.tsx
185+
│ ├── checkbox.tsx
186+
│ └── index.ts
187+
└── ui/ # Base UI components
188+
├── text-field.tsx
189+
├── checkbox.tsx
190+
└── index.ts
191+
```
192+
193+
## Required Exports
194+
Always export both the component and its props type:
195+
```typescript
196+
export { ComponentName };
197+
export type { ComponentNameProps };
198+
```
199+
200+
## Performance Considerations
201+
- Use React.memo for expensive form components
202+
- Avoid unnecessary re-renders by properly structuring form state
203+
- Consider field-level subscriptions for large forms
204+
205+
## Accessibility Requirements
206+
- All form fields must have proper labels
207+
- Use ARIA attributes for complex form interactions
208+
- Ensure keyboard navigation works correctly
209+
- Provide clear error announcements for screen readers
210+
211+
## Testing Integration
212+
- Form components should work with the existing Storybook testing patterns
213+
- Test both valid and invalid form states
214+
- Verify server-side validation integration
215+
- Test component composition and customization
216+
217+
Remember: Form components are the core of this library. Every form component should be intuitive, accessible, and integrate seamlessly with the Remix Hook Form + Zod validation pattern.
218+

0 commit comments

Comments
 (0)