-
Notifications
You must be signed in to change notification settings - Fork 0
feat: medusa-forms #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
90db72d
f218347
4a054aa
d05ea74
3c4b798
b837ce5
a5c0926
d6819ba
2f4a280
9d8b33c
c2e0790
b446142
4cb0966
bd9ecb9
d23ebab
25359e0
248f6ae
871b81d
eb24bde
7f02d10
a00c074
a790cdc
67fe131
0d48c37
bf5c98b
cb26e17
8fa205b
2bc8ec3
47379bc
b5505b2
d003476
706a6ef
b132ab4
848207a
0cb684f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| --- | ||
| type: Auto Attached | ||
| description: Rules for Medusa Forms component development patterns using @medusajs/ui and react-hook-form | ||
| globs: ["packages/medusa-forms/**/*.{ts,tsx}", "apps/docs/src/medusa-forms/**/*.{ts,tsx}"] | ||
| --- | ||
|
|
||
| You are an expert in React Hook Form, @medusajs/ui components, and Medusa design system integration for the lambda-curry/forms repository. | ||
|
|
||
| # Medusa Forms Component Patterns | ||
|
|
||
| ## Core Architecture Principles | ||
| - Medusa Forms use **react-hook-form** directly (not remix-hook-form) | ||
| - All UI components are built on **@medusajs/ui** as the base design system | ||
| - Follow the **controlled/** and **ui/** directory separation pattern | ||
| - Use the **Controller** pattern for form integration | ||
| - Maintain **FieldWrapper** consistency for all form fields | ||
|
|
||
| ## Required Imports for Medusa Forms | ||
|
|
||
| ### For Controlled Components | ||
| ```typescript | ||
| import { | ||
| Controller, | ||
| type ControllerProps, | ||
| type FieldValues, | ||
| type Path, | ||
| type RegisterOptions, | ||
| useFormContext, | ||
| } from 'react-hook-form'; | ||
| import { ComponentName, type Props as ComponentNameProps } from '../ui/ComponentName'; | ||
| ``` | ||
|
|
||
| ### For UI Components | ||
| ```typescript | ||
| import { ComponentName as MedusaComponentName } from '@medusajs/ui'; | ||
| import type * as React from 'react'; | ||
| import { FieldWrapper } from './FieldWrapper'; | ||
| import type { BasicFieldProps, MedusaComponentNameProps } from './types'; | ||
| ``` | ||
|
|
||
| ## Directory Structure Convention | ||
| ``` | ||
| packages/medusa-forms/src/ | ||
| ├── controlled/ # Form-aware wrapper components using Controller | ||
| │ ├── ControlledInput.tsx | ||
| │ ├── ControlledCheckbox.tsx | ||
| │ ├── ControlledSelect.tsx | ||
| │ └── index.ts | ||
| └── ui/ # Base UI components using @medusajs/ui | ||
| ├── Input.tsx | ||
| ├── FieldCheckbox.tsx | ||
| ├── Select.tsx | ||
| ├── FieldWrapper.tsx | ||
| └── types.d.ts | ||
| ``` | ||
|
|
||
| ## Controlled Component Pattern | ||
| All controlled components must follow this exact pattern: | ||
|
|
||
| ```typescript | ||
| type Props<T extends FieldValues> = ComponentNameProps & | ||
| Omit<ControllerProps, 'render'> & { | ||
| name: Path<T>; | ||
| rules?: Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>; | ||
| }; | ||
|
|
||
| export const ControlledComponentName = <T extends FieldValues>({ | ||
| name, | ||
| rules, | ||
| onChange, | ||
| ...props | ||
| }: Props<T>) => { | ||
| const { | ||
| control, | ||
| formState: { errors }, | ||
| } = useFormContext<T>(); | ||
|
|
||
| return ( | ||
| <Controller | ||
| control={control} | ||
| name={name} | ||
| rules={rules as Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>} | ||
| render={({ field }) => ( | ||
| <ComponentName | ||
| {...field} | ||
| {...props} | ||
| formErrors={errors} | ||
| onChange={(value) => { | ||
| if (onChange) onChange(value); | ||
| field.onChange(value); | ||
| }} | ||
| /> | ||
| )} | ||
| /> | ||
| ); | ||
| }; | ||
| ``` | ||
|
|
||
| ## UI Component Pattern | ||
| All UI components must use FieldWrapper and @medusajs/ui: | ||
|
|
||
| ```typescript | ||
| export type Props = MedusaComponentNameProps & | ||
| BasicFieldProps & { | ||
| ref?: React.Ref<HTMLInputElement>; // Adjust ref type based on component | ||
| }; | ||
|
|
||
| const Wrapper = FieldWrapper<Props>; | ||
|
|
||
| export const ComponentName: React.FC<Props> = ({ ref, ...props }) => ( | ||
|
Comment on lines
+103
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Prefer Because React reserves export type Props = MedusaComponentNameProps & BasicFieldProps & { ref?: React.Ref<HTMLInputElement> }will not work if the consumer writes Refactor the UI-component pattern to use - export const ComponentName: React.FC<Props> = ({ ref, ...props }) => (
+ export const ComponentName = React.forwardRef<HTMLInputElement, Props>(({ ...props }, ref) => (
<Wrapper {...props}>
{(inputProps) => <MedusaComponentName {...inputProps} ref={ref} />}
</Wrapper>
- );
+ });This keeps authoring ergonomics intact and aligns with React conventions. 🤖 Prompt for AI Agents |
||
| <Wrapper {...props}> | ||
| {(inputProps) => <MedusaComponentName {...inputProps} ref={ref} />} | ||
| </Wrapper> | ||
| ); | ||
| ``` | ||
|
|
||
| ## FieldWrapper Integration | ||
| - **Always** use FieldWrapper for consistent label, error, and styling patterns | ||
| - Pass `formErrors` prop to enable automatic error display | ||
| - Use `labelClassName`, `wrapperClassName`, `errorClassName` for styling customization | ||
|
|
||
| ```typescript | ||
| <FieldWrapper<ComponentProps> | ||
| wrapperClassName={wrapperClassName} | ||
| errorClassName={errorClassName} | ||
| formErrors={formErrors} | ||
| {...props} | ||
| > | ||
| {(fieldProps) => ( | ||
| <MedusaComponent {...fieldProps} ref={ref} /> | ||
| )} | ||
| </FieldWrapper> | ||
| ``` | ||
|
|
||
| ## @medusajs/ui Component Integration | ||
|
|
||
| ### Input Components | ||
| ```typescript | ||
| import { Input as MedusaInput } from '@medusajs/ui'; | ||
| // Use with FieldWrapper pattern | ||
| ``` | ||
|
|
||
| ### Checkbox Components | ||
| ```typescript | ||
| import { Checkbox as MedusaCheckbox } from '@medusajs/ui'; | ||
| // Special handling for checked state and onCheckedChange | ||
| ``` | ||
|
|
||
| ### Select Components | ||
| ```typescript | ||
| import { Select as MedusaSelect } from '@medusajs/ui'; | ||
| // Compound component pattern with Trigger, Content, Item | ||
| ``` | ||
|
|
||
| ### Currency Input Components | ||
| ```typescript | ||
| import { CurrencyInput as MedusaCurrencyInput } from '@medusajs/ui'; | ||
| // Special props: symbol, code, currency | ||
| ``` | ||
|
|
||
| ### Date Picker Components | ||
| ```typescript | ||
| import { DatePicker } from '@medusajs/ui'; | ||
| // Special props: dateFormat, minDate, maxDate, filterDate | ||
| ``` | ||
|
|
||
| ## Type Safety Requirements | ||
| - Use generic types `<T extends FieldValues>` for all controlled components | ||
| - Properly type `Path<T>` for name props | ||
| - Extend `BasicFieldProps` for all UI components | ||
| - Use proper ref types based on underlying HTML element | ||
|
|
||
| ## Error Handling Pattern | ||
| ```typescript | ||
| // In controlled components | ||
| const { | ||
| control, | ||
| formState: { errors }, | ||
| } = useFormContext<T>(); | ||
|
|
||
| // Pass errors to UI component | ||
| <ComponentName | ||
| {...field} | ||
| {...props} | ||
| formErrors={errors} | ||
| /> | ||
| ``` | ||
|
|
||
| ## Validation Integration | ||
| - Use `rules` prop for react-hook-form validation | ||
| - Support both built-in and custom validation rules | ||
| - Ensure error messages are user-friendly and specific | ||
|
|
||
| ```typescript | ||
| <ControlledInput | ||
| name="email" | ||
| label="Email Address" | ||
| rules={{ | ||
| required: 'Email is required', | ||
| pattern: { | ||
| value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, | ||
| message: 'Invalid email address' | ||
| } | ||
| }} | ||
| /> | ||
| ``` | ||
|
|
||
| ## Accessibility Requirements | ||
| - All form fields must have proper labels via FieldWrapper | ||
| - Use ARIA attributes provided by @medusajs/ui components | ||
| - Ensure keyboard navigation works correctly | ||
| - Provide clear error announcements for screen readers | ||
|
|
||
| ## Component Naming Conventions | ||
| - Controlled components: `ControlledComponentName` (e.g., `ControlledInput`, `ControlledCheckbox`) | ||
| - UI components: `ComponentName` (e.g., `Input`, `FieldCheckbox`) | ||
| - Props interfaces: `ComponentNameProps` | ||
| - File names: PascalCase matching component name | ||
|
|
||
| ## Export Requirements | ||
| Always export both the component and its props type: | ||
| ```typescript | ||
| export { ControlledComponentName }; | ||
| export type { Props as ControlledComponentNameProps }; | ||
| ``` | ||
|
|
||
| ## Performance Considerations | ||
| - Use React.memo for expensive form components when needed | ||
| - Avoid unnecessary re-renders by properly structuring form state | ||
| - Consider field-level subscriptions for large forms | ||
|
|
||
| ## Testing Integration | ||
| - Components should work with existing Storybook patterns | ||
| - Test both valid and invalid form states | ||
| - Verify @medusajs/ui component integration | ||
| - Test component composition and customization | ||
|
|
||
| ## Common Patterns to Avoid | ||
| - **Don't** use remix-hook-form patterns (use react-hook-form directly) | ||
| - **Don't** create custom UI components when @medusajs/ui equivalents exist | ||
| - **Don't** bypass FieldWrapper for form fields | ||
| - **Don't** mix controlled and uncontrolled patterns | ||
| - **Don't** forget to handle both onChange and field.onChange in controlled components | ||
|
|
||
| ## Medusa Design System Compliance | ||
| - Follow Medusa UI spacing and sizing conventions | ||
| - Use Medusa color tokens and design patterns | ||
| - Ensure components work with Medusa themes | ||
| - Maintain consistency with Medusa component APIs | ||
|
|
||
| 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. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing generics to
ControllerPropsfor full type-safetyControllerPropsis generic (ControllerProps<TFieldValues, TName>).Omitting the generics forces
anyinside the library and silently drops a lot of compile-time guarantees that the rest of the pattern is trying to enforce.This keeps the strong correlation between the form’s
FieldValuesand the controlled field name.📝 Committable suggestion
🤖 Prompt for AI Agents