@engagebay/engagebay-form-module 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/link.sh +2 -0
- package/package.json +30 -0
- package/src/api/index.ts +25 -0
- package/src/form/Form.tsx +157 -0
- package/src/form/FormField.tsx +80 -0
- package/src/form/FormFieldUtils.ts +241 -0
- package/src/form/FormFields.tsx +41 -0
- package/src/form/context/FormContext.tsx +66 -0
- package/src/form/formfields/ArrayField.tsx +169 -0
- package/src/form/formfields/BusinessHoursField.tsx +204 -0
- package/src/form/formfields/CheckboxButtonsField.tsx +97 -0
- package/src/form/formfields/CheckboxField.tsx +118 -0
- package/src/form/formfields/ColorPickerField.tsx +59 -0
- package/src/form/formfields/ComboMultiSelect.tsx +290 -0
- package/src/form/formfields/ComboSelect.tsx +278 -0
- package/src/form/formfields/DatePickerField.tsx +89 -0
- package/src/form/formfields/DateRangePickerField.tsx +104 -0
- package/src/form/formfields/DynamicMultiSelect.tsx +189 -0
- package/src/form/formfields/DynamicSelect.tsx +187 -0
- package/src/form/formfields/Error.tsx +15 -0
- package/src/form/formfields/ErrorContextHandler.tsx +77 -0
- package/src/form/formfields/FileUploadField.tsx +196 -0
- package/src/form/formfields/IframeField.tsx +65 -0
- package/src/form/formfields/InputField.tsx +67 -0
- package/src/form/formfields/InputGroupField.tsx +44 -0
- package/src/form/formfields/MultipleSelectField.tsx +98 -0
- package/src/form/formfields/NumberField.tsx +61 -0
- package/src/form/formfields/PasswordField.tsx +93 -0
- package/src/form/formfields/PhoneNumberField.tsx +163 -0
- package/src/form/formfields/RadioField.tsx +104 -0
- package/src/form/formfields/RadioGroupComponent.tsx +94 -0
- package/src/form/formfields/RangeField.tsx +53 -0
- package/src/form/formfields/SelectField.tsx +82 -0
- package/src/form/formfields/SwitchField.tsx +131 -0
- package/src/form/formfields/TextAreaField.tsx +48 -0
- package/src/form/formfields/TimeField.tsx +53 -0
- package/src/form/formfields/Typeahead.tsx +211 -0
- package/src/form/formfields/TypeaheadMultiSelect.tsx +203 -0
- package/src/form/formfields/UrlField.tsx +53 -0
- package/src/form/hooks/useDynamicReducer.tsx +42 -0
- package/src/form/schema/CustomValidators.ts +63 -0
- package/src/form/schema/FormFieldSchema.ts +342 -0
- package/src/form/util/RenderFormField.tsx +149 -0
- package/src/form/util/RenderListOptions.tsx +424 -0
- package/src/form/util/index.ts +185 -0
- package/src/util/LoaderWithText.tsx +28 -0
- package/src/util/svg/HELPER_ICONS.ts +16 -0
- package/src/util/svg/SVGIcon.tsx +23 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {RegisterOptions} from "react-hook-form";
|
|
2
|
+
import {FormFieldPatternsImpl, FormFieldSchema, OutputFormatType,} from "../schema/FormFieldSchema";
|
|
3
|
+
import {FormContextType} from "../context/FormContext";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Registers form field options based on the provided configuration.
|
|
7
|
+
*
|
|
8
|
+
* This function takes a form field configuration object (`FormFieldSchema`)
|
|
9
|
+
* and returns `RegisterOptions` to be used with form validation libraries
|
|
10
|
+
* (such as React Hook Form).
|
|
11
|
+
*
|
|
12
|
+
* @param {FormFieldSchema} fieldConfig - The configuration object for the form field.
|
|
13
|
+
*
|
|
14
|
+
* @returns {RegisterOptions} An object containing validation options for the form field.
|
|
15
|
+
*
|
|
16
|
+
* @property {boolean|string} required - Indicates if the field is required.
|
|
17
|
+
* The value is a string with a validation message if required, otherwise `false`.
|
|
18
|
+
*
|
|
19
|
+
* @property {RegExp} [pattern] - A regular expression pattern to validate the field value.
|
|
20
|
+
*
|
|
21
|
+
* @property {Function} [validate] - A custom validation function.
|
|
22
|
+
*
|
|
23
|
+
* @property {number} [maxLength] - Specifies the maximum length of the field value.
|
|
24
|
+
*
|
|
25
|
+
* @property {number} [minLength] - Specifies the minimum length of the field value.
|
|
26
|
+
*
|
|
27
|
+
* @property {number} [min] - Specifies the minimum value for numeric fields.
|
|
28
|
+
*
|
|
29
|
+
* @property {number} [max] - Specifies the maximum value for numeric fields.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const fieldConfig = {
|
|
33
|
+
* required: true,
|
|
34
|
+
* formFieldPattern: [
|
|
35
|
+
* FormFieldPatternImpl.URL
|
|
36
|
+
* ],
|
|
37
|
+
* maxLength: 10,
|
|
38
|
+
* minLength: 3,
|
|
39
|
+
* min: 1,
|
|
40
|
+
* max: 100,
|
|
41
|
+
* };
|
|
42
|
+
*
|
|
43
|
+
* USE CASE:
|
|
44
|
+
* const registerOptions = registerFormField(fieldConfig);
|
|
45
|
+
*
|
|
46
|
+
*/
|
|
47
|
+
export const registerFormField = (
|
|
48
|
+
fieldConfig: FormFieldSchema,
|
|
49
|
+
ignoreAsArray?: boolean
|
|
50
|
+
): RegisterOptions => {
|
|
51
|
+
let registerOptions: RegisterOptions = {
|
|
52
|
+
required: fieldConfig.required
|
|
53
|
+
? FormFieldPatternsImpl.REQUIRED.getMessage()
|
|
54
|
+
: false,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (fieldConfig?.validateSpaces) {
|
|
58
|
+
registerOptions.validate = {
|
|
59
|
+
notEmpty: (value: string) => value.trim() !== '' || 'This field cannot be empty or contain only spaces',
|
|
60
|
+
notSpaces: (value: string) => value.trim() !== '' || 'This field cannot be empty or contain only spaces',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (fieldConfig?.formFieldPattern) {
|
|
64
|
+
if (fieldConfig.formFieldPattern[0].getValidate()) {
|
|
65
|
+
registerOptions.validate =
|
|
66
|
+
fieldConfig.formFieldPattern[0].getValidate();
|
|
67
|
+
} else {
|
|
68
|
+
registerOptions.pattern =
|
|
69
|
+
fieldConfig?.formFieldPattern[0].getPattern();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (fieldConfig?.maxLength) {
|
|
74
|
+
registerOptions.maxLength = fieldConfig?.maxLength;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (fieldConfig?.minLength) {
|
|
78
|
+
registerOptions.minLength = fieldConfig?.minLength;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (fieldConfig?.min) {
|
|
82
|
+
registerOptions.min = fieldConfig?.min;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (fieldConfig?.max) {
|
|
86
|
+
registerOptions.max = fieldConfig?.max;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
fieldConfig?.outputFormat === OutputFormatType.ARRAY &&
|
|
91
|
+
!ignoreAsArray
|
|
92
|
+
) {
|
|
93
|
+
registerOptions.setValueAs = (value) =>
|
|
94
|
+
Array.isArray(value) ? [...value] : [value];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return registerOptions;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Handles changes to a form field, updating the form context and triggering
|
|
102
|
+
* any additional callbacks specified in the field configuration.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} value - The new value of the form field.
|
|
105
|
+
* @param {FormContextType} formContext - The form context object, which includes methods
|
|
106
|
+
* and properties for managing form state (e.g., `setValue`, `getFieldState`).
|
|
107
|
+
* @param {FormFieldSchema} fieldConfig - The configuration object for the form field, which
|
|
108
|
+
* may include properties such as `name`, `onChange`, and `submitOnChange`.
|
|
109
|
+
* @param {Function} onChange - An optional callback function to be called after the form field value
|
|
110
|
+
* has been updated.
|
|
111
|
+
*
|
|
112
|
+
* @returns {void}
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* const value = "example";
|
|
116
|
+
* const formContext = {
|
|
117
|
+
* setValue: (name, value, options) => { ... },
|
|
118
|
+
* getFieldState: (name) => ({ isDirty: false, isTouched: true }),
|
|
119
|
+
* onSubmit: () => { ... }
|
|
120
|
+
* };
|
|
121
|
+
*
|
|
122
|
+
* const fieldConfig = {
|
|
123
|
+
* name: "exampleField",
|
|
124
|
+
* onChange: (value) => { console.log("Field changed:", value); },
|
|
125
|
+
* submitOnChange: true,
|
|
126
|
+
* };
|
|
127
|
+
*
|
|
128
|
+
* const onChange = (value) => { console.log("External onChange:", value); };
|
|
129
|
+
*
|
|
130
|
+
* handleChange(value, formContext, fieldConfig, onChange);
|
|
131
|
+
*
|
|
132
|
+
* // This will:
|
|
133
|
+
* // - Update the form context with the new value.
|
|
134
|
+
* // - Call `fieldConfig.onChange` if it exists.
|
|
135
|
+
* // - Call `onChange` if it exists.
|
|
136
|
+
* // - Submit the form if `submitOnChange` is true.
|
|
137
|
+
*/
|
|
138
|
+
export const handleChange = (
|
|
139
|
+
value: any,
|
|
140
|
+
formContext: FormContextType,
|
|
141
|
+
fieldConfig: FormFieldSchema,
|
|
142
|
+
onChange?: (value: any) => void
|
|
143
|
+
): void => {
|
|
144
|
+
// if (fieldConfig.outputFormat === OutputFormatType.ARRAY) {
|
|
145
|
+
// value = [value];
|
|
146
|
+
// }
|
|
147
|
+
// Update the form context with the new value
|
|
148
|
+
formContext.setValue(fieldConfig.name, value, {
|
|
149
|
+
shouldValidate: true,
|
|
150
|
+
shouldDirty: formContext.getFieldState(fieldConfig.name).isDirty,
|
|
151
|
+
shouldTouch: formContext.getFieldState(fieldConfig.name).isTouched,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Call the onChange method from fieldConfig if it exists
|
|
155
|
+
if (fieldConfig.onChange) {
|
|
156
|
+
fieldConfig.onChange(value);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Call the external onChange callback if provided
|
|
160
|
+
if (onChange) onChange(value);
|
|
161
|
+
|
|
162
|
+
// Submit the form if submitOnChange is true and onSubmit exists in formContext
|
|
163
|
+
if (fieldConfig.submitOnChange && formContext.onSubmit) {
|
|
164
|
+
formContext.onSubmit();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const resetFormField = (formContext: FormContextType, name: string) => {
|
|
169
|
+
formContext.resetField(name);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
export const convertToTitleCase = (str: string): string => {
|
|
174
|
+
if (!str) return '-';
|
|
175
|
+
// Split the string by underscores
|
|
176
|
+
let words: string[] = str.split('_');
|
|
177
|
+
|
|
178
|
+
// Capitalize the first letter of each word
|
|
179
|
+
let capitalizedWords: string[] = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
|
|
180
|
+
|
|
181
|
+
// Join the words with a space
|
|
182
|
+
let result: string = capitalizedWords.join(' ');
|
|
183
|
+
|
|
184
|
+
return result;
|
|
185
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export function LoaderWithText() {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
id="loader-with-text"
|
|
7
|
+
className="flex items-center justify-center space-x-2"
|
|
8
|
+
>
|
|
9
|
+
<div aria-label="Loading..." role="status">
|
|
10
|
+
<svg
|
|
11
|
+
width="24"
|
|
12
|
+
height="24"
|
|
13
|
+
fill="none"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
strokeWidth="1.5"
|
|
16
|
+
viewBox="0 0 24 24"
|
|
17
|
+
strokeLinecap="round"
|
|
18
|
+
strokeLinejoin="round"
|
|
19
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
+
className="animate-spin w-4 h-4 stroke-slate-500"
|
|
21
|
+
>
|
|
22
|
+
<path d="M12 3v3m6.366-.366-2.12 2.12M21 12h-3m.366 6.366-2.12-2.12M12 21v-3m-6.366.366 2.12-2.12M3 12h3m-.366-6.366 2.12 2.12"></path>
|
|
23
|
+
</svg>
|
|
24
|
+
</div>
|
|
25
|
+
<span className="text-xs font-medium text-slate-500">Loading...</span>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const HELPER_ICONS: {
|
|
2
|
+
[key in string]: string;
|
|
3
|
+
} = {
|
|
4
|
+
starIcon: `<path
|
|
5
|
+
fill-rule="evenodd"
|
|
6
|
+
d="M8 1.75a.75.75 0 0 1 .692.462l1.41 3.393 3.664.293a.75.75 0 0 1 .428 1.317l-2.791 2.39.853 3.575a.75.75 0 0 1-1.12.814L7.998 12.08l-3.135 1.915a.75.75 0 0 1-1.12-.814l.852-3.574-2.79-2.39a.75.75 0 0 1 .427-1.318l3.663-.293 1.41-3.393A.75.75 0 0 1 8 1.75Z"
|
|
7
|
+
clip-rule="evenodd"
|
|
8
|
+
></path>`,
|
|
9
|
+
chevronDown: `<path fill-rule="evenodd"
|
|
10
|
+
d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z"
|
|
11
|
+
clip-rule="evenodd"
|
|
12
|
+
></path>`,
|
|
13
|
+
checkIcon: `<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0"/>`,
|
|
14
|
+
// <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z" clip-rule="evenodd"></path>`,
|
|
15
|
+
search: `<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"></path>`,
|
|
16
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { SVGAttributes } from "react";
|
|
2
|
+
import { HELPER_ICONS } from "./HELPER_ICONS";
|
|
3
|
+
|
|
4
|
+
const SVGIcon = (props: SVGAttributes<{}>) => {
|
|
5
|
+
if (!props.name) {
|
|
6
|
+
return <p>No name is specified</p>;
|
|
7
|
+
}
|
|
8
|
+
const icon = HELPER_ICONS[props.name];
|
|
9
|
+
return (
|
|
10
|
+
<svg
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
viewBox="0 0 16 16"
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
data-slot="icon"
|
|
16
|
+
{...props}
|
|
17
|
+
dangerouslySetInnerHTML={{ __html: icon }}
|
|
18
|
+
></svg>
|
|
19
|
+
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default SVGIcon;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES6", // Target ECMAScript 6
|
|
4
|
+
"module": "ESNext", // Use the latest module system
|
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"], // Include DOM and ESNext libraries
|
|
6
|
+
"jsx": "react-jsx", // JSX support for React 17+
|
|
7
|
+
"strict": true, // Enable all strict type-checking options
|
|
8
|
+
"esModuleInterop": true, // Enable interoperability between CommonJS and ES Modules
|
|
9
|
+
"allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export
|
|
10
|
+
"skipLibCheck": true, // Skip type checking of declaration files
|
|
11
|
+
"forceConsistentCasingInFileNames": true, // Ensure consistent casing in file names
|
|
12
|
+
"moduleResolution": "node", // Use Node.js module resolution strategy
|
|
13
|
+
"isolatedModules": true, // Treat each file as a module
|
|
14
|
+
"resolveJsonModule": true, // Allow importing JSON files
|
|
15
|
+
"noEmit": true, // Prevent TypeScript from emitting compiled output
|
|
16
|
+
"baseUrl": ".", // Set the base URL for module resolution
|
|
17
|
+
"paths": {
|
|
18
|
+
// Define path aliases
|
|
19
|
+
"@components/*": ["src/components/*"],
|
|
20
|
+
"@utils/*": ["src/utils/*"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": ["src"], // Include files in the 'src' directory
|
|
24
|
+
"exclude": ["node_modules", "build", "dist"] // Exclude files and directories from the compilation
|
|
25
|
+
}
|