@sio-group/form-react 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/CHANGELOG.md +97 -0
- package/README.md +783 -0
- package/ROADMAP.md +21 -0
- package/dist/index.cjs +1919 -0
- package/dist/index.d.cts +284 -0
- package/dist/index.d.ts +284 -0
- package/dist/index.js +1878 -0
- package/dist/styles/components/button.css +244 -0
- package/dist/styles/components/button.css.map +1 -0
- package/dist/styles/components/checkbox.css +90 -0
- package/dist/styles/components/checkbox.css.map +1 -0
- package/dist/styles/components/color.css +31 -0
- package/dist/styles/components/color.css.map +1 -0
- package/dist/styles/components/form-field.css +36 -0
- package/dist/styles/components/form-field.css.map +1 -0
- package/dist/styles/components/form-states.css +80 -0
- package/dist/styles/components/form-states.css.map +1 -0
- package/dist/styles/components/grid.css +818 -0
- package/dist/styles/components/grid.css.map +1 -0
- package/dist/styles/components/input.css +112 -0
- package/dist/styles/components/input.css.map +1 -0
- package/dist/styles/components/link.css +113 -0
- package/dist/styles/components/link.css.map +1 -0
- package/dist/styles/components/radio.css +104 -0
- package/dist/styles/components/radio.css.map +1 -0
- package/dist/styles/components/range.css +54 -0
- package/dist/styles/components/range.css.map +1 -0
- package/dist/styles/components/select.css +37 -0
- package/dist/styles/components/select.css.map +1 -0
- package/dist/styles/components/upload.css +54 -0
- package/dist/styles/components/upload.css.map +1 -0
- package/dist/styles/index.css +1733 -0
- package/dist/styles/index.css.map +1 -0
- package/package.json +42 -0
- package/screenshots/contact-form.png +0 -0
- package/screenshots/file-input.png +0 -0
- package/screenshots/invalid-username.png +0 -0
- package/screenshots/number-field.png +0 -0
- package/screenshots/radio-field.png +0 -0
- package/screenshots/range-field.png +0 -0
- package/screenshots/registration-form.png +0 -0
- package/screenshots/select-field.png +0 -0
- package/screenshots/textarea-field.png +0 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
# @sio-group/form-react
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/ISC)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
<!--
|
|
8
|
+
[](https://www.npmjs.com/package/@sio-group/form-validation)
|
|
9
|
+
[](https://www.npmjs.com/package/@sio-group/form-validation)
|
|
10
|
+
-->
|
|
11
|
+
|
|
12
|
+
A powerful, type-safe React form framework. This package provides ready-to-use form components with built-in validation, layout management, and extensive customization options. This package is designed to work seamlessly with `@sio-group/form-builder` and `@sio-group/form-validation`, but can be used independently.
|
|
13
|
+
|
|
14
|
+
Part of the SIO Form ecosystem, it consumes form definitions from `@sio-group/form-builder` and renders them with full type safety and accessibility in mind.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @sio-group/form-react
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Peer Dependencies:**
|
|
25
|
+
- `react`: ^18.0.0
|
|
26
|
+
- `react-dom`: ^18.0.0
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick Example
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { Form } from '@sio-group/form-react';
|
|
34
|
+
import { formBuilder } from '@sio-group/form-builder';
|
|
35
|
+
|
|
36
|
+
function ContactForm() {
|
|
37
|
+
const fields = formBuilder()
|
|
38
|
+
.addText('name', {
|
|
39
|
+
label: 'Full name',
|
|
40
|
+
required: true,
|
|
41
|
+
placeholder: 'John Doe'
|
|
42
|
+
})
|
|
43
|
+
.addEmail('email', {
|
|
44
|
+
label: 'Email address',
|
|
45
|
+
required: true,
|
|
46
|
+
placeholder: 'john@example.com'
|
|
47
|
+
})
|
|
48
|
+
.addTextarea('message', {
|
|
49
|
+
label: 'Message',
|
|
50
|
+
rows: 5,
|
|
51
|
+
placeholder: 'Your message...'
|
|
52
|
+
})
|
|
53
|
+
.getFields();
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Form
|
|
57
|
+
fields={fields}
|
|
58
|
+
submitAction={(values) => console.log('Form submitted:', values)}
|
|
59
|
+
submitLabel="Send Message"
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Voorbeeld
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
*A simple contact form*
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Features
|
|
73
|
+
|
|
74
|
+
- ✅ **Type-safe** - Full TypeScript support with inferred types
|
|
75
|
+
- ✅ **Built-in validation** - Automatic validation based on field configuration
|
|
76
|
+
- ✅ **Responsive layout** - Grid system with breakpoints (sm, md, lg)
|
|
77
|
+
- ✅ **Customizable buttons** - Multiple button variants and colors
|
|
78
|
+
- ✅ **Form state management** - Tracks dirty, touched, focused, and error states
|
|
79
|
+
- ✅ **Accessibility** - ARIA attributes and keyboard navigation
|
|
80
|
+
- ✅ **Offline support** - Disable forms when offline
|
|
81
|
+
- ✅ **File uploads** - Built-in file handling with validation
|
|
82
|
+
- ✅ **Icons** - Support for HTML and component icons
|
|
83
|
+
- ✅ **Flexible containers** - Custom form and button containers
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Core Components
|
|
88
|
+
|
|
89
|
+
### Form Component
|
|
90
|
+
|
|
91
|
+
The main component that renders your form with all fields and buttons.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { Form } from '@sio-group/form-react';
|
|
95
|
+
|
|
96
|
+
<Form
|
|
97
|
+
fields={fields}
|
|
98
|
+
layout={layoutConfig}
|
|
99
|
+
submitShow={true}
|
|
100
|
+
submitAction={handleSubmit}
|
|
101
|
+
submitLabel="Save"
|
|
102
|
+
cancelShow={true}
|
|
103
|
+
cancelAction={handleCancel}
|
|
104
|
+
cancelLabel="Cancel"
|
|
105
|
+
buttons={buttonConfig}
|
|
106
|
+
extraValidation={customGlobalValidation}
|
|
107
|
+
className="my-form"
|
|
108
|
+
container={CustomContainer}
|
|
109
|
+
buttonContainer={CustomButtonContainer}
|
|
110
|
+
/>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Individual Field Components
|
|
114
|
+
|
|
115
|
+
You can also use field components independently:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import {
|
|
119
|
+
useForm,
|
|
120
|
+
Input,
|
|
121
|
+
Textarea,
|
|
122
|
+
Select,
|
|
123
|
+
Radio,
|
|
124
|
+
Checkbox,
|
|
125
|
+
NumberInput,
|
|
126
|
+
RangeInput,
|
|
127
|
+
DateInput,
|
|
128
|
+
FileInput,
|
|
129
|
+
TextInput
|
|
130
|
+
} from '@sio-group/form-react';
|
|
131
|
+
|
|
132
|
+
// Use with useForm hook
|
|
133
|
+
const { register } = useForm();
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Input {...register('username', fieldConfig)} />
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## API Reference
|
|
143
|
+
|
|
144
|
+
### Form Props
|
|
145
|
+
|
|
146
|
+
| Prop | Type | Default | Description |
|
|
147
|
+
|----------------------|--------------------------------|--------------------------|------------------------------|
|
|
148
|
+
| `fields` | `FormField[]` | (required) | Array of form fields |
|
|
149
|
+
| `submitAction` | `(values: any) => void` | (required) | Submit handler |
|
|
150
|
+
| `layout` | `FormLayout[]` | `[]` | Custom layout configuration |
|
|
151
|
+
| `submitShow` | `boolean` | `true` | Show submit button |
|
|
152
|
+
| `submitLabel` | `string` | `'Bewaar'` | Submit button text |
|
|
153
|
+
| `cancelShow` | `boolean` | `false` | Show cancel button |
|
|
154
|
+
| `cancelLabel` | `string` | `'Annuleren'` | Cancel button text |
|
|
155
|
+
| `cancelAction` | `() => void` | - | Cancel handler |
|
|
156
|
+
| `buttons` | `(ButtonProps \| LinkProps)[]` | `[]` | Additional buttons |
|
|
157
|
+
| `extraValidation` | `(values: any) => boolean` | `() => true` | Extra validation |
|
|
158
|
+
| `className` | `string` | - | CSS class for form container |
|
|
159
|
+
| `style` | `React.CSSProperties` | - | Inline styles |
|
|
160
|
+
| `disableWhenOffline` | `boolean` | `true` | Disable form when offline |
|
|
161
|
+
| `container` | `React.ComponentType` | `DefaultContainer` | Custom form container |
|
|
162
|
+
| `buttonContainer` | `React.ComponentType` | `DefaultButtonContainer` | Custom button container |
|
|
163
|
+
|
|
164
|
+
### Layout Configuration
|
|
165
|
+
|
|
166
|
+
The `layout` prop allows you to arrange fields in a responsive grid:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
const layout = [
|
|
170
|
+
{
|
|
171
|
+
layout: { sm: 12, md: 6, lg: 4 }, // Responsive column spans
|
|
172
|
+
fields: ['name', 'email'] // Fields in this row
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
layout: { sm: 12, md: 12, lg: 8 },
|
|
176
|
+
fields: ['message'],
|
|
177
|
+
className: 'custom-row', // Optional CSS class
|
|
178
|
+
style: { marginBottom: '2rem' } // Optional inline styles
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Breakpoint options:**
|
|
184
|
+
- `sm`: Small (≥640px)
|
|
185
|
+
- `md`: Medium (≥768px)
|
|
186
|
+
- `lg`: Large (≥1024px)
|
|
187
|
+
|
|
188
|
+
### Button Configuration
|
|
189
|
+
|
|
190
|
+
Buttons can be configured as an array:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
const buttons = [
|
|
194
|
+
{
|
|
195
|
+
type: 'button',
|
|
196
|
+
variant: 'primary',
|
|
197
|
+
color: 'success',
|
|
198
|
+
label: 'Save Draft',
|
|
199
|
+
onClick: () => saveDraft()
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: 'button',
|
|
203
|
+
variant: 'secondary',
|
|
204
|
+
color: 'warning',
|
|
205
|
+
label: 'Reset',
|
|
206
|
+
onClick: () => reset()
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: 'link',
|
|
210
|
+
variant: 'link',
|
|
211
|
+
color: 'info',
|
|
212
|
+
label: 'Help',
|
|
213
|
+
href: '/help'
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Button Props:**
|
|
219
|
+
- `type: 'button' | 'link'`
|
|
220
|
+
- `variant: 'primary' | 'secondary' | 'link'`
|
|
221
|
+
- `color: 'default' | 'success' | 'warning' | 'error' | 'info'`
|
|
222
|
+
- `label: string`
|
|
223
|
+
- `onClick?: () => void` (for buttons)
|
|
224
|
+
- `href?: string` (for links)
|
|
225
|
+
- `loading?: boolean`
|
|
226
|
+
- `disabled?: boolean`
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Hooks
|
|
231
|
+
|
|
232
|
+
### useForm
|
|
233
|
+
|
|
234
|
+
A powerful hook for managing form state independently:
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
import { useForm } from '@sio-group/form-react';
|
|
238
|
+
|
|
239
|
+
function CustomForm() {
|
|
240
|
+
const { register, getValues, isValid, isBusy, reset, submit } = useForm();
|
|
241
|
+
|
|
242
|
+
const handleSubmit = async () => {
|
|
243
|
+
await submit(async (values) => {
|
|
244
|
+
await api.save(values);
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<form onSubmit={handleSubmit}>
|
|
250
|
+
<Input {...register('username', fieldConfig)} />
|
|
251
|
+
<button type="submit" disabled={!isValid() || isBusy()}>
|
|
252
|
+
Submit
|
|
253
|
+
</button>
|
|
254
|
+
</form>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**useForm Return Value:**
|
|
260
|
+
|
|
261
|
+
| Method | Description |
|
|
262
|
+
|--------------------------|--------------------------------|
|
|
263
|
+
| `register(name, config)` | Register a field and get props |
|
|
264
|
+
| `unregister(name)` | Remove a field from form |
|
|
265
|
+
| `setValue(name, value)` | Set field value |
|
|
266
|
+
| `getValues()` | Get all form values |
|
|
267
|
+
| `getValue(name)` | Get single field value |
|
|
268
|
+
| `reset()` | Reset form state |
|
|
269
|
+
| `isValid()` | Check if form is valid |
|
|
270
|
+
| `isDirty()` | Check if form has changes |
|
|
271
|
+
| `isBusy()` | Check if form is submitting |
|
|
272
|
+
| `submit(handler)` | Submit form with handler |
|
|
273
|
+
| `getField(name)` | Get field state |
|
|
274
|
+
|
|
275
|
+
### Conditional Fields
|
|
276
|
+
|
|
277
|
+
You can dynamically render fields based on other field values.
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
import { useForm, Input, Checkbox } from '@sio-group/form-react';
|
|
281
|
+
|
|
282
|
+
function Example() {
|
|
283
|
+
const { register, getValue } = useForm();
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<>
|
|
287
|
+
<Checkbox
|
|
288
|
+
{...register('subscribe', {
|
|
289
|
+
name: 'subscribe',
|
|
290
|
+
type: 'checkbox',
|
|
291
|
+
config: { label: 'Subscribe to newsletter' }
|
|
292
|
+
})}
|
|
293
|
+
/>
|
|
294
|
+
|
|
295
|
+
{getValue('subscribe') && (
|
|
296
|
+
<Input
|
|
297
|
+
{...register('email', {
|
|
298
|
+
name: 'email',
|
|
299
|
+
type: 'email',
|
|
300
|
+
config: {
|
|
301
|
+
label: 'Email address',
|
|
302
|
+
required: true
|
|
303
|
+
}
|
|
304
|
+
})}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
307
|
+
</>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Custom Validation
|
|
313
|
+
|
|
314
|
+
Default validation is automatically derived from the field configuration (`required`, `min`, `max`, `email`, etc.).
|
|
315
|
+
Additional validation rules can be added using the validations array.
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
import { Input } from '@sio-group/form-react';
|
|
319
|
+
|
|
320
|
+
<Input
|
|
321
|
+
{...register('username', {
|
|
322
|
+
name: 'username',
|
|
323
|
+
type: 'text',
|
|
324
|
+
config: {
|
|
325
|
+
label: 'Username',
|
|
326
|
+
required: true,
|
|
327
|
+
validations: [
|
|
328
|
+
(value) => value.length < 3 ? 'Username must contain at least 3 characters' : null,
|
|
329
|
+
(value) => value.includes('admin') ? 'Username cannot contain admin' : null
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
})}
|
|
333
|
+
/>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+

|
|
337
|
+
*Custom validation for admin in username*
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Field Components
|
|
342
|
+
|
|
343
|
+
### Input Types
|
|
344
|
+
|
|
345
|
+
All standard HTML input types are supported:
|
|
346
|
+
|
|
347
|
+
- `text`, `search`, `email`, `tel`, `password`, `url`
|
|
348
|
+
- `number` (with spinner options)
|
|
349
|
+
- `range` (with value display)
|
|
350
|
+
- `date`, `time`, `datetime-local`
|
|
351
|
+
- `color`
|
|
352
|
+
- `hidden`
|
|
353
|
+
- `file` (with file validation)
|
|
354
|
+
|
|
355
|
+
### Specialized Components
|
|
356
|
+
|
|
357
|
+
#### NumberInput
|
|
358
|
+
```tsx
|
|
359
|
+
<NumberInput
|
|
360
|
+
{...register('age', {
|
|
361
|
+
name: "age",
|
|
362
|
+
type: "number",
|
|
363
|
+
config: {
|
|
364
|
+
label: "Age",
|
|
365
|
+
min: 0,
|
|
366
|
+
max: 120,
|
|
367
|
+
step: 1,
|
|
368
|
+
spinner: 'horizontal' // or "vertical", true, false
|
|
369
|
+
}
|
|
370
|
+
}) as NumberFieldProps}
|
|
371
|
+
/>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+

|
|
375
|
+
*Number input with horizontal spinner*
|
|
376
|
+
|
|
377
|
+
#### RangeInput
|
|
378
|
+
```tsx
|
|
379
|
+
<RangeInput
|
|
380
|
+
{...register('volume', {
|
|
381
|
+
name: "volume",
|
|
382
|
+
type: "range",
|
|
383
|
+
config: {
|
|
384
|
+
label: "Volume",
|
|
385
|
+
min: 0,
|
|
386
|
+
max: 120,
|
|
387
|
+
step: 1,
|
|
388
|
+
showValue: true,
|
|
389
|
+
}
|
|
390
|
+
}) as NumberFieldProps}
|
|
391
|
+
/>
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+

|
|
395
|
+
*Range input with shown value*
|
|
396
|
+
|
|
397
|
+
#### FileInput
|
|
398
|
+
```tsx
|
|
399
|
+
<FileInput
|
|
400
|
+
{...register('documents', {
|
|
401
|
+
name: "documents",
|
|
402
|
+
type: "file",
|
|
403
|
+
config: {
|
|
404
|
+
label: "Documenten",
|
|
405
|
+
accept: ".pdf,.doc",
|
|
406
|
+
multiple: true,
|
|
407
|
+
filesize: 5120, // 5MB in KB
|
|
408
|
+
capture: false,
|
|
409
|
+
onFileRemove: (file) => console.log('Removed:', file),
|
|
410
|
+
onRemoveAll: (files) => console.log('All removed:', files)
|
|
411
|
+
}
|
|
412
|
+
}) as FileFieldProps}
|
|
413
|
+
/>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+

|
|
417
|
+
*Multiple file input*
|
|
418
|
+
|
|
419
|
+
#### Select
|
|
420
|
+
```tsx
|
|
421
|
+
<Select
|
|
422
|
+
{...register('country', {
|
|
423
|
+
name: 'country',
|
|
424
|
+
type: 'select',
|
|
425
|
+
config: {
|
|
426
|
+
options: [
|
|
427
|
+
{ value: 'be', label: 'Belgium' },
|
|
428
|
+
{ value: 'nl', label: 'Netherlands' },
|
|
429
|
+
{
|
|
430
|
+
label: 'Europe',
|
|
431
|
+
options: [
|
|
432
|
+
{ value: 'fr', label: 'France' }
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
],
|
|
436
|
+
multiple: false,
|
|
437
|
+
placeholder: "Choose a country"
|
|
438
|
+
}
|
|
439
|
+
}) as SelectFieldProps}
|
|
440
|
+
/>
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+

|
|
444
|
+
*Single select input*
|
|
445
|
+
|
|
446
|
+
#### Radio
|
|
447
|
+
```tsx
|
|
448
|
+
<Radio
|
|
449
|
+
{...register('country', {
|
|
450
|
+
name: "country",
|
|
451
|
+
type: "radio",
|
|
452
|
+
config: {
|
|
453
|
+
label: "Country",
|
|
454
|
+
options: ['Red', 'Green', 'Blue'],
|
|
455
|
+
inline: true
|
|
456
|
+
}
|
|
457
|
+
}) as RadioFieldProps}
|
|
458
|
+
/>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+

|
|
462
|
+
*Inline radio input*
|
|
463
|
+
|
|
464
|
+
#### Textarea
|
|
465
|
+
```tsx
|
|
466
|
+
<Textarea
|
|
467
|
+
{...register('bio', {
|
|
468
|
+
name: "bio",
|
|
469
|
+
type: "textarea",
|
|
470
|
+
config: {
|
|
471
|
+
label: "Bio",
|
|
472
|
+
placeholder: "Tell us about yourself...",
|
|
473
|
+
rows: 5,
|
|
474
|
+
cols: 40,
|
|
475
|
+
}
|
|
476
|
+
}) as TextareaFieldProps}
|
|
477
|
+
/>
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+

|
|
481
|
+
*Textarea input*
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## Standalone Components
|
|
486
|
+
|
|
487
|
+
### Controlled Usage (with `useForm`)
|
|
488
|
+
|
|
489
|
+
You can use the `useForm` hook to control how you use the form, centrally managing state, validation, and submission. This also demonstrates conditional rendering and error automation.
|
|
490
|
+
|
|
491
|
+
```javascript
|
|
492
|
+
import { useForm, Input, Button } from '@sio-group/form-react';
|
|
493
|
+
|
|
494
|
+
function FormWithHook() {
|
|
495
|
+
const { register, getValue, isValid, isBusy, submit } = useForm();
|
|
496
|
+
|
|
497
|
+
const handleSubmit = async (e) => {
|
|
498
|
+
e.preventDefault();
|
|
499
|
+
await submit(values => console.log('Form values:', values));
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<form noValidate>
|
|
504
|
+
<Input
|
|
505
|
+
{...register('email', {
|
|
506
|
+
name: 'email',
|
|
507
|
+
type: 'email',
|
|
508
|
+
config: {
|
|
509
|
+
label: 'Email Address',
|
|
510
|
+
required: true,
|
|
511
|
+
validations: [
|
|
512
|
+
val => val.includes('@') ? null : 'Must be a valid email',
|
|
513
|
+
]
|
|
514
|
+
}
|
|
515
|
+
})}
|
|
516
|
+
/>
|
|
517
|
+
|
|
518
|
+
<Button
|
|
519
|
+
type="submit"
|
|
520
|
+
variant="primary"
|
|
521
|
+
disabled={!isValid()}
|
|
522
|
+
onClick={handleSubmit}
|
|
523
|
+
>
|
|
524
|
+
{isBusy() ? 'sending' : 'Submit'}
|
|
525
|
+
</Button>
|
|
526
|
+
|
|
527
|
+
{getValue('email') && <p>Your email: {getValue('email')}</p>}
|
|
528
|
+
</form>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Uncontrolled Usage (without `useForm`)
|
|
534
|
+
|
|
535
|
+
All field components and buttons can also be used independently. You can keep and manage state for the form depending on your needs.
|
|
536
|
+
|
|
537
|
+
```javascript
|
|
538
|
+
import { useState } from 'react';
|
|
539
|
+
import { Input, Button } from '@sio-group/form-react';
|
|
540
|
+
|
|
541
|
+
function SimpleForm() {
|
|
542
|
+
const [value, setValue] = useState('');
|
|
543
|
+
const [error, setError] = useState('');
|
|
544
|
+
const [touched, setTouched] = useState(false);
|
|
545
|
+
const [focused, setFocused] = useState(false);
|
|
546
|
+
const [isValid, setIsValid] = useState(false);
|
|
547
|
+
|
|
548
|
+
const handleChange = (value) => {
|
|
549
|
+
if (!value) {
|
|
550
|
+
setError("This is wrong");
|
|
551
|
+
setIsValid(false);
|
|
552
|
+
} else {
|
|
553
|
+
setError("");
|
|
554
|
+
setIsValid(true);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
setValue(value);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const handleSubmit = (e) => {
|
|
561
|
+
e.preventDefault();
|
|
562
|
+
console.log(value);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return (
|
|
566
|
+
<form noValidate>
|
|
567
|
+
<TextInput
|
|
568
|
+
type="email"
|
|
569
|
+
id="email"
|
|
570
|
+
name="email"
|
|
571
|
+
value={value}
|
|
572
|
+
errors={error ? [error] : []}
|
|
573
|
+
touched={touched}
|
|
574
|
+
focused={focused}
|
|
575
|
+
disabled={false}
|
|
576
|
+
onChange={handleChange}
|
|
577
|
+
setFocused={setFocused}
|
|
578
|
+
setTouched={setTouched}
|
|
579
|
+
/>
|
|
580
|
+
|
|
581
|
+
<Button
|
|
582
|
+
type="submit"
|
|
583
|
+
variant="primary"
|
|
584
|
+
onClick={handleSubmit}
|
|
585
|
+
disabled={!isValid}
|
|
586
|
+
>
|
|
587
|
+
Submit
|
|
588
|
+
</Button>
|
|
589
|
+
</form>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Button Props
|
|
595
|
+
| Prop | Type | Default | Description |
|
|
596
|
+
|------------|------------------------------------------------------------|-------------|-------------------------|
|
|
597
|
+
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | Button type |
|
|
598
|
+
| `label` | `string` | - | Label of the Link |
|
|
599
|
+
| `onClick` | `(e: React.MouseEvent) => void` | - | Custom onClick function |
|
|
600
|
+
| `variant` | `'primary' \| 'secondary' \| 'link'` | `'primary'` | Visual variant |
|
|
601
|
+
| `color` | `'default' \| 'error' \| 'success' \| 'warning' \| 'info'` | `'default'` | Color theme |
|
|
602
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
|
|
603
|
+
| `block` | `boolean` | `false` | Full width |
|
|
604
|
+
| `loading` | `boolean` | `false` | Loading state |
|
|
605
|
+
| `disabled` | `boolean` | `false` | Disabled state |
|
|
606
|
+
|
|
607
|
+
### Link Props
|
|
608
|
+
| Prop | Type | Default | Description |
|
|
609
|
+
|------------|------------------------------------------------------------|-------------|--------------------------|
|
|
610
|
+
| `label` | `string` | - | Label of the Link |
|
|
611
|
+
| `to` | `string` | `'#'` | URL or pad |
|
|
612
|
+
| `onClick` | `(e: React.MouseEvent) => void` | - | Custom onClick function |
|
|
613
|
+
| `color` | `'default' \| 'error' \| 'success' \| 'warning' \| 'info'` | `'default'` | Color theme |
|
|
614
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
|
|
615
|
+
| `block` | `boolean` | `false` | Full width |
|
|
616
|
+
| `loading` | `boolean` | `false` | Loading state |
|
|
617
|
+
| `disabled` | `boolean` | `false` | Disabled state |
|
|
618
|
+
| `navigate` | `() => void` | - | Custom navigate function |
|
|
619
|
+
| `external` | `boolean` | `false` | Force external link |
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
## Styling
|
|
625
|
+
|
|
626
|
+
### Default Styles
|
|
627
|
+
|
|
628
|
+
Import the default styles:
|
|
629
|
+
|
|
630
|
+
```tsx
|
|
631
|
+
import '@sio-group/form-react/sio-form-style.css';
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Custom Styling
|
|
635
|
+
|
|
636
|
+
Each component accepts `className` and `style` props for custom styling:
|
|
637
|
+
|
|
638
|
+
```tsx
|
|
639
|
+
<Input
|
|
640
|
+
{...register('username', {
|
|
641
|
+
name: 'username',
|
|
642
|
+
type: 'text',
|
|
643
|
+
config: {
|
|
644
|
+
styling: {
|
|
645
|
+
className: 'custom-input',
|
|
646
|
+
style: {
|
|
647
|
+
backgroundColor: '#f0f0f0'
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
})}
|
|
652
|
+
/>
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Layout Classes
|
|
656
|
+
|
|
657
|
+
The form uses a responsive grid system. You can target these classes:
|
|
658
|
+
|
|
659
|
+
- `.sio-row` - Grid container
|
|
660
|
+
- `.sio-col-xs-*` - Column classes for breakpoints
|
|
661
|
+
- `.sio-col-sm-*`
|
|
662
|
+
- `.sio-col-md-*`
|
|
663
|
+
- `.sio-col-lg-*`
|
|
664
|
+
- `.sio-col-xl-*`
|
|
665
|
+
|
|
666
|
+
Example with Tailwind CSS:
|
|
667
|
+
```tsx
|
|
668
|
+
<Form
|
|
669
|
+
fields={fields}
|
|
670
|
+
className="max-w-2xl mx-auto"
|
|
671
|
+
layout={[
|
|
672
|
+
{ layout: { md: 6 }, fields: ['firstName'], className: 'pr-2' },
|
|
673
|
+
{ layout: { md: 6 }, fields: ['lastName'], className: 'pl-2' }
|
|
674
|
+
]}
|
|
675
|
+
/>
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## Complete Example
|
|
681
|
+
|
|
682
|
+
```tsx
|
|
683
|
+
import { Form } from '@sio-group/form-react';
|
|
684
|
+
import { formBuilder } from '@sio-group/form-builder';
|
|
685
|
+
import '@sio-group/form-react/sio-form-style.css';
|
|
686
|
+
|
|
687
|
+
function RegistrationForm() {
|
|
688
|
+
const fields = formBuilder()
|
|
689
|
+
.addText('firstName', {
|
|
690
|
+
label: 'First name',
|
|
691
|
+
required: true,
|
|
692
|
+
layout: { md: 6 }
|
|
693
|
+
})
|
|
694
|
+
.addText('lastName', {
|
|
695
|
+
label: 'Last name',
|
|
696
|
+
required: true,
|
|
697
|
+
layout: { md: 6 }
|
|
698
|
+
})
|
|
699
|
+
.addEmail('email', {
|
|
700
|
+
label: 'Email',
|
|
701
|
+
required: true,
|
|
702
|
+
layout: { md: 6 }
|
|
703
|
+
})
|
|
704
|
+
.addTelephone('phone', {
|
|
705
|
+
label: 'Phone',
|
|
706
|
+
layout: { md: 6 }
|
|
707
|
+
})
|
|
708
|
+
.addPassword('password', {
|
|
709
|
+
label: 'Password',
|
|
710
|
+
required: true,
|
|
711
|
+
layout: { md: 6 }
|
|
712
|
+
})
|
|
713
|
+
.addPassword('confirmPassword', {
|
|
714
|
+
label: 'Confirm password',
|
|
715
|
+
required: true,
|
|
716
|
+
layout: { md: 6 }
|
|
717
|
+
})
|
|
718
|
+
.addCheckbox('terms', {
|
|
719
|
+
label: 'I accept the terms and conditions',
|
|
720
|
+
required: true
|
|
721
|
+
})
|
|
722
|
+
.getFields();
|
|
723
|
+
|
|
724
|
+
const handleSubmit = (values) => {
|
|
725
|
+
console.log('Registration:', values);
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const customButtons = [
|
|
729
|
+
{
|
|
730
|
+
type: 'button',
|
|
731
|
+
variant: 'secondary',
|
|
732
|
+
color: 'info',
|
|
733
|
+
label: 'Clear form',
|
|
734
|
+
onClick: () => console.log('Clear')
|
|
735
|
+
}
|
|
736
|
+
];
|
|
737
|
+
|
|
738
|
+
return (
|
|
739
|
+
<Form
|
|
740
|
+
fields={fields}
|
|
741
|
+
submitAction={handleSubmit}
|
|
742
|
+
submitLabel="Register"
|
|
743
|
+
cancelShow={true}
|
|
744
|
+
cancelAction={() => console.log('Cancelled')}
|
|
745
|
+
buttons={customButtons}
|
|
746
|
+
className="registration-form"
|
|
747
|
+
layout={[
|
|
748
|
+
{ layout: { md: 6 }, fields: ['firstName'] },
|
|
749
|
+
{ layout: { md: 6 }, fields: ['lastName'] },
|
|
750
|
+
{ layout: { md: 6 }, fields: ['email'] },
|
|
751
|
+
{ layout: { md: 6 }, fields: ['phone'] },
|
|
752
|
+
{ layout: { md: 6 }, fields: ['password'] },
|
|
753
|
+
{ layout: { md: 6 }, fields: ['confirmPassword'] },
|
|
754
|
+
{ layout: { md: 12 }, fields: ['terms'] }
|
|
755
|
+
]}
|
|
756
|
+
/>
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+

|
|
762
|
+
*A simple registration form with simple layout*
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
## Ecosystem
|
|
767
|
+
|
|
768
|
+
`@sio-group/form-react` is part of the SIO Form ecosystem:
|
|
769
|
+
|
|
770
|
+
- **[@sio-group/form-types](../form-types/README.md)** - Shared type definitions
|
|
771
|
+
- **[@sio-group/form-builder](../form-builder/README.md)** - Define your form structure
|
|
772
|
+
- **[@sio-group/form-validation](../form-validation/README.md)** - Validate your data
|
|
773
|
+
- **[@sio-group/form-react](../form-react/README.md)** - This package: React renderer and hooks for the builder (you are here)
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
## Contributing
|
|
778
|
+
|
|
779
|
+
Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
780
|
+
|
|
781
|
+
## License
|
|
782
|
+
|
|
783
|
+
This project is licensed under the ISC License - see the [LICENSE](../../LICENSE) file for details.
|