@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,187 @@
|
|
|
1
|
+
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
|
|
2
|
+
import {
|
|
3
|
+
FieldOptionsSchema,
|
|
4
|
+
FormFieldComponentPropSchema,
|
|
5
|
+
FormFieldType,
|
|
6
|
+
OutputFormatType,
|
|
7
|
+
StoreStateSchema,
|
|
8
|
+
} from "../schema/FormFieldSchema";
|
|
9
|
+
import {RegisterOptions} from "react-hook-form";
|
|
10
|
+
import {FormContext} from "../context/FormContext";
|
|
11
|
+
import {Listbox, ListboxButton} from "@headlessui/react";
|
|
12
|
+
import {useDispatch} from "react-redux";
|
|
13
|
+
import {getListOptions} from "../FormFieldUtils";
|
|
14
|
+
import useDymanicReducer from "../hooks/useDynamicReducer";
|
|
15
|
+
import RenderFormField from "../util/RenderFormField";
|
|
16
|
+
import RenderListOptions, {renderListBoxValue,} from "../util/RenderListOptions";
|
|
17
|
+
import {handleChange, registerFormField} from "../util";
|
|
18
|
+
import {reachoAPI} from "../../api/index";
|
|
19
|
+
import axios from "axios";
|
|
20
|
+
|
|
21
|
+
const DynamicSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
22
|
+
props: FormFieldComponentPropSchema
|
|
23
|
+
) => {
|
|
24
|
+
const dynamicSelectRef = useRef<HTMLUListElement>(null);
|
|
25
|
+
|
|
26
|
+
const formContext = useContext(FormContext);
|
|
27
|
+
|
|
28
|
+
const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
|
|
29
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
30
|
+
|
|
31
|
+
useMemo(() => {
|
|
32
|
+
const {fieldConfig} = props;
|
|
33
|
+
|
|
34
|
+
// Initialize dropdownFieldConfig with defaults if not already set
|
|
35
|
+
fieldConfig.dropdownFieldConfig = {
|
|
36
|
+
showCreateOption:
|
|
37
|
+
fieldConfig.dropdownFieldConfig?.showCreateOption ?? false,
|
|
38
|
+
showDeleteOption:
|
|
39
|
+
fieldConfig.dropdownFieldConfig?.showDeleteOption ?? true,
|
|
40
|
+
showSearchBox:
|
|
41
|
+
fieldConfig.dropdownFieldConfig?.showSearchBox ?? true,
|
|
42
|
+
showSelectedCount:
|
|
43
|
+
fieldConfig.dropdownFieldConfig?.showSelectedCount ?? true,
|
|
44
|
+
};
|
|
45
|
+
if (
|
|
46
|
+
!formContext.getValues(props.fieldConfig.name) &&
|
|
47
|
+
props.fieldConfig.defaultValue
|
|
48
|
+
) {
|
|
49
|
+
formContext.setValue(
|
|
50
|
+
props.fieldConfig.name,
|
|
51
|
+
props.fieldConfig.defaultValue
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}, [props.fieldConfig]);
|
|
55
|
+
|
|
56
|
+
if (props.fieldConfig.store) {
|
|
57
|
+
// Get the state from the reducer
|
|
58
|
+
const {state} = useDymanicReducer<StoreStateSchema<any>>({
|
|
59
|
+
reducerConfig: props.fieldConfig.store,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const dispatch = useDispatch();
|
|
63
|
+
|
|
64
|
+
// Memoize the listOptions and loading state derived from state.data
|
|
65
|
+
const {listOptions, isLoading} = useMemo(() => {
|
|
66
|
+
const options = state && state.data && Array.isArray(state.data)
|
|
67
|
+
? getListOptions(state.data, props.fieldConfig.optionsConfig)
|
|
68
|
+
: [];
|
|
69
|
+
const loading = state && state.loading;
|
|
70
|
+
return {listOptions: options, isLoading: loading};
|
|
71
|
+
}, [state, props.fieldConfig.optionsConfig]);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
// Dispatch the initial fetch action if needed
|
|
75
|
+
if (props.fieldConfig.store?.initialFetchReducerAction && (!state || !state.data)) {
|
|
76
|
+
dispatch(props.fieldConfig.store.initialFetchReducerAction(props.fieldConfig.customUrlForRedux ? props.fieldConfig.customUrlForRedux : ''));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Set the listOptions and loading state when the state changes
|
|
80
|
+
setListOptions(listOptions);
|
|
81
|
+
setLoading(isLoading);
|
|
82
|
+
}, [isLoading]);
|
|
83
|
+
}
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
// Fetch data when fetchUrl is present
|
|
86
|
+
if (props.fieldConfig.fetchUrl) {
|
|
87
|
+
fetchData();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Set options only when they are available and valid
|
|
91
|
+
if (Array.isArray(props.fieldConfig.options) && props.fieldConfig.options.length > 0) {
|
|
92
|
+
let options = props.fieldConfig.options;
|
|
93
|
+
setListOptions(prevState => [...options, ...prevState]);
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const fetchData = useCallback(async () => {
|
|
98
|
+
if (!props.fieldConfig.fetchUrl) return;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
let response = await (props.fieldConfig.disableHeaderInFetch
|
|
102
|
+
? axios.get(props.fieldConfig.fetchUrl)
|
|
103
|
+
: reachoAPI.get(props.fieldConfig.fetchUrl));
|
|
104
|
+
|
|
105
|
+
if (response.data) {
|
|
106
|
+
const data: FieldOptionsSchema[] = getListOptions(response.data, props.fieldConfig.optionsConfig);
|
|
107
|
+
setListOptions(prev => [...prev, ...data]);
|
|
108
|
+
|
|
109
|
+
if (props.fieldConfig.fetchCallback) {
|
|
110
|
+
props.fieldConfig.fetchCallback(response);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
console.error(response.statusText);
|
|
114
|
+
setLoading(false);
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Fetch error:', error);
|
|
118
|
+
setLoading(false);
|
|
119
|
+
}
|
|
120
|
+
}, [props.fieldConfig.fetchUrl, props.fieldConfig.optionsConfig, props.fieldConfig.fetchCallback, props.fieldConfig.disableHeaderInFetch]);
|
|
121
|
+
|
|
122
|
+
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
123
|
+
const hookProps = useMemo(() => formContext.register(props.fieldConfig.name, registerOptions), [formContext, props.fieldConfig.name, registerOptions]);
|
|
124
|
+
|
|
125
|
+
function getInput() {
|
|
126
|
+
return (
|
|
127
|
+
<Listbox
|
|
128
|
+
as={"div"}
|
|
129
|
+
{...hookProps}
|
|
130
|
+
className={`relative form-listbox flex-1`}
|
|
131
|
+
value={
|
|
132
|
+
formContext.getValues(props.fieldConfig.name)
|
|
133
|
+
? props.fieldConfig.outputFormat ===
|
|
134
|
+
OutputFormatType.ARRAY
|
|
135
|
+
? formContext.getValues(props.fieldConfig.name)[0]
|
|
136
|
+
: formContext.getValues(props.fieldConfig.name)
|
|
137
|
+
: undefined
|
|
138
|
+
}
|
|
139
|
+
onChange={(val) => {
|
|
140
|
+
const currentValue = formContext.getValues(props.fieldConfig.name);
|
|
141
|
+
|
|
142
|
+
// If the value matches, set it to null, otherwise set it to val
|
|
143
|
+
const newValue = currentValue === val ? null : val;
|
|
144
|
+
|
|
145
|
+
handleChange(newValue, formContext, props.fieldConfig, props.onChange);
|
|
146
|
+
}}
|
|
147
|
+
defaultValue={props.fieldConfig.defaultValue}
|
|
148
|
+
name={props.fieldConfig.name}
|
|
149
|
+
disabled={props.fieldConfig.disabled}>
|
|
150
|
+
<ListboxButton
|
|
151
|
+
className={
|
|
152
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
153
|
+
? "form-listbox-select " +
|
|
154
|
+
props.fieldConfig.customClassNames.fieldClassName
|
|
155
|
+
: "form-listbox-select"
|
|
156
|
+
}>
|
|
157
|
+
{renderListBoxValue(
|
|
158
|
+
formContext,
|
|
159
|
+
props.fieldConfig,
|
|
160
|
+
listOptions,
|
|
161
|
+
props.onChange
|
|
162
|
+
)}
|
|
163
|
+
</ListboxButton>
|
|
164
|
+
<RenderListOptions
|
|
165
|
+
formContext={formContext}
|
|
166
|
+
onChange={props.onChange}
|
|
167
|
+
formField={FormFieldType.DYNAMIC_SELECT}
|
|
168
|
+
ref={dynamicSelectRef}
|
|
169
|
+
fieldConfig={props.fieldConfig}
|
|
170
|
+
listOptions={listOptions}
|
|
171
|
+
setListOptions={setListOptions}
|
|
172
|
+
loading={loading}
|
|
173
|
+
setLoading={setLoading}
|
|
174
|
+
/>
|
|
175
|
+
</Listbox>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if(props.fieldConfig.hideWhenNoResults && listOptions.length==0 )
|
|
179
|
+
{
|
|
180
|
+
return <></>
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput}/>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
export default DynamicSelect;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const Error = ({ type, patternMessage }: { type: any; patternMessage?: string | undefined }) => {
|
|
4
|
+
return (
|
|
5
|
+
<>
|
|
6
|
+
{type === 'required' && <span className="field-error">This field is required</span>}
|
|
7
|
+
{type === 'minLength' && <span className="field-error">This is invalid field</span>}
|
|
8
|
+
{type === 'maxLength' && <span className="field-error">This field cannot exceed characters</span>}
|
|
9
|
+
{type === 'min' && <span className="field-error">This is invalid field</span>}
|
|
10
|
+
{type === 'pattern' && <span className="field-error">{patternMessage}</span>}
|
|
11
|
+
{type === 'validate' && <span className="field-error">{patternMessage}</span>}
|
|
12
|
+
</>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
export default Error;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { ContextType, useContext } from "react";
|
|
2
|
+
import Error from "./Error";
|
|
3
|
+
import { FormFieldSchema } from "../schema/FormFieldSchema";
|
|
4
|
+
import { FormContext } from "../context/FormContext";
|
|
5
|
+
|
|
6
|
+
export interface ErrorContextProps {
|
|
7
|
+
fieldConfig: FormFieldSchema;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function ErrorContextHandler({
|
|
11
|
+
fieldConfig,
|
|
12
|
+
}: ErrorContextProps) {
|
|
13
|
+
const formContext = useContext(FormContext);
|
|
14
|
+
|
|
15
|
+
const getPatternMessage = () => {
|
|
16
|
+
try {
|
|
17
|
+
if (
|
|
18
|
+
formContext.getFieldState &&
|
|
19
|
+
formContext.getFieldState(fieldConfig.name) &&
|
|
20
|
+
formContext.getFieldState(fieldConfig.name).error?.type === "validate"
|
|
21
|
+
) {
|
|
22
|
+
return formContext.getFieldState(fieldConfig.name).error?.message;
|
|
23
|
+
}
|
|
24
|
+
return (
|
|
25
|
+
fieldConfig &&
|
|
26
|
+
fieldConfig.formFieldPattern &&
|
|
27
|
+
(fieldConfig.errorMessage ||
|
|
28
|
+
fieldConfig.formFieldPattern[0].getMessage())
|
|
29
|
+
);
|
|
30
|
+
} catch (error) {}
|
|
31
|
+
return;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// for object fields
|
|
35
|
+
function getError() {
|
|
36
|
+
const errorMessage: JSX.Element[] = [];
|
|
37
|
+
fieldConfig.children?.map((field) => {
|
|
38
|
+
if (formContext.errors && formContext.errors[field.name]) {
|
|
39
|
+
errorMessage.push(<ErrorContextHandler fieldConfig={field} />);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return <>{errorMessage.length > 0 && errorMessage[0]}</>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const checkError = () => {
|
|
47
|
+
let error: JSX.Element | null = null;
|
|
48
|
+
|
|
49
|
+
formContext.errors &&
|
|
50
|
+
formContext.errors[fieldConfig.name] &&
|
|
51
|
+
formContext.errors[fieldConfig.name]?.type &&
|
|
52
|
+
(error = (
|
|
53
|
+
<Error
|
|
54
|
+
type={formContext.errors[fieldConfig.name]?.type}
|
|
55
|
+
patternMessage={getPatternMessage()}
|
|
56
|
+
></Error>
|
|
57
|
+
));
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Checks for Nested Oject Array
|
|
61
|
+
*/
|
|
62
|
+
if (error === null) {
|
|
63
|
+
formContext.getFieldState &&
|
|
64
|
+
formContext.getFieldState(fieldConfig.name).error &&
|
|
65
|
+
(error = (
|
|
66
|
+
<Error
|
|
67
|
+
type={formContext.getFieldState(fieldConfig.name).error?.type}
|
|
68
|
+
patternMessage={getPatternMessage()}
|
|
69
|
+
></Error>
|
|
70
|
+
));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return error;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return <>{checkError()}</>;
|
|
77
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { useContext, useState } from "react";
|
|
2
|
+
import { RegisterOptions } from "react-hook-form";
|
|
3
|
+
import { FormContext } from "../context/FormContext";
|
|
4
|
+
import {
|
|
5
|
+
FormFieldComponentPropSchema,
|
|
6
|
+
FormFieldPatternsImpl,
|
|
7
|
+
} from "../schema/FormFieldSchema";
|
|
8
|
+
import { Description } from "@headlessui/react";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import RenderFormField from "../util/RenderFormField";
|
|
11
|
+
import clsx from "clsx";
|
|
12
|
+
const FileUploadField: React.FC<FormFieldComponentPropSchema> = (
|
|
13
|
+
props: FormFieldComponentPropSchema
|
|
14
|
+
) => {
|
|
15
|
+
const formContext = useContext(FormContext);
|
|
16
|
+
const [file, setFile] = useState<File | null>(
|
|
17
|
+
formContext.getValues(props.fieldConfig.name)
|
|
18
|
+
? formContext.getValues(props.fieldConfig.name)
|
|
19
|
+
: null
|
|
20
|
+
);
|
|
21
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
22
|
+
const [isDragging, setIsDragging] = useState<boolean>(false);
|
|
23
|
+
|
|
24
|
+
let registerOptions: RegisterOptions = {
|
|
25
|
+
required: props.fieldConfig.required
|
|
26
|
+
? FormFieldPatternsImpl.REQUIRED.getMessage()
|
|
27
|
+
: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (props.fieldConfig?.formFieldPattern) {
|
|
31
|
+
registerOptions.pattern =
|
|
32
|
+
props.fieldConfig?.formFieldPattern[0].getPattern();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let hookprops = formContext.register(
|
|
36
|
+
props.fieldConfig.name,
|
|
37
|
+
registerOptions
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const handleDragOver = (e: React.DragEvent<HTMLLabelElement>) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
setIsDragging(true);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleDragLeave = () => {
|
|
46
|
+
setIsDragging(false);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleDrop = (e: React.DragEvent<HTMLLabelElement>) => {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
setIsDragging(false);
|
|
52
|
+
const files = e.dataTransfer.files;
|
|
53
|
+
if (files && files.length > 0) {
|
|
54
|
+
handleChange(files);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleChange = async (_fileList: FileList | null) => {
|
|
59
|
+
setLoading(true);
|
|
60
|
+
if (_fileList && _fileList.length) {
|
|
61
|
+
const _file: File = _fileList[0];
|
|
62
|
+
setFile(_file);
|
|
63
|
+
formContext.setValue(props.fieldConfig.name, _file, {
|
|
64
|
+
shouldValidate: true,
|
|
65
|
+
shouldDirty: formContext.getFieldState(props.fieldConfig.name)
|
|
66
|
+
.isDirty,
|
|
67
|
+
shouldTouch: formContext.getFieldState(props.fieldConfig.name)
|
|
68
|
+
.isTouched,
|
|
69
|
+
});
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
setLoading(false);
|
|
72
|
+
}, 1000);
|
|
73
|
+
if (props.fieldConfig.submitOnChange && formContext.onSubmit)
|
|
74
|
+
formContext.onSubmit();
|
|
75
|
+
if (props.onChange) props.onChange(_file);
|
|
76
|
+
if (props.fieldConfig.onChange) props.fieldConfig.onChange(_file);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const isImageFile = (file: any) => {
|
|
81
|
+
if (!file) return false;
|
|
82
|
+
const validImageTypes = ["image/jpeg", "image/jpg", "image/png"];
|
|
83
|
+
return validImageTypes.includes(file.type);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const getInput = () => {
|
|
87
|
+
return (
|
|
88
|
+
<label
|
|
89
|
+
htmlFor="dropzone-file"
|
|
90
|
+
className={clsx(
|
|
91
|
+
props.fieldConfig.customClassNames?.wrapperClassName ||
|
|
92
|
+
"flex flex-col items-center justify-center w-full h-64 border-2 border-dashed rounded-lg cursor-pointer",
|
|
93
|
+
isDragging
|
|
94
|
+
? "border-blue-500 bg-blue-100"
|
|
95
|
+
: "border-gray-300 bg-gray-50 hover:bg-gray-100"
|
|
96
|
+
)}
|
|
97
|
+
onDragOver={handleDragOver}
|
|
98
|
+
onDragLeave={handleDragLeave}
|
|
99
|
+
onDrop={handleDrop}
|
|
100
|
+
>
|
|
101
|
+
|
|
102
|
+
{loading ? (
|
|
103
|
+
<Description>Loading...</Description>
|
|
104
|
+
) : (
|
|
105
|
+
props.fieldConfig.fieldContainer ? <props.fieldConfig.fieldContainer /> :
|
|
106
|
+
<div className={"flex flex-col items-center justify-center pt-5 pb-6"}>
|
|
107
|
+
{file ? (
|
|
108
|
+
<>
|
|
109
|
+
{isImageFile(file) ? (
|
|
110
|
+
<div className="flex">
|
|
111
|
+
<img
|
|
112
|
+
src={URL.createObjectURL(
|
|
113
|
+
file
|
|
114
|
+
)}
|
|
115
|
+
alt="Preview"
|
|
116
|
+
className="m-2"
|
|
117
|
+
style={{
|
|
118
|
+
maxWidth: "50px",
|
|
119
|
+
maxHeight: "50px",
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
<span className="m-2 ml-2">
|
|
123
|
+
{file?.name}
|
|
124
|
+
</span>
|
|
125
|
+
</div>
|
|
126
|
+
) : (
|
|
127
|
+
<div className="flex">
|
|
128
|
+
<span className="m-2">
|
|
129
|
+
{file?.name}
|
|
130
|
+
</span>{" "}
|
|
131
|
+
<button
|
|
132
|
+
onClick={(e) => {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
formContext.setValue(
|
|
135
|
+
props.fieldConfig.name,cnull
|
|
136
|
+
);
|
|
137
|
+
setFile(null);
|
|
138
|
+
}}
|
|
139
|
+
className="btn btn-outline-dark btn-sm ">
|
|
140
|
+
X
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</>
|
|
145
|
+
) : (
|
|
146
|
+
<>
|
|
147
|
+
<svg
|
|
148
|
+
className="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400"
|
|
149
|
+
aria-hidden="true"
|
|
150
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
151
|
+
fill="none"
|
|
152
|
+
viewBox="0 0 20 16">
|
|
153
|
+
<path
|
|
154
|
+
stroke="currentColor"
|
|
155
|
+
strokeLinecap="round"
|
|
156
|
+
strokeLinejoin="round"
|
|
157
|
+
strokeWidth="2"
|
|
158
|
+
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
|
|
159
|
+
/>
|
|
160
|
+
</svg>
|
|
161
|
+
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
|
162
|
+
<span className="font-semibold">
|
|
163
|
+
Click to upload or drag and drop
|
|
164
|
+
</span>
|
|
165
|
+
</p>
|
|
166
|
+
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
167
|
+
{props.fieldConfig.placeholder
|
|
168
|
+
? props.fieldConfig.placeholder
|
|
169
|
+
: "-- placeholder --"}
|
|
170
|
+
</p>
|
|
171
|
+
</>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
<input
|
|
176
|
+
id="dropzone-file"
|
|
177
|
+
type="file"
|
|
178
|
+
className="hidden"
|
|
179
|
+
accept={
|
|
180
|
+
props.fieldConfig.fileAccept
|
|
181
|
+
? props.fieldConfig.fileAccept
|
|
182
|
+
: ""
|
|
183
|
+
}
|
|
184
|
+
onChange={(e) => handleChange(e.target.files)}
|
|
185
|
+
disabled={props.fieldConfig.readOnly}
|
|
186
|
+
/>
|
|
187
|
+
</label>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export default FileUploadField;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { RegisterOptions } from "react-hook-form";
|
|
2
|
+
import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
|
|
3
|
+
import { useContext, useEffect, useRef } from "react";
|
|
4
|
+
import { FormContext } from "../context/FormContext";
|
|
5
|
+
import RenderFormField from "../util/RenderFormField";
|
|
6
|
+
import { handleChange, registerFormField } from "../util";
|
|
7
|
+
|
|
8
|
+
const IframeField: React.FC<FormFieldComponentPropSchema> = (
|
|
9
|
+
props: FormFieldComponentPropSchema
|
|
10
|
+
) => {
|
|
11
|
+
const formContext = useContext(FormContext);
|
|
12
|
+
|
|
13
|
+
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
14
|
+
|
|
15
|
+
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
16
|
+
|
|
17
|
+
const iframe = useRef<HTMLIFrameElement>(null);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (iframe.current) {
|
|
21
|
+
const iframeDocument =
|
|
22
|
+
iframe.current.contentDocument ||
|
|
23
|
+
iframe.current.contentWindow?.document;
|
|
24
|
+
if (iframeDocument) {
|
|
25
|
+
iframeDocument.open();
|
|
26
|
+
iframeDocument.write(
|
|
27
|
+
"<!DOCTYPE html><html><head></head><body></body></html>"
|
|
28
|
+
);
|
|
29
|
+
iframeDocument.close();
|
|
30
|
+
|
|
31
|
+
const div = iframeDocument.createElement("div");
|
|
32
|
+
div.className = "iframe-content";
|
|
33
|
+
div.innerHTML = formContext.getValues(props.fieldConfig.name) || "";
|
|
34
|
+
|
|
35
|
+
div.setAttribute(
|
|
36
|
+
"contenteditable",
|
|
37
|
+
props.fieldConfig.readOnly ? "false" : "true"
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
div.oninput = (e: Event) => {
|
|
41
|
+
const value = (e.target as HTMLDivElement).innerHTML;
|
|
42
|
+
handleChange(value, formContext, props.fieldConfig, props.onChange);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
iframeDocument.body.appendChild(div);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}, [props.fieldConfig.name, props.fieldConfig.readOnly, props.onChange]);
|
|
49
|
+
|
|
50
|
+
const getInput = () => {
|
|
51
|
+
return (
|
|
52
|
+
<iframe
|
|
53
|
+
ref={iframe}
|
|
54
|
+
className={`form-input ${
|
|
55
|
+
props.fieldConfig.customClassNames?.fieldClassName || "flex-1"
|
|
56
|
+
}`}
|
|
57
|
+
></iframe>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
export default IframeField;
|
|
@@ -0,0 +1,67 @@
|
|
|
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 RenderFormField from "../util/RenderFormField";
|
|
6
|
+
import { handleChange, registerFormField } from "../util";
|
|
7
|
+
import { Input } from "@headlessui/react";
|
|
8
|
+
|
|
9
|
+
const InputField: 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(
|
|
17
|
+
props.fieldConfig.name,
|
|
18
|
+
registerOptions
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Raw Form Field
|
|
23
|
+
*/
|
|
24
|
+
const getInput = () => {
|
|
25
|
+
return (
|
|
26
|
+
<Input
|
|
27
|
+
{...hookProps}
|
|
28
|
+
// ref={props.fieldConfig.ref}
|
|
29
|
+
type="text"
|
|
30
|
+
maxLength={props.fieldConfig?.maxLength}
|
|
31
|
+
minLength={props.fieldConfig?.minLength}
|
|
32
|
+
placeholder={props.fieldConfig?.placeholder}
|
|
33
|
+
readOnly={props.fieldConfig?.readOnly}
|
|
34
|
+
disabled={props.fieldConfig?.disabled}
|
|
35
|
+
autoComplete={props.fieldConfig?.autoComplete}
|
|
36
|
+
defaultValue={props.fieldConfig.defaultValue as string}
|
|
37
|
+
className={`form-input ${
|
|
38
|
+
props.fieldConfig.customClassNames?.fieldClassName ||
|
|
39
|
+
"flex-1"
|
|
40
|
+
}`}
|
|
41
|
+
onKeyDown={(e) => {
|
|
42
|
+
const inputElement = e.target as HTMLInputElement;
|
|
43
|
+
const { selectionStart, value } = inputElement;
|
|
44
|
+
|
|
45
|
+
if (e.key === " " && selectionStart === 0) {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
}
|
|
48
|
+
props.fieldConfig.onKeyDown &&
|
|
49
|
+
props.fieldConfig.onKeyDown(e);
|
|
50
|
+
}}
|
|
51
|
+
onChange={(e) => {
|
|
52
|
+
handleChange(
|
|
53
|
+
e.target.value,
|
|
54
|
+
formContext,
|
|
55
|
+
props.fieldConfig,
|
|
56
|
+
props.onChange
|
|
57
|
+
);
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
export default InputField;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useContext } from "react";
|
|
2
|
+
import FormField from "../FormField";
|
|
3
|
+
import { FormContext } from "../context/FormContext";
|
|
4
|
+
import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
|
|
5
|
+
import RenderFormField from "../util/RenderFormField";
|
|
6
|
+
|
|
7
|
+
const InputGroupField: React.FC<FormFieldComponentPropSchema> = (
|
|
8
|
+
props: FormFieldComponentPropSchema
|
|
9
|
+
) => {
|
|
10
|
+
function getInput() {
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
{props.fieldConfig.children?.map((child, index: number) => {
|
|
14
|
+
child.disableDefaultWrapper = true;
|
|
15
|
+
if (
|
|
16
|
+
!child.customClassNames ||
|
|
17
|
+
child.customClassNames?.fieldClassName === undefined ||
|
|
18
|
+
child.customClassNames?.fieldClassName === null ||
|
|
19
|
+
child.customClassNames?.fieldClassName === ""
|
|
20
|
+
) {
|
|
21
|
+
child.customClassNames = {};
|
|
22
|
+
if (props.fieldConfig.children) {
|
|
23
|
+
if (index === 0) {
|
|
24
|
+
child.customClassNames.fieldClassName =
|
|
25
|
+
"input-group-first-field";
|
|
26
|
+
} else if (index === props.fieldConfig.children?.length - 1) {
|
|
27
|
+
child.customClassNames.fieldClassName = "input-group-end-field";
|
|
28
|
+
} else {
|
|
29
|
+
child.customClassNames.fieldClassName =
|
|
30
|
+
"input-group-middle-field";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return <FormField fieldConfig={child}></FormField>;
|
|
35
|
+
})}
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
export default InputGroupField;
|