Skip to content

Commit fb8e091

Browse files
refactor: nest shadcn/ui registry in packages/medusa-forms
- Move registry structure to packages/medusa-forms/ for better monorepo organization - Create shadcn-compatible versions of existing components in src/registry/ - Preserve original medusa-forms components in src/controlled/ (unchanged) - Update registry.json paths to reflect nested structure - Generate individual JSON registry files for each component - Add comprehensive documentation explaining the relationship between registry and original components - Registry components use @medusajs/ui directly while original components use internal wrappers
1 parent a9a2741 commit fb8e091

20 files changed

Lines changed: 156 additions & 137 deletions

REGISTRY.md renamed to packages/medusa-forms/REGISTRY.md

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# Medusa Forms shadcn/ui Registry
22

3-
This repository provides a custom shadcn/ui registry for medusa-forms components, allowing developers to install them using the native shadcn CLI.
3+
This package provides a custom shadcn/ui registry for medusa-forms components, allowing developers to install them using the native shadcn CLI.
44

55
## Installation
66

77
You can install individual medusa-forms components using the shadcn CLI:
88

99
```bash
1010
# Install individual components
11-
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/registry/controlled-input.json
12-
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/registry/controlled-select.json
13-
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/registry/controlled-checkbox.json
14-
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/registry/controlled-textarea.json
15-
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/registry/controlled-datepicker.json
16-
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/registry/controlled-currency-input.json
11+
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/packages/medusa-forms/registry/controlled-input.json
12+
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/packages/medusa-forms/registry/controlled-select.json
13+
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/packages/medusa-forms/registry/controlled-checkbox.json
14+
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/packages/medusa-forms/registry/controlled-textarea.json
15+
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/packages/medusa-forms/registry/controlled-datepicker.json
16+
npx shadcn@latest add https://raw.githubusercontent.com/lambda-curry/forms/main/packages/medusa-forms/registry/controlled-currency-input.json
1717
```
1818

1919
## Available Components
@@ -148,12 +148,13 @@ Before using these components, make sure you have:
148148
**TypeScript Support**: Built-in TypeScript compatibility
149149
**Zero Maintenance**: No CLI package to maintain
150150
**Instant Adoption**: Works with existing shadcn projects
151-
**v0 Compatible**: Components work with v0.dev
151+
**v0 Compatible**: Components work with v0.dev
152+
**Monorepo Structure**: Registry is properly nested within the medusa-forms package
152153

153154
## Registry Structure
154155

155156
```
156-
lambda-curry/forms/
157+
packages/medusa-forms/
157158
├── registry.json # Main registry configuration
158159
├── registry/ # Component registry files
159160
│ ├── controlled-input.json
@@ -162,20 +163,38 @@ lambda-curry/forms/
162163
│ ├── controlled-textarea.json
163164
│ ├── controlled-datepicker.json
164165
│ └── controlled-currency-input.json
165-
└── src/registry/ # Component source files
166-
├── controlled-input.tsx
167-
├── controlled-select.tsx
168-
├── controlled-checkbox.tsx
169-
├── controlled-textarea.tsx
170-
├── controlled-datepicker.tsx
171-
└── controlled-currency-input.tsx
166+
├── src/registry/ # Registry-compatible component source files
167+
│ ├── controlled-input.tsx
168+
│ ├── controlled-select.tsx
169+
│ ├── controlled-checkbox.tsx
170+
│ ├── controlled-textarea.tsx
171+
│ ├── controlled-datepicker.tsx
172+
│ └── controlled-currency-input.tsx
173+
└── src/controlled/ # Original medusa-forms components (unchanged)
174+
├── ControlledInput.tsx
175+
├── ControlledSelect.tsx
176+
├── ControlledCheckbox.tsx
177+
├── ControlledTextArea.tsx
178+
├── ControlledDatePicker.tsx
179+
└── ControlledCurrencyInput.tsx
172180
```
173181

182+
## Relationship to Existing Components
183+
184+
The registry components in `src/registry/` are **shadcn-compatible versions** of the existing medusa-forms components in `src/controlled/`. Key differences:
185+
186+
- **Registry components**: Use `@medusajs/ui` directly, include built-in labels and error handling, follow shadcn patterns
187+
- **Original components**: Use internal UI wrappers (`FieldWrapper`, etc.), part of the medusa-forms package API
188+
189+
Both sets of components serve different use cases:
190+
- Use **registry components** when installing via shadcn CLI into your project
191+
- Use **original components** when importing the `@lambdacurry/medusa-forms` package directly
192+
174193
## Contributing
175194

176195
To add new components to the registry:
177196

178-
1. Create the component in `src/registry/`
197+
1. Create the shadcn-compatible component in `src/registry/`
179198
2. Add it to the main `registry.json`
180199
3. Generate the individual JSON file in `registry/`
181200
4. Update this documentation
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"registryDependencies": [],
1414
"files": [
1515
{
16-
"path": "src/registry/controlled-input.tsx",
16+
"path": "packages/medusa-forms/src/registry/controlled-input.tsx",
1717
"type": "registry:component"
1818
}
1919
]
@@ -27,7 +27,7 @@
2727
"registryDependencies": [],
2828
"files": [
2929
{
30-
"path": "src/registry/controlled-select.tsx",
30+
"path": "packages/medusa-forms/src/registry/controlled-select.tsx",
3131
"type": "registry:component"
3232
}
3333
]
@@ -41,7 +41,7 @@
4141
"registryDependencies": [],
4242
"files": [
4343
{
44-
"path": "src/registry/controlled-checkbox.tsx",
44+
"path": "packages/medusa-forms/src/registry/controlled-checkbox.tsx",
4545
"type": "registry:component"
4646
}
4747
]
@@ -55,7 +55,7 @@
5555
"registryDependencies": [],
5656
"files": [
5757
{
58-
"path": "src/registry/controlled-textarea.tsx",
58+
"path": "packages/medusa-forms/src/registry/controlled-textarea.tsx",
5959
"type": "registry:component"
6060
}
6161
]
@@ -69,7 +69,7 @@
6969
"registryDependencies": [],
7070
"files": [
7171
{
72-
"path": "src/registry/controlled-datepicker.tsx",
72+
"path": "packages/medusa-forms/src/registry/controlled-datepicker.tsx",
7373
"type": "registry:component"
7474
}
7575
]
@@ -83,7 +83,7 @@
8383
"registryDependencies": [],
8484
"files": [
8585
{
86-
"path": "src/registry/controlled-currency-input.tsx",
86+
"path": "packages/medusa-forms/src/registry/controlled-currency-input.tsx",
8787
"type": "registry:component"
8888
}
8989
]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "controlled-checkbox",
3+
"type": "registry:ui",
4+
"description": "A checkbox component with react-hook-form integration",
5+
"dependencies": [
6+
"@medusajs/ui",
7+
"react-hook-form",
8+
"@hookform/error-message"
9+
],
10+
"files": [
11+
{
12+
"name": "controlled-checkbox.tsx",
13+
"content": "\"use client\"\n\nimport type { ComponentProps } from 'react'\nimport {\n Controller,\n type ControllerProps,\n type FieldValues,\n type Path,\n type RegisterOptions,\n useFormContext,\n} from 'react-hook-form'\nimport { Checkbox, Label } from '@medusajs/ui'\nimport { ErrorMessage } from '@hookform/error-message'\n\nexport type ControlledCheckboxProps<T extends FieldValues> = Omit<ControllerProps, 'render'> & {\n name: Path<T>\n rules?: Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>\n label?: string\n description?: string\n required?: boolean\n disabled?: boolean\n className?: string\n onChange?: (checked: boolean) => void\n} & ComponentProps<typeof Checkbox>\n\n/**\n * A controlled checkbox component that integrates with react-hook-form.\n * \n * @example\n * ```tsx\n * import { useForm, FormProvider } from 'react-hook-form'\n * import { ControlledCheckbox } from '@/components/ui/controlled-checkbox'\n * \n * function MyForm() {\n * const methods = useForm()\n * \n * return (\n * <FormProvider {...methods}>\n * <form onSubmit={methods.handleSubmit(onSubmit)}>\n * <ControlledCheckbox\n * name=\"terms\"\n * label=\"I agree to the terms and conditions\"\n * rules={{ required: 'You must agree to the terms' }}\n * />\n * </form>\n * </FormProvider>\n * )\n * }\n * ```\n */\nexport const ControlledCheckbox = <T extends FieldValues>({\n name,\n rules,\n onChange,\n label,\n description,\n required,\n ...props\n}: ControlledCheckboxProps<T>) => {\n const {\n control,\n formState: { errors },\n } = useFormContext<T>()\n\n return (\n <div className=\"space-y-2\">\n <Controller\n control={control}\n name={name}\n rules={rules as Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>}\n render={({ field }) => (\n <div className=\"flex items-start space-x-2\">\n <Checkbox\n {...field}\n {...props}\n id={name}\n checked={field.value}\n onCheckedChange={(checked) => {\n const booleanValue = checked === true\n if (onChange) {\n onChange(booleanValue)\n }\n field.onChange(booleanValue)\n }}\n />\n <div className=\"grid gap-1.5 leading-none\">\n {label && (\n <Label htmlFor={name}>\n {label}\n {required && <span className=\"text-red-500 ml-1\">*</span>}\n </Label>\n )}\n {description && (\n <p className=\"text-xs text-muted-foreground\">\n {description}\n </p>\n )}\n </div>\n </div>\n )}\n />\n <ErrorMessage\n errors={errors}\n name={name}\n render={({ message }) => (\n <p className=\"text-sm text-red-500\">{message}</p>\n )}\n />\n </div>\n )\n}\n\n"
14+
}
15+
]
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "controlled-currency-input",
3+
"type": "registry:ui",
4+
"description": "A currency-input component with react-hook-form integration",
5+
"dependencies": [
6+
"@medusajs/ui",
7+
"react-hook-form",
8+
"@hookform/error-message"
9+
],
10+
"files": [
11+
{
12+
"name": "controlled-currency-input.tsx",
13+
"content": "\"use client\"\n\nimport type { ComponentProps } from 'react'\nimport {\n Controller,\n type ControllerProps,\n type FieldValues,\n type Path,\n type RegisterOptions,\n useFormContext,\n} from 'react-hook-form'\nimport { CurrencyInput, Label } from '@medusajs/ui'\nimport { ErrorMessage } from '@hookform/error-message'\n\nexport type ControlledCurrencyInputProps<T extends FieldValues> = Omit<ControllerProps, 'render'> & {\n name: Path<T>\n rules?: Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>\n label?: string\n placeholder?: string\n required?: boolean\n disabled?: boolean\n className?: string\n currency?: string\n onChange?: (value: number | undefined) => void\n} & ComponentProps<typeof CurrencyInput>\n\n/**\n * A controlled currency input component that integrates with react-hook-form.\n * \n * @example\n * ```tsx\n * import { useForm, FormProvider } from 'react-hook-form'\n * import { ControlledCurrencyInput } from '@/components/ui/controlled-currency-input'\n * \n * function MyForm() {\n * const methods = useForm()\n * \n * return (\n * <FormProvider {...methods}>\n * <form onSubmit={methods.handleSubmit(onSubmit)}>\n * <ControlledCurrencyInput\n * name=\"price\"\n * label=\"Price\"\n * placeholder=\"Enter price\"\n * currency=\"USD\"\n * rules={{ required: 'Price is required' }}\n * />\n * </form>\n * </FormProvider>\n * )\n * }\n * ```\n */\nexport const ControlledCurrencyInput = <T extends FieldValues>({\n name,\n rules,\n onChange,\n label,\n required,\n ...props\n}: ControlledCurrencyInputProps<T>) => {\n const {\n control,\n formState: { errors },\n } = useFormContext<T>()\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <Label htmlFor={name}>\n {label}\n {required && <span className=\"text-red-500 ml-1\">*</span>}\n </Label>\n )}\n <Controller\n control={control}\n name={name}\n rules={rules as Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>}\n render={({ field }) => (\n <CurrencyInput\n {...field}\n {...props}\n id={name}\n value={field.value}\n onValueChange={(value) => {\n if (onChange) {\n onChange(value)\n }\n field.onChange(value)\n }}\n />\n )}\n />\n <ErrorMessage\n errors={errors}\n name={name}\n render={({ message }) => (\n <p className=\"text-sm text-red-500\">{message}</p>\n )}\n />\n </div>\n )\n}\n\n"
14+
}
15+
]
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "controlled-datepicker",
3+
"type": "registry:ui",
4+
"description": "A datepicker component with react-hook-form integration",
5+
"dependencies": [
6+
"@medusajs/ui",
7+
"react-hook-form",
8+
"@hookform/error-message"
9+
],
10+
"files": [
11+
{
12+
"name": "controlled-datepicker.tsx",
13+
"content": "\"use client\"\n\nimport type { ComponentProps } from 'react'\nimport {\n Controller,\n type ControllerProps,\n type FieldValues,\n type Path,\n type RegisterOptions,\n useFormContext,\n} from 'react-hook-form'\nimport { DatePicker, Label } from '@medusajs/ui'\nimport { ErrorMessage } from '@hookform/error-message'\n\nexport type ControlledDatePickerProps<T extends FieldValues> = Omit<ControllerProps, 'render'> & {\n name: Path<T>\n rules?: Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>\n label?: string\n placeholder?: string\n required?: boolean\n disabled?: boolean\n className?: string\n onChange?: (date: Date | undefined) => void\n} & ComponentProps<typeof DatePicker>\n\n/**\n * A controlled date picker component that integrates with react-hook-form.\n * \n * @example\n * ```tsx\n * import { useForm, FormProvider } from 'react-hook-form'\n * import { ControlledDatePicker } from '@/components/ui/controlled-datepicker'\n * \n * function MyForm() {\n * const methods = useForm()\n * \n * return (\n * <FormProvider {...methods}>\n * <form onSubmit={methods.handleSubmit(onSubmit)}>\n * <ControlledDatePicker\n * name=\"birthDate\"\n * label=\"Birth Date\"\n * placeholder=\"Select your birth date\"\n * rules={{ required: 'Birth date is required' }}\n * />\n * </form>\n * </FormProvider>\n * )\n * }\n * ```\n */\nexport const ControlledDatePicker = <T extends FieldValues>({\n name,\n rules,\n onChange,\n label,\n placeholder,\n required,\n ...props\n}: ControlledDatePickerProps<T>) => {\n const {\n control,\n formState: { errors },\n } = useFormContext<T>()\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <Label htmlFor={name}>\n {label}\n {required && <span className=\"text-red-500 ml-1\">*</span>}\n </Label>\n )}\n <Controller\n control={control}\n name={name}\n rules={rules as Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>}\n render={({ field }) => (\n <DatePicker\n {...field}\n {...props}\n value={field.value}\n onChange={(date) => {\n if (onChange) {\n onChange(date)\n }\n field.onChange(date)\n }}\n placeholder={placeholder}\n />\n )}\n />\n <ErrorMessage\n errors={errors}\n name={name}\n render={({ message }) => (\n <p className=\"text-sm text-red-500\">{message}</p>\n )}\n />\n </div>\n )\n}\n\n"
14+
}
15+
]
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "controlled-input",
3+
"type": "registry:ui",
4+
"description": "A input component with react-hook-form integration",
5+
"dependencies": [
6+
"@medusajs/ui",
7+
"react-hook-form",
8+
"@hookform/error-message"
9+
],
10+
"files": [
11+
{
12+
"name": "controlled-input.tsx",
13+
"content": "\"use client\"\n\nimport type { ComponentProps } from 'react'\nimport {\n Controller,\n type ControllerProps,\n type FieldValues,\n type Path,\n type RegisterOptions,\n useFormContext,\n} from 'react-hook-form'\nimport { Input, Label } from '@medusajs/ui'\nimport { ErrorMessage } from '@hookform/error-message'\n\nexport type ControlledInputProps<T extends FieldValues> = Omit<ControllerProps, 'render'> & {\n name: Path<T>\n rules?: Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>\n label?: string\n placeholder?: string\n required?: boolean\n disabled?: boolean\n className?: string\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void\n} & ComponentProps<typeof Input>\n\n/**\n * A controlled input component that integrates with react-hook-form.\n * \n * @example\n * ```tsx\n * import { useForm, FormProvider } from 'react-hook-form'\n * import { ControlledInput } from '@/components/ui/controlled-input'\n * \n * function MyForm() {\n * const methods = useForm()\n * \n * return (\n * <FormProvider {...methods}>\n * <form onSubmit={methods.handleSubmit(onSubmit)}>\n * <ControlledInput\n * name=\"email\"\n * label=\"Email\"\n * placeholder=\"Enter your email\"\n * rules={{ required: 'Email is required' }}\n * />\n * </form>\n * </FormProvider>\n * )\n * }\n * ```\n */\nexport const ControlledInput = <T extends FieldValues>({\n name,\n rules,\n onChange,\n label,\n required,\n ...props\n}: ControlledInputProps<T>) => {\n const {\n control,\n formState: { errors },\n } = useFormContext<T>()\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <Label htmlFor={name}>\n {label}\n {required && <span className=\"text-red-500 ml-1\">*</span>}\n </Label>\n )}\n <Controller\n control={control}\n name={name}\n rules={rules as Omit<RegisterOptions<T, Path<T>>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'>}\n render={({ field }) => (\n <Input\n {...field}\n {...props}\n id={name}\n onChange={(evt) => {\n if (onChange) {\n onChange(evt)\n }\n field.onChange(evt)\n }}\n />\n )}\n />\n <ErrorMessage\n errors={errors}\n name={name}\n render={({ message }) => (\n <p className=\"text-sm text-red-500\">{message}</p>\n )}\n />\n </div>\n )\n}\n\n"
14+
}
15+
]
16+
}

0 commit comments

Comments
 (0)