@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 +432 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
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
|
+
}
|