@engagebay/engagebay-form-module 1.0.2-beta.5 → 1.0.2-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/package.json +1 -3
- package/src/form/FormFieldUtils.ts +4 -0
- package/src/form/formfields/BusinessHoursField.tsx +2 -4
- package/src/form/formfields/NumberField.tsx +1 -1
- package/src/form/formfields/Typeahead2.tsx +283 -0
- package/src/form/formfields/TypeaheadMultiSelect.tsx +7 -9
- package/src/form/schema/FormFieldSchema.ts +3 -0
- package/src/form/util/RenderListOptions.tsx +39 -37
package/README.md
CHANGED
|
@@ -124,3 +124,22 @@ const fieldSchema: FormFieldSchema = {
|
|
|
124
124
|
|
|
125
125
|
- **`FormFieldPatternsImpl`**
|
|
126
126
|
Class providing validation patterns and messages for fields (e.g., `REQUIRED`, `EMAIL`, `URL`).
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Context and providers (this package only)
|
|
131
|
+
|
|
132
|
+
These contexts are used **inside this package** (and by other packages in the eb-ui-components repo that depend on it). Consuming applications (other projects) have their own context trees and may wrap this package differently.
|
|
133
|
+
|
|
134
|
+
- **FormContext** — Provided by `<Form>`. Required for all field components used via `FormField` / `FormFields` (they use `useContext(FormContext)`).
|
|
135
|
+
- **AxiosConfigProvider** — Optional. Only needed for components that call APIs (e.g. dynamic options). Use `useAxiosConfig()` or `useAxiosConfigOptional()`.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Implementing base components (this package)
|
|
140
|
+
|
|
141
|
+
When adding or changing form field components **in this package**:
|
|
142
|
+
|
|
143
|
+
- **Form-context components** — Must run inside `<Form>`. Use `useContext(FormContext)` for `register`, `setValue`, `control`, `errors`, etc. Register the component in `FormFieldUtils.ts` and export it from the package’s main entry (`index.js`).
|
|
144
|
+
- **Context-free components** — If a component does not need form or API context, note it in JSDoc (e.g. “Does not require FormContext”).
|
|
145
|
+
- **New contexts** — If you add a new React context in this package, document it in this README and list which components use it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@engagebay/engagebay-form-module",
|
|
3
|
-
"version": "1.0.2-beta.
|
|
3
|
+
"version": "1.0.2-beta.6",
|
|
4
4
|
"description": "Provide base form components to reacho",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"@heroicons/react": ">=2.1.5",
|
|
17
17
|
"@reduxjs/toolkit": ">=2.2.7",
|
|
18
18
|
"@tippyjs/react": ">=4.2.6",
|
|
19
|
-
"@types/lodash": ">=4.17.7",
|
|
20
19
|
"@types/react-redux": ">=7.1.33",
|
|
21
20
|
"axios": ">=1.7.2",
|
|
22
21
|
"clsx": ">=2.1.1",
|
|
@@ -32,7 +31,6 @@
|
|
|
32
31
|
"@heroicons/react": ">=2.1.5",
|
|
33
32
|
"@reduxjs/toolkit": ">=2.2.7",
|
|
34
33
|
"@tippyjs/react": ">=4.2.6",
|
|
35
|
-
"@types/lodash": ">=4.17.7",
|
|
36
34
|
"@types/react-redux": ">=7.1.33",
|
|
37
35
|
"axios": ">=1.7.2",
|
|
38
36
|
"clsx": ">=2.1.1",
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
FormFieldType,
|
|
35
35
|
OptionMappingConfig,
|
|
36
36
|
} from "./schema/FormFieldSchema";
|
|
37
|
+
import Typeahead2 from "./formfields/Typeahead2";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* @property {React.FC<FormFieldComponentPropSchema>} component - React component for a form field.
|
|
@@ -139,6 +140,9 @@ const formFieldComponents: FormComponentSchema = {
|
|
|
139
140
|
[FormFieldType[FormFieldType.TYPEAHEAD_MULTI_SELECT]]: {
|
|
140
141
|
component: TypeaheadMultiSelect,
|
|
141
142
|
},
|
|
143
|
+
[FormFieldType[FormFieldType.TYPEAHEAD_2]]: {
|
|
144
|
+
component: Typeahead2,
|
|
145
|
+
},
|
|
142
146
|
[FormFieldType[FormFieldType.COMBO_SELECT]]: {
|
|
143
147
|
component: ComboSelect,
|
|
144
148
|
},
|
|
@@ -21,7 +21,6 @@ import FormField from "../FormField";
|
|
|
21
21
|
import RenderFormField from "../util/RenderFormField";
|
|
22
22
|
import { TrashIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|
23
23
|
import Tippy from "@tippyjs/react";
|
|
24
|
-
import { set } from "lodash";
|
|
25
24
|
|
|
26
25
|
const defaultBusinessHours = {
|
|
27
26
|
MONDAY: {
|
|
@@ -124,9 +123,8 @@ export const BusinessHoursField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
124
123
|
<div className="w-full sm:w-full text-end mr-[2.3em]">
|
|
125
124
|
<button
|
|
126
125
|
type="button"
|
|
127
|
-
className={`text-end text-primary cursor-pointer font-[13px] font-medium ${
|
|
128
|
-
|
|
129
|
-
}`}
|
|
126
|
+
className={`text-end text-primary cursor-pointer font-[13px] font-medium ${getValues(`${mappedName}.sessions`)?.length > 1 ? "mr-9" : ""
|
|
127
|
+
}`}
|
|
130
128
|
onClick={() => {
|
|
131
129
|
const lastEndTime =
|
|
132
130
|
getValues(`${mappedName}.sessions`)?.[
|
|
@@ -28,7 +28,7 @@ const NumberField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
28
28
|
step={props.fieldConfig.decimalAllowed ? 0.01 : 1}
|
|
29
29
|
defaultValue={props.fieldConfig.defaultValue as string}
|
|
30
30
|
className={`form-input ${
|
|
31
|
-
props.fieldConfig.customClassNames?.fieldClassName || "flex-1
|
|
31
|
+
props.fieldConfig.customClassNames?.fieldClassName || "flex-1"
|
|
32
32
|
}`}
|
|
33
33
|
onKeyDown={(e) => {
|
|
34
34
|
props.fieldConfig.onKeyDown && props.fieldConfig.onKeyDown(e);
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
import { Listbox, ListboxButton } from "@headlessui/react";
|
|
11
|
+
import { RegisterOptions } from "react-hook-form";
|
|
12
|
+
|
|
13
|
+
import { FormContext } from "../context/FormContext";
|
|
14
|
+
import { getListOption, getListOptions } from "../FormFieldUtils";
|
|
15
|
+
import {
|
|
16
|
+
FieldOptionsSchema,
|
|
17
|
+
FormFieldComponentPropSchema,
|
|
18
|
+
FormFieldType,
|
|
19
|
+
} from "../schema/FormFieldSchema";
|
|
20
|
+
import { handleChange, registerFormField } from "../util";
|
|
21
|
+
import RenderFormField from "../util/RenderFormField";
|
|
22
|
+
import RenderListOptions, {
|
|
23
|
+
renderListBoxValue,
|
|
24
|
+
} from "../util/RenderListOptions";
|
|
25
|
+
import axios from "axios";
|
|
26
|
+
import { getAxiosInstance } from "../../api";
|
|
27
|
+
|
|
28
|
+
const Typeahead2: React.FC<FormFieldComponentPropSchema> = (
|
|
29
|
+
props: FormFieldComponentPropSchema,
|
|
30
|
+
) => {
|
|
31
|
+
const dynamicSelectRef = useRef<HTMLUListElement>(null);
|
|
32
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
33
|
+
const formContext = useContext(FormContext);
|
|
34
|
+
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
35
|
+
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
36
|
+
const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
|
|
37
|
+
const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
|
|
38
|
+
[],
|
|
39
|
+
);
|
|
40
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (
|
|
44
|
+
!formContext.getValues(props.fieldConfig.name) &&
|
|
45
|
+
props.fieldConfig.defaultValue
|
|
46
|
+
) {
|
|
47
|
+
formContext.setValue(
|
|
48
|
+
props.fieldConfig.name,
|
|
49
|
+
props.fieldConfig.defaultValue,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
let values: any | any[] | undefined =
|
|
53
|
+
formContext.getValues(props.fieldConfig.name) ||
|
|
54
|
+
props.fieldConfig.defaultValue;
|
|
55
|
+
|
|
56
|
+
if (values && selectedValues.length < 1) {
|
|
57
|
+
if (props.fieldConfig.fetchSavedDataUrl) {
|
|
58
|
+
fetchValue(values);
|
|
59
|
+
} else {
|
|
60
|
+
setSelectedValues(
|
|
61
|
+
Array.isArray(values)
|
|
62
|
+
? values.map((val: any) => ({ label: val, value: val }))
|
|
63
|
+
: [values],
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
const fetchData = useMemo(() => {
|
|
72
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
73
|
+
|
|
74
|
+
return (_query: string | undefined) => {
|
|
75
|
+
// Clear the previous timer
|
|
76
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
77
|
+
|
|
78
|
+
// Set a new timer
|
|
79
|
+
timeoutId = setTimeout(async () => {
|
|
80
|
+
// 1. Cancel the previous request if it's still pending
|
|
81
|
+
if (abortControllerRef.current) {
|
|
82
|
+
abortControllerRef.current.abort();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 2. Create a new controller for the current request
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
abortControllerRef.current = controller;
|
|
88
|
+
|
|
89
|
+
if (!_query || _query.length < 3) {
|
|
90
|
+
setListOptions([]);
|
|
91
|
+
setLoading(false);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setLoading(true);
|
|
95
|
+
try {
|
|
96
|
+
if (!props.fieldConfig.fetchUrl) return;
|
|
97
|
+
|
|
98
|
+
let url = props.fieldConfig.fetchUrl;
|
|
99
|
+
|
|
100
|
+
url = url.includes("?") ? url + "&q=" + _query : url + "?q=" + _query;
|
|
101
|
+
|
|
102
|
+
const axiosInstance = getAxiosInstance(
|
|
103
|
+
formContext.axiosInstance,
|
|
104
|
+
props.fieldConfig,
|
|
105
|
+
);
|
|
106
|
+
const response = await axiosInstance.get(url);
|
|
107
|
+
|
|
108
|
+
if (controller === abortControllerRef.current) {
|
|
109
|
+
if (response?.data) {
|
|
110
|
+
const data: FieldOptionsSchema[] = getListOptions(
|
|
111
|
+
response?.data,
|
|
112
|
+
props.fieldConfig.optionsConfig,
|
|
113
|
+
);
|
|
114
|
+
setListOptions([...data]);
|
|
115
|
+
|
|
116
|
+
if (props.fieldConfig.fetchCallback) {
|
|
117
|
+
props.fieldConfig.fetchCallback(response);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error("Fetch error:", err);
|
|
123
|
+
} finally {
|
|
124
|
+
// Only stop loading if this was the "active" request
|
|
125
|
+
if (controller === abortControllerRef.current) {
|
|
126
|
+
setLoading(false);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}, 250);
|
|
130
|
+
};
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
// Cleanup on unmount
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
return () => {
|
|
136
|
+
abortControllerRef.current?.abort();
|
|
137
|
+
};
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
const fetchValue = async (value: string | any | any[]) => {
|
|
141
|
+
try {
|
|
142
|
+
if (
|
|
143
|
+
props.fieldConfig.ignoreFetchValue ||
|
|
144
|
+
!props.fieldConfig.fetchSavedDataUrl
|
|
145
|
+
) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let url = props.fieldConfig.fetchSavedDataUrl;
|
|
150
|
+
|
|
151
|
+
// url = url.includes("?")
|
|
152
|
+
// ? url + "&values=" + value
|
|
153
|
+
// : url + "?values=" + value;
|
|
154
|
+
|
|
155
|
+
let response = await (props.fieldConfig.disableHeaderInFetch
|
|
156
|
+
? axios.post(url, Array.isArray(value) ? value : [value])
|
|
157
|
+
: formContext.axiosInstance?.post(
|
|
158
|
+
url,
|
|
159
|
+
Array.isArray(value) ? value : [value],
|
|
160
|
+
));
|
|
161
|
+
if (response?.data) {
|
|
162
|
+
const data: FieldOptionsSchema[] = getListOptions(
|
|
163
|
+
response?.data,
|
|
164
|
+
props.fieldConfig.optionsConfig,
|
|
165
|
+
);
|
|
166
|
+
let values: any[] = formContext.getValues(props.fieldConfig.name) || [];
|
|
167
|
+
setSelectedValues(
|
|
168
|
+
[
|
|
169
|
+
...data,
|
|
170
|
+
props.fieldConfig.dropdownFieldConfig?.isSuggestionBox &&
|
|
171
|
+
values &&
|
|
172
|
+
Array.isArray(values)
|
|
173
|
+
? values
|
|
174
|
+
.filter(
|
|
175
|
+
(value) =>
|
|
176
|
+
data.some((d) => d.value !== value) || data.length == 0,
|
|
177
|
+
)
|
|
178
|
+
.map((val) => ({ label: val, value: val }))
|
|
179
|
+
: [],
|
|
180
|
+
].flat(),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
} catch (err) { }
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const updateListOptions = (data: any) => {
|
|
187
|
+
const resData: FieldOptionsSchema = getListOption(
|
|
188
|
+
data,
|
|
189
|
+
props.fieldConfig.optionsConfig,
|
|
190
|
+
);
|
|
191
|
+
setListOptions([]);
|
|
192
|
+
setSelectedValues((prev) =>
|
|
193
|
+
props.fieldConfig.isMultiple ? [...prev, resData] : [resData],
|
|
194
|
+
);
|
|
195
|
+
let result = formContext.getValues(props.fieldConfig.name) || [];
|
|
196
|
+
result = !props.fieldConfig.isMultiple
|
|
197
|
+
? resData.value
|
|
198
|
+
: result.includes(resData.value)
|
|
199
|
+
? [...result.filter((v: string) => v != resData.value), resData.value]
|
|
200
|
+
: [...result, resData.value];
|
|
201
|
+
formContext.setValue(props.fieldConfig.name, result);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const getInput = () => {
|
|
205
|
+
return (
|
|
206
|
+
<Listbox
|
|
207
|
+
as={"div"}
|
|
208
|
+
{...hookProps}
|
|
209
|
+
value={
|
|
210
|
+
props.fieldConfig.isMultiple
|
|
211
|
+
? formContext.getValues(props.fieldConfig.name)
|
|
212
|
+
? formContext.getValues(props.fieldConfig.name)
|
|
213
|
+
: []
|
|
214
|
+
: formContext.getValues(props.fieldConfig.name)
|
|
215
|
+
}
|
|
216
|
+
name={props.fieldConfig.name}
|
|
217
|
+
defaultValue={props.fieldConfig.defaultValue}
|
|
218
|
+
key={props.fieldConfig.name}
|
|
219
|
+
className={"relative form-listbox flex-1 overflow-hidden"}
|
|
220
|
+
onChange={(selectedOptions) => {
|
|
221
|
+
let currentValue = formContext.getValues(props.fieldConfig.name);
|
|
222
|
+
const newValue =
|
|
223
|
+
currentValue === selectedOptions ? null : selectedOptions;
|
|
224
|
+
|
|
225
|
+
if (props.fieldConfig.isMultiple) {
|
|
226
|
+
currentValue = listOptions.filter((op) =>
|
|
227
|
+
selectedOptions.includes(op.value),
|
|
228
|
+
);
|
|
229
|
+
setSelectedValues((prev) => [...prev, ...currentValue]);
|
|
230
|
+
} else {
|
|
231
|
+
const selected = listOptions.find((o) => o.value == newValue);
|
|
232
|
+
selected && setSelectedValues([selected]);
|
|
233
|
+
}
|
|
234
|
+
setListOptions([]);
|
|
235
|
+
handleChange(
|
|
236
|
+
newValue,
|
|
237
|
+
formContext,
|
|
238
|
+
props.fieldConfig,
|
|
239
|
+
props.onChange,
|
|
240
|
+
);
|
|
241
|
+
}}
|
|
242
|
+
multiple={props.fieldConfig.isMultiple}
|
|
243
|
+
>
|
|
244
|
+
<ListboxButton
|
|
245
|
+
className={
|
|
246
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
247
|
+
? "form-listbox-select " +
|
|
248
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
249
|
+
: "form-listbox-select"
|
|
250
|
+
}
|
|
251
|
+
>
|
|
252
|
+
{renderListBoxValue(
|
|
253
|
+
formContext,
|
|
254
|
+
props.fieldConfig,
|
|
255
|
+
[...selectedValues, ...listOptions],
|
|
256
|
+
props.onChange,
|
|
257
|
+
)}
|
|
258
|
+
</ListboxButton>
|
|
259
|
+
<RenderListOptions
|
|
260
|
+
formContext={formContext}
|
|
261
|
+
onChange={props.onChange}
|
|
262
|
+
formField={FormFieldType.TYPEAHEAD_2}
|
|
263
|
+
ref={dynamicSelectRef}
|
|
264
|
+
fieldConfig={props.fieldConfig}
|
|
265
|
+
listOptions={listOptions}
|
|
266
|
+
setListOptions={setListOptions}
|
|
267
|
+
loading={loading}
|
|
268
|
+
setLoading={setLoading}
|
|
269
|
+
createCallback={(data) => updateListOptions(data)}
|
|
270
|
+
queryCallback={(query) => fetchData(query)}
|
|
271
|
+
/>
|
|
272
|
+
</Listbox>
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
|
|
276
|
+
return <></>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
export default Typeahead2;
|
|
@@ -60,8 +60,6 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
60
60
|
let url = props.fieldConfig.fetchUrl;
|
|
61
61
|
if (_query) {
|
|
62
62
|
url = url.includes("?") ? url + "&q=" + _query : url + "?q=" + _query;
|
|
63
|
-
} else {
|
|
64
|
-
url = url.includes("?") ? url + "&q=" + null : url + "?q=" + null;
|
|
65
63
|
}
|
|
66
64
|
const axiosInstance = getAxiosInstance(
|
|
67
65
|
formContext.axiosInstance,
|
|
@@ -128,16 +126,16 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
128
126
|
...data,
|
|
129
127
|
props.fieldConfig.dropdownFieldConfig?.isSuggestionBox && values
|
|
130
128
|
? values
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
.filter(
|
|
130
|
+
(value) =>
|
|
131
|
+
data.some((d) => d.value !== value) || data.length == 0,
|
|
132
|
+
)
|
|
133
|
+
.map((val) => ({ label: val, value: val }))
|
|
136
134
|
: [],
|
|
137
135
|
].flat(),
|
|
138
136
|
);
|
|
139
137
|
}
|
|
140
|
-
} catch (err) {}
|
|
138
|
+
} catch (err) { }
|
|
141
139
|
};
|
|
142
140
|
|
|
143
141
|
const updateListOptions = (data: any) => {
|
|
@@ -181,7 +179,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
181
179
|
className={
|
|
182
180
|
props.fieldConfig.customClassNames?.fieldClassName
|
|
183
181
|
? "form-listbox-select " +
|
|
184
|
-
|
|
182
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
185
183
|
: "form-listbox-select"
|
|
186
184
|
}
|
|
187
185
|
>
|
|
@@ -37,6 +37,7 @@ export enum FormFieldType {
|
|
|
37
37
|
DYNAMIC_MULTI_SELECT = "DYNAMIC_MULTI_SELECT",
|
|
38
38
|
|
|
39
39
|
TYPEAHEAD = "TYPEAHEAD",
|
|
40
|
+
TYPEAHEAD_2 = "TYPEAHEAD_2",
|
|
40
41
|
TYPEAHEAD_MULTI_SELECT = "TYPEAHEAD_MULTI_SELECT",
|
|
41
42
|
PHONE_NUMBER_INPUT = "PHONE_NUMBER_INPUT",
|
|
42
43
|
SWITCH = "SWITCH",
|
|
@@ -129,10 +130,12 @@ export type FormFieldSchema = {
|
|
|
129
130
|
submitOnChange?: boolean;
|
|
130
131
|
formFieldPattern?: FormFieldPatternsImpl[];
|
|
131
132
|
fetchUrl?: string;
|
|
133
|
+
fetchSavedDataUrl?: string;
|
|
132
134
|
postUrl?: string;
|
|
133
135
|
fileAccept?: string;
|
|
134
136
|
icon?: ReactNode;
|
|
135
137
|
outputFormat?: OutputFormatType;
|
|
138
|
+
isMultiple?: boolean;
|
|
136
139
|
children?: FormFieldSchema[];
|
|
137
140
|
defaultOptions?: FieldOptionsSchema[];
|
|
138
141
|
optionsConfig?: OptionMappingConfig;
|
|
@@ -102,15 +102,15 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
102
102
|
|
|
103
103
|
const filteredList = query
|
|
104
104
|
? resultList.filter((item) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
const normalizedLabel = fieldConfig.dropdownFieldConfig
|
|
106
|
+
?.isCaseSensitive
|
|
107
|
+
? item.label
|
|
108
|
+
: item.label.toLowerCase();
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
return normalizedLabel
|
|
111
|
+
.replace(/\s+/g, "")
|
|
112
|
+
.includes(caseSensitive.replace(/\s+/g, ""));
|
|
113
|
+
})
|
|
114
114
|
: resultList;
|
|
115
115
|
|
|
116
116
|
let nullGroupOptions: any[] = [];
|
|
@@ -127,13 +127,15 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
127
127
|
}, {});
|
|
128
128
|
|
|
129
129
|
const handleQueryCallback = useCallback(() => {
|
|
130
|
-
if (filteredList.length
|
|
130
|
+
if (filteredList.length < 5 && isTypeahead) {
|
|
131
131
|
queryCallback && queryCallback(query);
|
|
132
132
|
}
|
|
133
133
|
}, [filteredList]);
|
|
134
134
|
|
|
135
135
|
useEffect(() => {
|
|
136
|
-
|
|
136
|
+
if (formField == FormFieldType.TYPEAHEAD_2)
|
|
137
|
+
queryCallback && queryCallback(query);
|
|
138
|
+
else handleQueryCallback();
|
|
137
139
|
}, [query]);
|
|
138
140
|
|
|
139
141
|
let enableCreateFlag =
|
|
@@ -143,6 +145,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
143
145
|
|
|
144
146
|
let validTypeaheadFields = [
|
|
145
147
|
FormFieldType.TYPEAHEAD,
|
|
148
|
+
FormFieldType.TYPEAHEAD_2,
|
|
146
149
|
FormFieldType.TYPEAHEAD_MULTI_SELECT,
|
|
147
150
|
];
|
|
148
151
|
let isTypeahead: boolean = validTypeaheadFields.indexOf(formField) > -1;
|
|
@@ -166,11 +169,10 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
166
169
|
key={option.value}
|
|
167
170
|
disabled={option.isDisabled}
|
|
168
171
|
onClick={() => setTimeout(resetToDefault, 300)}
|
|
169
|
-
className={`form-listbox-option ${
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}`}
|
|
172
|
+
className={`form-listbox-option ${selected
|
|
173
|
+
? " bg-gray-100 text-gray-900"
|
|
174
|
+
: "hover:bg-gray-100 text-gray-700"
|
|
175
|
+
}`}
|
|
174
176
|
value={option.value}
|
|
175
177
|
>
|
|
176
178
|
{fieldConfig.fieldOptionWrapper ? (
|
|
@@ -179,9 +181,8 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
179
181
|
<>
|
|
180
182
|
<div className="flex items-center justify-between gap-x-1.5">
|
|
181
183
|
<span
|
|
182
|
-
className={`block truncate w-full ${fieldConfig.customClassNames?.optionClassName} space-x-2 ${
|
|
183
|
-
|
|
184
|
-
}`}
|
|
184
|
+
className={`block truncate w-full ${fieldConfig.customClassNames?.optionClassName} space-x-2 ${selected ? "font-medium" : "font-normal"
|
|
185
|
+
}`}
|
|
185
186
|
>
|
|
186
187
|
{option.icon && (
|
|
187
188
|
<span className="listbox-svg">{option.icon}</span>
|
|
@@ -203,7 +204,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
203
204
|
)}
|
|
204
205
|
</span>
|
|
205
206
|
{isTypeahead &&
|
|
206
|
-
|
|
207
|
+
!fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
|
|
207
208
|
<input
|
|
208
209
|
type="checkbox"
|
|
209
210
|
className="form-checkbox"
|
|
@@ -399,28 +400,28 @@ export function renderListBoxValue(
|
|
|
399
400
|
{values.length > 1
|
|
400
401
|
? `${values.length} selected`
|
|
401
402
|
: listOptions.find((option) => option.value == value[0])?.label ||
|
|
402
|
-
|
|
403
|
+
values[0]}
|
|
403
404
|
</span>
|
|
404
405
|
{getDeleteButton()}
|
|
405
406
|
</span>
|
|
406
407
|
) : (
|
|
407
408
|
Array.isArray(values) &&
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
409
|
+
values.map((opt: any) => {
|
|
410
|
+
const option = listOptions.find((option) => option.value == opt);
|
|
411
|
+
if (!option && values.length == 0) return getPlaceholder();
|
|
411
412
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
413
|
+
return (
|
|
414
|
+
<span key={option?.value} className="form-selected-badge">
|
|
415
|
+
<Tippy content={option?.label} delay={500}>
|
|
416
|
+
<span className="form-selected-badge-name">
|
|
417
|
+
{option?.label}
|
|
418
|
+
</span>
|
|
419
|
+
</Tippy>
|
|
419
420
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
421
|
+
{getDeleteButton(opt)}
|
|
422
|
+
</span>
|
|
423
|
+
);
|
|
424
|
+
})
|
|
424
425
|
);
|
|
425
426
|
};
|
|
426
427
|
const getDeleteButton = (option?: string) => {
|
|
@@ -452,9 +453,10 @@ export function renderListBoxValue(
|
|
|
452
453
|
)
|
|
453
454
|
);
|
|
454
455
|
};
|
|
455
|
-
let outputFormat =
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
let outputFormat =
|
|
457
|
+
fieldConfig.outputFormat != undefined
|
|
458
|
+
? fieldConfig.outputFormat === OutputFormatType.STRING
|
|
459
|
+
: false;
|
|
458
460
|
const getPlaceholder = () => (
|
|
459
461
|
<span className="form-placeholder">
|
|
460
462
|
{fieldConfig.placeholder || "Select any option"}
|