rhf-dynamic-forms 1.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 +692 -0
- package/dist/index.cjs +1611 -0
- package/dist/index.d.cts +1124 -0
- package/dist/index.d.mts +1124 -0
- package/dist/index.mjs +1557 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
# Dynamic Forms
|
|
2
|
+
|
|
3
|
+
Configuration-driven form generation library for React with react-hook-form and Zod integration.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Overview](#overview)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Configuration Reference](#configuration-reference)
|
|
13
|
+
- [FormConfiguration](#formconfiguration)
|
|
14
|
+
- [Field Types](#field-types)
|
|
15
|
+
- [Validation Configuration](#validation-configuration)
|
|
16
|
+
- [Container Layout](#container-layout)
|
|
17
|
+
- [Usage Examples](#usage-examples)
|
|
18
|
+
- [Nested Field Paths](#nested-field-paths)
|
|
19
|
+
- [Two-Column Layout](#two-column-layout)
|
|
20
|
+
- [Custom Field Component](#custom-field-component)
|
|
21
|
+
- [JSON Logic Conditional Validation](#json-logic-conditional-validation)
|
|
22
|
+
- [API Reference](#api-reference)
|
|
23
|
+
- [DynamicForm Props](#dynamicform-props)
|
|
24
|
+
- [Validation Options](#validation-options)
|
|
25
|
+
- [Hooks](#hooks)
|
|
26
|
+
- [Exports](#exports)
|
|
27
|
+
- [Creating Field Components](#creating-field-components)
|
|
28
|
+
- [Development](#development)
|
|
29
|
+
- [Tech Stack](#tech-stack)
|
|
30
|
+
- [Contributing](#contributing)
|
|
31
|
+
- [License](#license)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Overview
|
|
36
|
+
|
|
37
|
+
Dynamic Forms enables rapid deployment of data collection forms by defining form structures, validations, and display logic through declarative JSON configurations. Instead of writing custom form components for each use case, describe your form as data and let the library handle rendering and validation.
|
|
38
|
+
|
|
39
|
+
**Key Benefits:**
|
|
40
|
+
- Define forms as JSON configuration
|
|
41
|
+
- Flexible validation: external resolver, Zod schema, or config-driven
|
|
42
|
+
- Full react-hook-form integration
|
|
43
|
+
- Nested field paths with dot notation
|
|
44
|
+
- Conditional visibility and validation with JSON Logic
|
|
45
|
+
- Field dependencies with cascading resets
|
|
46
|
+
- Select fields with static/dynamic options
|
|
47
|
+
- Array fields for repeatable groups
|
|
48
|
+
- Extensible component architecture
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install dynamic-forms
|
|
54
|
+
# or
|
|
55
|
+
pnpm add dynamic-forms
|
|
56
|
+
# or
|
|
57
|
+
yarn add dynamic-forms
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Peer Dependencies:**
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install react react-dom
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { DynamicForm, type FormConfiguration, type FieldComponentRegistry } from 'dynamic-forms';
|
|
70
|
+
|
|
71
|
+
// 1. Define your form configuration
|
|
72
|
+
const config: FormConfiguration = {
|
|
73
|
+
name: "Contact Form",
|
|
74
|
+
elements: [
|
|
75
|
+
{
|
|
76
|
+
type: "text",
|
|
77
|
+
name: "fullName",
|
|
78
|
+
label: "Full Name",
|
|
79
|
+
validation: { required: true, minLength: 2 },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "email",
|
|
83
|
+
name: "email",
|
|
84
|
+
label: "Email Address",
|
|
85
|
+
validation: { required: true },
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// 2. Create field components (or use a UI library)
|
|
91
|
+
const fieldComponents: FieldComponentRegistry = {
|
|
92
|
+
text: ({ field, fieldState, config }) => (
|
|
93
|
+
<div>
|
|
94
|
+
<label>{config.label}</label>
|
|
95
|
+
<input {...field} placeholder={config.placeholder} />
|
|
96
|
+
{fieldState.error && <span>{fieldState.error.message}</span>}
|
|
97
|
+
</div>
|
|
98
|
+
),
|
|
99
|
+
email: ({ field, fieldState, config }) => (
|
|
100
|
+
<div>
|
|
101
|
+
<label>{config.label}</label>
|
|
102
|
+
<input {...field} type="email" placeholder={config.placeholder} />
|
|
103
|
+
{fieldState.error && <span>{fieldState.error.message}</span>}
|
|
104
|
+
</div>
|
|
105
|
+
),
|
|
106
|
+
boolean: ({ field, config }) => (
|
|
107
|
+
<label>
|
|
108
|
+
<input {...field} type="checkbox" checked={field.value} />
|
|
109
|
+
{config.label}
|
|
110
|
+
</label>
|
|
111
|
+
),
|
|
112
|
+
phone: ({ field, config }) => (
|
|
113
|
+
<div>
|
|
114
|
+
<label>{config.label}</label>
|
|
115
|
+
<input {...field} type="tel" />
|
|
116
|
+
</div>
|
|
117
|
+
),
|
|
118
|
+
date: ({ field, config }) => (
|
|
119
|
+
<div>
|
|
120
|
+
<label>{config.label}</label>
|
|
121
|
+
<input {...field} type="date" />
|
|
122
|
+
</div>
|
|
123
|
+
),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// 3. Render the form
|
|
127
|
+
function App() {
|
|
128
|
+
return (
|
|
129
|
+
<DynamicForm
|
|
130
|
+
config={config}
|
|
131
|
+
fieldComponents={fieldComponents}
|
|
132
|
+
onSubmit={(data) => console.log('Submitted:', data)}
|
|
133
|
+
>
|
|
134
|
+
<button type="submit">Submit</button>
|
|
135
|
+
</DynamicForm>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Configuration Reference
|
|
141
|
+
|
|
142
|
+
### FormConfiguration
|
|
143
|
+
|
|
144
|
+
The root configuration object that defines your form structure.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface FormConfiguration {
|
|
148
|
+
name?: string; // Optional form identifier
|
|
149
|
+
elements: FormElement[]; // Array of fields and layouts
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Field Types
|
|
154
|
+
|
|
155
|
+
The library supports the following built-in field types:
|
|
156
|
+
|
|
157
|
+
| Type | Description | Default Value Type |
|
|
158
|
+
|------|-------------|-------------------|
|
|
159
|
+
| `text` | Single-line text input | `string` |
|
|
160
|
+
| `email` | Email input with validation | `string` |
|
|
161
|
+
| `boolean` | Checkbox or toggle | `boolean` |
|
|
162
|
+
| `phone` | Telephone number input | `string` |
|
|
163
|
+
| `date` | Date picker | `string` |
|
|
164
|
+
| `select` | Dropdown/multi-select with options | `string \| string[]` |
|
|
165
|
+
| `array` | Repeatable field groups | `array` |
|
|
166
|
+
| `custom` | User-defined component | `unknown` |
|
|
167
|
+
|
|
168
|
+
### Field Element Structure
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
interface FieldElement {
|
|
172
|
+
type: "text" | "email" | "boolean" | "phone" | "date" | "custom";
|
|
173
|
+
name: string; // Field path (supports dot notation)
|
|
174
|
+
label?: string; // Display label
|
|
175
|
+
placeholder?: string; // Placeholder text
|
|
176
|
+
defaultValue?: string | number | boolean | null;
|
|
177
|
+
validation?: ValidationConfig; // Validation rules
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Validation Configuration
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
interface ValidationConfig {
|
|
185
|
+
required?: boolean; // Field must have a value
|
|
186
|
+
minLength?: number; // Minimum text length
|
|
187
|
+
maxLength?: number; // Maximum text length
|
|
188
|
+
pattern?: string; // Regex pattern
|
|
189
|
+
message?: string; // Custom error message
|
|
190
|
+
condition?: JsonLogicRule; // JSON Logic condition
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Container Layout
|
|
195
|
+
|
|
196
|
+
Create multi-column layouts with containers:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
{
|
|
200
|
+
type: "container",
|
|
201
|
+
columns: [
|
|
202
|
+
{
|
|
203
|
+
type: "column",
|
|
204
|
+
width: "50%",
|
|
205
|
+
elements: [
|
|
206
|
+
{ type: "text", name: "firstName", label: "First Name" },
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
type: "column",
|
|
211
|
+
width: "50%",
|
|
212
|
+
elements: [
|
|
213
|
+
{ type: "text", name: "lastName", label: "Last Name" },
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Usage Examples
|
|
221
|
+
|
|
222
|
+
### Nested Field Paths
|
|
223
|
+
|
|
224
|
+
Use dot notation to create nested data structures:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const config: FormConfiguration = {
|
|
228
|
+
elements: [
|
|
229
|
+
{ type: "text", name: "contact.firstName", label: "First Name" },
|
|
230
|
+
{ type: "text", name: "contact.lastName", label: "Last Name" },
|
|
231
|
+
{ type: "email", name: "contact.email", label: "Email" },
|
|
232
|
+
{ type: "text", name: "address.street", label: "Street" },
|
|
233
|
+
{ type: "text", name: "address.city", label: "City" },
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Submitted data structure:
|
|
238
|
+
// {
|
|
239
|
+
// contact: { firstName: "John", lastName: "Doe", email: "john@example.com" },
|
|
240
|
+
// address: { street: "123 Main St", city: "New York" }
|
|
241
|
+
// }
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Two-Column Layout
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const config: FormConfiguration = {
|
|
248
|
+
elements: [
|
|
249
|
+
{
|
|
250
|
+
type: "container",
|
|
251
|
+
columns: [
|
|
252
|
+
{
|
|
253
|
+
type: "column",
|
|
254
|
+
width: "calc(50% - 0.5rem)", // Account for gap
|
|
255
|
+
elements: [
|
|
256
|
+
{ type: "email", name: "email", label: "Email", validation: { required: true } },
|
|
257
|
+
{ type: "date", name: "birthDate", label: "Birth Date" },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
type: "column",
|
|
262
|
+
width: "calc(50% - 0.5rem)",
|
|
263
|
+
elements: [
|
|
264
|
+
{ type: "phone", name: "phone", label: "Phone" },
|
|
265
|
+
{ type: "text", name: "company", label: "Company" },
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Custom Field Component
|
|
275
|
+
|
|
276
|
+
Register custom components for specialized inputs. You can use simple components or fully typed definitions with Zod schema validation:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
import {
|
|
280
|
+
defineCustomComponent,
|
|
281
|
+
type CustomComponentRegistry,
|
|
282
|
+
type CustomComponentRenderProps,
|
|
283
|
+
} from 'dynamic-forms';
|
|
284
|
+
import { z } from 'zod/v4';
|
|
285
|
+
|
|
286
|
+
// Option 1: Simple component
|
|
287
|
+
const SimpleRating = ({ field, config, componentProps }: CustomComponentRenderProps) => {
|
|
288
|
+
const maxStars = (componentProps?.maxStars as number) ?? 5;
|
|
289
|
+
return (
|
|
290
|
+
<div>
|
|
291
|
+
<label>{config.label}</label>
|
|
292
|
+
<div>
|
|
293
|
+
{Array.from({ length: maxStars }, (_, i) => (
|
|
294
|
+
<button key={i} type="button" onClick={() => field.onChange(i + 1)}>
|
|
295
|
+
{i < (field.value ?? 0) ? '★' : '☆'}
|
|
296
|
+
</button>
|
|
297
|
+
))}
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Option 2: Type-safe definition with Zod schema validation
|
|
304
|
+
const RatingField = defineCustomComponent({
|
|
305
|
+
component: ({ field, componentProps }) => (
|
|
306
|
+
<div className="rating">
|
|
307
|
+
{Array.from({ length: componentProps.maxStars }, (_, i) => (
|
|
308
|
+
<button key={i} type="button" onClick={() => field.onChange(i + 1)}>
|
|
309
|
+
{i < (field.value as number ?? 0) ? '★' : '☆'}
|
|
310
|
+
</button>
|
|
311
|
+
))}
|
|
312
|
+
</div>
|
|
313
|
+
),
|
|
314
|
+
propsSchema: z.object({
|
|
315
|
+
maxStars: z.number().int().min(1).max(10).default(5),
|
|
316
|
+
}),
|
|
317
|
+
defaultProps: { maxStars: 5 },
|
|
318
|
+
displayName: 'RatingField',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Register custom components
|
|
322
|
+
const customComponents: CustomComponentRegistry = {
|
|
323
|
+
SimpleRating,
|
|
324
|
+
RatingField,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Use in configuration
|
|
328
|
+
const config: FormConfiguration = {
|
|
329
|
+
elements: [
|
|
330
|
+
{
|
|
331
|
+
type: "custom",
|
|
332
|
+
name: "rating",
|
|
333
|
+
label: "Rate our service",
|
|
334
|
+
component: "RatingField",
|
|
335
|
+
componentProps: { maxStars: 10 }, // Validated against propsSchema
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// Pass to DynamicForm
|
|
341
|
+
<DynamicForm
|
|
342
|
+
config={config}
|
|
343
|
+
fieldComponents={fieldComponents}
|
|
344
|
+
customComponents={customComponents}
|
|
345
|
+
onSubmit={handleSubmit}
|
|
346
|
+
/>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### JSON Logic Conditional Validation
|
|
350
|
+
|
|
351
|
+
Use JSON Logic for complex validation rules that depend on other field values:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
const config: FormConfiguration = {
|
|
355
|
+
elements: [
|
|
356
|
+
{
|
|
357
|
+
type: "boolean",
|
|
358
|
+
name: "hasPhone",
|
|
359
|
+
label: "I have a phone number",
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
type: "phone",
|
|
363
|
+
name: "phone",
|
|
364
|
+
label: "Phone Number",
|
|
365
|
+
validation: {
|
|
366
|
+
// Valid if: hasPhone is false OR phone matches 10-digit pattern
|
|
367
|
+
condition: {
|
|
368
|
+
or: [
|
|
369
|
+
{ "!": { var: "hasPhone" } },
|
|
370
|
+
{
|
|
371
|
+
and: [
|
|
372
|
+
{ var: "hasPhone" },
|
|
373
|
+
{ regex_match: ["^[0-9]{10}$", { var: "phone" }] },
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
message: "Please enter a valid 10-digit phone number",
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
type: "boolean",
|
|
383
|
+
name: "acceptTerms",
|
|
384
|
+
label: "I accept the terms and conditions",
|
|
385
|
+
validation: {
|
|
386
|
+
// Checkbox must be checked
|
|
387
|
+
condition: { var: "acceptTerms" },
|
|
388
|
+
message: "You must accept the terms and conditions",
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Available JSON Logic Operations:**
|
|
396
|
+
- Standard operators: `var`, `and`, `or`, `!`, `==`, `!=`, `>`, `<`, `>=`, `<=`, `if`
|
|
397
|
+
- Custom: `regex_match` - `["pattern", { var: "fieldName" }]`
|
|
398
|
+
|
|
399
|
+
## API Reference
|
|
400
|
+
|
|
401
|
+
### DynamicForm Props
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
interface DynamicFormProps {
|
|
405
|
+
// Required
|
|
406
|
+
config: FormConfiguration; // Form configuration
|
|
407
|
+
fieldComponents: FieldComponentRegistry; // Component implementations
|
|
408
|
+
onSubmit: (data: FormData) => void; // Submit handler
|
|
409
|
+
|
|
410
|
+
// Optional - Validation (priority order: resolver > schema > config-driven)
|
|
411
|
+
resolver?: Resolver<FormData>; // Custom react-hook-form resolver (Yup, Joi, etc.)
|
|
412
|
+
schema?: ZodSchema; // External Zod schema (wrapped with visibility-aware resolver)
|
|
413
|
+
|
|
414
|
+
// Optional - Components
|
|
415
|
+
initialData?: FormData; // Pre-fill form values
|
|
416
|
+
customComponents?: CustomComponentRegistry; // Custom field components
|
|
417
|
+
customContainers?: CustomContainerRegistry; // Custom layout containers
|
|
418
|
+
|
|
419
|
+
// Optional - Event handlers
|
|
420
|
+
onChange?: (data: FormData, field: string) => void;
|
|
421
|
+
onError?: (errors: unknown) => void;
|
|
422
|
+
onReset?: () => void;
|
|
423
|
+
onValidationChange?: (errors: unknown, isValid: boolean) => void;
|
|
424
|
+
|
|
425
|
+
// Optional - Form behavior
|
|
426
|
+
mode?: "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
|
|
427
|
+
invisibleFieldValidation?: "skip" | "validate" | "warn";
|
|
428
|
+
fieldWrapper?: FieldWrapperFunction; // Wrap each field with custom component
|
|
429
|
+
|
|
430
|
+
// Optional - HTML attributes
|
|
431
|
+
className?: string;
|
|
432
|
+
style?: CSSProperties;
|
|
433
|
+
id?: string;
|
|
434
|
+
children?: React.ReactNode; // Submit button, etc.
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Validation Options
|
|
439
|
+
|
|
440
|
+
The library supports three approaches to validation:
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
// Option 1: External resolver (full control - Yup, Joi, Vest, custom)
|
|
444
|
+
import { yupResolver } from '@hookform/resolvers/yup';
|
|
445
|
+
<DynamicForm resolver={yupResolver(yupSchema)} ... />
|
|
446
|
+
|
|
447
|
+
// Option 2: External Zod schema (wrapped with visibility-aware resolver)
|
|
448
|
+
<DynamicForm schema={myZodSchema} invisibleFieldValidation="skip" ... />
|
|
449
|
+
|
|
450
|
+
// Option 3: Config-driven (auto-generated from field validation configs)
|
|
451
|
+
<DynamicForm config={configWithValidation} ... />
|
|
452
|
+
|
|
453
|
+
// Option 4: No validation (omit resolver, schema, and validation in config)
|
|
454
|
+
<DynamicForm config={simpleConfig} ... />
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Hooks
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
// Access form context inside nested components
|
|
461
|
+
const { config, form } = useDynamicFormContext();
|
|
462
|
+
|
|
463
|
+
// Safe version that returns null outside form context
|
|
464
|
+
const context = useDynamicFormContextSafe();
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Field Component Props
|
|
468
|
+
|
|
469
|
+
All field components receive these props:
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
interface BaseFieldProps {
|
|
473
|
+
field: ControllerRenderProps; // react-hook-form: value, onChange, onBlur, ref
|
|
474
|
+
fieldState: ControllerFieldState; // error, invalid, isTouched, isDirty
|
|
475
|
+
config: FieldElement; // Field configuration
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Exports
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// Components
|
|
483
|
+
export { DynamicForm } from 'dynamic-forms';
|
|
484
|
+
|
|
485
|
+
// Hooks
|
|
486
|
+
export { useDynamicFormContext, useDynamicFormContextSafe } from 'dynamic-forms';
|
|
487
|
+
|
|
488
|
+
// Custom Components
|
|
489
|
+
export {
|
|
490
|
+
defineCustomComponent, // Type-safe component definition helper
|
|
491
|
+
ConfigurationError, // Error class for invalid configurations
|
|
492
|
+
} from 'dynamic-forms';
|
|
493
|
+
|
|
494
|
+
// Types
|
|
495
|
+
export type {
|
|
496
|
+
FormConfiguration,
|
|
497
|
+
FormElement,
|
|
498
|
+
FieldElement,
|
|
499
|
+
ContainerElement,
|
|
500
|
+
ColumnElement,
|
|
501
|
+
ValidationConfig,
|
|
502
|
+
FieldComponentRegistry,
|
|
503
|
+
CustomComponentRegistry,
|
|
504
|
+
CustomContainerRegistry,
|
|
505
|
+
FormData,
|
|
506
|
+
ZodSchema,
|
|
507
|
+
// Custom component types
|
|
508
|
+
CustomComponentDefinition,
|
|
509
|
+
CustomComponentRenderProps,
|
|
510
|
+
// Field component types
|
|
511
|
+
TextFieldComponent,
|
|
512
|
+
EmailFieldComponent,
|
|
513
|
+
BooleanFieldComponent,
|
|
514
|
+
PhoneFieldComponent,
|
|
515
|
+
DateFieldComponent,
|
|
516
|
+
SelectFieldComponent,
|
|
517
|
+
ArrayFieldComponent,
|
|
518
|
+
CustomFieldComponent,
|
|
519
|
+
// Field element types
|
|
520
|
+
SelectFieldElement,
|
|
521
|
+
ArrayFieldElement,
|
|
522
|
+
SelectOption,
|
|
523
|
+
} from 'dynamic-forms';
|
|
524
|
+
|
|
525
|
+
// Utilities
|
|
526
|
+
export {
|
|
527
|
+
parseConfiguration,
|
|
528
|
+
safeParseConfiguration,
|
|
529
|
+
generateZodSchema,
|
|
530
|
+
createVisibilityAwareResolver,
|
|
531
|
+
calculateVisibility,
|
|
532
|
+
flattenFields,
|
|
533
|
+
getFieldNames,
|
|
534
|
+
mergeDefaults,
|
|
535
|
+
applyJsonLogic,
|
|
536
|
+
evaluateCondition,
|
|
537
|
+
isFieldElement,
|
|
538
|
+
isContainerElement,
|
|
539
|
+
isColumnElement,
|
|
540
|
+
isCustomFieldElement,
|
|
541
|
+
isArrayFieldElement,
|
|
542
|
+
} from 'dynamic-forms';
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Creating Field Components
|
|
546
|
+
|
|
547
|
+
Field components are React components that render form inputs. They receive react-hook-form controller props for state management.
|
|
548
|
+
|
|
549
|
+
```tsx
|
|
550
|
+
import type { TextFieldComponent } from 'dynamic-forms';
|
|
551
|
+
|
|
552
|
+
const TextField: TextFieldComponent = ({ field, fieldState, config }) => {
|
|
553
|
+
return (
|
|
554
|
+
<div className="field">
|
|
555
|
+
{config.label && (
|
|
556
|
+
<label htmlFor={field.name}>
|
|
557
|
+
{config.label}
|
|
558
|
+
{config.validation?.required && <span>*</span>}
|
|
559
|
+
</label>
|
|
560
|
+
)}
|
|
561
|
+
|
|
562
|
+
<input
|
|
563
|
+
id={field.name}
|
|
564
|
+
type="text"
|
|
565
|
+
placeholder={config.placeholder}
|
|
566
|
+
aria-invalid={fieldState.invalid}
|
|
567
|
+
aria-describedby={fieldState.error ? `${field.name}-error` : undefined}
|
|
568
|
+
{...field}
|
|
569
|
+
/>
|
|
570
|
+
|
|
571
|
+
{fieldState.error && (
|
|
572
|
+
<span id={`${field.name}-error`} role="alert">
|
|
573
|
+
{fieldState.error.message}
|
|
574
|
+
</span>
|
|
575
|
+
)}
|
|
576
|
+
</div>
|
|
577
|
+
);
|
|
578
|
+
};
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
## Development
|
|
582
|
+
|
|
583
|
+
### Scripts
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
pnpm dev # Start dev server (localhost:3000)
|
|
587
|
+
pnpm build # Build library
|
|
588
|
+
pnpm test # Run tests
|
|
589
|
+
pnpm test:watch # Run tests in watch mode
|
|
590
|
+
pnpm typecheck # TypeScript type checking
|
|
591
|
+
pnpm lint # Check for lint errors
|
|
592
|
+
pnpm lint:fix # Auto-fix lint errors
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Project Structure
|
|
596
|
+
|
|
597
|
+
```text
|
|
598
|
+
src/
|
|
599
|
+
├── components/ # React components
|
|
600
|
+
│ ├── FormRenderer # Renders all elements
|
|
601
|
+
│ ├── ElementRenderer # Routes to field/container
|
|
602
|
+
│ ├── FieldRenderer # Renders fields via registry
|
|
603
|
+
│ └── ContainerRenderer # Renders layouts
|
|
604
|
+
├── context/ # React context
|
|
605
|
+
├── hooks/ # useDynamicFormContext
|
|
606
|
+
├── parser/ # Config parsing
|
|
607
|
+
├── schema/ # Zod schema generation
|
|
608
|
+
├── resolver/ # Visibility-aware resolver
|
|
609
|
+
├── validation/ # JSON Logic evaluation
|
|
610
|
+
├── types/ # TypeScript definitions
|
|
611
|
+
└── utils/ # Utilities
|
|
612
|
+
|
|
613
|
+
sample/ # Sample application
|
|
614
|
+
├── App.tsx # Demo form
|
|
615
|
+
├── fields/ # Sample field components
|
|
616
|
+
└── containers/ # Sample containers
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### Running the Sample App
|
|
620
|
+
|
|
621
|
+
```bash
|
|
622
|
+
pnpm dev
|
|
623
|
+
# Open http://localhost:3000
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
## Tech Stack
|
|
627
|
+
|
|
628
|
+
- **React 19** - UI framework
|
|
629
|
+
- **react-hook-form** - Form state management
|
|
630
|
+
- **Zod v4** - Schema validation
|
|
631
|
+
- **TypeScript** - Type safety
|
|
632
|
+
- **Vitest** - Testing
|
|
633
|
+
- **tsdown** - Library bundling (ESM + CJS)
|
|
634
|
+
- **Vite** - Dev server
|
|
635
|
+
|
|
636
|
+
## Contributing
|
|
637
|
+
|
|
638
|
+
### Branch Naming Convention
|
|
639
|
+
|
|
640
|
+
Create branches using the format: `type/description`
|
|
641
|
+
|
|
642
|
+
| Type | Purpose | Example |
|
|
643
|
+
|------|---------|---------|
|
|
644
|
+
| `feat/` | New features | `feat/custom-validators` |
|
|
645
|
+
| `fix/` | Bug fixes | `fix/nested-path-resolution` |
|
|
646
|
+
| `refactor/` | Code refactoring | `refactor/schema-generation` |
|
|
647
|
+
| `docs/` | Documentation | `docs/api-reference` |
|
|
648
|
+
| `chore/` | Maintenance tasks | `chore/update-dependencies` |
|
|
649
|
+
| `test/` | Test additions/fixes | `test/array-field-coverage` |
|
|
650
|
+
|
|
651
|
+
### Commit Messages
|
|
652
|
+
|
|
653
|
+
This project uses [Conventional Commits](https://www.conventionalcommits.org/) for automated versioning and changelog generation.
|
|
654
|
+
|
|
655
|
+
**Format:**
|
|
656
|
+
```text
|
|
657
|
+
type(scope): description
|
|
658
|
+
|
|
659
|
+
[optional body]
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Examples:**
|
|
663
|
+
```bash
|
|
664
|
+
feat(schema): add support for custom validators
|
|
665
|
+
fix(components): resolve visibility calculation bug
|
|
666
|
+
refactor(parser): simplify configuration parsing logic
|
|
667
|
+
docs(readme): add contributing guidelines
|
|
668
|
+
chore(deps): update react-hook-form to v7.72
|
|
669
|
+
test(utils): add edge case tests for flattenFields
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
| Type | Description | Version Bump |
|
|
673
|
+
|------|-------------|--------------|
|
|
674
|
+
| `feat` | New feature | Minor (1.0.0 → 1.1.0) |
|
|
675
|
+
| `fix` | Bug fix | Patch (1.0.0 → 1.0.1) |
|
|
676
|
+
| `feat!` or `BREAKING CHANGE` | Breaking change | Major (1.0.0 → 2.0.0) |
|
|
677
|
+
| `docs`, `chore`, `refactor`, `test` | Non-release changes | None |
|
|
678
|
+
|
|
679
|
+
For detailed release workflow, see [docs/release-workflow.md](docs/release-workflow.md).
|
|
680
|
+
|
|
681
|
+
### Pull Request Process
|
|
682
|
+
|
|
683
|
+
1. Create a branch from `main` using the naming convention above
|
|
684
|
+
2. Make your changes with conventional commit messages
|
|
685
|
+
3. Ensure all checks pass: `pnpm test && pnpm typecheck && pnpm lint`
|
|
686
|
+
4. Open a pull request to `main`
|
|
687
|
+
5. Address review feedback
|
|
688
|
+
6. Squash and merge (or rebase) when approved
|
|
689
|
+
|
|
690
|
+
## License
|
|
691
|
+
|
|
692
|
+
MIT
|