@connect-soft/form-generator 1.1.0-alpha5 → 1.1.0-alpha7

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 CHANGED
@@ -16,7 +16,8 @@
16
16
  - **Imperative API**: Control form via ref (`setValues`, `reset`, `submit`, etc.)
17
17
  - **Flexible**: Register custom field components with a simple API
18
18
  - **Validation**: Built-in Zod validation support
19
- - **Layouts**: Support for sections and multi-column layouts
19
+ - **Array Fields**: Repeatable field groups with `useFieldArray` integration
20
+ - **Custom Layouts**: Full control over form layout via render props
20
21
  - **Lightweight**: No UI dependencies, minimal footprint
21
22
  - **HTML Fallbacks**: Works out of the box with native HTML inputs
22
23
 
@@ -69,58 +70,105 @@ function MyForm() {
69
70
 
70
71
  ## Registering Custom Components
71
72
 
72
- Register your own UI components to replace the HTML fallbacks:
73
+ Register your own UI components to replace the HTML fallbacks. Field components are fully responsible for rendering their own label, description, and error messages:
73
74
 
74
75
  ```typescript
75
- import { registerFields, registerFormComponents } from '@connect-soft/form-generator';
76
+ import { registerFields, registerFormComponent } from '@connect-soft/form-generator';
76
77
  import { Input } from './ui/input';
77
78
  import { Label } from './ui/label';
78
79
  import { Checkbox } from './ui/checkbox';
79
80
  import { Button } from './ui/button';
80
81
 
81
- // Register field components
82
+ // Register field components - they handle their own rendering
82
83
  registerFields({
83
- text: ({ field, formField }) => (
84
- <Input
85
- {...formField}
86
- type={field.fieldType || 'text'}
87
- placeholder={field.placeholder}
88
- disabled={field.disabled}
89
- />
84
+ text: ({ field, formField, fieldState }) => (
85
+ <div className="form-field">
86
+ {field.label && <Label>{field.label}{field.required && ' *'}</Label>}
87
+ <Input
88
+ {...formField}
89
+ type={field.fieldType || 'text'}
90
+ placeholder={field.placeholder}
91
+ disabled={field.disabled}
92
+ />
93
+ {field.description && <p className="text-sm text-muted">{field.description}</p>}
94
+ {fieldState.error && <span className="text-red-500 text-sm">{fieldState.error.message}</span>}
95
+ </div>
90
96
  ),
91
- number: ({ field, formField }) => (
92
- <Input
93
- {...formField}
94
- type="number"
95
- min={field.min}
96
- max={field.max}
97
- />
97
+ number: ({ field, formField, fieldState }) => (
98
+ <div className="form-field">
99
+ {field.label && <Label>{field.label}</Label>}
100
+ <Input
101
+ {...formField}
102
+ type="number"
103
+ min={field.min}
104
+ max={field.max}
105
+ />
106
+ {fieldState.error && <span className="text-red-500 text-sm">{fieldState.error.message}</span>}
107
+ </div>
98
108
  ),
99
- checkbox: {
100
- component: ({ field, formField }) => (
109
+ checkbox: ({ field, formField }) => (
110
+ <div className="flex items-center gap-2">
101
111
  <Checkbox
102
112
  checked={formField.value}
103
113
  onCheckedChange={formField.onChange}
104
114
  disabled={field.disabled}
105
115
  />
106
- ),
107
- options: {
108
- className: 'flex items-center gap-2',
109
- }
110
- },
116
+ {field.label && <Label>{field.label}</Label>}
117
+ </div>
118
+ ),
111
119
  });
112
120
 
113
- // Register form wrapper components
114
- registerFormComponents({
115
- FormItem: ({ children, className }) => <div className={className}>{children}</div>,
116
- FormLabel: Label,
117
- FormMessage: ({ children }) => <span className="text-red-500 text-sm">{children}</span>,
118
- SubmitButton: Button,
119
- });
121
+ // Register the submit button component
122
+ registerFormComponent('SubmitButton', Button);
123
+
124
+ // Optional: Register field wrappers for custom styling
125
+ registerFormComponent('FieldWrapper', ({ children, name, type, className }) => (
126
+ <div className={`field-wrapper field-${type}`} data-field={name}>
127
+ {children}
128
+ </div>
129
+ ));
130
+
131
+ registerFormComponent('FieldsWrapper', ({ children }) => (
132
+ <div className="form-fields">
133
+ {children}
134
+ </div>
135
+ ));
120
136
  ```
121
137
 
122
138
  ---
123
139
 
140
+ ## Field Wrappers
141
+
142
+ Customize how fields are wrapped without modifying individual field components:
143
+
144
+ ### FieldWrapper
145
+
146
+ Wraps each individual field. Receives the field's `name`, `type`, and `className`:
147
+
148
+ ```typescript
149
+ registerFormComponent('FieldWrapper', ({ children, name, type, className }) => (
150
+ <div className={`form-group ${className || ''}`} data-field-type={type}>
151
+ {children}
152
+ </div>
153
+ ));
154
+ ```
155
+
156
+ ### FieldsWrapper
157
+
158
+ Wraps all fields together (excludes the submit button). Useful for grid layouts:
159
+
160
+ ```typescript
161
+ registerFormComponent('FieldsWrapper', ({ children, className }) => (
162
+ <div className={`grid grid-cols-2 gap-4 ${className || ''}`}>
163
+ {children}
164
+ </div>
165
+ ));
166
+ ```
167
+
168
+ Both default to `React.Fragment`, adding zero DOM overhead when not customized.
169
+
170
+ ---
171
+
124
172
  ## Field Types
125
173
 
126
174
  Built-in HTML fallback types:
@@ -230,40 +278,96 @@ const schema = z.object({
230
278
 
231
279
  ---
232
280
 
233
- ## Layouts
281
+ ## Array Fields
234
282
 
235
- Organize fields with sections and columns:
283
+ Create repeatable field groups with `useFieldArray` integration:
236
284
 
237
285
  ```typescript
238
286
  const fields = [
287
+ { type: 'text', name: 'name', label: 'Name' },
239
288
  {
240
- type: 'section',
241
- title: 'Personal Information',
242
- children: [
243
- { type: 'text', name: 'firstName', label: 'First Name' },
244
- { type: 'text', name: 'lastName', label: 'Last Name' },
245
- ],
246
- },
247
- {
248
- type: 'columns',
249
- columns: [
250
- {
251
- width: '1',
252
- children: [
253
- { type: 'text', name: 'city', label: 'City' },
254
- ],
255
- },
256
- {
257
- width: '1',
258
- children: [
259
- { type: 'text', name: 'zip', label: 'ZIP Code' },
260
- ],
261
- },
289
+ type: 'array',
290
+ name: 'contacts',
291
+ label: 'Contacts',
292
+ fields: [
293
+ { type: 'text', name: 'email', label: 'Email' },
294
+ { type: 'text', name: 'phone', label: 'Phone' },
262
295
  ],
296
+ minItems: 1,
297
+ maxItems: 5,
263
298
  },
264
- ];
299
+ ] as const;
300
+ ```
301
+
302
+ ### Default Array Rendering
303
+
304
+ Array fields render automatically with add/remove functionality:
305
+
306
+ ```typescript
307
+ <FormGenerator
308
+ fields={fields}
309
+ onSubmit={(values) => {
310
+ console.log(values.contacts); // Array<{ email: string, phone: string }>
311
+ }}
312
+ />
265
313
  ```
266
314
 
315
+ ### Custom Array Rendering with useArrayField
316
+
317
+ For full control, use the `useArrayField` hook in a custom layout:
318
+
319
+ ```typescript
320
+ import { FormGenerator, useArrayField } from '@connect-soft/form-generator';
321
+
322
+ <FormGenerator fields={fields} onSubmit={handleSubmit}>
323
+ {({ fields, arrays, buttons }) => {
324
+ const contacts = useArrayField(arrays.contacts.field);
325
+
326
+ return (
327
+ <div>
328
+ {fields.name}
329
+
330
+ <h3>Contacts</h3>
331
+ {contacts.items.map(({ id, index, remove, fields: itemFields }) => (
332
+ <div key={id} className="contact-row">
333
+ {itemFields.email}
334
+ {itemFields.phone}
335
+ {contacts.canRemove && (
336
+ <button type="button" onClick={remove}>Remove</button>
337
+ )}
338
+ </div>
339
+ ))}
340
+
341
+ {contacts.canAppend && (
342
+ <button type="button" onClick={contacts.append}>
343
+ Add Contact
344
+ </button>
345
+ )}
346
+
347
+ {buttons.submit}
348
+ </div>
349
+ );
350
+ }}
351
+ </FormGenerator>
352
+ ```
353
+
354
+ ### useArrayField Return Values
355
+
356
+ | Property | Type | Description |
357
+ |----------|------|-------------|
358
+ | `items` | `Array<{ id, index }>` | Array items with unique ids |
359
+ | `append` | `() => void` | Add new empty item |
360
+ | `appendWith` | `(values) => void` | Add item with values |
361
+ | `prepend` | `() => void` | Add item at beginning |
362
+ | `remove` | `(index) => void` | Remove item at index |
363
+ | `move` | `(from, to) => void` | Move item |
364
+ | `swap` | `(a, b) => void` | Swap two items |
365
+ | `insert` | `(index, values?) => void` | Insert at index |
366
+ | `canAppend` | `boolean` | Can add more items (respects maxItems) |
367
+ | `canRemove` | `boolean` | Can remove items (respects minItems) |
368
+ | `renderField` | `(index, name) => ReactElement` | Render single field |
369
+ | `renderItem` | `(index) => Record<string, ReactElement>` | Render all fields for item |
370
+
267
371
  ---
268
372
 
269
373
  ## Custom Layouts
@@ -297,7 +401,7 @@ The render function receives:
297
401
  | Property | Type | Description |
298
402
  |----------|------|-------------|
299
403
  | `fields` | `TemplateFields` | Pre-rendered fields (see below) |
300
- | `layouts` | `TemplateLayouts` | Pre-rendered named layouts |
404
+ | `arrays` | `Record<string, TemplateArrayField>` | Array field definitions (use with `useArrayField`) |
301
405
  | `buttons` | `{ submit, reset? }` | Pre-rendered buttons |
302
406
  | `title` | `string` | Form title prop |
303
407
  | `description` | `string` | Form description prop |
@@ -306,7 +410,8 @@ The render function receives:
306
410
  | `isValid` | `boolean` | Form validity state |
307
411
  | `isDirty` | `boolean` | Form dirty state |
308
412
  | `renderField` | `function` | Manual field renderer |
309
- | `renderLayout` | `function` | Manual layout renderer |
413
+ | `FieldWrapper` | `ComponentType` | Registered FieldWrapper component |
414
+ | `FieldsWrapper` | `ComponentType` | Registered FieldsWrapper component |
310
415
 
311
416
  ### Fields Object
312
417