@insticc/genericform 2.0.4 → 2.0.6
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 +500 -0
- package/build/index.css +1 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# @insticc/genericform
|
|
2
|
+
|
|
3
|
+
A flexible, schema-driven React form component that renders any combination of field types from a plain JavaScript array. No boilerplate, built-in validation, modal support, and full external state control out of the box.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [@insticc/genericform](#insticcgenericform)
|
|
10
|
+
- [Table of Contents](#table-of-contents)
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Peer Dependencies](#peer-dependencies)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [Field Types](#field-types)
|
|
15
|
+
- [Options format](#options-format)
|
|
16
|
+
- [Field Schema Reference](#field-schema-reference)
|
|
17
|
+
- [Common properties](#common-properties)
|
|
18
|
+
- [Style properties (all optional)](#style-properties-all-optional)
|
|
19
|
+
- [Type-specific properties](#type-specific-properties)
|
|
20
|
+
- [GenericForm Props](#genericform-props)
|
|
21
|
+
- [`customActions`](#customactions)
|
|
22
|
+
- [Validation](#validation)
|
|
23
|
+
- [Layout \& Grouping](#layout--grouping)
|
|
24
|
+
- [Inline fields](#inline-fields)
|
|
25
|
+
- [Groups](#groups)
|
|
26
|
+
- [Two-column split](#two-column-split)
|
|
27
|
+
- [External State Control](#external-state-control)
|
|
28
|
+
- [Modal Mode](#modal-mode)
|
|
29
|
+
- [Theming (CSS Variables)](#theming-css-variables)
|
|
30
|
+
- [Demo Components](#demo-components)
|
|
31
|
+
- [License](#license)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @insticc/genericform
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Import the component and its stylesheet:
|
|
42
|
+
|
|
43
|
+
```jsx
|
|
44
|
+
import GenericForm from '@insticc/genericform';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Peer Dependencies
|
|
50
|
+
|
|
51
|
+
The following must be installed in your project:
|
|
52
|
+
|
|
53
|
+
| Package | Version |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `react` | ≥ 17 |
|
|
56
|
+
| `react-dom` | ≥ 17 |
|
|
57
|
+
| `react-bootstrap` | ≥ 2 |
|
|
58
|
+
| `semantic-ui-react` | ≥ 2 |
|
|
59
|
+
| `react-datetime` | ≥ 3 |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```jsx
|
|
66
|
+
import { useState } from 'react';
|
|
67
|
+
import GenericForm from '@insticc/genericform';
|
|
68
|
+
import '@insticc/genericform/dist/index.css';
|
|
69
|
+
|
|
70
|
+
const MyForm = () => {
|
|
71
|
+
const [result, setResult] = useState(null);
|
|
72
|
+
|
|
73
|
+
const fields = [
|
|
74
|
+
{ name: 'firstName', type: 'text', label: 'First Name', required: true, value: '' },
|
|
75
|
+
{ name: 'lastName', type: 'text', label: 'Last Name', required: true, value: '' },
|
|
76
|
+
{ name: 'email', type: 'email', label: 'Email', required: true, value: '' },
|
|
77
|
+
{ name: 'age', type: 'int', label: 'Age', min: 0, max: 120, value: '' },
|
|
78
|
+
{
|
|
79
|
+
name: 'country', type: 'select', label: 'Country', value: '',
|
|
80
|
+
options: [
|
|
81
|
+
{ value: 'PT', text: 'Portugal' },
|
|
82
|
+
{ value: 'ES', text: 'Spain' },
|
|
83
|
+
{ value: 'FR', text: 'France' },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<GenericForm
|
|
90
|
+
title="New User"
|
|
91
|
+
fields={fields}
|
|
92
|
+
submitText="Save"
|
|
93
|
+
onSubmit={(data) => setResult(data)}
|
|
94
|
+
onCancel={() => setResult(null)}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The `onSubmit` callback receives the complete form data object keyed by each field's `name`.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Field Types
|
|
105
|
+
|
|
106
|
+
| `type` value | Rendered as | Key extra props |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `text` / `string` | Text input | `placeholder`, `minLength`, `maxLength` |
|
|
109
|
+
| `email` | Email input (format validated) | `placeholder` |
|
|
110
|
+
| `password` | Password input | `placeholder` |
|
|
111
|
+
| `int` / `number` | Number input | `min`, `max`, `step` |
|
|
112
|
+
| `textArea` | Textarea | `rows`, `height`, `maxLength` |
|
|
113
|
+
| `date` | Date picker (DD/MM/YYYY) | `placeholder` |
|
|
114
|
+
| `datetime` | Date + time picker | `placeholder` |
|
|
115
|
+
| `time` | Time picker (HH:mm) | `placeholder` |
|
|
116
|
+
| `year` | Year picker | `minYear`, `maxYear` |
|
|
117
|
+
| `select` | Native `<select>` | `options`, `placeholder`, `multiple` |
|
|
118
|
+
| `multipleDropdown` | Semantic UI multi-select | `options`, `placeholder` |
|
|
119
|
+
| `search` | Semantic UI Search | `options`, `isLoading`, `resultRenderer` |
|
|
120
|
+
| `toggle` | Toggle switch | — |
|
|
121
|
+
| `checkbox` | Single checkbox | `onOptionChange` |
|
|
122
|
+
| `checkboxGroup` | Group of checkboxes | `options`, `grouped`, `onOptionChange` |
|
|
123
|
+
| `radioGroup` | Stacked radio buttons | `options`, `onOptionChange` |
|
|
124
|
+
| `radioGroupInline` | Inline radio buttons | `options`, `onOptionChange` |
|
|
125
|
+
| `json` | JSON textarea editor | `datatype`, `allowModeSwitch`, `storeAsString`, `rows` |
|
|
126
|
+
| `accordion` | Collapsible section | `sections`, `defaultOpen`, `activeAllIndex` |
|
|
127
|
+
| `component` | Arbitrary JSX embed | `component` |
|
|
128
|
+
| `label` | Section heading | — |
|
|
129
|
+
| `hr` / `separator` | Horizontal rule | — |
|
|
130
|
+
|
|
131
|
+
### Options format
|
|
132
|
+
|
|
133
|
+
The `select`, `multipleDropdown`, `checkboxGroup`, and radio types all accept options in any of these shapes — the component resolves them automatically:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
{ value: 'pt', text: 'Portugal' } // most common
|
|
137
|
+
{ id: 'admin', label: 'Admin' }
|
|
138
|
+
{ name: 'read', label: 'Read' }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Field Schema Reference
|
|
144
|
+
|
|
145
|
+
Every item in the `fields` array is a plain object. The properties below apply across all (or most) field types.
|
|
146
|
+
|
|
147
|
+
### Common properties
|
|
148
|
+
|
|
149
|
+
| Property | Type | Default | Description |
|
|
150
|
+
|---|---|---|---|
|
|
151
|
+
| `name` | `string` | — | **Required.** Unique key; used as the form data key. |
|
|
152
|
+
| `type` | `string` | `'text'` | Field type (see table above). |
|
|
153
|
+
| `label` / `displayName` | `string \| node` | — | Label shown above the field. |
|
|
154
|
+
| `value` | `any` | — | Initial (or controlled) value. |
|
|
155
|
+
| `required` | `bool` | `false` | Adds `*` indicator and enforces non-empty on submit. |
|
|
156
|
+
| `disabled` | `bool` | `false` | Disables the input. |
|
|
157
|
+
| `placeholder` | `string` | `displayName` | Input placeholder text. |
|
|
158
|
+
| `tooltip` | `string` | — | Tooltip shown next to the label. |
|
|
159
|
+
| `showHelp` | `bool` | `false` | Show ⓘ help icon. |
|
|
160
|
+
| `helpText` | `string` | — | Text shown in the help icon tooltip. |
|
|
161
|
+
| `errorMessage` | `string` | auto | Custom message shown on validation failure. |
|
|
162
|
+
| `validate` | `function` | — | `(value, formData) => string \| null`. Return a string to show as error. |
|
|
163
|
+
| `onChange` | `function` | — | `(value, formData) => void`. Per-field change callback (in addition to `onFieldChange`). |
|
|
164
|
+
| `hide` | `bool \| function` | `false` | `(formData, field) => bool`. Hides the field dynamically. |
|
|
165
|
+
| `inline` | `bool` | `false` | Renders this field side-by-side with adjacent `inline` fields. |
|
|
166
|
+
| `group` | `string` | — | Groups fields under a named section heading. Groups render in alphabetical order, ungrouped fields last. |
|
|
167
|
+
|
|
168
|
+
### Style properties (all optional)
|
|
169
|
+
|
|
170
|
+
| Property | Type | Description |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `style` / `mainDivStyle` | `object` | Style applied to the outer wrapper `<div>`. |
|
|
173
|
+
| `labelStyle` | `object` | Style applied to the `<label>` element. |
|
|
174
|
+
| `spanStyle` | `object` | Style applied to the required `*` span. |
|
|
175
|
+
| `inputStyle` | `object` | Style applied to the input element. |
|
|
176
|
+
| `tooltipStyle` | `object` | Style applied to the tooltip span. |
|
|
177
|
+
| `className` | `string` | Extra CSS class for text/email/password inputs. |
|
|
178
|
+
|
|
179
|
+
### Type-specific properties
|
|
180
|
+
|
|
181
|
+
**`int` / `number`**
|
|
182
|
+
```js
|
|
183
|
+
{ name: 'score', type: 'number', min: 0, max: 100, step: 0.5, value: '' }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**`textArea`**
|
|
187
|
+
```js
|
|
188
|
+
{ name: 'bio', type: 'textArea', rows: 4, height: '120px', maxLength: 300, value: '' }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**`select`**
|
|
192
|
+
```js
|
|
193
|
+
{
|
|
194
|
+
name: 'country', type: 'select',
|
|
195
|
+
options: [{ value: 'PT', text: 'Portugal' }],
|
|
196
|
+
multiple: false, // set true for native multi-select
|
|
197
|
+
value: '',
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**`checkboxGroup`**
|
|
202
|
+
```js
|
|
203
|
+
{
|
|
204
|
+
name: 'perms', type: 'checkboxGroup',
|
|
205
|
+
value: ['read'], // array of selected names
|
|
206
|
+
options: [
|
|
207
|
+
{ name: 'read', label: 'Read' },
|
|
208
|
+
{ name: 'write', label: 'Write' },
|
|
209
|
+
{ name: 'delete', label: 'Delete' },
|
|
210
|
+
],
|
|
211
|
+
grouped: false, // true renders a visual group border
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**`json`**
|
|
216
|
+
```js
|
|
217
|
+
{
|
|
218
|
+
name: 'config', type: 'json',
|
|
219
|
+
datatype: 'object', // 'string' | 'number' | 'object'
|
|
220
|
+
allowModeSwitch: true, // toggle between single / multiple (array) mode
|
|
221
|
+
storeAsString: false, // keep raw string in formData instead of parsed value
|
|
222
|
+
rows: 6,
|
|
223
|
+
value: '',
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**`accordion`**
|
|
228
|
+
```js
|
|
229
|
+
{
|
|
230
|
+
name: 'advanced', type: 'accordion',
|
|
231
|
+
defaultOpen: false,
|
|
232
|
+
activeAllIndex: false, // open all sections by default
|
|
233
|
+
sections: [
|
|
234
|
+
{ title: 'Config', icon: '⚙️', content: <p>…</p> },
|
|
235
|
+
{ title: 'Danger zone', icon: '⚠️', content: <p>…</p> },
|
|
236
|
+
],
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**`component`**
|
|
241
|
+
```js
|
|
242
|
+
{
|
|
243
|
+
name: 'avatar', type: 'component',
|
|
244
|
+
displayName: 'Avatar preview',
|
|
245
|
+
component: <MyAvatarWidget />,
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## GenericForm Props
|
|
252
|
+
|
|
253
|
+
| Prop | Type | Default | Description |
|
|
254
|
+
|---|---|---|---|
|
|
255
|
+
| `fields` | `array` | `[]` | **Required.** Array of field schema objects. |
|
|
256
|
+
| `title` | `string \| node` | — | Form heading. |
|
|
257
|
+
| `titleIcon` | `node` | — | Icon rendered before the title. |
|
|
258
|
+
| `titleStyle` | `object` | — | Style for the title element. |
|
|
259
|
+
| `onSubmit` | `async function` | — | `(formData) => void`. Called after successful validation. |
|
|
260
|
+
| `handleSubmit` | `function` | — | Legacy: receives the native submit event instead of formData. |
|
|
261
|
+
| `onCancel` | `function` | — | Called when the Cancel button is clicked. |
|
|
262
|
+
| `onDataChange` | `function` | — | `(formData) => void`. Fired on every field change with the full snapshot. |
|
|
263
|
+
| `onFieldChange` | `function` | — | `({ name, value, option? }) => void`. Fired per field change. |
|
|
264
|
+
| `validateOnChange` | `bool` | `true` | Validate each field as the user types. |
|
|
265
|
+
| `validateOnSubmit` | `bool` | `true` | Validate all fields before calling `onSubmit`. |
|
|
266
|
+
| `submitText` | `string` | `'Submit'` | Label on the submit button. |
|
|
267
|
+
| `cancelText` | `string` | `'Cancel'` | Label on the cancel button. |
|
|
268
|
+
| `showSubmit` | `bool` | `true` | Show the submit button. |
|
|
269
|
+
| `showCancel` | `bool` | `true` | Show the cancel button. |
|
|
270
|
+
| `haveSaveButton` | `bool` | `true` | Alias for `showSubmit`. |
|
|
271
|
+
| `submitButton` | `object` | `{}` | `{ className, style, icon }` overrides for the submit button. |
|
|
272
|
+
| `cancelButton` | `object` | `{}` | `{ className, style, icon }` overrides for the cancel button. |
|
|
273
|
+
| `customActions` | `array` | `[]` | Extra buttons in the actions bar (see below). |
|
|
274
|
+
| `splitForm` | `bool` | `false` | Render fields in a two-column layout. |
|
|
275
|
+
| `splitField` | `string` | — | Field `name` at which to split left/right columns. |
|
|
276
|
+
| `splitGroup` | `bool` | `false` | Split at the group boundary instead of a specific field. |
|
|
277
|
+
| `enableModal` | `bool` | `false` | Wrap the form in a React Bootstrap `<Modal>`. |
|
|
278
|
+
| `modalShow` | `bool` | `false` | Controls modal visibility. |
|
|
279
|
+
| `modalSize` | `string` | `'lg'` | Bootstrap modal size (`'sm'`, `'lg'`, `'xl'`). |
|
|
280
|
+
| `disableDefaultContainer` | `bool` | `false` | Remove the default card/shadow wrapper `<div>`. |
|
|
281
|
+
| `containerStyle` | `object` | `{}` | Style for the outermost container. |
|
|
282
|
+
| `formStyle` | `object` | — | Style for the `<form>` element. |
|
|
283
|
+
| `fieldsStyle` | `object` | — | Style for each fields section wrapper. |
|
|
284
|
+
| `actionsDivStyle` | `object` | — | Style for the actions bar wrapper. |
|
|
285
|
+
| `actionsStyle` | `object` | — | Style applied to every action button. |
|
|
286
|
+
| `handleSearchChange` | `function` | — | Passed directly to the `search` field type. |
|
|
287
|
+
| `handleResultSelect` | `function` | — | Passed directly to the `search` field type. |
|
|
288
|
+
| `resultRenderer` | `function` | — | Custom result item renderer for the `search` type. |
|
|
289
|
+
|
|
290
|
+
### `customActions`
|
|
291
|
+
|
|
292
|
+
Inject additional buttons into the actions bar:
|
|
293
|
+
|
|
294
|
+
```jsx
|
|
295
|
+
<GenericForm
|
|
296
|
+
fields={fields}
|
|
297
|
+
customActions={[
|
|
298
|
+
{
|
|
299
|
+
label: 'Reset',
|
|
300
|
+
icon: <ResetIcon />, // optional
|
|
301
|
+
className: 'ufg-button-secondary',
|
|
302
|
+
style: {},
|
|
303
|
+
disabled: false,
|
|
304
|
+
onClick: () => resetMyForm(),
|
|
305
|
+
},
|
|
306
|
+
]}
|
|
307
|
+
/>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Validation
|
|
313
|
+
|
|
314
|
+
Validation runs on change (if `validateOnChange`) and on submit (if `validateOnSubmit`). Built-in rules cover the most common cases:
|
|
315
|
+
|
|
316
|
+
| Rule | Prop | Example |
|
|
317
|
+
|---|---|---|
|
|
318
|
+
| Required | `required: true` | Non-empty value |
|
|
319
|
+
| Email format | `type: 'email'` | Must match `x@x.x` |
|
|
320
|
+
| Min length | `minLength: 3` | At least 3 characters |
|
|
321
|
+
| Max length | `maxLength: 20` | At most 20 characters |
|
|
322
|
+
| Numeric min | `min: 0` | Value ≥ 0 |
|
|
323
|
+
| Numeric max | `max: 100` | Value ≤ 100 |
|
|
324
|
+
| Custom | `validate` function | Any logic |
|
|
325
|
+
|
|
326
|
+
**Custom validation:**
|
|
327
|
+
|
|
328
|
+
```js
|
|
329
|
+
{
|
|
330
|
+
name: 'website', type: 'text', value: '',
|
|
331
|
+
validate: (value, formData) => {
|
|
332
|
+
if (value && !value.startsWith('https://')) return 'Must start with https://';
|
|
333
|
+
return null; // no error
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Cross-field validation** — the second argument is the live form data object:
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
{
|
|
342
|
+
name: 'notifyEmail', type: 'email', value: '',
|
|
343
|
+
hide: (fd) => !fd.notify,
|
|
344
|
+
validate: (value, fd) =>
|
|
345
|
+
fd.notify && !value ? 'Required when notifications are enabled' : null,
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Override the default error message with `errorMessage`:
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
{ name: 'name', type: 'text', required: true, errorMessage: 'Please enter your full name.' }
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Layout & Grouping
|
|
358
|
+
|
|
359
|
+
### Inline fields
|
|
360
|
+
|
|
361
|
+
Set `inline: true` on consecutive fields to render them side-by-side:
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
{ name: 'firstName', type: 'text', label: 'First name', inline: true, value: '' },
|
|
365
|
+
{ name: 'lastName', type: 'text', label: 'Last name', inline: true, value: '' },
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Groups
|
|
369
|
+
|
|
370
|
+
Assign a `group` string to organise fields under labelled sections. Groups are sorted alphabetically; ungrouped fields appear last.
|
|
371
|
+
|
|
372
|
+
```js
|
|
373
|
+
{ name: 'email', type: 'email', group: 'Contact', value: '' },
|
|
374
|
+
{ name: 'phone', type: 'text', group: 'Contact', value: '' },
|
|
375
|
+
{ name: 'country', type: 'select', group: 'Address', value: '', options: [...] },
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Two-column split
|
|
379
|
+
|
|
380
|
+
```jsx
|
|
381
|
+
<GenericForm
|
|
382
|
+
fields={fields}
|
|
383
|
+
splitForm
|
|
384
|
+
splitField="address" // everything up to and including 'address' goes left
|
|
385
|
+
/>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Use `splitGroup` to split at a group boundary rather than a specific field.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## External State Control
|
|
393
|
+
|
|
394
|
+
`GenericForm` manages its own internal state, but every field's `value` prop is watched. When a `value` changes, the form merges only that field — which means you can drive specific fields from a parent `useState` without re-initialising the whole form.
|
|
395
|
+
|
|
396
|
+
The recommended pattern for full external control uses `onDataChange`:
|
|
397
|
+
|
|
398
|
+
```jsx
|
|
399
|
+
const [formState, setFormState] = useState({
|
|
400
|
+
firstName: '', lastName: '', country: '', city: '',
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Auto-fill city when country changes
|
|
404
|
+
const handleFieldChange = ({ name, value }) => {
|
|
405
|
+
if (name === 'country') {
|
|
406
|
+
setFormState(prev => ({ ...prev, city: CITY_BY_COUNTRY[value] ?? '' }));
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const fields = [
|
|
411
|
+
{ name: 'firstName', type: 'text', label: 'First name', value: formState.firstName },
|
|
412
|
+
{ name: 'lastName', type: 'text', label: 'Last name', value: formState.lastName },
|
|
413
|
+
{ name: 'country', type: 'select', label: 'Country', value: formState.country, options: COUNTRIES },
|
|
414
|
+
{ name: 'city', type: 'text', label: 'City', value: formState.city, disabled: true },
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
<GenericForm
|
|
418
|
+
fields={fields}
|
|
419
|
+
onDataChange={setFormState}
|
|
420
|
+
onFieldChange={handleFieldChange}
|
|
421
|
+
onSubmit={(data) => console.log('Saved:', data)}
|
|
422
|
+
/>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
You can also write directly to `formState` from outside the form (e.g. a "Load profile" button) — the form will detect the changed `value` props and update only the affected fields.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Modal Mode
|
|
430
|
+
|
|
431
|
+
Wrap the form in a React Bootstrap modal with no extra markup:
|
|
432
|
+
|
|
433
|
+
```jsx
|
|
434
|
+
const [show, setShow] = useState(false);
|
|
435
|
+
|
|
436
|
+
<GenericForm
|
|
437
|
+
title="Edit User"
|
|
438
|
+
fields={fields}
|
|
439
|
+
enableModal
|
|
440
|
+
modalShow={show}
|
|
441
|
+
modalSize="lg"
|
|
442
|
+
onSubmit={(data) => { saveUser(data); setShow(false); }}
|
|
443
|
+
onCancel={() => setShow(false)}
|
|
444
|
+
/>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
> **Note:** Import React Bootstrap's CSS in your app root if you haven't already:
|
|
448
|
+
> ```js
|
|
449
|
+
> import 'bootstrap/dist/css/bootstrap.min.css';
|
|
450
|
+
> ```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Theming (CSS Variables)
|
|
455
|
+
|
|
456
|
+
Override any of the following CSS custom properties to match your design system:
|
|
457
|
+
|
|
458
|
+
```css
|
|
459
|
+
:root {
|
|
460
|
+
--ufg-primary: #1d4ed8; /* submit button, active toggle */
|
|
461
|
+
--ufg-primary-dark: #1e40af;
|
|
462
|
+
--ufg-primary-darker: #1e3a8a;
|
|
463
|
+
--ufg-error: #dc2626; /* validation error text */
|
|
464
|
+
--ufg-border: #cbd5e1; /* input borders */
|
|
465
|
+
--ufg-bg: #ffffff; /* card background */
|
|
466
|
+
--ufg-surface: #fafafa; /* secondary surfaces */
|
|
467
|
+
--ufg-text: #111111; /* primary text */
|
|
468
|
+
--ufg-text-secondary: #6b7280; /* placeholder, help text */
|
|
469
|
+
--ufg-radius: 10px; /* input border radius */
|
|
470
|
+
--ufg-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Demo Components
|
|
477
|
+
|
|
478
|
+
Two reference implementations are bundled and exported:
|
|
479
|
+
|
|
480
|
+
```jsx
|
|
481
|
+
import GenericForm, { Demo, Demo2 } from '@insticc/genericform';
|
|
482
|
+
|
|
483
|
+
// Every field type, conditional visibility, validation, custom actions:
|
|
484
|
+
<Demo />
|
|
485
|
+
|
|
486
|
+
// Full external state management with live formState sidebar:
|
|
487
|
+
<Demo2 />
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
`Demo` covers all 18 field types, conditional field visibility via `hide`, cross-field validation, inline layout, accordion, and custom action buttons.
|
|
491
|
+
|
|
492
|
+
`Demo2` demonstrates the `onDataChange` + `onFieldChange` pattern for parent-driven state, including dependent field auto-fill (country → city) and a live field-change event log.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## License
|
|
497
|
+
|
|
498
|
+
ISC
|
|
499
|
+
|
|
500
|
+
---
|
package/build/index.css
CHANGED
package/package.json
CHANGED