@insticc/genericform 2.1.2 → 2.1.3

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
@@ -1,500 +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
-
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
500
  ---
@@ -1,13 +1,12 @@
1
1
  "use strict";
2
2
 
3
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
6
  exports["default"] = void 0;
8
7
  var _propTypes = _interopRequireDefault(require("prop-types"));
9
- var _defaults = require("./defaults");
10
8
  var _react = require("react");
9
+ var _defaults = require("./defaults");
11
10
  var _jsxRuntime = require("react/jsx-runtime");
12
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
13
12
  function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
@@ -25,6 +24,7 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
25
24
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
26
25
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
27
26
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
27
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
28
28
  var IndeterminateCheckbox = function IndeterminateCheckbox(_ref) {
29
29
  var id = _ref.id,
30
30
  checked = _ref.checked,
@@ -82,6 +82,15 @@ var CustomCheckboxGroup = function CustomCheckboxGroup(_ref2) {
82
82
  inputStyle = _ref2$inputStyle === void 0 ? {} : _ref2$inputStyle,
83
83
  _ref2$tooltipStyle = _ref2.tooltipStyle,
84
84
  tooltipStyle = _ref2$tooltipStyle === void 0 ? {} : _ref2$tooltipStyle;
85
+ var isObject = (0, _react.useMemo)(function () {
86
+ return !!options && _typeof(options[0]) === "object";
87
+ }, [options]);
88
+ var getValue = (0, _react.useCallback)(function (opt) {
89
+ return isObject ? opt.id || opt.value || opt.name : opt;
90
+ }, [isObject]);
91
+ var getLabel = (0, _react.useCallback)(function (opt) {
92
+ return isObject ? opt.label || opt.text || opt.name : opt;
93
+ }, [isObject]);
85
94
  var _useState = (0, _react.useState)({}),
86
95
  _useState2 = _slicedToArray(_useState, 2),
87
96
  collapsed = _useState2[0],
@@ -95,16 +104,16 @@ var CustomCheckboxGroup = function CustomCheckboxGroup(_ref2) {
95
104
  var getChildValues = (0, _react.useCallback)(function (opt) {
96
105
  var _opt$options$map, _opt$options;
97
106
  return (_opt$options$map = (_opt$options = opt.options) === null || _opt$options === void 0 ? void 0 : _opt$options.map(function (s) {
98
- return s.name || s.value;
107
+ return getValue(s);
99
108
  })) !== null && _opt$options$map !== void 0 ? _opt$options$map : [];
100
109
  }, []);
101
110
  var isAllChecked = (0, _react.useCallback)(function (opt) {
102
- var parentVal = opt.name || opt.value;
111
+ var parentVal = getValue(opt);
103
112
  var children = getChildValues(opt);
104
113
  return children.length > 0 && checked.includes(parentVal) && children.every(function (v) {
105
114
  return checked.includes(v);
106
115
  });
107
- }, [checked, getChildValues]);
116
+ }, [checked, getValue, getChildValues]);
108
117
  var isSomeChecked = (0, _react.useCallback)(function (opt) {
109
118
  var children = getChildValues(opt);
110
119
  return children.some(function (v) {
@@ -119,14 +128,14 @@ var CustomCheckboxGroup = function CustomCheckboxGroup(_ref2) {
119
128
  onOptionChange === null || onOptionChange === void 0 || onOptionChange(data);
120
129
  }, [onChange, onOptionChange, name, checked]);
121
130
  var handleParentChange = (0, _react.useCallback)(function (opt, state) {
122
- var parentVal = opt.name || opt.value;
131
+ var parentVal = getValue(opt);
123
132
  var children = getChildValues(opt);
124
- var data = state ? _toConsumableArray(new Set(parentCheckAll ? [].concat(_toConsumableArray(checked), [parentVal, children]) : [].concat(_toConsumableArray(checked), [parentVal]))) : checked.filter(function (v) {
133
+ var data = state ? _toConsumableArray(new Set(parentCheckAll ? [].concat(_toConsumableArray(checked), [parentVal], _toConsumableArray(children)) : [].concat(_toConsumableArray(checked), [parentVal]))) : checked.filter(function (v) {
125
134
  return v !== parentVal && !children.includes(v);
126
135
  });
127
136
  onChange === null || onChange === void 0 || onChange(name, data);
128
137
  onOptionChange === null || onOptionChange === void 0 || onOptionChange(data);
129
- }, [onChange, onOptionChange, name, checked, getChildValues]);
138
+ }, [onChange, onOptionChange, name, checked, parentCheckAll, getValue, getChildValues]);
130
139
  var toggleCollapse = (0, _react.useCallback)(function (idx) {
131
140
  setCollapsed(function (prev) {
132
141
  return _objectSpread(_objectSpread({}, prev), {}, _defineProperty({}, idx, !prev[idx]));
@@ -150,7 +159,7 @@ var CustomCheckboxGroup = function CustomCheckboxGroup(_ref2) {
150
159
  var _opt$options2;
151
160
  var hasChildren = !!((_opt$options2 = opt.options) !== null && _opt$options2 !== void 0 && _opt$options2.length);
152
161
  var isCollapsed = collapsible && !!collapsed[idx];
153
- var parentVal = opt.name || opt.value;
162
+ var parentVal = getValue(opt);
154
163
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
155
164
  children: [idx > 0 && grouped && /*#__PURE__*/(0, _jsxRuntime.jsx)("hr", {
156
165
  style: {
@@ -201,7 +210,7 @@ var CustomCheckboxGroup = function CustomCheckboxGroup(_ref2) {
201
210
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("label", {
202
211
  htmlFor: "".concat(name, "-").concat(idx),
203
212
  style: opt.labelStyle,
204
- children: opt.label || opt.text
213
+ children: getLabel(opt)
205
214
  })]
206
215
  })]
207
216
  }), hasChildren && !isCollapsed && /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
@@ -210,24 +219,25 @@ var CustomCheckboxGroup = function CustomCheckboxGroup(_ref2) {
210
219
  paddingLeft: collapsible ? '2rem' : '1.25rem'
211
220
  },
212
221
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {}), opt.options.map(function (subopt, subidx) {
222
+ var suboptVal = getValue(subopt);
213
223
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
214
224
  className: "ufg-checkbox-item",
215
225
  style: inputStyle,
216
226
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("input", {
217
227
  id: "".concat(name, "-").concat(idx, "-").concat(subidx),
218
228
  type: "checkbox",
219
- checked: checked.includes(subopt.name || subopt.value),
229
+ checked: checked.includes(suboptVal),
220
230
  disabled: !!subopt.disabled,
221
231
  style: subopt.inputStyle,
222
232
  onChange: function onChange(e) {
223
- return handleChange(subopt.name || subopt.value, e.target.checked);
233
+ return handleChange(suboptVal, e.target.checked);
224
234
  }
225
235
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("label", {
226
236
  htmlFor: "".concat(name, "-").concat(idx, "-").concat(subidx),
227
237
  style: subopt.labelStyle,
228
- children: subopt.label || subopt.text
238
+ children: getLabel(subopt)
229
239
  })]
230
- }, subopt.name || subopt.value);
240
+ }, suboptVal);
231
241
  })]
232
242
  })]
233
243
  }, "".concat(name, "-").concat(idx));
@@ -4,9 +4,11 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports["default"] = void 0;
7
+ var _propTypes = _interopRequireDefault(require("prop-types"));
7
8
  var _react = require("react");
8
9
  var _defaults = require("./defaults");
9
10
  var _jsxRuntime = require("react/jsx-runtime");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
10
12
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
11
13
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
12
14
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -28,6 +30,8 @@ var CustomFileInput = function CustomFileInput(_ref) {
28
30
  error = _ref.error,
29
31
  _ref$tooltip = _ref.tooltip,
30
32
  tooltip = _ref$tooltip === void 0 ? "" : _ref$tooltip,
33
+ _ref$tooltipInline = _ref.tooltipInline,
34
+ tooltipInline = _ref$tooltipInline === void 0 ? false : _ref$tooltipInline,
31
35
  _ref$showHelp = _ref.showHelp,
32
36
  showHelp = _ref$showHelp === void 0 ? false : _ref$showHelp,
33
37
  _ref$helpText = _ref.helpText,
@@ -114,7 +118,7 @@ var CustomFileInput = function CustomFileInput(_ref) {
114
118
  color: '#aaa'
115
119
  },
116
120
  children: "No file selected"
117
- })]
121
+ }), tooltipInline && (0, _defaults.tooltipJsx)(tooltip, tooltipStyle)]
118
122
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("input", {
119
123
  id: "form-input-".concat(name),
120
124
  ref: ref,
@@ -125,7 +129,27 @@ var CustomFileInput = function CustomFileInput(_ref) {
125
129
  },
126
130
  disabled: disabled,
127
131
  onChange: handleChange
128
- }), (0, _defaults.errorJsx)(error), (0, _defaults.tooltipJsx)(tooltip, tooltipStyle)]
132
+ }), (0, _defaults.errorJsx)(error), !tooltipInline && (0, _defaults.tooltipJsx)(tooltip, tooltipStyle)]
129
133
  });
130
134
  };
135
+ CustomFileInput.propType = {
136
+ onChange: _propTypes["default"].func.isRequired,
137
+ name: _propTypes["default"].string.isRequired,
138
+ label: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].object]),
139
+ value: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number, _propTypes["default"].array, _propTypes["default"].object]),
140
+ accept: _propTypes["default"].string,
141
+ placeholder: _propTypes["default"].string,
142
+ disabled: _propTypes["default"].bool,
143
+ required: _propTypes["default"].bool,
144
+ error: _propTypes["default"].string,
145
+ tooltip: _propTypes["default"].string,
146
+ tooltipInline: _propTypes["default"].bool,
147
+ showHelp: _propTypes["default"].bool,
148
+ helpText: _propTypes["default"].string,
149
+ mainDivStyle: _propTypes["default"].object,
150
+ labelStyle: _propTypes["default"].object,
151
+ spanStyle: _propTypes["default"].object,
152
+ inputStyle: _propTypes["default"].object,
153
+ tooltipStyle: _propTypes["default"].object
154
+ };
131
155
  var _default = exports["default"] = CustomFileInput;
@@ -4,11 +4,11 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports["default"] = void 0;
7
- var _propTypes = _interopRequireWildcard(require("prop-types"));
7
+ var _propTypes = _interopRequireDefault(require("prop-types"));
8
+ var _react = require("react");
8
9
  var _defaults = require("./defaults");
9
10
  var _jsxRuntime = require("react/jsx-runtime");
10
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
11
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
12
12
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
13
13
  var CustomRadioGroup = function CustomRadioGroup(_ref) {
14
14
  var _onChange = _ref.onChange,
@@ -43,7 +43,9 @@ var CustomRadioGroup = function CustomRadioGroup(_ref) {
43
43
  inputStyle = _ref$inputStyle === void 0 ? {} : _ref$inputStyle,
44
44
  _ref$tooltipStyle = _ref.tooltipStyle,
45
45
  tooltipStyle = _ref$tooltipStyle === void 0 ? {} : _ref$tooltipStyle;
46
- var isObject = _typeof(options[0]) === "object";
46
+ var isObject = (0, _react.useMemo)(function () {
47
+ return !!options && _typeof(options[0]) === "object";
48
+ }, [options]);
47
49
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
48
50
  className: "ufg-field-group",
49
51
  id: name,
package/build/index.js CHANGED
@@ -51,12 +51,6 @@ Object.defineProperty(exports, "CustomInput", {
51
51
  return _CustomInput["default"];
52
52
  }
53
53
  });
54
- Object.defineProperty(exports, "CustomInputAdd", {
55
- enumerable: true,
56
- get: function get() {
57
- return _CustomInputAdd["default"];
58
- }
59
- });
60
54
  Object.defineProperty(exports, "CustomJson", {
61
55
  enumerable: true,
62
56
  get: function get() {
@@ -705,7 +699,8 @@ var GenericForm = function GenericForm(_ref) {
705
699
  grouped: field.grouped,
706
700
  collapsible: field.collapsible,
707
701
  defaultCollapsed: field.defaultCollapsed,
708
- enableParent: field.enableParent
702
+ enableParent: field.enableParent,
703
+ parentCheckAll: field.parentCheckAll
709
704
  }), field.name);
710
705
  case 'radioGroup':
711
706
  case 'radioGroupInline':
@@ -747,6 +742,7 @@ var GenericForm = function GenericForm(_ref) {
747
742
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_CustomFileInput["default"], _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, cp), sp), fp), {}, {
748
743
  value: value,
749
744
  accept: field.accept,
745
+ tooltipInline: field.tooltipInline,
750
746
  onChange: fieldHandler(field)
751
747
  }), field.name);
752
748
  case 'inputAdd':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insticc/genericform",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "A generic form with a comprehensive set of field types, validation, and external state management",
5
5
  "main": "build/index.js",
6
6
  "scripts": {