Selected: {watchedValues.filter(Boolean).length} of {watchedValues.length}
@@ -302,6 +269,7 @@ const MultipleCheckboxesForm = () => {
};
export const MultipleCheckboxes: Story = {
+ args: { name: 'selectAll', label: 'Select All' },
render: () =>
,
};
@@ -318,7 +286,7 @@ const CompleteFormExampleComponent = () => {
mode: 'onChange',
});
- const onSubmit = (data: any) => {
+ const onSubmit = (data: Record
) => {
alert(`Form submitted with data: ${JSON.stringify(data, null, 2)}`);
};
@@ -326,23 +294,29 @@ const CompleteFormExampleComponent = () => {
);
};
export const CompleteFormExample: Story = {
+ args: { name: 'exampleForm', label: 'Complete form example' },
render: () => ,
};
diff --git a/apps/docs/src/medusa-forms/ControlledDatePicker.stories.tsx b/apps/docs/src/medusa-forms/ControlledDatePicker.stories.tsx
index e027610..01b3bf6 100644
--- a/apps/docs/src/medusa-forms/ControlledDatePicker.stories.tsx
+++ b/apps/docs/src/medusa-forms/ControlledDatePicker.stories.tsx
@@ -49,7 +49,8 @@ const RequiredFieldValidationComponent = () => {
});
const onSubmit = (data: unknown) => {
- console.log('Form submitted:', data);
+ // Form data processed successfully
+ alert(`Form data submitted: ${JSON.stringify(data)}`);
};
return (
diff --git a/apps/docs/src/medusa-forms/ControlledSelect.stories.tsx b/apps/docs/src/medusa-forms/ControlledSelect.stories.tsx
index 6f0cd93..123154e 100644
--- a/apps/docs/src/medusa-forms/ControlledSelect.stories.tsx
+++ b/apps/docs/src/medusa-forms/ControlledSelect.stories.tsx
@@ -457,7 +457,7 @@ export const InteractiveDemo: Story = {
const handleSelectChange = (value: unknown) => {
setSelectedValue(value as string);
- console.log('Select value changed:', value);
+ // Value changed - could trigger side effects here
};
return (
diff --git a/apps/docs/src/medusa-forms/ControlledTextArea.stories.tsx b/apps/docs/src/medusa-forms/ControlledTextArea.stories.tsx
index a4282e2..e89f6f5 100644
--- a/apps/docs/src/medusa-forms/ControlledTextArea.stories.tsx
+++ b/apps/docs/src/medusa-forms/ControlledTextArea.stories.tsx
@@ -128,7 +128,8 @@ const RequiredFieldForm = () => {
});
const onSubmit = (data: unknown) => {
- console.log('Form submitted:', data);
+ // Form data processed successfully
+ alert(`Form data submitted: ${JSON.stringify(data)}`);
};
return (
@@ -242,7 +243,8 @@ const ValidationErrorForm = () => {
});
const onSubmit = (data: unknown) => {
- console.log('Form submitted:', data);
+ // Form data processed successfully
+ alert(`Form data submitted: ${JSON.stringify(data)}`);
};
const hasError = !!form.formState.errors.message;
@@ -331,7 +333,8 @@ const ComprehensiveForm = () => {
});
const onSubmit = (data: unknown) => {
- console.log('Comprehensive form submitted:', data);
+ // Form data processed successfully
+ alert(`Form data submitted: ${JSON.stringify(data)}`);
};
return (
diff --git a/apps/docs/src/medusa-forms/FormIntegrationExamples.stories.tsx b/apps/docs/src/medusa-forms/FormIntegrationExamples.stories.tsx
index f167bd4..82da8b4 100644
--- a/apps/docs/src/medusa-forms/FormIntegrationExamples.stories.tsx
+++ b/apps/docs/src/medusa-forms/FormIntegrationExamples.stories.tsx
@@ -9,6 +9,12 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
+// Regex patterns defined at top level for performance
+const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
+const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/;
+const SKU_REGEX = /^[A-Z0-9-]+$/;
+const DIMENSIONS_REGEX = /^\d+(\.\d+)?\s*x\s*\d+(\.\d+)?\s*x\s*\d+(\.\d+)?\s*(inches?|in|cm|centimeters?)?$/i;
+
const meta = {
title: 'Medusa Forms/Form Integration Examples',
component: () => null, // No single component
@@ -86,8 +92,8 @@ export const CompleteRegistrationFormExample: Story = {
await new Promise((resolve) => setTimeout(resolve, 2000));
setIsSubmitting(false);
- setSubmitResult(`Registration successful for ${data.firstName} ${data.lastName}!`);
- console.log('Registration data:', data);
+ setSubmitResult(`Registration successful! Welcome, ${data.firstName}!`);
+ // Registration data processed successfully
};
const countryOptions = [
@@ -142,7 +148,7 @@ export const CompleteRegistrationFormExample: Story = {
rules={{
required: 'Email is required',
pattern: {
- value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
+ value: EMAIL_REGEX,
message: 'Invalid email address',
},
}}
@@ -183,7 +189,7 @@ export const CompleteRegistrationFormExample: Story = {
required: 'Password is required',
minLength: { value: 8, message: 'Password must be at least 8 characters' },
pattern: {
- value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
+ value: PASSWORD_REGEX,
message:
'Password must contain at least one uppercase letter, one lowercase letter, and one number',
},
@@ -317,7 +323,7 @@ export const ProductCreationFormExample: Story = {
setIsSubmitting(false);
setSubmitResult(`Product "${data.name}" created successfully!`);
- console.log('Product data:', data);
+ // Product data processed successfully
};
const categoryOptions = [
@@ -380,7 +386,7 @@ export const ProductCreationFormExample: Story = {
rules={{
required: 'SKU is required',
pattern: {
- value: /^[A-Z0-9-]+$/,
+ value: SKU_REGEX,
message: 'SKU must contain only uppercase letters, numbers, and hyphens',
},
}}
@@ -429,7 +435,7 @@ export const ProductCreationFormExample: Story = {
rules={{
required: 'Dimensions are required',
pattern: {
- value: /^\d+(\.\d+)?\s*x\s*\d+(\.\d+)?\s*x\s*\d+(\.\d+)?\s*(inches?|in|cm|centimeters?)?$/i,
+ value: DIMENSIONS_REGEX,
message: 'Please enter dimensions in format: L x W x H (e.g., 10 x 8 x 6 inches)',
},
}}
@@ -590,7 +596,7 @@ export const FormValidationShowcase: Story = {
rules={{
required: 'Email is required',
pattern: {
- value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
+ value: EMAIL_REGEX,
message: 'Invalid email format',
},
}}
diff --git a/biome.json b/biome.json
index 058d760..7b15575 100644
--- a/biome.json
+++ b/biome.json
@@ -5,7 +5,10 @@
"clientKind": "git",
"useIgnoreFile": false
},
- "files": { "ignoreUnknown": false, "ignore": [".turbo", "yarn.lock"] },
+ "files": {
+ "ignoreUnknown": false,
+ "ignore": [".turbo", "yarn.lock", "./apps/docs/storybook-static", "./packages/medusa-forms/dist", "**/package.json"]
+ },
"organizeImports": { "enabled": true },
"formatter": {
"enabled": true,
@@ -55,5 +58,17 @@
"useImportExtensions": "off"
}
}
- }
+ },
+ "overrides": [
+ {
+ "include": ["./apps/docs/**/*"],
+ "linter": {
+ "rules": {
+ "correctness": {
+ "noUnusedFunctionParameters": "off"
+ }
+ }
+ }
+ }
+ ]
}
diff --git a/package.json b/package.json
index fceb7f2..2126c8c 100644
--- a/package.json
+++ b/package.json
@@ -2,10 +2,7 @@
"name": "medusa-forms",
"version": "0.1.1",
"private": true,
- "workspaces": [
- "apps/*",
- "packages/*"
- ],
+ "workspaces": ["apps/*", "packages/*"],
"scripts": {
"start": "yarn dev",
"dev": "turbo run dev",
diff --git a/packages/medusa-forms/package.json b/packages/medusa-forms/package.json
index 0a08a81..cc89ff4 100644
--- a/packages/medusa-forms/package.json
+++ b/packages/medusa-forms/package.json
@@ -4,9 +4,7 @@
"main": "./dist/cjs/index.cjs",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
- "files": [
- "dist"
- ],
+ "files": ["dist"],
"exports": {
".": {
"import": {
diff --git a/packages/medusa-forms/src/index.ts b/packages/medusa-forms/src/index.ts
index ca92d9f..588b8c0 100644
--- a/packages/medusa-forms/src/index.ts
+++ b/packages/medusa-forms/src/index.ts
@@ -1,2 +1 @@
export * from './controlled';
-
diff --git a/packages/medusa-forms/src/ui/FieldCheckbox.tsx b/packages/medusa-forms/src/ui/FieldCheckbox.tsx
index 37cc063..0331d3f 100644
--- a/packages/medusa-forms/src/ui/FieldCheckbox.tsx
+++ b/packages/medusa-forms/src/ui/FieldCheckbox.tsx
@@ -36,7 +36,10 @@ export const FieldCheckbox: React.FC = ({
{...fieldProps}
ref={ref}
checked={props.checked}
- onChange={(e) => {}}
+ onChange={(_e) => {
+ // this is a noop since we handle the onChange in the onCheckedChange handler,
+ // but we need to pass a function to the onChange prop because the MedusaCheckbox component expects it
+ }}
onCheckedChange={(checked) => {
onChange?.(checked);
}}
diff --git a/packages/medusa-forms/src/ui/FileUpload.tsx b/packages/medusa-forms/src/ui/FileUpload.tsx
index b4d88d3..e7f9919 100644
--- a/packages/medusa-forms/src/ui/FileUpload.tsx
+++ b/packages/medusa-forms/src/ui/FileUpload.tsx
@@ -39,32 +39,36 @@ const FileUpload: FC = ({
}
};
- const handleFileDrop = (e: DragEvent) => {
- setUploadError(false);
- e.preventDefault();
-
+ const extractFilesFromItems = (items: DataTransferItemList): File[] => {
const files: File[] = [];
-
- if (e.dataTransfer.items) {
- // use DataTransferItemList interface to access the file(s)
- for (let i = 0; i < e.dataTransfer.items.length; i += 1) {
- // if dropped items are not files, reject them
- if (e.dataTransfer.items[i].kind === 'file') {
- const file = e.dataTransfer.items[i].getAsFile();
-
- if (file && filetypes.indexOf(file.type) > -1) {
- files.push(file);
- }
+ for (const item of items) {
+ if (item.kind === 'file') {
+ const file = item.getAsFile();
+ if (file && filetypes.indexOf(file.type) > -1) {
+ files.push(file);
}
}
- } else {
- // use DataTransfer interface to access the file(s)
- for (let i = 0; i < e.dataTransfer.files.length; i += 1) {
- if (filetypes.indexOf(e.dataTransfer.files[i].type) > -1) {
- files.push(e.dataTransfer.files[i]);
- }
+ }
+ return files;
+ };
+
+ const extractFilesFromFileList = (fileList: FileList): File[] => {
+ const files: File[] = [];
+ for (const file of fileList) {
+ if (filetypes.indexOf(file.type) > -1) {
+ files.push(file);
}
}
+ return files;
+ };
+
+ const handleFileDrop = (e: DragEvent) => {
+ setUploadError(false);
+ e.preventDefault();
+
+ const files = e.dataTransfer.items
+ ? extractFilesFromItems(e.dataTransfer.items)
+ : extractFilesFromFileList(e.dataTransfer.files);
if (files.length === 1) {
onFileChosen(files);
diff --git a/packages/medusa-forms/src/ui/types.d.ts b/packages/medusa-forms/src/ui/types.d.ts
index cafd967..2ddd712 100644
--- a/packages/medusa-forms/src/ui/types.d.ts
+++ b/packages/medusa-forms/src/ui/types.d.ts
@@ -1,4 +1,7 @@
+import type { CalendarDate, CalendarDateTime, Granularity } from '@internationalized/date';
+import type { BaseDatePickerProps } from '@medusajs/ui';
import type { ReactNode, RefAttributes } from 'react';
+import type * as React from 'react';
import type { Props, SelectInstance } from 'react-select';
import type { CreatableProps } from 'react-select/creatable';
@@ -34,41 +37,16 @@ export type MedusaInputProps = React.InputHTMLAttributes & {
size?: 'small' | 'base';
};
-interface PickerProps extends CalendarProps {
- /**
- * The class name to apply on the date picker.
- */
- className?: string;
- /**
- * Whether the date picker's input is disabled.
- */
- disabled?: boolean;
- /**
- * Whether the date picker's input is required.
- */
- required?: boolean;
- /**
- * The date picker's placeholder.
- */
- placeholder?: string;
- /**
- * The date picker's size.
- */
- size?: 'small' | 'base';
- /**
- * Whether to show a time picker along with the date picker.
- */
- showTimePicker?: boolean;
- /**
- * Translation keys for the date picker. Use this to localize the date picker.
- */
- translations?: Translations;
- id?: string;
- 'aria-invalid'?: boolean;
- 'aria-label'?: string;
- 'aria-labelledby'?: string;
- 'aria-required'?: boolean;
-}
+type Option = {
+ label: string;
+ value: string;
+};
+
+type IsMulti = boolean;
+type Group = {
+ label: string;
+ options: Option[];
+};
type DatePickerValueProps = {
defaultValue?: Date | null;
@@ -83,7 +61,7 @@ type DatePickerValueProps = {
className?: string;
modal?: boolean;
};
-interface DatePickerProps
+export interface DatePickerProps
extends Omit, keyof DatePickerValueProps>,
DatePickerValueProps {}
@@ -111,7 +89,7 @@ export type SearchableSelectProps = Props