@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.
Files changed (50) hide show
  1. package/README.md +126 -0
  2. package/link.sh +2 -0
  3. package/package.json +30 -0
  4. package/src/api/index.ts +25 -0
  5. package/src/form/Form.tsx +157 -0
  6. package/src/form/FormField.tsx +80 -0
  7. package/src/form/FormFieldUtils.ts +241 -0
  8. package/src/form/FormFields.tsx +41 -0
  9. package/src/form/context/FormContext.tsx +66 -0
  10. package/src/form/formfields/ArrayField.tsx +169 -0
  11. package/src/form/formfields/BusinessHoursField.tsx +204 -0
  12. package/src/form/formfields/CheckboxButtonsField.tsx +97 -0
  13. package/src/form/formfields/CheckboxField.tsx +118 -0
  14. package/src/form/formfields/ColorPickerField.tsx +59 -0
  15. package/src/form/formfields/ComboMultiSelect.tsx +290 -0
  16. package/src/form/formfields/ComboSelect.tsx +278 -0
  17. package/src/form/formfields/DatePickerField.tsx +89 -0
  18. package/src/form/formfields/DateRangePickerField.tsx +104 -0
  19. package/src/form/formfields/DynamicMultiSelect.tsx +189 -0
  20. package/src/form/formfields/DynamicSelect.tsx +187 -0
  21. package/src/form/formfields/Error.tsx +15 -0
  22. package/src/form/formfields/ErrorContextHandler.tsx +77 -0
  23. package/src/form/formfields/FileUploadField.tsx +196 -0
  24. package/src/form/formfields/IframeField.tsx +65 -0
  25. package/src/form/formfields/InputField.tsx +67 -0
  26. package/src/form/formfields/InputGroupField.tsx +44 -0
  27. package/src/form/formfields/MultipleSelectField.tsx +98 -0
  28. package/src/form/formfields/NumberField.tsx +61 -0
  29. package/src/form/formfields/PasswordField.tsx +93 -0
  30. package/src/form/formfields/PhoneNumberField.tsx +163 -0
  31. package/src/form/formfields/RadioField.tsx +104 -0
  32. package/src/form/formfields/RadioGroupComponent.tsx +94 -0
  33. package/src/form/formfields/RangeField.tsx +53 -0
  34. package/src/form/formfields/SelectField.tsx +82 -0
  35. package/src/form/formfields/SwitchField.tsx +131 -0
  36. package/src/form/formfields/TextAreaField.tsx +48 -0
  37. package/src/form/formfields/TimeField.tsx +53 -0
  38. package/src/form/formfields/Typeahead.tsx +211 -0
  39. package/src/form/formfields/TypeaheadMultiSelect.tsx +203 -0
  40. package/src/form/formfields/UrlField.tsx +53 -0
  41. package/src/form/hooks/useDynamicReducer.tsx +42 -0
  42. package/src/form/schema/CustomValidators.ts +63 -0
  43. package/src/form/schema/FormFieldSchema.ts +342 -0
  44. package/src/form/util/RenderFormField.tsx +149 -0
  45. package/src/form/util/RenderListOptions.tsx +424 -0
  46. package/src/form/util/index.ts +185 -0
  47. package/src/util/LoaderWithText.tsx +28 -0
  48. package/src/util/svg/HELPER_ICONS.ts +16 -0
  49. package/src/util/svg/SVGIcon.tsx +23 -0
  50. package/tsconfig.json +25 -0
@@ -0,0 +1,82 @@
1
+ import React, {useContext, useEffect, useRef, useState,} from "react";
2
+
3
+ import {RegisterOptions} from "react-hook-form";
4
+
5
+ import {FieldOptionsSchema, FormFieldComponentPropSchema, FormFieldType,} from "../schema/FormFieldSchema";
6
+ import {FormContext} from "../context/FormContext";
7
+ import {Listbox, ListboxButton,} from "@headlessui/react";
8
+ import RenderFormField from "../util/RenderFormField";
9
+ import RenderListOptions, {renderListBoxValue,} from "../util/RenderListOptions";
10
+ import {handleChange, registerFormField} from "../util";
11
+
12
+ const SelectField: React.FC<FormFieldComponentPropSchema> = ({
13
+ fieldConfig,
14
+ onChange,
15
+ }: FormFieldComponentPropSchema) => {
16
+ const selectRef = useRef<HTMLUListElement>(null);
17
+ const formContext = useContext(FormContext);
18
+ const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>(
19
+ fieldConfig.options ? fieldConfig.options : []
20
+ );
21
+ let registerOptions: RegisterOptions = registerFormField(fieldConfig);
22
+
23
+ let hookProps = formContext.register(fieldConfig.name, registerOptions);
24
+
25
+ useEffect(() => {
26
+ if (
27
+ (fieldConfig.options && fieldConfig.options.length > 0) ||
28
+ (fieldConfig.forceUpdate && fieldConfig.options)
29
+ ) {
30
+ setListOptions(fieldConfig.options);
31
+ }
32
+ if (!formContext.getValues(fieldConfig.name) && fieldConfig.defaultValue) {
33
+ formContext.setValue(fieldConfig.name, fieldConfig.defaultValue);
34
+ }
35
+ }, [fieldConfig.options, fieldConfig.forceUpdate]);
36
+
37
+ function getInput() {
38
+ return (
39
+ <Listbox
40
+ as={"div"}
41
+ {...hookProps}
42
+ value={formContext.getValues(fieldConfig.name)}
43
+ defaultValue={fieldConfig.defaultValue}
44
+ onChange={(val) => {
45
+ const currentValue = formContext.getValues(fieldConfig.name);
46
+
47
+ // If the value matches, set it to null, otherwise set it to val
48
+ const newValue = currentValue === val ? null : val;
49
+
50
+ handleChange(newValue, formContext, fieldConfig, onChange);
51
+ }}
52
+ disabled={fieldConfig.disabled}
53
+ className={"relative form-listbox flex-1"}>
54
+ <ListboxButton
55
+ className={
56
+ fieldConfig.customClassNames?.fieldClassName
57
+ ? "form-listbox-select " +
58
+ fieldConfig.customClassNames?.fieldClassName
59
+ : "form-listbox-select"
60
+ }>
61
+ {renderListBoxValue(
62
+ formContext,
63
+ fieldConfig,
64
+ listOptions,
65
+ onChange
66
+ )}
67
+ </ListboxButton>
68
+ <RenderListOptions
69
+ ref={selectRef}
70
+ fieldConfig={fieldConfig}
71
+ formContext={formContext}
72
+ formField={FormFieldType.SELECT}
73
+ listOptions={listOptions}
74
+ setListOptions={setListOptions}
75
+ />
76
+ </Listbox>
77
+ );
78
+ }
79
+
80
+ return <RenderFormField fieldConfig={fieldConfig} getInput={getInput}/>;
81
+ };
82
+ export default SelectField;
@@ -0,0 +1,131 @@
1
+ import {Switch} from '@headlessui/react';
2
+ import React, {Fragment, useContext, useEffect, useState} from 'react';
3
+ import {RegisterOptions} from "react-hook-form";
4
+ import {FormContext} from "../context/FormContext";
5
+ import {FormFieldComponentPropSchema} from "../schema/FormFieldSchema";
6
+ import RenderFormField from "../util/RenderFormField";
7
+ import {handleChange, registerFormField} from "../util";
8
+ import XMarkIcon from "@heroicons/react/20/solid/XMarkIcon";
9
+ import CheckIcon from "@heroicons/react/20/solid/CheckIcon";
10
+ import clsx from 'clsx';
11
+
12
+ const SwitchField: React.FC<FormFieldComponentPropSchema> = (props) => {
13
+ const formContext = useContext(FormContext);
14
+
15
+ const [enabled, setEnabled] = useState<boolean>(
16
+ formContext.getValues(props.fieldConfig.name) ?? false
17
+ );
18
+ useEffect(() => {
19
+ if (props.fieldConfig.defaultValue && formContext.getValues(props.fieldConfig.name)==null || undefined) {
20
+
21
+ formContext.setValue(props.fieldConfig.name, props.fieldConfig.defaultValue);
22
+ setEnabled(props.fieldConfig.defaultValue);
23
+
24
+ }
25
+ }, []);
26
+
27
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
28
+
29
+ let hookProps = formContext.register(
30
+ props.fieldConfig.name,
31
+ registerOptions
32
+ );
33
+
34
+ function getInput() {
35
+ return (
36
+ <Switch
37
+ defaultChecked={props.fieldConfig.defaultValue as boolean}
38
+ {...hookProps}
39
+ onChange={(checked: boolean) => {
40
+ handleChange(
41
+ checked,
42
+ formContext,
43
+ props.fieldConfig,
44
+ props.onChange
45
+ );
46
+ setEnabled(checked);
47
+ }}
48
+ checked={enabled}
49
+ className="group relative inline-flex h-4 w-9 shrink-0 cursor-pointer items-center justify-center rounded-full focus:outline-none switch-container"
50
+ >
51
+ <span className="sr-only">Use setting</span>
52
+ <span aria-hidden="true" className="pointer-events-none absolute size-full rounded-md bg-white switch-toggle-off" />
53
+ <span
54
+ aria-hidden="true"
55
+ className="pointer-events-none absolute mx-auto h-3 w-9 rounded-full bg-gray-200 transition-colors duration-200 ease-in-out group-data-[checked]:bg-[#4361ee] switch-toggle-on"
56
+ />
57
+ <span
58
+ aria-hidden="true"
59
+ className="pointer-events-none absolute left-0 inline-block size-4 transform rounded-full border border-gray-200 bg-white shadow ring-0 transition-transform duration-200 ease-in-out group-data-[checked]:translate-x-5 switch-toggle-button"
60
+ />
61
+ </Switch>
62
+ // <Switch
63
+ // defaultChecked={props.fieldConfig.defaultValue as boolean}
64
+ // {...hookProps}
65
+ // onChange={(checked: boolean) => {
66
+ // handleChange(
67
+ // checked,
68
+ // formContext,
69
+ // props.fieldConfig,
70
+ // props.onChange
71
+ // );
72
+ // setEnabled(checked);
73
+ // }}
74
+ // checked={enabled}
75
+ // style={{width:'44px'}}
76
+ // className={clsx(
77
+ // enabled ? 'switch-checked' : 'switch',
78
+ // 'relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 !form-switch'
79
+ // )}>
80
+ // {({checked}) => (
81
+ // <>
82
+ // <span className="sr-only">Use setting</span>
83
+ // <span
84
+ // aria-hidden="true"
85
+ // style={{width:'20px', height:'20px'}}
86
+ // className={`${checked ? "translate-x-full" : "translate-x-0"}
87
+ // relative pointer-events-none inline-block transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out`}
88
+ // >
89
+
90
+ // <span className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
91
+ // {checked ? (
92
+ // <CheckIcon style={{width:'12px', height:'12px'}} />
93
+ // ) : (
94
+ // <XMarkIcon style={{width:'12px', height:'12px'}} />
95
+ // )}
96
+ // </span>
97
+
98
+ // </span>
99
+ // {/* <span className="sr-only"></span>
100
+ // <span
101
+ // aria-hidden="true"
102
+ // className="pointer-events-none absolute h-full w-full rounded-md bhite"
103
+ // />
104
+ // <span
105
+ // aria-hidden="true"
106
+ // className={clsx(
107
+ // checked
108
+ // ? "bg-[#377dff80]"
109
+ // : "bg-[#bdc1c6] shadow-md",
110
+ // "pointer-events-none absolute mx-auto h-3 w-8 rounded-full transition-colors duration-200 ease-in-out"
111
+ // )}
112
+ // />
113
+ // <span
114
+ // className={clsx(
115
+ // checked
116
+ // ? "translate-x-4 bg-[#377dff] shadow-md"
117
+ // : "translate-x-0 bg-white",
118
+ // "pointer-events-none inline-block h-4 w-4 transform rounded-full shadow ring-0 transition duration-200 ease-in-out"
119
+ // )}></span> */}
120
+ // </>
121
+ // )}
122
+ // </Switch>
123
+ );
124
+ }
125
+
126
+ return (
127
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput}/>
128
+ );
129
+ };
130
+
131
+ export default SwitchField;
@@ -0,0 +1,48 @@
1
+ import React, { useContext } from "react";
2
+ import { RegisterOptions } from "react-hook-form";
3
+ import { FormContext } from "../context/FormContext";
4
+ import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
5
+ import { Textarea } from "@headlessui/react";
6
+ import RenderFormField from "../util/RenderFormField";
7
+ import { handleChange, registerFormField } from "../util";
8
+
9
+ const TextAreaField: React.FC<FormFieldComponentPropSchema> = (
10
+ props: FormFieldComponentPropSchema
11
+ ) => {
12
+ const formContext = useContext(FormContext);
13
+
14
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
15
+
16
+ let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
17
+
18
+ function getInput() {
19
+ return (
20
+ <Textarea
21
+ spellCheck="false"
22
+ rows={props.fieldConfig.rows ? props.fieldConfig.rows : 5}
23
+ {...hookProps}
24
+ placeholder={props.fieldConfig?.placeholder}
25
+ readOnly={props.fieldConfig?.readOnly}
26
+ disabled={props.fieldConfig?.disabled}
27
+ autoComplete={props.fieldConfig?.autoComplete}
28
+ defaultValue={props.fieldConfig.defaultValue as string}
29
+ className={`form-input ${
30
+ props.fieldConfig.customClassNames?.fieldClassName || "flex-1"
31
+ }`}
32
+ onChange={(e) => {
33
+ handleChange(
34
+ e.target.value,
35
+ formContext,
36
+ props.fieldConfig,
37
+ props.onChange
38
+ );
39
+ }}
40
+ ></Textarea>
41
+ );
42
+ }
43
+
44
+ return (
45
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
46
+ );
47
+ };
48
+ export default TextAreaField;
@@ -0,0 +1,53 @@
1
+ import React, { useContext } from "react";
2
+ import { RegisterOptions } from "react-hook-form";
3
+ import { FormContext } from "../context/FormContext";
4
+ import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
5
+ import { Input } from "@headlessui/react";
6
+ import RenderFormField from "../util/RenderFormField";
7
+ import { handleChange, registerFormField } from "../util";
8
+
9
+ const TimeField: React.FC<FormFieldComponentPropSchema> = (
10
+ props: FormFieldComponentPropSchema
11
+ ) => {
12
+ const formContext = useContext(FormContext);
13
+
14
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
15
+
16
+ let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
17
+
18
+ /**
19
+ * Raw Form Field
20
+ */
21
+ const getInput = () => {
22
+ return (
23
+ <Input
24
+ type="time"
25
+ {...hookProps}
26
+ placeholder={props.fieldConfig?.placeholder}
27
+ readOnly={props.fieldConfig?.readOnly}
28
+ disabled={props.fieldConfig?.disabled}
29
+ autoComplete={props.fieldConfig?.autoComplete}
30
+ defaultValue={props.fieldConfig.defaultValue as string}
31
+ className={`form-input ${
32
+ props.fieldConfig.customClassNames?.fieldClassName || "flex-1"
33
+ }`}
34
+ onChange={(e) => {
35
+ handleChange(
36
+ e.target.value,
37
+ formContext,
38
+ props.fieldConfig,
39
+ props.onChange
40
+ );
41
+ }}
42
+ onClick={(event) => {
43
+ event.currentTarget.showPicker();
44
+ }}
45
+ />
46
+ );
47
+ };
48
+
49
+ return (
50
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
51
+ );
52
+ };
53
+ export default TimeField;
@@ -0,0 +1,211 @@
1
+ import React, {
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from "react";
8
+
9
+ import { Listbox, ListboxButton } from "@headlessui/react";
10
+ import axios from "axios";
11
+ import { RegisterOptions } from "react-hook-form";
12
+ import { reachoAPI } from "../../api";
13
+ import { FormContext } from "../context/FormContext";
14
+ import { getListOption, getListOptions } from "../FormFieldUtils";
15
+ import {
16
+ FieldOptionsSchema,
17
+ FormFieldComponentPropSchema,
18
+ FormFieldType,
19
+ OutputFormatType,
20
+ } from "../schema/FormFieldSchema";
21
+ import { handleChange, registerFormField } from "../util";
22
+ import RenderFormField from "../util/RenderFormField";
23
+ import RenderListOptions, {
24
+ renderListBoxValue,
25
+ } from "../util/RenderListOptions";
26
+
27
+ const Typeahead: React.FC<FormFieldComponentPropSchema> = (
28
+ props: FormFieldComponentPropSchema
29
+ ) => {
30
+ const dynamicSelectRef = useRef<HTMLUListElement>(null);
31
+ const formContext = useContext(FormContext);
32
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
33
+ let hookProps = formContext.register(
34
+ props.fieldConfig.name,
35
+ registerOptions
36
+ );
37
+ const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
38
+ const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
39
+ []
40
+ );
41
+ const [loading, setLoading] = useState<boolean>(true);
42
+
43
+ useEffect(() => {
44
+ if (
45
+ !formContext.getValues(props.fieldConfig.name) &&
46
+ props.fieldConfig.defaultValue
47
+ ) {
48
+ formContext.setValue(
49
+ props.fieldConfig.name,
50
+ props.fieldConfig.defaultValue
51
+ );
52
+ }
53
+ fetchData(undefined);
54
+ }, []);
55
+
56
+ const fetchData = useCallback(
57
+ async (_query: string | undefined) => {
58
+ setLoading(true);
59
+ try {
60
+ if (!props.fieldConfig.fetchUrl) return;
61
+
62
+ let url = props.fieldConfig.fetchUrl;
63
+ if (_query) {
64
+ url = url.includes("?")
65
+ ? url + "&q=" + _query
66
+ : url + "?q=" + _query;
67
+ }
68
+
69
+ let response = await (props.fieldConfig.disableHeaderInFetch
70
+ ? axios.get(url)
71
+ : reachoAPI.get(url));
72
+ if (response.data) {
73
+ const data: FieldOptionsSchema[] = getListOptions(
74
+ response.data,
75
+ props.fieldConfig.optionsConfig
76
+ );
77
+ setListOptions([...data]);
78
+ let value = formContext.getValues(props.fieldConfig.name);
79
+ if (
80
+ value &&
81
+ data.find((i) => i.value !== value)
82
+ ) {
83
+ if (selectedValues.find((i) => i.value !== value))
84
+ fetchValue(value);
85
+ }
86
+ if (props.fieldConfig.fetchCallback) {
87
+ props.fieldConfig.fetchCallback(response);
88
+ }
89
+ } else {
90
+ console.error(response.statusText);
91
+ }
92
+ } catch (err) {
93
+ } finally {
94
+ setLoading(false);
95
+ }
96
+ },
97
+ [props.fieldConfig.fetchUrl]
98
+ );
99
+
100
+ const fetchValue = async (value: string) => {
101
+ try {
102
+ if (
103
+ props.fieldConfig.ignoreFetchValue ||
104
+ !props.fieldConfig.fetchUrl
105
+ ) {
106
+ return;
107
+ }
108
+
109
+ let url = props.fieldConfig.fetchUrl;
110
+
111
+ if (value)
112
+ url = url.includes("?")
113
+ ? url + "&values=" + value
114
+ : url + "?values=" + value;
115
+
116
+ let response = await (
117
+ props.fieldConfig.disableHeaderInFetch
118
+ ? axios.get(url)
119
+ : reachoAPI.get(url)
120
+ );
121
+ if (response.data) {
122
+ const data: FieldOptionsSchema[] = getListOptions(
123
+ response.data,
124
+ props.fieldConfig.optionsConfig
125
+ );
126
+ setSelectedValues([...data]);
127
+ }
128
+ } catch (err) { }
129
+ };
130
+
131
+ const updateListOptions = (data: any) => {
132
+ const resData: FieldOptionsSchema = getListOption(
133
+ data,
134
+ props.fieldConfig.optionsConfig
135
+ );
136
+ setSelectedValues((perv) => [resData]);
137
+ formContext.setValue(props.fieldConfig.name, resData.value);
138
+ };
139
+
140
+ function getInput() {
141
+ return (
142
+ <Listbox
143
+ as={"div"}
144
+ {...hookProps}
145
+ className={`relative form-listbox flex-1`}
146
+ value={
147
+ formContext.getValues(props.fieldConfig.name)
148
+ ? props.fieldConfig.outputFormat ===
149
+ OutputFormatType.ARRAY
150
+ ? formContext.getValues(props.fieldConfig.name)[0]
151
+ : formContext.getValues(props.fieldConfig.name)
152
+ : undefined
153
+ }
154
+ onChange={(val) => {
155
+ const currentValue = formContext.getValues(
156
+ props.fieldConfig.name
157
+ );
158
+
159
+ // If the value matches, set it to null, otherwise set it to val
160
+ const newValue = currentValue === val ? null : val;
161
+
162
+ const selected = listOptions.find(o => o.value == newValue);
163
+ selected && setSelectedValues([selected]);
164
+ handleChange(
165
+ newValue,
166
+ formContext,
167
+ props.fieldConfig,
168
+ props.onChange
169
+ );
170
+ }}
171
+ name={props.fieldConfig.name}
172
+ disabled={props.fieldConfig.disabled}>
173
+ <ListboxButton
174
+ className={
175
+ props.fieldConfig.customClassNames?.fieldClassName
176
+ ? "form-listbox-select " +
177
+ props.fieldConfig.customClassNames?.fieldClassName
178
+ : "form-listbox-select"
179
+ }>
180
+ {renderListBoxValue(
181
+ formContext,
182
+ props.fieldConfig,
183
+ [...selectedValues, ...listOptions],
184
+ props.onChange
185
+ )}
186
+ </ListboxButton>
187
+ <RenderListOptions
188
+ formContext={formContext}
189
+ onChange={props.onChange}
190
+ formField={FormFieldType.TYPEAHEAD}
191
+ ref={dynamicSelectRef}
192
+ fieldConfig={props.fieldConfig}
193
+ listOptions={[...selectedValues, ...listOptions]}
194
+ setListOptions={setListOptions}
195
+ loading={loading}
196
+ setLoading={setLoading}
197
+ createCallback={(data) => updateListOptions(data)}
198
+ queryCallback={(query) => fetchData(query)}
199
+ />
200
+ </Listbox>
201
+ );
202
+ }
203
+ if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
204
+ return <></>;
205
+ }
206
+
207
+ return (
208
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
209
+ );
210
+ };
211
+ export default Typeahead;