@mehdashti/forms 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,432 @@
1
+ # @mehdashti/forms
2
+
3
+ > Comprehensive form validation and management for Smart Platform
4
+
5
+ Built on React Hook Form + Zod with smart defaults and seamless integration with @mehdashti/ui components.
6
+
7
+ ## Features
8
+
9
+ - ✅ **React Hook Form + Zod** - Type-safe validation with automatic error handling
10
+ - ✅ **Real-time Validation** - Validate on change by default
11
+ - ✅ **Smart Defaults** - Sensible defaults based on APISmith patterns
12
+ - ✅ **Form Field Components** - Pre-built field components with error display
13
+ - ✅ **Field Arrays** - Dynamic form fields support
14
+ - ✅ **TypeScript First** - Full type safety with excellent IntelliSense
15
+ - ✅ **@mehdashti/ui Integration** - Works seamlessly with platform UI components
16
+ - ✅ **Lightweight** - Minimal bundle size with tree-shaking support
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add @mehdashti/forms react-hook-form zod @hookform/resolvers
22
+ ```
23
+
24
+ **Peer Dependencies:**
25
+ ```bash
26
+ pnpm add react react-dom @mehdashti/ui @mehdashti/contracts
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### Basic Form
32
+
33
+ ```typescript
34
+ import { useSmartForm, FormField, z } from '@mehdashti/forms'
35
+
36
+ const loginSchema = z.object({
37
+ email: z.string().email('Invalid email address'),
38
+ password: z.string().min(8, 'Password must be at least 8 characters'),
39
+ })
40
+
41
+ function LoginForm() {
42
+ const form = useSmartForm({
43
+ schema: loginSchema,
44
+ onSubmit: async (data) => {
45
+ await loginUser(data)
46
+ },
47
+ })
48
+
49
+ return (
50
+ <form onSubmit={form.handleSubmit}>
51
+ <FormField
52
+ name="email"
53
+ label="Email"
54
+ type="email"
55
+ required
56
+ control={form.control}
57
+ placeholder="Enter your email"
58
+ />
59
+
60
+ <FormField
61
+ name="password"
62
+ label="Password"
63
+ type="password"
64
+ required
65
+ control={form.control}
66
+ />
67
+
68
+ <button type="submit" disabled={form.isSubmitting}>
69
+ {form.isSubmitting ? 'Loading...' : 'Login'}
70
+ </button>
71
+ </form>
72
+ )
73
+ }
74
+ ```
75
+
76
+ ### Form with Select
77
+
78
+ ```typescript
79
+ import { useSmartForm, FormField, FormSelect, z } from '@mehdashti/forms'
80
+
81
+ const connectionSchema = z.object({
82
+ name: z.string().min(1, 'Name is required'),
83
+ type: z.enum(['oracle', 'mssql', 'postgresql']),
84
+ host: z.string().min(1, 'Host is required'),
85
+ port: z.number().min(1).max(65535),
86
+ })
87
+
88
+ function ConnectionForm() {
89
+ const form = useSmartForm({
90
+ schema: connectionSchema,
91
+ defaultValues: {
92
+ type: 'oracle',
93
+ port: 1521,
94
+ },
95
+ onSubmit: async (data) => {
96
+ await createConnection(data)
97
+ },
98
+ })
99
+
100
+ return (
101
+ <form onSubmit={form.handleSubmit}>
102
+ <FormField
103
+ name="name"
104
+ label="Connection Name"
105
+ required
106
+ control={form.control}
107
+ />
108
+
109
+ <FormSelect
110
+ name="type"
111
+ label="Database Type"
112
+ required
113
+ control={form.control}
114
+ options={[
115
+ { value: 'oracle', label: 'Oracle' },
116
+ { value: 'mssql', label: 'MS SQL Server' },
117
+ { value: 'postgresql', label: 'PostgreSQL' },
118
+ ]}
119
+ />
120
+
121
+ <FormField
122
+ name="host"
123
+ label="Host"
124
+ required
125
+ control={form.control}
126
+ placeholder="192.168.1.100"
127
+ />
128
+
129
+ <FormField
130
+ name="port"
131
+ label="Port"
132
+ type="number"
133
+ required
134
+ control={form.control}
135
+ />
136
+
137
+ <button type="submit">Save Connection</button>
138
+ </form>
139
+ )
140
+ }
141
+ ```
142
+
143
+ ### Dynamic Field Arrays
144
+
145
+ ```typescript
146
+ import { useSmartForm, useSmartFieldArray, FormField, z } from '@mehdashti/forms'
147
+
148
+ const orderSchema = z.object({
149
+ customerName: z.string().min(1),
150
+ items: z.array(
151
+ z.object({
152
+ name: z.string().min(1, 'Item name is required'),
153
+ quantity: z.number().min(1, 'Quantity must be at least 1'),
154
+ })
155
+ ),
156
+ })
157
+
158
+ function OrderForm() {
159
+ const form = useSmartForm({
160
+ schema: orderSchema,
161
+ defaultValues: {
162
+ items: [{ name: '', quantity: 1 }],
163
+ },
164
+ onSubmit: async (data) => {
165
+ await createOrder(data)
166
+ },
167
+ })
168
+
169
+ const { fields, append, remove } = useSmartFieldArray({
170
+ name: 'items',
171
+ control: form.control,
172
+ defaultValue: { name: '', quantity: 1 },
173
+ })
174
+
175
+ return (
176
+ <form onSubmit={form.handleSubmit}>
177
+ <FormField
178
+ name="customerName"
179
+ label="Customer Name"
180
+ required
181
+ control={form.control}
182
+ />
183
+
184
+ <div>
185
+ <h3>Order Items</h3>
186
+ {fields.map((field, index) => (
187
+ <div key={field.id} className="flex gap-2">
188
+ <FormField
189
+ name={`items.${index}.name`}
190
+ label="Item Name"
191
+ required
192
+ control={form.control}
193
+ />
194
+
195
+ <FormField
196
+ name={`items.${index}.quantity`}
197
+ label="Quantity"
198
+ type="number"
199
+ required
200
+ control={form.control}
201
+ />
202
+
203
+ <button type="button" onClick={() => remove(index)}>
204
+ Remove
205
+ </button>
206
+ </div>
207
+ ))}
208
+
209
+ <button type="button" onClick={() => append()}>
210
+ Add Item
211
+ </button>
212
+ </div>
213
+
214
+ <button type="submit">Create Order</button>
215
+ </form>
216
+ )
217
+ }
218
+ ```
219
+
220
+ ### Custom Validation
221
+
222
+ ```typescript
223
+ import { useSmartForm, FormField, z } from '@mehdashti/forms'
224
+
225
+ const registrationSchema = z
226
+ .object({
227
+ email: z.string().email(),
228
+ password: z.string().min(8),
229
+ confirmPassword: z.string(),
230
+ })
231
+ .refine((data) => data.password === data.confirmPassword, {
232
+ message: "Passwords don't match",
233
+ path: ['confirmPassword'],
234
+ })
235
+
236
+ function RegistrationForm() {
237
+ const form = useSmartForm({
238
+ schema: registrationSchema,
239
+ onSubmit: async (data) => {
240
+ await registerUser(data)
241
+ },
242
+ onError: (errors) => {
243
+ console.error('Validation errors:', errors)
244
+ },
245
+ })
246
+
247
+ return (
248
+ <form onSubmit={form.handleSubmit}>
249
+ <FormField
250
+ name="email"
251
+ label="Email"
252
+ type="email"
253
+ required
254
+ control={form.control}
255
+ />
256
+
257
+ <FormField
258
+ name="password"
259
+ label="Password"
260
+ type="password"
261
+ required
262
+ control={form.control}
263
+ helpText="Must be at least 8 characters"
264
+ />
265
+
266
+ <FormField
267
+ name="confirmPassword"
268
+ label="Confirm Password"
269
+ type="password"
270
+ required
271
+ control={form.control}
272
+ />
273
+
274
+ <button type="submit">Register</button>
275
+ </form>
276
+ )
277
+ }
278
+ ```
279
+
280
+ ## API Reference
281
+
282
+ ### `useSmartForm(options)`
283
+
284
+ Main hook for form management.
285
+
286
+ **Options:**
287
+
288
+ | Option | Type | Default | Description |
289
+ |--------|------|---------|-------------|
290
+ | `schema` | `ZodSchema` | required | Zod validation schema |
291
+ | `onSubmit` | `function` | required | Submit handler |
292
+ | `onError` | `function` | optional | Error handler |
293
+ | `defaultValues` | `object` | optional | Initial form values |
294
+ | `realtimeValidation` | `boolean` | `true` | Enable validation on change |
295
+
296
+ **Returns:**
297
+
298
+ | Property | Type | Description |
299
+ |----------|------|-------------|
300
+ | `handleSubmit` | `function` | Form submit handler |
301
+ | `isSubmitting` | `boolean` | Whether form is submitting |
302
+ | `hasErrors` | `boolean` | Whether form has validation errors |
303
+ | `getError` | `function` | Get error message for a field |
304
+ | `control` | `Control` | React Hook Form control |
305
+ | `watch` | `function` | Watch field values |
306
+ | `setValue` | `function` | Set field value |
307
+ | `reset` | `function` | Reset form |
308
+ | `...` | | All React Hook Form methods |
309
+
310
+ ### `<FormField>`
311
+
312
+ Smart form field component with automatic error handling.
313
+
314
+ **Props:**
315
+
316
+ | Prop | Type | Default | Description |
317
+ |------|------|---------|-------------|
318
+ | `name` | `string` | required | Field name |
319
+ | `label` | `string` | required | Field label |
320
+ | `type` | `string` | `'text'` | Input type |
321
+ | `required` | `boolean` | `false` | Show required indicator |
322
+ | `placeholder` | `string` | optional | Placeholder text |
323
+ | `helpText` | `string` | optional | Help text below field |
324
+ | `disabled` | `boolean` | `false` | Disable field |
325
+ | `control` | `Control` | required | Form control |
326
+ | `render` | `function` | optional | Custom render function |
327
+
328
+ ### `<FormSelect>`
329
+
330
+ Select field component.
331
+
332
+ **Props:**
333
+
334
+ Same as `FormField` plus:
335
+
336
+ | Prop | Type | Description |
337
+ |------|------|-------------|
338
+ | `options` | `SelectOption[]` | Select options |
339
+
340
+ ### `<FormCheckbox>`
341
+
342
+ Checkbox field component.
343
+
344
+ **Props:**
345
+
346
+ | Prop | Type | Description |
347
+ |------|------|-------------|
348
+ | `name` | `string` | Field name |
349
+ | `label` | `string` | Checkbox label |
350
+ | `description` | `string` | Description text |
351
+ | `required` | `boolean` | Show required indicator |
352
+ | `control` | `Control` | Form control |
353
+
354
+ ### `useSmartFieldArray(options)`
355
+
356
+ Hook for managing dynamic field arrays.
357
+
358
+ **Options:**
359
+
360
+ | Option | Type | Description |
361
+ |--------|------|-------------|
362
+ | `name` | `string` | Field array name |
363
+ | `control` | `Control` | Form control |
364
+ | `defaultValue` | `any` | Default value for new items |
365
+
366
+ **Returns:**
367
+
368
+ | Property | Type | Description |
369
+ |----------|------|-------------|
370
+ | `fields` | `array` | Current field array |
371
+ | `append` | `function` | Add new field |
372
+ | `remove` | `function` | Remove field by index |
373
+ | `insert` | `function` | Insert field at index |
374
+ | `move` | `function` | Move field |
375
+ | `swap` | `function` | Swap two fields |
376
+
377
+ ## Integration with @mehdashti/ui
378
+
379
+ The form components are designed to work seamlessly with @mehdashti/ui. You can also use @mehdashti/ui components directly:
380
+
381
+ ```typescript
382
+ import { useSmartForm, z } from '@mehdashti/forms'
383
+ import { Button, Input, Label } from '@mehdashti/ui'
384
+ import { Controller } from 'react-hook-form'
385
+
386
+ function CustomForm() {
387
+ const form = useSmartForm({
388
+ schema: z.object({
389
+ name: z.string().min(1),
390
+ }),
391
+ onSubmit: async (data) => {
392
+ console.log(data)
393
+ },
394
+ })
395
+
396
+ return (
397
+ <form onSubmit={form.handleSubmit}>
398
+ <Controller
399
+ name="name"
400
+ control={form.control}
401
+ render={({ field, fieldState: { error } }) => (
402
+ <div>
403
+ <Label>Name</Label>
404
+ <Input {...field} />
405
+ {error && <p className="text-destructive">{error.message}</p>}
406
+ </div>
407
+ )}
408
+ />
409
+
410
+ <Button type="submit">Submit</Button>
411
+ </form>
412
+ )
413
+ }
414
+ ```
415
+
416
+ ## Best Practices
417
+
418
+ 1. **Export schemas** for reuse and testing
419
+ 2. **Use TypeScript** for type safety
420
+ 3. **Keep validation logic in schemas** not in components
421
+ 4. **Use `watch`** sparingly for performance
422
+ 5. **Debounce expensive validations** using Zod's `refine` with async
423
+ 6. **Use `reset`** after successful submission
424
+ 7. **Handle errors gracefully** with `onError` callback
425
+
426
+ ## Examples
427
+
428
+ See the [examples directory](../../examples/forms) for complete working examples.
429
+
430
+ ## License
431
+
432
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,212 @@
1
+ import { useForm, Controller, useFieldArray } from 'react-hook-form';
2
+ import { zodResolver } from '@hookform/resolvers/zod';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ export { z } from 'zod';
5
+
6
+ // src/use-smart-form.ts
7
+ function useSmartForm(options) {
8
+ const { schema, onSubmit, onError, realtimeValidation = true, ...formOptions } = options;
9
+ const form = useForm({
10
+ resolver: zodResolver(schema),
11
+ mode: realtimeValidation ? "onChange" : formOptions.mode || "onSubmit",
12
+ ...formOptions
13
+ });
14
+ const {
15
+ handleSubmit: rhfHandleSubmit,
16
+ formState: { isSubmitting, errors }
17
+ } = form;
18
+ const hasErrors = Object.keys(errors).length > 0;
19
+ const getError = (name) => {
20
+ const error = errors[name];
21
+ return error?.message;
22
+ };
23
+ const handleSubmit = rhfHandleSubmit(
24
+ async (data) => {
25
+ try {
26
+ await onSubmit(data);
27
+ } catch (error) {
28
+ if (onError) {
29
+ onError(error);
30
+ } else {
31
+ console.error("Form submission error:", error);
32
+ }
33
+ }
34
+ },
35
+ (errors2) => {
36
+ if (onError) {
37
+ onError(errors2);
38
+ }
39
+ }
40
+ );
41
+ return {
42
+ ...form,
43
+ handleSubmit,
44
+ isSubmitting,
45
+ hasErrors,
46
+ getError
47
+ };
48
+ }
49
+ function FormField({
50
+ name,
51
+ label,
52
+ type = "text",
53
+ placeholder,
54
+ required = false,
55
+ helpText,
56
+ disabled = false,
57
+ className,
58
+ control,
59
+ render
60
+ }) {
61
+ return /* @__PURE__ */ jsx(
62
+ Controller,
63
+ {
64
+ name,
65
+ control,
66
+ render: ({ field, fieldState: { error } }) => /* @__PURE__ */ jsxs("div", { className: `space-y-2 ${className || ""}`, children: [
67
+ /* @__PURE__ */ jsxs("label", { htmlFor: String(name), className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: [
68
+ label,
69
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
70
+ ] }),
71
+ render ? render(field) : /* @__PURE__ */ jsx(
72
+ "input",
73
+ {
74
+ id: String(name),
75
+ type,
76
+ placeholder,
77
+ disabled,
78
+ className: `flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${error ? "border-destructive" : ""}`,
79
+ "aria-invalid": !!error,
80
+ ...field,
81
+ value: field.value || ""
82
+ }
83
+ ),
84
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error.message }),
85
+ helpText && !error && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: helpText })
86
+ ] })
87
+ }
88
+ );
89
+ }
90
+ function FormSelect({
91
+ name,
92
+ label,
93
+ options,
94
+ placeholder,
95
+ required = false,
96
+ helpText,
97
+ disabled = false,
98
+ className,
99
+ control
100
+ }) {
101
+ return /* @__PURE__ */ jsx(
102
+ Controller,
103
+ {
104
+ name,
105
+ control,
106
+ render: ({ field, fieldState: { error } }) => /* @__PURE__ */ jsxs("div", { className: `space-y-2 ${className || ""}`, children: [
107
+ /* @__PURE__ */ jsxs("label", { htmlFor: String(name), className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: [
108
+ label,
109
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
110
+ ] }),
111
+ /* @__PURE__ */ jsxs(
112
+ "select",
113
+ {
114
+ id: String(name),
115
+ disabled,
116
+ className: `flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${error ? "border-destructive" : ""}`,
117
+ "aria-invalid": !!error,
118
+ ...field,
119
+ value: field.value || "",
120
+ children: [
121
+ placeholder && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
122
+ options.map((option) => /* @__PURE__ */ jsx(
123
+ "option",
124
+ {
125
+ value: option.value,
126
+ disabled: option.disabled,
127
+ children: option.label
128
+ },
129
+ String(option.value)
130
+ ))
131
+ ]
132
+ }
133
+ ),
134
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error.message }),
135
+ helpText && !error && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: helpText })
136
+ ] })
137
+ }
138
+ );
139
+ }
140
+ function FormCheckbox({
141
+ name,
142
+ label,
143
+ description,
144
+ required = false,
145
+ helpText,
146
+ disabled = false,
147
+ className,
148
+ control
149
+ }) {
150
+ return /* @__PURE__ */ jsx(
151
+ Controller,
152
+ {
153
+ name,
154
+ control,
155
+ render: ({ field, fieldState: { error } }) => /* @__PURE__ */ jsxs("div", { className: `space-y-2 ${className || ""}`, children: [
156
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start space-x-3", children: [
157
+ /* @__PURE__ */ jsx(
158
+ "input",
159
+ {
160
+ id: String(name),
161
+ type: "checkbox",
162
+ disabled,
163
+ className: `h-4 w-4 rounded border-input ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${error ? "border-destructive" : ""}`,
164
+ "aria-invalid": !!error,
165
+ checked: field.value || false,
166
+ onChange: (e) => field.onChange(e.target.checked),
167
+ onBlur: field.onBlur
168
+ }
169
+ ),
170
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1 leading-none", children: [
171
+ /* @__PURE__ */ jsxs(
172
+ "label",
173
+ {
174
+ htmlFor: String(name),
175
+ className: "text-sm font-medium cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
176
+ children: [
177
+ label,
178
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
179
+ ]
180
+ }
181
+ ),
182
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description })
183
+ ] })
184
+ ] }),
185
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error.message }),
186
+ helpText && !error && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: helpText })
187
+ ] })
188
+ }
189
+ );
190
+ }
191
+ function useSmartFieldArray({
192
+ name,
193
+ control,
194
+ defaultValue
195
+ }) {
196
+ const fieldArray = useFieldArray({
197
+ name,
198
+ control
199
+ });
200
+ const { append: rhfAppend, ...rest } = fieldArray;
201
+ const append = (value) => {
202
+ rhfAppend(value || defaultValue);
203
+ };
204
+ return {
205
+ ...rest,
206
+ append
207
+ };
208
+ }
209
+
210
+ export { FormCheckbox, FormField, FormSelect, useSmartFieldArray, useSmartForm };
211
+ //# sourceMappingURL=index.js.map
212
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/use-smart-form.ts","../src/form-field.tsx","../src/form-select.tsx","../src/form-checkbox.tsx","../src/use-field-array.ts"],"names":["errors","jsx","Controller","jsxs","rhfUseFieldArray"],"mappings":"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","file":"index.js","sourcesContent":["import { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport type { FieldValues, Path, UseFormReturn } from 'react-hook-form'\nimport type { z } from 'zod'\n\nexport interface SmartFormOptions<TFieldValues extends FieldValues = FieldValues> {\n schema: z.ZodSchema<TFieldValues>\n onSubmit: (data: TFieldValues) => void | Promise<void>\n onError?: (errors: Record<string, unknown>) => void\n realtimeValidation?: boolean\n defaultValues?: Partial<TFieldValues>\n mode?: 'onSubmit' | 'onBlur' | 'onChange' | 'onTouched' | 'all'\n}\n\n/**\n * Enhanced React Hook Form with Zod validation and smart defaults\n */\nexport function useSmartForm<TFieldValues extends FieldValues = FieldValues>(\n options: SmartFormOptions<TFieldValues>\n) {\n const { schema, onSubmit, onError, realtimeValidation = true, ...formOptions } = options\n\n const form = useForm<TFieldValues>({\n resolver: zodResolver(schema),\n mode: realtimeValidation ? 'onChange' : (formOptions.mode || 'onSubmit'),\n ...formOptions,\n })\n\n const {\n handleSubmit: rhfHandleSubmit,\n formState: { isSubmitting, errors },\n } = form\n\n const hasErrors = Object.keys(errors).length > 0\n\n const getError = (name: Path<TFieldValues>): string | undefined => {\n const error = errors[name]\n return error?.message as string | undefined\n }\n\n const handleSubmit = rhfHandleSubmit(\n async (data) => {\n try {\n await onSubmit(data)\n } catch (error) {\n if (onError) {\n onError(error as Record<string, unknown>)\n } else {\n console.error('Form submission error:', error)\n }\n }\n },\n (errors) => {\n if (onError) {\n onError(errors)\n }\n }\n )\n\n return {\n ...form,\n handleSubmit,\n isSubmitting,\n hasErrors,\n getError,\n }\n}\n\nexport type SmartFormReturn<TFieldValues extends FieldValues = FieldValues> = ReturnType<typeof useSmartForm<TFieldValues>>\n","import { Controller } from 'react-hook-form'\nimport type { FieldValues } from 'react-hook-form'\nimport type { FormFieldProps } from './form-types'\n\n/**\n * Smart Form Field component with automatic error handling\n *\n * @example\n * ```tsx\n * <FormField\n * name=\"email\"\n * label=\"Email\"\n * type=\"email\"\n * required\n * control={form.control}\n * placeholder=\"Enter your email\"\n * />\n * ```\n */\nexport function FormField<TFieldValues extends FieldValues = FieldValues>({\n name,\n label,\n type = 'text',\n placeholder,\n required = false,\n helpText,\n disabled = false,\n className,\n control,\n render,\n}: FormFieldProps<TFieldValues>) {\n return (\n <Controller\n name={name}\n control={control}\n render={({ field, fieldState: { error } }) => (\n <div className={`space-y-2 ${className || ''}`}>\n <label htmlFor={String(name)} className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n {label}\n {required && <span className=\"text-destructive ml-1\">*</span>}\n </label>\n\n {render ? (\n render(field)\n ) : (\n <input\n id={String(name)}\n type={type}\n placeholder={placeholder}\n disabled={disabled}\n className={`flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${\n error ? 'border-destructive' : ''\n }`}\n aria-invalid={!!error}\n {...field}\n value={field.value || ''}\n />\n )}\n\n {error && (\n <p className=\"text-sm text-destructive\">{error.message}</p>\n )}\n\n {helpText && !error && (\n <p className=\"text-xs text-muted-foreground\">{helpText}</p>\n )}\n </div>\n )}\n />\n )\n}\n","import { Controller } from 'react-hook-form'\nimport type { FieldValues } from 'react-hook-form'\nimport type { FormSelectProps } from './form-types'\n\n/**\n * Smart Form Select component\n *\n * @example\n * ```tsx\n * <FormSelect\n * name=\"country\"\n * label=\"Country\"\n * required\n * control={form.control}\n * options={[\n * { value: 'us', label: 'United States' },\n * { value: 'uk', label: 'United Kingdom' },\n * ]}\n * />\n * ```\n */\nexport function FormSelect<TFieldValues extends FieldValues = FieldValues>({\n name,\n label,\n options,\n placeholder,\n required = false,\n helpText,\n disabled = false,\n className,\n control,\n}: FormSelectProps<TFieldValues>) {\n return (\n <Controller\n name={name}\n control={control}\n render={({ field, fieldState: { error } }) => (\n <div className={`space-y-2 ${className || ''}`}>\n <label htmlFor={String(name)} className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n {label}\n {required && <span className=\"text-destructive ml-1\">*</span>}\n </label>\n\n <select\n id={String(name)}\n disabled={disabled}\n className={`flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${\n error ? 'border-destructive' : ''\n }`}\n aria-invalid={!!error}\n {...field}\n value={field.value || ''}\n >\n {placeholder && (\n <option value=\"\" disabled>\n {placeholder}\n </option>\n )}\n {options.map((option) => (\n <option\n key={String(option.value)}\n value={option.value}\n disabled={option.disabled}\n >\n {option.label}\n </option>\n ))}\n </select>\n\n {error && (\n <p className=\"text-sm text-destructive\">{error.message}</p>\n )}\n\n {helpText && !error && (\n <p className=\"text-xs text-muted-foreground\">{helpText}</p>\n )}\n </div>\n )}\n />\n )\n}\n","import { Controller } from 'react-hook-form'\nimport type { FieldValues } from 'react-hook-form'\nimport type { FormCheckboxProps } from './form-types'\n\n/**\n * Smart Form Checkbox component\n *\n * @example\n * ```tsx\n * <FormCheckbox\n * name=\"acceptTerms\"\n * label=\"I accept the terms and conditions\"\n * required\n * control={form.control}\n * description=\"You must accept the terms to continue\"\n * />\n * ```\n */\nexport function FormCheckbox<TFieldValues extends FieldValues = FieldValues>({\n name,\n label,\n description,\n required = false,\n helpText,\n disabled = false,\n className,\n control,\n}: FormCheckboxProps<TFieldValues>) {\n return (\n <Controller\n name={name}\n control={control}\n render={({ field, fieldState: { error } }) => (\n <div className={`space-y-2 ${className || ''}`}>\n <div className=\"flex items-start space-x-3\">\n <input\n id={String(name)}\n type=\"checkbox\"\n disabled={disabled}\n className={`h-4 w-4 rounded border-input ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${\n error ? 'border-destructive' : ''\n }`}\n aria-invalid={!!error}\n checked={field.value || false}\n onChange={(e) => field.onChange(e.target.checked)}\n onBlur={field.onBlur}\n />\n <div className=\"flex-1 space-y-1 leading-none\">\n <label\n htmlFor={String(name)}\n className=\"text-sm font-medium cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n >\n {label}\n {required && <span className=\"text-destructive ml-1\">*</span>}\n </label>\n {description && (\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n )}\n </div>\n </div>\n\n {error && (\n <p className=\"text-sm text-destructive\">{error.message}</p>\n )}\n\n {helpText && !error && (\n <p className=\"text-xs text-muted-foreground\">{helpText}</p>\n )}\n </div>\n )}\n />\n )\n}\n","import { useFieldArray as rhfUseFieldArray } from 'react-hook-form'\nimport type { FieldValues, ArrayPath } from 'react-hook-form'\nimport type { UseFormReturn } from 'react-hook-form'\n\n/**\n * Field array options\n */\nexport interface FieldArrayOptions<TFieldValues extends FieldValues = FieldValues> {\n /**\n * Field array name\n */\n name: ArrayPath<TFieldValues>\n\n /**\n * Form control instance\n */\n control: UseFormReturn<TFieldValues>['control']\n\n /**\n * Default value for new items\n */\n defaultValue?: unknown\n}\n\n/**\n * Wrapper around React Hook Form's useFieldArray with smart defaults\n *\n * @example\n * ```tsx\n * const { fields, append, remove } = useSmartFieldArray({\n * name: 'items',\n * control: form.control,\n * defaultValue: { name: '', quantity: 0 }\n * })\n * ```\n */\nexport function useSmartFieldArray<TFieldValues extends FieldValues = FieldValues>({\n name,\n control,\n defaultValue,\n}: FieldArrayOptions<TFieldValues>) {\n const fieldArray = rhfUseFieldArray({\n name,\n control,\n })\n\n const { append: rhfAppend, ...rest } = fieldArray\n\n // Enhanced append with default value\n const append = (value?: unknown) => {\n rhfAppend((value || defaultValue) as never)\n }\n\n return {\n ...rest,\n append,\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@mehdashti/forms",
3
+ "version": "0.1.0",
4
+ "description": "Form validation and management for Smart Platform",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "dev": "tsup --watch",
15
+ "clean": "rm -rf dist",
16
+ "typecheck": "tsc --noEmit"
17
+ },
18
+ "dependencies": {
19
+ "react-hook-form": "^7.53.2",
20
+ "zod": "^3.24.1",
21
+ "@hookform/resolvers": "^3.9.1"
22
+ },
23
+ "peerDependencies": {
24
+ "react": "^18.0.0 || ^19.0.0",
25
+ "react-dom": "^18.0.0 || ^19.0.0",
26
+ "@mehdashti/ui": "^0.1.0",
27
+ "@mehdashti/contracts": "^0.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^18.3.18",
31
+ "@types/react-dom": "^18.3.5",
32
+ "typescript": "^5.9.3",
33
+ "tsup": "^8.3.5"
34
+ },
35
+ "keywords": [
36
+ "forms",
37
+ "validation",
38
+ "react-hook-form",
39
+ "zod",
40
+ "smart-platform"
41
+ ],
42
+ "author": "mehdashti",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/mehdashti/smart-platform.git",
47
+ "directory": "packages/forms"
48
+ }
49
+ }