|
| 1 | +--- |
| 2 | +type: Auto Attached |
| 3 | +description: Rules for Medusa Forms component development patterns using @medusajs/ui and react-hook-form |
| 4 | +globs: ["packages/medusa-forms/**/*.{ts,tsx}", "apps/docs/src/medusa-forms/**/*.{ts,tsx}"] |
| 5 | +--- |
| 6 | + |
| 7 | +You are an expert in React Hook Form, @medusajs/ui components, and Medusa design system integration for the lambda-curry/forms repository. |
| 8 | + |
| 9 | +# Medusa Forms Component Patterns |
| 10 | + |
| 11 | +## Core Architecture Principles |
| 12 | +- Medusa Forms use **react-hook-form** directly (not remix-hook-form) |
| 13 | +- All UI components are built on **@medusajs/ui** as the base design system |
| 14 | +- Follow the **controlled/** and **ui/** directory separation pattern |
| 15 | +- Use the **Controller** pattern for form integration |
| 16 | +- Maintain **FieldWrapper** consistency for all form fields |
| 17 | + |
| 18 | +## Required Imports for Medusa Forms |
| 19 | + |
| 20 | +### For Controlled Components |
| 21 | +```typescript |
| 22 | +import { |
| 23 | + Controller, |
| 24 | + type ControllerProps, |
| 25 | + type FieldValues, |
| 26 | + type Path, |
| 27 | + type RegisterOptions, |
| 28 | + useFormContext, |
| 29 | +} from 'react-hook-form'; |
| 30 | +import { ComponentName, type Props as ComponentNameProps } from '../ui/ComponentName'; |
| 31 | +``` |
| 32 | + |
| 33 | +### For UI Components |
| 34 | +```typescript |
| 35 | +import { ComponentName as MedusaComponentName } from '@medusajs/ui'; |
| 36 | +import type * as React from 'react'; |
| 37 | +import { FieldWrapper } from './FieldWrapper'; |
| 38 | +import type { BasicFieldProps, MedusaComponentNameProps } from './types'; |
| 39 | +``` |
| 40 | + |
| 41 | +## Directory Structure Convention |
| 42 | +``` |
| 43 | +packages/medusa-forms/src/ |
| 44 | +├── controlled/ # Form-aware wrapper components using Controller |
| 45 | +│ ├── ControlledInput.tsx |
| 46 | +│ ├── ControlledCheckbox.tsx |
| 47 | +│ ├── ControlledSelect.tsx |
| 48 | +│ └── index.ts |
| 49 | +└── ui/ # Base UI components using @medusajs/ui |
| 50 | + ├── Input.tsx |
| 51 | + ├── FieldCheckbox.tsx |
| 52 | + ├── Select.tsx |
| 53 | + ├── FieldWrapper.tsx |
| 54 | + └── types.d.ts |
| 55 | +``` |
| 56 | + |
| 57 | +## Controlled Component Pattern |
| 58 | +All controlled components must follow this exact pattern: |
| 59 | + |
| 60 | +```typescript |
| 61 | +type Props<T extends FieldValues> = ComponentNameProps & |
| 62 | + Omit<ControllerProps, 'render'> & { |
| 63 | + name: Path<T>; |
| 64 | + rules?: Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>; |
| 65 | + }; |
| 66 | + |
| 67 | +export const ControlledComponentName = <T extends FieldValues>({ |
| 68 | + name, |
| 69 | + rules, |
| 70 | + onChange, |
| 71 | + ...props |
| 72 | +}: Props<T>) => { |
| 73 | + const { |
| 74 | + control, |
| 75 | + formState: { errors }, |
| 76 | + } = useFormContext<T>(); |
| 77 | + |
| 78 | + return ( |
| 79 | + <Controller |
| 80 | + control={control} |
| 81 | + name={name} |
| 82 | + rules={rules as Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>} |
| 83 | + render={({ field }) => ( |
| 84 | + <ComponentName |
| 85 | + {...field} |
| 86 | + {...props} |
| 87 | + formErrors={errors} |
| 88 | + onChange={(value) => { |
| 89 | + if (onChange) onChange(value); |
| 90 | + field.onChange(value); |
| 91 | + }} |
| 92 | + /> |
| 93 | + )} |
| 94 | + /> |
| 95 | + ); |
| 96 | +}; |
| 97 | +``` |
| 98 | + |
| 99 | +## UI Component Pattern |
| 100 | +All UI components must use FieldWrapper and @medusajs/ui: |
| 101 | + |
| 102 | +```typescript |
| 103 | +export type Props = MedusaComponentNameProps & |
| 104 | + BasicFieldProps & { |
| 105 | + ref?: React.Ref<HTMLInputElement>; // Adjust ref type based on component |
| 106 | + }; |
| 107 | + |
| 108 | +const Wrapper = FieldWrapper<Props>; |
| 109 | + |
| 110 | +export const ComponentName: React.FC<Props> = ({ ref, ...props }) => ( |
| 111 | + <Wrapper {...props}> |
| 112 | + {(inputProps) => <MedusaComponentName {...inputProps} ref={ref} />} |
| 113 | + </Wrapper> |
| 114 | +); |
| 115 | +``` |
| 116 | + |
| 117 | +## FieldWrapper Integration |
| 118 | +- **Always** use FieldWrapper for consistent label, error, and styling patterns |
| 119 | +- Pass `formErrors` prop to enable automatic error display |
| 120 | +- Use `labelClassName`, `wrapperClassName`, `errorClassName` for styling customization |
| 121 | + |
| 122 | +```typescript |
| 123 | +<FieldWrapper<ComponentProps> |
| 124 | + wrapperClassName={wrapperClassName} |
| 125 | + errorClassName={errorClassName} |
| 126 | + formErrors={formErrors} |
| 127 | + {...props} |
| 128 | +> |
| 129 | + {(fieldProps) => ( |
| 130 | + <MedusaComponent {...fieldProps} ref={ref} /> |
| 131 | + )} |
| 132 | +</FieldWrapper> |
| 133 | +``` |
| 134 | + |
| 135 | +## @medusajs/ui Component Integration |
| 136 | + |
| 137 | +### Input Components |
| 138 | +```typescript |
| 139 | +import { Input as MedusaInput } from '@medusajs/ui'; |
| 140 | +// Use with FieldWrapper pattern |
| 141 | +``` |
| 142 | + |
| 143 | +### Checkbox Components |
| 144 | +```typescript |
| 145 | +import { Checkbox as MedusaCheckbox } from '@medusajs/ui'; |
| 146 | +// Special handling for checked state and onCheckedChange |
| 147 | +``` |
| 148 | + |
| 149 | +### Select Components |
| 150 | +```typescript |
| 151 | +import { Select as MedusaSelect } from '@medusajs/ui'; |
| 152 | +// Compound component pattern with Trigger, Content, Item |
| 153 | +``` |
| 154 | + |
| 155 | +### Currency Input Components |
| 156 | +```typescript |
| 157 | +import { CurrencyInput as MedusaCurrencyInput } from '@medusajs/ui'; |
| 158 | +// Special props: symbol, code, currency |
| 159 | +``` |
| 160 | + |
| 161 | +### Date Picker Components |
| 162 | +```typescript |
| 163 | +import { DatePicker } from '@medusajs/ui'; |
| 164 | +// Special props: dateFormat, minDate, maxDate, filterDate |
| 165 | +``` |
| 166 | + |
| 167 | +## Type Safety Requirements |
| 168 | +- Use generic types `<T extends FieldValues>` for all controlled components |
| 169 | +- Properly type `Path<T>` for name props |
| 170 | +- Extend `BasicFieldProps` for all UI components |
| 171 | +- Use proper ref types based on underlying HTML element |
| 172 | + |
| 173 | +## Error Handling Pattern |
| 174 | +```typescript |
| 175 | +// In controlled components |
| 176 | +const { |
| 177 | + control, |
| 178 | + formState: { errors }, |
| 179 | +} = useFormContext<T>(); |
| 180 | + |
| 181 | +// Pass errors to UI component |
| 182 | +<ComponentName |
| 183 | + {...field} |
| 184 | + {...props} |
| 185 | + formErrors={errors} |
| 186 | +/> |
| 187 | +``` |
| 188 | + |
| 189 | +## Validation Integration |
| 190 | +- Use `rules` prop for react-hook-form validation |
| 191 | +- Support both built-in and custom validation rules |
| 192 | +- Ensure error messages are user-friendly and specific |
| 193 | + |
| 194 | +```typescript |
| 195 | +<ControlledInput |
| 196 | + name="email" |
| 197 | + label="Email Address" |
| 198 | + rules={{ |
| 199 | + required: 'Email is required', |
| 200 | + pattern: { |
| 201 | + value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, |
| 202 | + message: 'Invalid email address' |
| 203 | + } |
| 204 | + }} |
| 205 | +/> |
| 206 | +``` |
| 207 | + |
| 208 | +## Accessibility Requirements |
| 209 | +- All form fields must have proper labels via FieldWrapper |
| 210 | +- Use ARIA attributes provided by @medusajs/ui components |
| 211 | +- Ensure keyboard navigation works correctly |
| 212 | +- Provide clear error announcements for screen readers |
| 213 | + |
| 214 | +## Component Naming Conventions |
| 215 | +- Controlled components: `ControlledComponentName` (e.g., `ControlledInput`, `ControlledCheckbox`) |
| 216 | +- UI components: `ComponentName` (e.g., `Input`, `FieldCheckbox`) |
| 217 | +- Props interfaces: `ComponentNameProps` |
| 218 | +- File names: PascalCase matching component name |
| 219 | + |
| 220 | +## Export Requirements |
| 221 | +Always export both the component and its props type: |
| 222 | +```typescript |
| 223 | +export { ControlledComponentName }; |
| 224 | +export type { Props as ControlledComponentNameProps }; |
| 225 | +``` |
| 226 | + |
| 227 | +## Performance Considerations |
| 228 | +- Use React.memo for expensive form components when needed |
| 229 | +- Avoid unnecessary re-renders by properly structuring form state |
| 230 | +- Consider field-level subscriptions for large forms |
| 231 | + |
| 232 | +## Testing Integration |
| 233 | +- Components should work with existing Storybook patterns |
| 234 | +- Test both valid and invalid form states |
| 235 | +- Verify @medusajs/ui component integration |
| 236 | +- Test component composition and customization |
| 237 | + |
| 238 | +## Common Patterns to Avoid |
| 239 | +- **Don't** use remix-hook-form patterns (use react-hook-form directly) |
| 240 | +- **Don't** create custom UI components when @medusajs/ui equivalents exist |
| 241 | +- **Don't** bypass FieldWrapper for form fields |
| 242 | +- **Don't** mix controlled and uncontrolled patterns |
| 243 | +- **Don't** forget to handle both onChange and field.onChange in controlled components |
| 244 | + |
| 245 | +## Medusa Design System Compliance |
| 246 | +- Follow Medusa UI spacing and sizing conventions |
| 247 | +- Use Medusa color tokens and design patterns |
| 248 | +- Ensure components work with Medusa themes |
| 249 | +- Maintain consistency with Medusa component APIs |
| 250 | + |
| 251 | +Remember: Medusa Forms are specifically designed to integrate with the Medusa ecosystem. Always prioritize @medusajs/ui component usage and follow Medusa design system principles while maintaining the react-hook-form integration patterns. |
| 252 | + |
0 commit comments